From 84ae873e1c19ca7d2ffc5c98248285706dae818b Mon Sep 17 00:00:00 2001
From: rk <94314517@qq.com>
Date: 星期五, 26 六月 2026 18:07:37 +0800
Subject: [PATCH] 功能开发

---
 server/services/src/main/java/com/doumee/service/business/DouyinProductService.java             |   34 
 server/services/src/main/java/com/doumee/core/douyin/dto/DouyinVerifyReq.java                   |   41 
 server/services/src/main/java/com/doumee/service/business/MemberRidesTrackService.java          |   28 
 server/.gitignore                                                                               |    1 
 server/services/src/main/java/com/doumee/core/douyin/dto/DouyinVerifyParam.java                 |   36 
 server/web/src/main/java/com/doumee/api/web/DouyinApi.java                                      |  226 ++
 server/services/src/main/java/com/doumee/core/douyin/dto/DouyinShopPoiResp.java                 |   51 
 server/services/src/main/java/com/doumee/service/business/impl/MemberRidesServiceImpl.java      |   12 
 server/services/src/main/java/com/doumee/dao/business/web/response/HomeResponse.java            |    3 
 server/services/src/main/java/com/doumee/core/constants/Constants.java                          |  126 +
 server/services/src/main/java/com/doumee/service/system/impl/SystemDictDataServiceImpl.java     |   72 
 server/web/src/main/java/com/doumee/jtt808/web/service/Jtt808Service.java                       |   50 
 server/services/src/main/java/com/doumee/dao/business/web/request/BikeIncomeQueryDTO.java       |   35 
 server/services/src/main/java/com/doumee/dao/business/vo/IncomeStatVO.java                      |   39 
 server/services/src/main/java/com/doumee/service/business/impl/MemberRidesTrackServiceImpl.java |   48 
 server/services/src/main/java/com/doumee/core/douyin/dto/DouyinPrepareReq.java                  |   35 
 server/.claude/plans/douyin-verify-page-and-cancel-migration.md                                 |   88 
 server/platform/src/main/java/com/doumee/api/system/SystemDictDataController.java               |   21 
 server/services/src/main/java/com/doumee/core/douyin/dto/DouyinPrepareParam.java                |   25 
 server/services/src/main/java/com/doumee/dao/business/DouyinVerifyRecordMapper.java             |   14 
 server/services/src/main/java/com/doumee/core/douyin/dto/DouyinOnlineQueryReq.java              |   46 
 server/services/src/main/java/com/doumee/dao/business/vo/OrderRidesDetailVO.java                |   35 
 server/services/src/main/java/com/doumee/core/douyin/dto/DouyinProductDTO.java                  |   76 
 server/services/src/main/java/com/doumee/core/douyin/dto/DouyinOnlineQueryResp.java             |   36 
 server/services/src/main/java/com/doumee/dao/business/model/DouyinProductSku.java               |   56 
 server/services/src/main/java/com/doumee/dao/business/vo/DouyinVerifyRecordPageVO.java          |   68 
 server/services/src/main/java/com/doumee/dao/business/model/MemberRidesTrack.java               |   53 
 server/db/Shiro.sql                                                                             |  112 +
 server/services/src/main/java/com/doumee/dao/business/vo/OperationOrderVO.java                  |   63 
 server/services/src/main/java/com/doumee/core/douyin/dto/DouyinCancelParam.java                 |   19 
 server/services/src/main/resources/application-pro.yml                                          |    8 
 server/services/src/main/java/com/doumee/service/business/impl/GoodsorderServiceImpl.java       |    1 
 server/services/src/main/java/com/doumee/core/douyin/dto/DouyinPrepareResp.java                 |   93 +
 server/platform/src/main/resources/application.yml                                              |    2 
 server/services/src/main/java/com/doumee/core/douyin/dto/DouyinCancelReq.java                   |   31 
 server/services/src/main/java/com/doumee/dao/business/DouyinProductMapper.java                  |   14 
 server/services/src/main/java/com/doumee/dao/business/web/request/OperationOrderQueryDTO.java   |   26 
 server/接口变更说明.txt                                                                               |   69 
 server/services/src/main/java/com/doumee/service/business/impl/ReportServiceImpl.java           |  620 ++++++
 server/services/src/main/java/com/doumee/core/douyin/DouyinProperties.java                      |   26 
 server/services/src/main/java/com/doumee/core/douyin/dto/DouyinClientTokenResp.java             |   37 
 server/services/src/main/java/com/doumee/service/business/impl/DouyinProductServiceImpl.java    |  293 +++
 server/platform/src/main/java/com/doumee/api/business/DouyinProductController.java              |   60 
 server/services/src/main/java/com/doumee/dao/business/vo/OverviewStatVO.java                    |   29 
 server/services/src/main/java/com/doumee/dao/business/DouyinProductSkuMapper.java               |   13 
 server/services/src/main/java/com/doumee/dao/business/MemberRidesTrackMapper.java               |   14 
 server/services/src/main/java/com/doumee/dao/business/model/DouyinVerifyRecord.java             |  101 +
 server/services/src/main/java/com/doumee/core/douyin/dto/DouyinSkuDTO.java                      |   40 
 server/services/src/main/java/com/doumee/dao/business/vo/IncomeDailyVO.java                     |   27 
 server/services/src/main/java/com/doumee/dao/business/DouyinVerifyLogMapper.java                |   13 
 server/services/src/main/java/com/doumee/service/business/DouyinVerifyLogService.java           |   17 
 server/services/src/main/java/com/doumee/core/douyin/dto/DouyinBaseResp.java                    |   52 
 server/services/src/main/java/com/doumee/service/business/impl/DouyinVerifyLogServiceImpl.java  |   28 
 server/platform/src/main/java/com/doumee/api/business/DouyinVerifyController.java               |  149 +
 server/services/src/main/java/com/doumee/service/business/impl/DouyinVerifyServiceImpl.java     |  573 ++++++
 server/services/src/main/java/com/doumee/dao/business/vo/BikeIncomeStatVO.java                  |   31 
 server/services/src/main/java/com/doumee/core/douyin/dto/DouyinVerifyResp.java                  |   59 
 server/services/src/main/java/com/doumee/dao/business/vo/OperationCenterVO.java                 |   41 
 server/db/changeSql.sql                                                                         |  282 +++
 server/web/src/main/java/com/doumee/api/web/ReportController.java                               |  116 +
 server/services/src/main/java/com/doumee/core/douyin/DouyinClient.java                          |  491 +++++
 server/services/src/main/java/com/doumee/core/douyin/dto/DouyinCancelResp.java                  |   51 
 server/services/src/main/resources/application-dev.yml                                          |    7 
 server/services/src/main/java/com/doumee/core/douyin/dto/DouyinClientTokenReq.java              |   27 
 server/services/src/main/java/com/doumee/service/business/ReportService.java                    |   71 
 server/services/src/main/java/com/doumee/dao/business/model/DouyinVerifyLog.java                |   65 
 server/services/src/main/java/com/doumee/core/track/RideActiveInfo.java                         |   21 
 server/services/src/main/java/com/doumee/dao/business/vo/OrderRideTrackVO.java                  |   31 
 server/services/src/main/java/com/doumee/dao/business/vo/OrderRideItemVO.java                   |   50 
 server/services/src/main/java/com/doumee/service/business/DouyinVerifyService.java              |   54 
 server/services/src/main/java/com/doumee/core/track/RideActiveCache.java                        |   92 +
 server/services/src/main/java/com/doumee/service/system/SystemDictDataService.java              |   19 
 server/services/src/main/java/com/doumee/dao/business/model/DouyinProduct.java                  |   83 
 server/web/src/main/resources/application.yml                                                   |    2 
 server/services/src/main/java/com/doumee/dao/business/web/response/DouyinConfigDTO.java         |   31 
 75 files changed, 5,507 insertions(+), 2 deletions(-)

diff --git a/server/.claude/plans/douyin-verify-page-and-cancel-migration.md b/server/.claude/plans/douyin-verify-page-and-cancel-migration.md
new file mode 100644
index 0000000..b4c61d4
--- /dev/null
+++ b/server/.claude/plans/douyin-verify-page-and-cancel-migration.md
@@ -0,0 +1,88 @@
+# 鏍搁攢璁板綍瀵瑰鍒嗛〉 + 鎾ら攢鏍搁攢杩佺Щ绠$悊绔�
+
+> 鐘舵��:**璁″垝宸插畾,寰呮墽琛�**(2026-06-26 鏆傚瓨,鐢ㄦ埛浼樺厛澶勭悊鍒殑闂)
+> 鍏宠仈闇�姹�:鍒哥爜鏍搁獙璁板綍澧炲姞瀵瑰鍒嗛〉鎺ュ彛 + web 绔挙閿�鏍搁攢杩佺Щ鍒扮鐞嗙
+
+## 宸茬‘璁ゅ彛寰�(AskUserQuestion 绛斿)
+
+1. 銆岃鍗曠紪鍙�(discount_member)銆�= **`discount_member.goodsorder_id`**(鏍搁攢鏃惰嚜鍔ㄥ缓鐨� goodsorder 璁㈠崟缂栫爜)
+2. 銆屽洟璐晢鍝佸悕绉般��= `douyin_product.product_name`;銆屾姈闊冲埜鍚嶇О銆�= **`discount_member.name`**(鏈湴濂楅鍚�)
+3. 鎾ら攢鏍搁攢杩佺Щ绠$悊绔悗 **鍙栨秷銆屾牳閿�鍚�1灏忔椂鍐呫�嶉檺鍒�**,杩愯惀闅忔椂鍙挙閿�
+4. web 绔師 `/web/douyin/cancel` **鍒犻櫎**,绾縼绉诲埌绠$悊绔�
+5. 銆屽厬鎹汉銆�= **`member.name`**(浼氬憳濮撳悕);鎵嬫満鍙疯劚鏁忚鍒� `138****1234`(鍓�3鍚�4,<7浣嶅師鏍�)
+6. 鎾ら攢鏍搁攢**鍚屾浣滃簾**鏈湴濂楅鍗�(`discount_member.status`=1 浣滃簾)+ 璁� `discount_log`
+7. 鏌ヨ鏉′欢澧炲姞**鎾ら攢鐘舵��**(`cancel_status` 0鏈挙閿�/1宸叉挙閿�)绛涢��
+8. 绠$悊绔挙閿�**钀� `douyin_verify_log`**(operateType=CANCEL),娌跨敤灏忕▼搴忔牳閿�瀹¤閾捐矾
+
+## 瀛楁鏄犲皠
+
+### 鏌ヨ鏉′欢(3 椤�)
+| 鏉′欢 | 瀛楁 |
+|---|---|
+| 鎶栭煶鍒哥爜 | `verify_record.origin_code`(eq,鍒哥爜鍞竴) |
+| 楠屽埜鐘舵�� | `verify_record.verify_status`(eq,0鎴愬姛 1澶辫触) |
+| 鎾ら攢鐘舵�� | `verify_record.cancel_status`(eq,0鏈挙閿� 1宸叉挙閿�) |
+
+### 杩斿洖 VO(`DouyinVerifyRecordPageVO`)
+| 鍒楄〃瀛楁 | 鏉ユ簮 | 鍏宠仈 |
+|---|---|---|
+| 璁㈠崟缂栧彿 | `discount_member.goodsorder_id` | LEFT JOIN discount_member ON `discount_member_id` |
+| 浼氬憳openid | `member.openid` | LEFT JOIN member ON `discount_member.member_id` |
+| 浼氬憳鎵嬫満鍙�(鑴辨晱) | `member.phone` 鈫� `138****1234` | 鍚屼笂 join,service 閬嶅巻鑴辨晱 |
+| 鍥㈣喘鍟嗗搧鍚嶇О | `douyin_product.product_name` | LEFT JOIN douyin_product ON `verify.product_id = product.product_id`(闈炰富閿�) |
+| 鎶栭煶鍒稿悕绉� | `discount_member.name` | discount_member join |
+| 鎶栭煶鍒哥被鍨�(绫荤洰) | `douyin_product.category` | douyin_product join |
+| 楠屽埜鏃堕棿 | `verify_record.verify_time` | 涓昏〃 |
+| 鍏戞崲浜� | `member.name` | 鏍搁攢鎿嶄綔浜�=璐拱浼氬憳鏈汉,澶嶇敤 member join 鍙栧鍚� |
+| 鐘舵�� | `verify_status`+`cancel_status` 缁煎悎鏂囨 `statusName`(宸插厬鎹�/宸叉挙閿�/鏍搁攢澶辫触)+ 鍘熷�� | 涓昏〃 |
+
+> 鍏戞崲浜�:`verify_user_id` 涓� `discount_member.member_id` 鍚屼负鏍搁攢浼氬憳 id(鏍搁攢鏃� operator 鍗冲椁愬綊灞炰汉),澶嶇敤鍚屼竴娆� member join 鍙栧鍚嶃��
+> 鑴辨晱瑙勫垯:`138****1234`(鍓�3鍚�4,涓棿4浣�*),<7 浣嶅師鏍疯繑鍥炪��
+
+## MPJ 鍏宠仈璁捐
+
+涓昏〃 `douyin_verify_record`(鍒悕 `t`)
+- LEFT JOIN `discount_member` dm ON `t.discount_member_id = dm.id`
+- LEFT JOIN `member` m ON `dm.member_id = m.id`
+- LEFT JOIN `douyin_product` p ON `t.product_id = p.product_id`(**闈炰富閿瓧娈�**,MPJ leftJoin 鏀寔浠绘剰瀛楁)
+
+selectAs:dm.goodsorder_id鈫抩rderCode銆乵.openid鈫抦emberOpenid銆乵.phone鈫抦emberPhone(鑴辨晱)銆乸.product_name鈫抪roductName銆乨m.name鈫抍ouponName銆乸.category鈫抍ategory銆乵.name鈫抏xchangerName;selectAll(DouyinVerifyRecord)甯� id/verify_time/verify_status/cancel_status/origin_code 绛夈��
+
+## 鏂囦欢鏀瑰姩娓呭崟
+
+### 鏂板
+1. `services/src/main/java/com/doumee/dao/business/vo/DouyinVerifyRecordPageVO.java` 鈥� 鍒嗛〉杩斿洖 VO(鍚� originCode/verifyStatus 鏌ヨ鍏ュ弬 + 娓叉煋瀛楁 + statusName)
+2. `platform/src/main/java/com/doumee/api/business/DouyinVerifyController.java` 鈥� `@RequestMapping("/business/douyinVerify")`
+   - `POST /page` 鈫� findManagePage,`@RequiresPermissions("business:douyinVerify:query")`
+   - `POST /cancel` 鈫� cancel,`@RequiresPermissions("business:douyinVerify:cancel")`,`@PreventRepeat`
+
+### 淇敼
+3. `services/src/main/java/com/doumee/dao/business/DouyinVerifyRecordMapper.java` 鈥� `BaseMapper` 鈫� `MPJJoinMapper`(娌跨敤 DouyinProductMapper 妯″紡,鏀寔 selectJoinPage)
+4. `services/src/main/java/com/doumee/service/business/DouyinVerifyService.java` 鈥� 鏂板 `findManagePage(PageWrap<DouyinVerifyRecordPageVO>) -> PageData<DouyinVerifyRecordPageVO>`(**涓嶅姩**鐜版湁 `findPage`,web `/web/douyin/page` 浠嶇敤)
+5. `services/src/main/java/com/doumee/service/business/impl/DouyinVerifyServiceImpl.java`
+   - 鏂板 `findManagePage`:MPJ 涓夎〃 leftJoin + selectAs 鏄犲皠 + 鎵嬫満鍙疯劚鏁� + statusName 鏂囨
+   - **鍒犻櫎 cancel 鐨勩�屾牳閿�鍚�1灏忔椂鍐呫�嶇獥鍙f鏌�**(鐜� line 374-378)
+   - cancel 鎴愬姛鍚�**浣滃簾濂楅鍗�**:缁� `rec.discountMemberId` 鎶� `discount_member.status` 缃� 1 + 璁� `discount_log`(鍙傜収 backGoodsorder 閫�鍗″啓娉�)
+   - cancel 鍔� `@Transactional`(鎶栭煶鎾ら攢/浣滃簾濂楅/璁板綍鏇存柊鍘熷瓙);鎾ら攢鏃ュ織鐢� controller 钀� douyin_verify_log
+6. `web/src/main/java/com/doumee/api/web/DouyinApi.java` 鈥� 鍒� `cancel` 鏂规硶 + 浠呭叾鐢ㄧ殑 `OPERATE_CANCEL` 甯搁噺 + `fillByRecord` 鐨� cancel 鍒嗘敮 / `CANCEL_DONE`;娓呯悊 `DouyinCancelParam` import
+7. `services/src/main/java/com/doumee/core/douyin/dto/DouyinCancelParam.java` 鈥� 娉ㄩ噴鐢便�寃eb 绔皬绋嬪簭銆嶆敼涓恒�岀鐞嗙銆�
+8. `db/Shiro.sql` 鈥� 骞傜瓑鐧昏 `business:douyinVerify:query`銆乣business:douyinVerify:cancel` + 鎺堟潈 role 1
+
+## 鍙栬垗 / 涓嶅姩椤�
+- **涓嶅姩** `findPage`(web 绔� `/web/douyin/page` 淇濈暀)銆乿erify/prepare 閾捐矾銆佸晢鍝佸悓姝�/缁戝畾銆乣openDiscountForVerify`銆�
+- **鎾ら攢琛屼负(澧炲己)**:鎶栭煶鎾ら攢鎴愬姛鍚� 鈫� 浣滃簾鏈湴濂楅鍗�(`discount_member.status`=1)+ 璁� `discount_log` + 鏇存柊 verify_record(cancel_status/time/user)銆�
+- **鎾ら攢鏃ュ織**:绠$悊绔挙閿�钀� `douyin_verify_log`(operateType=CANCEL,娌跨敤 web 绔偅濂�);cancel service 鍔� `@Transactional`,鎶栭煶鎾ら攢/浣滃簾濂楅/璁板綍鏇存柊鍘熷瓙銆�
+
+## 楠岃瘉
+- `mvn -pl platform -am clean compile -o`(services + web + platform 鍏ㄩ噺)
+- 鎵ц `db/Shiro.sql`(骞傜瓑)
+- 鎵嬫祴 `/doc.html`:`POST /business/douyinVerify/page`(鍒哥爜/鐘舵�佺瓫閫� + 鍚勫瓧娈靛洖濉� + 鎵嬫満鍙疯劚鏁�)銆乣POST /business/douyinVerify/cancel`(瓒呮椂涔熻兘鎾ら攢)
+
+## 鍏抽敭鐜扮姸澶囧繕(鎵ц鏃跺弬鑰�)
+- `Member.phone`(model line 96)銆乣Member.openid`(line 66);`discount_member.code`(绁ㄥ彿)銆乣goodsorder_id`;`member.id` 鍏宠仈 `discount_member.member_id`
+- platform 鍙栫櫥褰曠敤鎴�:`BaseController.getLoginUser().getId()`(Shiro `LoginUserInfo`)
+- `DouyinVerifyRecordMapper` 鐜颁负 `BaseMapper`(寰呮敼 MPJJoinMapper)
+- cancel 鍏ュ弬 `DouyinCancelParam{id}`;cancel 1 灏忔椂绐楀彛鍦� `DouyinVerifyServiceImpl` line 374-378
+- `douyin_product.product_id` 涓轰笟鍔″敮涓�閿�(upsertProduct 鎸� product_id selectOne 鏃� limit,瑙嗕负鍞竴),LEFT JOIN 涓嶄細鑶ㄨ儉
+- `DiscountMemberServiceImpl` 鏄� MPJ leftJoin + selectAs + 瀛愭煡璇㈡爣閲忕殑榛勯噾鍏堜緥
+- 鎾ら攢浣滃簾濂楅鍗″啓娉曞弬鐓� `GoodsorderServiceImpl.backGoodsorder`(line 1020-1042):鎸� `rec.discountMemberId` 鏌� discount_member,浠� status=0 鎵嶄綔搴�(update set status=1 where id)+ 璁� DiscountLog(type=1, editInfo="鎾ら攢鏍搁攢浣滃簾", creator=operator, discountMemberId, goodsorderId=dm.goodsorderId);宸蹭綔搴熻烦杩�(骞傜瓑)
diff --git a/server/.gitignore b/server/.gitignore
index 80ea99d..e1fa7c0 100644
--- a/server/.gitignore
+++ b/server/.gitignore
@@ -24,3 +24,4 @@
 # Package Files #
 *.jar
 *.war
+/CLAUDE.md
diff --git a/server/db/Shiro.sql b/server/db/Shiro.sql
new file mode 100644
index 0000000..5dd6169
--- /dev/null
+++ b/server/db/Shiro.sql
@@ -0,0 +1,112 @@
+-- ============================================================
+-- Shiro 鏉冮檺鍒濆鍖� 鈥斺�� 鎶栭煶鍟嗗搧(鍚屾 / 鏌ヨ / 缁戝畾濂楅)
+-- 鏁版嵁搴�: PostgreSQL (park_bike, system_* 绯诲垪琛�)
+-- 浣滆��  : rk
+-- 鏃ユ湡  : 2026-06-25
+-- 璇存槑  :
+--   1) 鏈剼鏈�愬箓绛夈��,鍙噸澶嶆墽琛�,涓嶄骇鐢熼噸澶嶆潈闄愭垨閲嶅鎺堟潈銆�
+--   2) Shiro 鐨� @RequiresPermissions 鍖归厤 system_permission.code銆�
+--   3) 鎶栭煶銆愰獙鍒搞�戠被鎺ュ彛涓� web 绔娇鐢�(JWT 閴存潈),涓嶈蛋 Shiro,
+--      鏁呬笉鍦ㄦ澶勭櫥璁版潈闄愩��
+--   4) 榛樿鎺堟潈缁欒秴绠¤鑹� id = 1;鑻ヤ笉鍚岃鏇挎崲銆傝彍鍗�(system_menu)鍙﹁閰嶇疆銆�
+-- ============================================================
+
+
+-- ------------------------------------------------------------
+-- 涓�銆佹姈闊冲晢鍝佹潈闄愬畾涔�(system_permission)
+-- ------------------------------------------------------------
+
+INSERT INTO system_permission (code, name, remark, fixed, deleted, create_time, update_time)
+SELECT 'business:douyinProduct:sync', '鎶栭煶鍟嗗搧-鍚屾', '浠庢姈闊冲悓姝ュ洟璐晢鍝佸埌鏈湴', 0, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
+WHERE NOT EXISTS (SELECT 1 FROM system_permission WHERE code = 'business:douyinProduct:sync' AND deleted = 0);
+
+INSERT INTO system_permission (code, name, remark, fixed, deleted, create_time, update_time)
+SELECT 'business:douyinProduct:query', '鎶栭煶鍟嗗搧-鏌ヨ', '鎶栭煶鍟嗗搧鍒嗛〉/璇︽儏/鑱旇皟娴嬭瘯鏌ヨ', 0, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
+WHERE NOT EXISTS (SELECT 1 FROM system_permission WHERE code = 'business:douyinProduct:query' AND deleted = 0);
+
+INSERT INTO system_permission (code, name, remark, fixed, deleted, create_time, update_time)
+SELECT 'business:douyinProduct:bind', '鎶栭煶鍟嗗搧-缁戝畾濂楅', '灏嗘姈闊冲晢鍝� out_id 缁戝畾鏈湴濂楅(discount.id),绌哄�艰В缁�', 0, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
+WHERE NOT EXISTS (SELECT 1 FROM system_permission WHERE code = 'business:douyinProduct:bind' AND deleted = 0);
+
+
+-- ------------------------------------------------------------
+-- 浜屻�佹巿鏉冪粰瓒呯骇绠$悊鍛樿鑹�(system_role_permission,榛樿 id = 1)
+-- ------------------------------------------------------------
+
+INSERT INTO system_role_permission (role_id, permission_id, deleted, create_time, update_time)
+SELECT 1 AS role_id, p.id AS permission_id, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
+FROM system_permission p
+WHERE p.deleted = 0
+  AND p.code IN (
+       'business:douyinProduct:sync',
+       'business:douyinProduct:query',
+       'business:douyinProduct:bind'
+  )
+  AND NOT EXISTS (
+       SELECT 1 FROM system_role_permission rp
+       WHERE rp.role_id = 1 AND rp.permission_id = p.id AND rp.deleted = 0
+  );
+
+
+-- ============================================================
+-- 闄�:缁欍�愬叾瀹冭鑹层�戞巿鏉冪殑妯℃澘(鎸夐渶鍙栨秷娉ㄩ噴浣跨敤)
+-- ------------------------------------------------------------
+-- INSERT INTO system_role_permission (role_id, permission_id, deleted, create_time, update_time)
+-- SELECT r.id, p.id, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
+-- FROM system_permission p
+-- CROSS JOIN system_role r
+-- WHERE p.deleted = 0
+--   AND r.deleted = 0
+--   AND r.name LIKE '%杩愯惀%'                  -- 鈫� 鏀规垚鐩爣瑙掕壊鍚嶅叧閿瓧
+--   AND p.code LIKE 'business:douyinProduct%'
+--   AND NOT EXISTS (
+--        SELECT 1 FROM system_role_permission rp
+--        WHERE rp.role_id = r.id AND rp.permission_id = p.id AND rp.deleted = 0
+--   );
+-- ============================================================
+
+
+-- ============================================================
+-- 鏁版嵁鎶ヨ〃(姒傝 / 鏀跺叆杞﹀瀷鍒嗘瀽 / 鏀跺叆缁熻)鈥斺�� 涓嶇櫥璁版潈闄愮偣
+-- 浣滆��  : rk
+-- 鏃ユ湡  : 2026-06-26
+-- 璇存槑  :
+--   1) 鏁版嵁鎶ヨ〃鎺ュ彛鐢� web 绔�(JWT)杩佺Щ鑷� platform 绔�(/business/report/*)銆�
+--   2) 銆愰厤缃害瀹氥�戞姤琛ㄧ被涓哄彧璇荤粺璁�,涓嶅仛鑿滃崟/鎸夐挳绾ф潈闄愰檺鍒�:
+--      Controller 涓婁笉鎸� @RequiresPermissions,浠呭彈 Shiro authc 鐧诲綍鏍¢獙淇濇姢,
+--      浠讳綍鐧诲綍鍚庡彴鐨勮处鍙峰潎鍙闂�;鏁呬笉鐧昏 system_permission 鏉冮檺鐐�,
+--      涔熶笉鍐� system_role_permission 鎺堟潈銆�
+--   3) 濡傛棩鍚庨渶瑕佺粏绮掑害鏉冮檺,鍐嶄簬姝よ拷鍔� business:report:query 瀹氫箟骞舵巿鏉冦��
+-- ============================================================
+
+
+-- ============================================================
+-- 鎶栭煶鍒告牳閿�(绠$悊绔�:鏍搁攢璁板綍鍒嗛〉 / 鎾ら攢鏍搁攢)
+-- 浣滆��  : rk
+-- 鏃ユ湡  : 2026-06-26
+-- 璇存槑  :
+--   1) 鎾ら攢鏍搁攢鐢� web 绔�(/web/douyin/cancel,JWT)杩佺Щ鑷� platform 绔�(/business/douyinVerify/cancel,Shiro)銆�
+--   2) 鏍搁攢璁板綍瀵瑰鍒嗛〉 /business/douyinVerify/page 浜﹀湪 platform 绔��
+--   3) 骞傜瓑鐧昏 query/cancel 涓や釜鏉冮檺鐐�,榛樿鎺堟潈瓒呯 role_id = 1銆�
+-- ============================================================
+
+INSERT INTO system_permission (code, name, remark, fixed, deleted, create_time, update_time)
+SELECT 'business:douyinVerify:query', '鎶栭煶鏍搁攢-鏌ヨ', '鎶栭煶鍒告牳閿�璁板綍瀵瑰鍒嗛〉鏌ヨ', 0, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
+WHERE NOT EXISTS (SELECT 1 FROM system_permission WHERE code = 'business:douyinVerify:query' AND deleted = 0);
+
+INSERT INTO system_permission (code, name, remark, fixed, deleted, create_time, update_time)
+SELECT 'business:douyinVerify:cancel', '鎶栭煶鏍搁攢-鎾ら攢', '鎾ら攢鎶栭煶鍒告牳閿�(绠$悊绔�,浣滃簾鏈湴濂楅鍗�)', 0, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
+WHERE NOT EXISTS (SELECT 1 FROM system_permission WHERE code = 'business:douyinVerify:cancel' AND deleted = 0);
+
+INSERT INTO system_role_permission (role_id, permission_id, deleted, create_time, update_time)
+SELECT 1 AS role_id, p.id AS permission_id, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
+FROM system_permission p
+WHERE p.deleted = 0
+  AND p.code IN (
+       'business:douyinVerify:query',
+       'business:douyinVerify:cancel'
+  )
+  AND NOT EXISTS (
+       SELECT 1 FROM system_role_permission rp
+       WHERE rp.role_id = 1 AND rp.permission_id = p.id AND rp.deleted = 0
+  );
diff --git a/server/db/changeSql.sql b/server/db/changeSql.sql
new file mode 100644
index 0000000..d0110fe
--- /dev/null
+++ b/server/db/changeSql.sql
@@ -0,0 +1,282 @@
+-- ============================================================
+-- 鏁版嵁搴撳彉鏇磋褰� (changeSql.sql)
+-- 瑙勫垯:姣忔琛ㄧ粨鏋�/鍩虹鏁版嵁鍙樻洿,鍦ㄦ枃浠躲�愭湯灏捐拷鍔犮�戜竴涓柊鍧�,
+--       涓嶈淇敼鎴栧垹闄ゅ巻鍙插潡銆傛墽琛屾椂鎸変粠涓婂埌涓嬮『搴忎緷娆℃墽琛屾柊澧為儴鍒嗐��
+-- 鏁版嵁搴�: PostgreSQL (park_bike)
+-- ============================================================
+
+
+-- ------------------------------------------------------------
+-- 2026-06-22  v3.0.1  鎺ュ叆鎶栭煶鍒告牳閿� 鈥斺�� 鏂板鎶栭煶鍟嗗搧琛�
+-- 浣滆��: rk
+-- 璇存槑: 鐢ㄤ簬钀藉湴鎶栭煶銆屾煡璇㈠晢鍝佺嚎涓婃暟鎹垪琛ㄣ�嶆帴鍙e悓姝ヨ繃鏉ョ殑鍥㈣喘鍟嗗搧
+-- ------------------------------------------------------------
+
+-- 鎶栭煶鍟嗗搧涓昏〃
+CREATE TABLE "douyin_product" (
+  "id"             varchar(64)   NOT NULL,
+  "product_id"     varchar(64)   NOT NULL,
+  "out_id"         varchar(128),
+  "product_name"   varchar(255),
+  "category"       varchar(64),
+  "product_type"   smallint,
+  "online_status"  smallint      DEFAULT 1,
+  "account_id"     varchar(64),
+  "sync_date"      timestamp,
+  "raw_content"    text,
+  "create_date"    timestamp     DEFAULT CURRENT_TIMESTAMP,
+  "creator"        varchar(64),
+  "edit_date"      timestamp,
+  "editor"         varchar(64),
+  "isdeleted"      smallint      DEFAULT 0,
+  PRIMARY KEY ("id")
+);
+CREATE UNIQUE INDEX uk_douyin_product_product_id ON "douyin_product" ("product_id") WHERE "isdeleted" = 0;
+COMMENT ON TABLE  "douyin_product" IS '鎶栭煶鍟嗗搧(鍥㈣喘)绾夸笂鏁版嵁';
+COMMENT ON COLUMN "douyin_product"."product_id"    IS '鎶栭煶鍟嗗搧ID(涓氬姟鍞竴閿�,鐢ㄤ簬upsert)';
+COMMENT ON COLUMN "douyin_product"."out_id"        IS '澶栭儴鍟嗗搧ID';
+COMMENT ON COLUMN "douyin_product"."online_status" IS '鍦ㄧ嚎鐘舵�� 1鍦ㄧ嚎 2涓嬬嚎 3灏佺';
+COMMENT ON COLUMN "douyin_product"."account_id"    IS '鏉ュ鍟嗘埛鏍硅处鎴稩D';
+COMMENT ON COLUMN "douyin_product"."sync_date"     IS '鏈�杩戝悓姝ユ椂闂�';
+COMMENT ON COLUMN "douyin_product"."raw_content"   IS '鎶栭煶鍘熷鍝嶅簲蹇収(渚夸簬杩芥函)';
+COMMENT ON COLUMN "douyin_product"."isdeleted"     IS '鏄惁宸插垹闄� 0鏈垹闄� 1宸插垹闄�';
+
+-- 鎶栭煶鍟嗗搧 SKU 瀛愯〃
+CREATE TABLE "douyin_product_sku" (
+  "id"             varchar(64)   NOT NULL,
+  "product_id"     varchar(64)   NOT NULL,
+  "sku_id"         varchar(64),
+  "title"          varchar(255),
+  "third_sku_id"   varchar(128),
+  "sku_out_id"     varchar(128),
+  "market_price"   bigint,
+  "groupon_type"   smallint,
+  "voucher_type"   smallint,
+  "create_date"    timestamp     DEFAULT CURRENT_TIMESTAMP,
+  "edit_date"      timestamp,
+  "isdeleted"      smallint      DEFAULT 0,
+  PRIMARY KEY ("id")
+);
+CREATE INDEX idx_douyin_product_sku_pid ON "douyin_product_sku" ("product_id");
+COMMENT ON TABLE  "douyin_product_sku" IS '鎶栭煶鍟嗗搧SKU';
+COMMENT ON COLUMN "douyin_product_sku"."product_id"   IS '鍏宠仈 douyin_product.product_id(鎶栭煶鍟嗗搧ID)';
+COMMENT ON COLUMN "douyin_product_sku"."market_price" IS '甯傚満浠�(鍒�)';
+COMMENT ON COLUMN "douyin_product_sku"."isdeleted"    IS '鏄惁宸插垹闄� 0鏈垹闄� 1宸插垹闄�';
+
+
+-- ------------------------------------------------------------
+-- 2026-06-22  v3.0.1  鎺ュ叆鎶栭煶鍒告牳閿� 鈥斺�� 鏂板鎶栭煶鍒告牳閿�璁板綍琛�
+-- 浣滆��: rk
+-- 璇存槑: 璁板綍鎶栭煶鍥㈣喘鍒哥殑楠屽埜(鏍搁攢)/鎾ら攢娴佹按,鎾ら攢鏍搁攢涓庡璐︿緷璧栨湰琛�
+-- ------------------------------------------------------------
+
+CREATE TABLE "douyin_verify_record" (
+  "id"              varchar(64)   NOT NULL,
+  "verify_id"       varchar(64),
+  "certificate_id"  varchar(64),
+  "order_id"        varchar(64),
+  "origin_code"     varchar(128),
+  "encrypted_code"  varchar(255),
+  "poi_id"          varchar(64),
+  "account_id"      varchar(64),
+  "product_id"      varchar(64),
+  "product_name"    varchar(255),
+  "pay_amount"      bigint,
+  "verify_status"   smallint      DEFAULT 1,
+  "verify_time"     timestamp,
+  "verify_user_id"  varchar(64),
+  "verify_msg"      varchar(255),
+  "cancel_status"   smallint      DEFAULT 0,
+  "cancel_time"     timestamp,
+  "cancel_user_id"  varchar(64),
+  "cancel_msg"      varchar(255),
+  "raw_request"     text,
+  "raw_response"    text,
+  "create_date"     timestamp     DEFAULT CURRENT_TIMESTAMP,
+  "creator"         varchar(64),
+  "edit_date"       timestamp,
+  "editor"          varchar(64),
+  "isdeleted"       smallint      DEFAULT 0,
+  PRIMARY KEY ("id")
+);
+CREATE INDEX idx_dvr_verify_id  ON "douyin_verify_record" ("verify_id");
+CREATE INDEX idx_dvr_cert_id    ON "douyin_verify_record" ("certificate_id");
+CREATE INDEX idx_dvr_order_id   ON "douyin_verify_record" ("order_id");
+COMMENT ON TABLE  "douyin_verify_record" IS '鎶栭煶鍒告牳閿�璁板綍';
+COMMENT ON COLUMN "douyin_verify_record"."verify_id"      IS '楠屽埜杩斿洖鐨勪竴娆℃牳閿�鍞竴鏍囪瘑,鎾ら攢蹇呯敤';
+COMMENT ON COLUMN "douyin_verify_record"."certificate_id" IS '鍒告爣璇�,鎾ら攢蹇呯敤';
+COMMENT ON COLUMN "douyin_verify_record"."encrypted_code" IS '鍔犲瘑鍒哥爜(prepare杩斿洖,verify鍏ュ弬)';
+COMMENT ON COLUMN "douyin_verify_record"."pay_amount"     IS '瀹炰粯閲戦(鍒�)';
+COMMENT ON COLUMN "douyin_verify_record"."verify_status"  IS '鏍搁攢缁撴灉 0鎴愬姛 1澶辫触';
+COMMENT ON COLUMN "douyin_verify_record"."cancel_status"  IS '鎾ら攢鐘舵�� 0鏈挙閿� 1宸叉挙閿�';
+COMMENT ON COLUMN "douyin_verify_record"."raw_request"    IS '璇锋眰蹇収(渚夸簬杩芥函)';
+COMMENT ON COLUMN "douyin_verify_record"."raw_response"   IS '鍝嶅簲蹇収(渚夸簬杩芥函)';
+COMMENT ON COLUMN "douyin_verify_record"."isdeleted"      IS '鏄惁宸插垹闄� 0鏈垹闄� 1宸插垹闄�';
+
+
+-- ------------------------------------------------------------
+-- 2026-06-25  v3.0.1  鎶栭煶鍟嗗搧 out_id 璇箟鍙樻洿 鈥斺�� 鏀圭敱绠$悊绔粦瀹氭湰鍦板椁�
+-- 浣滆��: rk
+-- 璇存槑: out_id 涓嶅啀鐢辨姈闊冲悓姝ュ啓鍏�,鏀逛负绠$悊绔粦瀹氭湰鍦板椁愪富閿�(discount.id)銆�
+--       瀛楁缁撴瀯涓嶅彉,浠呮洿鏂板垪娉ㄩ噴(瑕嗙洊鏃ф敞閲�,骞傜瓑鍙噸澶嶆墽琛�);
+--       鍚屾閫昏緫 upsertProduct 宸插仠姝㈢敤鎶栭煶杩斿洖鐨� out_id 瑕嗙洊鏈湴鍊笺��
+-- ------------------------------------------------------------
+
+COMMENT ON COLUMN "douyin_product"."out_id" IS '缁戝畾鏈湴濂楅ID(discount.id,绠$悊绔淮鎶�,鎶栭煶鍚屾涓嶅啓鍏�)';
+
+
+-- ------------------------------------------------------------
+-- 2026-06-25  v3.0.1  鎶栭煶楠屽埜鎿嶄綔鏃ュ織琛� 鈥斺�� web 绔帴鍙f搷浣滄祦姘�
+-- 浣滆��: rk
+-- 璇存槑: 璁板綍 web 绔� prepare/verify/cancel 姣忔璋冪敤鐨勬搷浣滄祦姘�(璋�/浣曟椂/缁撴灉/鑰楁椂/IP)銆�
+--       瀹屾暣瀹¤:鍚姹傚叆鍙備笌鎶栭煶鍝嶅簲鍘熸枃;鏍搁攢涓氬姟鏁版嵁浠嶇敱 douyin_verify_record 鎵胯浇銆�
+-- ------------------------------------------------------------
+
+CREATE TABLE "douyin_verify_log" (
+  "id"               varchar(64)   NOT NULL,
+  "operate_type"     smallint,
+  "api_path"         varchar(64),
+  "member_id"        varchar(64),
+  "verify_record_id" varchar(64),
+  "poi_id"           varchar(64),
+  "origin_code"      varchar(128),
+  "result"           smallint,
+  "error_msg"        varchar(500),
+  "raw_request"      text,
+  "raw_response"     text,
+  "ip"               varchar(64),
+  "cost_ms"          integer,
+  "create_date"      timestamp     DEFAULT CURRENT_TIMESTAMP,
+  "isdeleted"        smallint      DEFAULT 0,
+  PRIMARY KEY ("id")
+);
+CREATE INDEX idx_dvl_member ON "douyin_verify_log" ("member_id");
+CREATE INDEX idx_dvl_record ON "douyin_verify_log" ("verify_record_id");
+COMMENT ON TABLE  "douyin_verify_log" IS '鎶栭煶楠屽埜鎿嶄綔鏃ュ織(web绔帴鍙f搷浣滄祦姘�)';
+COMMENT ON COLUMN "douyin_verify_log"."operate_type"     IS '鎿嶄綔绫诲瀷 0楠屽埜鍑嗗 1鏍搁攢 2鎾ら攢鏍搁攢';
+COMMENT ON COLUMN "douyin_verify_log"."api_path"         IS '鎺ュ彛璺緞';
+COMMENT ON COLUMN "douyin_verify_log"."member_id"        IS '鎿嶄綔浜轰細鍛業D';
+COMMENT ON COLUMN "douyin_verify_log"."verify_record_id" IS '鍏宠仈鏍搁攢璁板綍 douyin_verify_record.id';
+COMMENT ON COLUMN "douyin_verify_log"."poi_id"           IS '鏍搁攢闂ㄥ簵';
+COMMENT ON COLUMN "douyin_verify_log"."origin_code"      IS '鍒哥爜蹇収';
+COMMENT ON COLUMN "douyin_verify_log"."result"           IS '鎿嶄綔缁撴灉 0鎴愬姛 1澶辫触';
+COMMENT ON COLUMN "douyin_verify_log"."error_msg"        IS '澶辫触鎻忚堪';
+COMMENT ON COLUMN "douyin_verify_log"."raw_request"      IS '璇锋眰鍏ュ弬蹇収(JSON)';
+COMMENT ON COLUMN "douyin_verify_log"."raw_response"     IS '鎶栭煶鍝嶅簲鍘熸枃蹇収';
+COMMENT ON COLUMN "douyin_verify_log"."ip"               IS '璇锋眰IP';
+COMMENT ON COLUMN "douyin_verify_log"."cost_ms"          IS '鑰楁椂(姣)';
+COMMENT ON COLUMN "douyin_verify_log"."isdeleted"        IS '鏄惁宸插垹闄� 0鏈垹闄� 1宸插垹闄�';
+
+
+-- ------------------------------------------------------------
+-- 2026-06-25  v3.0.1  鎶栭煶鏍搁攢寮�濂楅 鈥斺�� 鏍搁攢璁板綍鍔犲椁愬崱ID鍒�
+-- 浣滆��: rk
+-- 璇存槑: 鏍搁攢鎴愬姛寮�閫� discount_member 鍚�,鍥炲~濂楅鍗D鍒版牳閿�璁板綍,渚夸簬杩芥函銆�
+-- 娉ㄦ剰: goodsorder.payWay 鏂板鍙栧�� 2=鎶栭煶鍒告牳閿�,鍓嶇鏀粯鏂瑰紡灞曠ず闇�閰嶅悎(鑻ュ瓨鍦ㄥ瓧鍏歌〃鍒欏悓姝ユ柊澧�)銆�
+-- ------------------------------------------------------------
+
+ALTER TABLE "douyin_verify_record" ADD COLUMN IF NOT EXISTS "discount_member_id" varchar(64);
+COMMENT ON COLUMN "douyin_verify_record"."discount_member_id" IS '鏍搁攢鎴愬姛寮�閫氱殑濂楅鍗D(discount_member.id)';
+
+
+-- ------------------------------------------------------------
+-- 2026-06-25  v3.0.1  鎶栭煶鏍搁攢閰嶇疆鍏ュ瓧鍏� 鈥斺�� 鏀瑰悗鍙扮淮鎶ゃ�佸厤閲嶅惎
+-- 浣滆��: rk
+-- 璇存槑: client_key / client_secret / account_id / poi_id 浠� yml 杩佸埌瀛楀吀
+--       (system_dict + system_dict_data);闂ㄥ簵ID绛夊彉鍔ㄦ椂,鍚庡彴 /system/dictData 鏀瑰嵆鍙�,鏃犻渶閲嶅惎銆�
+-- 鎵ц鍓�:鎶� 4 澶勩�屽緟濉�嶆浛鎹负鐪熷疄鍊�(client_secret 鍕挎彁浜ょ湡鍊煎埌浠撳簱)銆�
+-- 鐜:PostgreSQL 13+ 鍐呯疆 gen_random_uuid();浣庣増鏈渶鍏� CREATE EXTENSION pgcrypto銆�
+-- 鍒涘缓浜�/鏇存柊浜�:鍥哄畾 system_user.id = 722ecd1e-e903-45b1-a839-c591c0af0d7e銆�
+-- ------------------------------------------------------------
+
+-- 鐖�:鎶栭煶鏍搁攢閰嶇疆瀛楀吀(宸插瓨鍦ㄥ垯璺宠繃,骞傜瓑)
+INSERT INTO system_dict (code, name, remark, create_user, update_user, create_time, update_time, deleted)
+SELECT 'DOUYIN_CONFIG', '鎶栭煶鏍搁攢閰嶇疆',
+       '鎶栭煶鐢熸椿鏈嶅姟鍥㈣喘鏍搁攢閰嶇疆(client_key/client_secret/account_id/poi_id)',
+       '722ecd1e-e903-45b1-a839-c591c0af0d7e', '722ecd1e-e903-45b1-a839-c591c0af0d7e',
+       CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0
+WHERE NOT EXISTS (SELECT 1 FROM system_dict WHERE code = 'DOUYIN_CONFIG' AND deleted = 0);
+
+-- 瀛�:鍥涢」閰嶇疆(label 鍏ㄥぇ鍐�,涓� Java 甯搁噺 Constants.DOUYIN_* 瀵瑰簲;code 瀛楁瀛樸�屽�笺��)
+INSERT INTO system_dict_data (id, dict_id, code, label, sort, disabled, info, create_user, update_user, create_time, update_time, deleted)
+SELECT gen_random_uuid()::text, d.id, '寰呭~', 'CLIENT_KEY', 1, 0, '鎶栭煶搴旂敤 client_key',
+       '722ecd1e-e903-45b1-a839-c591c0af0d7e', '722ecd1e-e903-45b1-a839-c591c0af0d7e',
+       CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0
+FROM system_dict d WHERE d.code = 'DOUYIN_CONFIG' AND d.deleted = 0
+  AND NOT EXISTS (SELECT 1 FROM system_dict_data WHERE dict_id = d.id AND label = 'CLIENT_KEY' AND deleted = 0);
+
+INSERT INTO system_dict_data (id, dict_id, code, label, sort, disabled, info, create_user, update_user, create_time, update_time, deleted)
+SELECT gen_random_uuid()::text, d.id, '寰呭~', 'CLIENT_SECRET', 2, 0, '鎶栭煶搴旂敤 client_secret',
+       '722ecd1e-e903-45b1-a839-c591c0af0d7e', '722ecd1e-e903-45b1-a839-c591c0af0d7e',
+       CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0
+FROM system_dict d WHERE d.code = 'DOUYIN_CONFIG' AND d.deleted = 0
+  AND NOT EXISTS (SELECT 1 FROM system_dict_data WHERE dict_id = d.id AND label = 'CLIENT_SECRET' AND deleted = 0);
+
+INSERT INTO system_dict_data (id, dict_id, code, label, sort, disabled, info, create_user, update_user, create_time, update_time, deleted)
+SELECT gen_random_uuid()::text, d.id, '寰呭~', 'ACCOUNT_ID', 3, 0, '鏉ュ鍟嗘埛鏍硅处鎴稩D',
+       '722ecd1e-e903-45b1-a839-c591c0af0d7e', '722ecd1e-e903-45b1-a839-c591c0af0d7e',
+       CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0
+FROM system_dict d WHERE d.code = 'DOUYIN_CONFIG' AND d.deleted = 0
+  AND NOT EXISTS (SELECT 1 FROM system_dict_data WHERE dict_id = d.id AND label = 'ACCOUNT_ID' AND deleted = 0);
+
+INSERT INTO system_dict_data (id, dict_id, code, label, sort, disabled, info, create_user, update_user, create_time, update_time, deleted)
+SELECT gen_random_uuid()::text, d.id, '寰呭~', 'POI_ID', 4, 0, '鏍搁攢闂ㄥ簵ID(鍗曢棬搴�)',
+       '722ecd1e-e903-45b1-a839-c591c0af0d7e', '722ecd1e-e903-45b1-a839-c591c0af0d7e',
+       CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0
+FROM system_dict d WHERE d.code = 'DOUYIN_CONFIG' AND d.deleted = 0
+  AND NOT EXISTS (SELECT 1 FROM system_dict_data WHERE dict_id = d.id AND label = 'POI_ID' AND deleted = 0);
+
+
+-- ------------------------------------------------------------
+-- 2026-06-25  v3.0.1  璁㈠崟杞ㄨ抗 鈥斺�� 鏂板鐢佃溅楠戣杞ㄨ抗琛�
+-- 浣滆��: rk
+-- 璇存槑: 浠呯數杞�(type=1,璧� JT/T 808)楠戣涓骇鐢熷疄鏃惰建杩�;鑷杞�(type=0)璧� MQTT 鏃� GPS 涓婃姤,涓嶄骇鐢熴��
+--       鍐欏叆鐐瑰敮涓�:Jtt808Service.updateBikesInfo(0200 浣嶇疆鎶ユ枃 for 寰幆鍐�)銆�
+--       璁㈠崟鍏宠仈缁� Redis 缂撳瓨(ride:active:{bikeCode})鑾峰彇,閬垮厤绉掔骇涓婃姤楂橀鏌� member_rides銆�
+--       鍏宠仈瀛楁璇存槑:rides_id=楠戣璁㈠崟 member_rides.id;order_id=鏀粯璁㈠崟 member_rides.ordre_id鈫抔oodsorder.id(寮�閿佹椂鏈粦瀹氬垯涓虹┖)銆�
+-- ------------------------------------------------------------
+
+CREATE TABLE "member_rides_track" (
+  "id"           varchar(64)   NOT NULL,
+  "rides_id"     varchar(64),
+  "order_id"     varchar(64),
+  "bike_id"      varchar(64),
+  "bike_code"    varchar(64),
+  "longitude"    numeric(10,7),
+  "latitude"     numeric(10,7),
+  "report_time"  timestamp,
+  "create_date"  timestamp     DEFAULT CURRENT_TIMESTAMP,
+  "isdeleted"    smallint      DEFAULT 0,
+  PRIMARY KEY ("id")
+);
+CREATE INDEX idx_mrt_rides_time ON "member_rides_track" ("rides_id", "report_time");
+CREATE INDEX idx_mrt_order      ON "member_rides_track" ("order_id");
+CREATE INDEX idx_mrt_bike       ON "member_rides_track" ("bike_id");
+COMMENT ON TABLE  "member_rides_track" IS '鐢佃溅楠戣杞ㄨ抗(JT/T 808 浣嶇疆涓婃姤)';
+COMMENT ON COLUMN "member_rides_track"."rides_id"    IS '楠戣璁㈠崟涓婚敭鈶� member_rides.id';
+COMMENT ON COLUMN "member_rides_track"."order_id"    IS '鏀粯璁㈠崟涓婚敭鈶� member_rides.ordre_id 鈫� goodsorder.id(鍙兘涓虹┖)';
+COMMENT ON COLUMN "member_rides_track"."bike_id"     IS '杞﹁締涓婚敭 bikes.id';
+COMMENT ON COLUMN "member_rides_track"."bike_code"   IS '杞﹁締缂栫爜 bikes.code';
+COMMENT ON COLUMN "member_rides_track"."longitude"   IS '缁忓害(楂樺痉 GCJ02,WGS84 杞崲鍚�)';
+COMMENT ON COLUMN "member_rides_track"."latitude"    IS '绾害(楂樺痉 GCJ02,WGS84 杞崲鍚�)';
+COMMENT ON COLUMN "member_rides_track"."report_time" IS '璁惧涓婃姤鏃堕棿 deviceTime';
+COMMENT ON COLUMN "member_rides_track"."isdeleted"   IS '鏄惁宸插垹闄� 0鏈垹闄� 1宸插垹闄�';
+
+
+-- ------------------------------------------------------------
+-- 2026-06-26  v3.0.1  灏忕▼搴忛椤靛鍔犮�屾姈闊冲埜鍏戞崲璇存槑銆嶅瓧鍏搁」
+-- 浣滆��: rk
+-- 璇存槑: 褰掑叆鏃㈡湁 MINI_PROGRAMME 瀛楀吀(涓� STOP_SERVE_TIPS 鍚屾簮);
+--       灏忕▼搴忛椤� /web/home/home 璇诲彇鍚庝笅鍙�,灞曠ず鎶栭煶鍒稿厬鎹㈣鍒欒鏄庛��
+--       label 鍏ㄥぇ鍐�,涓� Java 甯搁噺 Constants.DOUYIN_EXCHANGE_TIPS 瀵瑰簲;
+--       code 瀛楁瀛樸�屽�笺��(璇存槑鏂囨),鎵ц鍓嶅彲鍏堢疆榛樿鏂囨銆�
+-- 鐜:PostgreSQL 13+ 鍐呯疆 gen_random_uuid();浣庣増鏈渶鍏� CREATE EXTENSION pgcrypto銆�
+-- ------------------------------------------------------------
+
+INSERT INTO system_dict_data (id, dict_id, code, label, sort, disabled, info, create_user, update_user, create_time, update_time, deleted)
+SELECT gen_random_uuid()::text, d.id,
+       '鎶栭煶鍒稿厬鎹㈣鍒欒鏄庤鍜ㄨ闂ㄥ簵', 'DOUYIN_EXCHANGE_TIPS', 99, 0, '鎶栭煶鍒稿厬鎹㈣鏄�(灏忕▼搴忛椤靛睍绀�)',
+       '722ecd1e-e903-45b1-a839-c591c0af0d7e', '722ecd1e-e903-45b1-a839-c591c0af0d7e',
+       CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0
+FROM system_dict d WHERE d.code = 'MINI_PROGRAMME' AND d.deleted = 0
+  AND NOT EXISTS (SELECT 1 FROM system_dict_data WHERE dict_id = d.id AND label = 'DOUYIN_EXCHANGE_TIPS' AND deleted = 0);
diff --git a/server/platform/src/main/java/com/doumee/api/business/DouyinProductController.java b/server/platform/src/main/java/com/doumee/api/business/DouyinProductController.java
new file mode 100644
index 0000000..064e8b3
--- /dev/null
+++ b/server/platform/src/main/java/com/doumee/api/business/DouyinProductController.java
@@ -0,0 +1,60 @@
+package com.doumee.api.business;
+
+import com.doumee.api.BaseController;
+import com.doumee.core.annotation.pr.PreventRepeat;
+import com.doumee.core.model.ApiResponse;
+import com.doumee.core.model.PageData;
+import com.doumee.core.model.PageWrap;
+import com.doumee.dao.business.model.DouyinProduct;
+import com.doumee.service.business.DouyinProductService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 鎶栭煶鍟嗗搧(鍥㈣喘)
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+@Api(tags = "鎶栭煶鍟嗗搧")
+@RestController
+@RequestMapping("/business/douyinProduct")
+public class DouyinProductController extends BaseController {
+
+    @Autowired
+    private DouyinProductService douyinProductService;
+
+    @PreventRepeat
+    @ApiOperation("浠庢姈闊冲悓姝ュ晢鍝�(鍏ㄩ噺)")
+    @PostMapping("/sync")
+    @RequiresPermissions("business:douyinProduct:sync")
+    public ApiResponse<Integer> sync() {
+        return ApiResponse.success(douyinProductService.syncFromDouyin());
+    }
+
+    @ApiOperation("鍒嗛〉鏌ヨ")
+    @PostMapping("/page")
+    @RequiresPermissions("business:douyinProduct:query")
+    public ApiResponse<PageData<DouyinProduct>> findPage(@RequestBody PageWrap<DouyinProduct> pageWrap) {
+        return ApiResponse.success(douyinProductService.findPage(pageWrap));
+    }
+
+    @ApiOperation("鏍规嵁ID鏌ヨ(鍚玈KU)")
+    @GetMapping("/{id}")
+    @RequiresPermissions("business:douyinProduct:query")
+    public ApiResponse<DouyinProduct> findById(@PathVariable String id) {
+        return ApiResponse.success(douyinProductService.findById(id));
+    }
+
+    @PreventRepeat
+    @ApiOperation("缁戝畾/瑙g粦鏈湴濂楅(outId 鍏宠仈 discount.id;discountId 涓虹┖鍗宠В缁�)")
+    @PostMapping("/bindDiscount")
+    public ApiResponse bindDiscount(@RequestParam String id,
+                                    @RequestParam(required = false) String discountId) {
+        douyinProductService.bindDiscount(id, discountId);
+        return ApiResponse.success(null);
+    }
+}
diff --git a/server/platform/src/main/java/com/doumee/api/business/DouyinVerifyController.java b/server/platform/src/main/java/com/doumee/api/business/DouyinVerifyController.java
new file mode 100644
index 0000000..68dc39d
--- /dev/null
+++ b/server/platform/src/main/java/com/doumee/api/business/DouyinVerifyController.java
@@ -0,0 +1,149 @@
+package com.doumee.api.business;
+
+import com.alibaba.fastjson.JSON;
+import com.doumee.api.BaseController;
+import com.doumee.core.annotation.pr.PreventRepeat;
+import com.doumee.core.constants.Constants;
+import com.doumee.core.douyin.DouyinClient;
+import com.doumee.core.douyin.dto.DouyinBaseResp;
+import com.doumee.core.douyin.dto.DouyinCancelParam;
+import com.doumee.core.douyin.dto.DouyinShopPoiResp;
+import com.doumee.core.model.ApiResponse;
+import com.doumee.core.model.LoginUserInfo;
+import com.doumee.core.model.PageData;
+import com.doumee.core.model.PageWrap;
+import com.doumee.core.utils.ID;
+import com.doumee.dao.business.model.DouyinVerifyLog;
+import com.doumee.dao.business.model.DouyinVerifyRecord;
+import com.doumee.dao.business.vo.DouyinVerifyRecordPageVO;
+import com.doumee.service.business.DouyinVerifyLogService;
+import com.doumee.service.business.DouyinVerifyService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 鎶栭煶鍒告牳閿�(绠$悊绔�):鏍搁攢璁板綍瀵瑰鍒嗛〉 + 鎾ら攢鏍搁攢銆�
+ * <p>鎾ら攢鏍搁攢鐢� web 绔�(/web/douyin/cancel)杩佺Щ鑷虫,閴存潈鐢� JWT 鏀逛负 Shiro;
+ * 绠$悊绔负杩愯惀琛ユ晳鍦烘櫙,涓嶅彈銆屾牳閿�鍚�1灏忔椂鍐呫�嶉檺鍒躲�傛瘡娆℃挙閿�钀� douyin_verify_log 瀹¤銆�
+ *
+ * @author rk
+ * @date 2026/06/26
+ */
+@Slf4j
+@Api(tags = "鎶栭煶鍒告牳閿�")
+@RestController
+@RequestMapping("/business/douyinVerify")
+public class DouyinVerifyController extends BaseController {
+
+    @Autowired
+    private DouyinVerifyService douyinVerifyService;
+    @Autowired
+    private DouyinVerifyLogService douyinVerifyLogService;
+    /** 鎶栭煶 HTTP 瀹㈡埛绔�:闂ㄥ簵鍒楄〃鏌ヨ涓烘棤鐘舵�侀�忎紶,鐩存帴璋冩姈闊�,涓嶇粡 Service */
+    @Autowired
+    private DouyinClient douyinClient;
+
+    @ApiOperation("鏍搁攢璁板綍鍒嗛〉(瀵瑰)")
+    @PostMapping("/page")
+    @RequiresPermissions("business:douyinVerify:query")
+    public ApiResponse<PageData<DouyinVerifyRecordPageVO>> findPage(@RequestBody PageWrap<DouyinVerifyRecordPageVO> pageWrap) {
+        return ApiResponse.success(douyinVerifyService.findManagePage(pageWrap));
+    }
+
+    @ApiOperation("鏌ヨ鎶栭煶鍟嗘埛涓嬮棬搴�(鐢ㄤ簬閫夋牳閿�闂ㄥ簵;account_id 浠庡瓧鍏歌鍙�)")
+    @PostMapping("/poiList")
+    @RequiresPermissions("business:douyinVerify:query")
+    public ApiResponse<List<String>> poiList() {
+        // 闂ㄥ簵鏌ヨ涓烘棤鐘舵�侀�忎紶(鏃犺惤搴�),Controller 鈫� DouyinClient 鐩磋繛;account_id 鐢� Client 浠庡瓧鍏歌鍙�
+        DouyinBaseResp<DouyinShopPoiResp> resp = douyinClient.shopPoiQuery();
+        List<DouyinShopPoiResp.Poi> pois = resp == null || resp.getData() == null ? null : resp.getData().getPois();
+        if (pois == null || pois.isEmpty()) {
+            return ApiResponse.success(Collections.emptyList());
+        }
+        // 浠呮彁鍙栭棬搴桰D,杩囨护 poi 鑺傜偣鎴� poiId 涓虹┖鐨勬潯鐩�
+        List<String> poiIds = pois.stream()
+                .filter(p -> p != null && p.getPoi() != null && StringUtils.isNotBlank(p.getPoi().getPoiId()))
+                .map(p -> p.getPoi().getPoiId())
+                .collect(Collectors.toList());
+        return ApiResponse.success(poiIds);
+    }
+
+    @PreventRepeat
+    @ApiOperation("鎾ら攢鏍搁攢(绠$悊绔�,涓嶅彈1灏忔椂闄愬埗;鎴愬姛鍚庝綔搴熸湰鍦板椁愬崱)")
+    @PostMapping("/cancel")
+    @RequiresPermissions("business:douyinVerify:cancel")
+    public ApiResponse<DouyinVerifyRecord> cancel(@RequestBody DouyinCancelParam param, HttpServletRequest request) {
+        long start = System.currentTimeMillis();
+        DouyinVerifyLog opLog = baseLog(Constants.DOUYIN_VERIFY_OPERATE_TYPE.CANCEL.getKey(), "/business/douyinVerify/cancel", start, request);
+        opLog.setRawRequest(JSON.toJSONString(param));
+        try {
+            // 鎿嶄綔浜哄彇 Shiro 鐧诲綍鐢ㄦ埛 id(绠$悊绔鐞嗗憳,闈炰細鍛�)
+            LoginUserInfo loginUser = getLoginUser();
+            String operator = loginUser == null ? null : loginUser.getId();
+            DouyinVerifyRecord rec = douyinVerifyService.cancel(param, operator);
+            fillByRecord(opLog, rec);
+            return ApiResponse.success(rec);
+        } catch (Throwable e) {
+            opLog.setResult(Constants.DOUYIN_VERIFY_LOG_RESULT.FAIL.getKey());
+            opLog.setErrorMsg(e.getMessage());
+            throw e;
+        } finally {
+            saveLog(opLog);
+        }
+    }
+
+    // ---------------- 鎾ら攢鎿嶄綔鏃ュ織杈呭姪 ----------------
+
+    /** 鏋勯�犱竴鏉℃挙閿�鎿嶄綔鏃ュ織楠ㄦ灦(涓婚敭/绫诲瀷/璺緞/IP/鑰楁椂/鏃堕棿) */
+    private DouyinVerifyLog baseLog(int operateType, String apiPath, long start, HttpServletRequest request) {
+        DouyinVerifyLog l = new DouyinVerifyLog();
+        l.setId(ID.nextGUID());
+        l.setOperateType(operateType);
+        l.setApiPath(apiPath);
+        // 绠$悊绔搷浣滈潪浼氬憳,memberId 鐣欑┖
+        l.setIp(request.getRemoteAddr());
+        l.setCostMs((int) (System.currentTimeMillis() - start));
+        l.setCreateDate(new Date());
+        l.setIsdeleted(Constants.ZERO);
+        return l;
+    }
+
+    /** 鎾ら攢鎴愬姛鍚�,鐢ㄦ牳閿�璁板綍鍥炲~鏃ュ織鐨勪笟鍔″瓧娈典笌缁撴灉 */
+    private void fillByRecord(DouyinVerifyLog opLog, DouyinVerifyRecord rec) {
+        if (rec == null) {
+            opLog.setResult(Constants.DOUYIN_VERIFY_LOG_RESULT.FAIL.getKey());
+            return;
+        }
+        opLog.setVerifyRecordId(rec.getId());
+        opLog.setOriginCode(rec.getOriginCode());
+        if (StringUtils.isNotBlank(rec.getPoiId())) {
+            opLog.setPoiId(rec.getPoiId());
+        }
+        opLog.setRawResponse(rec.getRawResponse());
+        opLog.setResult(Constants.equalsInteger(rec.getCancelStatus(), Constants.DOUYIN_VERIFY_CANCEL_STATUS.DONE.getKey()) ? Constants.DOUYIN_VERIFY_LOG_RESULT.SUCCESS.getKey() : Constants.DOUYIN_VERIFY_LOG_RESULT.FAIL.getKey());
+        opLog.setErrorMsg(rec.getCancelMsg());
+    }
+
+    /** 钀藉簱鎿嶄綔鏃ュ織;鏃ュ織鑷韩寮傚父涓嶆姏鍑�,閬垮厤褰卞搷涓绘祦绋� */
+    private void saveLog(DouyinVerifyLog opLog) {
+        try {
+            douyinVerifyLogService.record(opLog);
+        } catch (Exception e) {
+            log.warn("璁板綍鎶栭煶鎾ら攢鎿嶄綔鏃ュ織澶辫触 type={}, recordId={}", opLog.getOperateType(), opLog.getVerifyRecordId(), e);
+        }
+    }
+}
diff --git a/server/platform/src/main/java/com/doumee/api/system/SystemDictDataController.java b/server/platform/src/main/java/com/doumee/api/system/SystemDictDataController.java
index aefd40c..90b679c 100644
--- a/server/platform/src/main/java/com/doumee/api/system/SystemDictDataController.java
+++ b/server/platform/src/main/java/com/doumee/api/system/SystemDictDataController.java
@@ -7,6 +7,7 @@
 import com.doumee.core.model.ApiResponse;
 import com.doumee.core.model.PageData;
 import com.doumee.core.model.PageWrap;
+import com.doumee.dao.business.web.response.DouyinConfigDTO;
 import com.doumee.dao.business.web.response.MiniProgrammeDTO;
 import com.doumee.dao.system.dto.QuerySystemDictDataDTO;
 import com.doumee.dao.system.model.SystemDictData;
@@ -94,4 +95,24 @@
         systemDictDataService.updateMiniProgrammeDTO(miniProgrammeDTO);
         return ApiResponse.success(null);
     }
+
+    @ApiOperation("鎶栭煶鏍搁攢閰嶇疆鈥斺�旀煡璇�")
+    @PostMapping("/getDouyinConfigDTO")
+    public ApiResponse<DouyinConfigDTO> getDouyinConfigDTO(){
+        return ApiResponse.success(systemDictDataService.getDouyinConfigDTO());
+    }
+
+    @ApiOperation("鎶栭煶鏍搁攢閰嶇疆鈥斺�旀洿鏂板簲鐢ㄩ厤缃�(client_key/client_secret/account_id)")
+    @PostMapping("/updateDouyinAppConfigDTO")
+    public ApiResponse updateDouyinAppConfigDTO(@RequestBody DouyinConfigDTO douyinConfigDTO){
+        systemDictDataService.updateDouyinAppConfigDTO(douyinConfigDTO);
+        return ApiResponse.success(null);
+    }
+
+    @ApiOperation("鎶栭煶鏍搁攢閰嶇疆鈥斺�旀洿鏂版牳閿�闂ㄥ簵ID(鍗曢棬搴�)")
+    @PostMapping("/updateDouyinPoiIdDTO")
+    public ApiResponse updateDouyinPoiIdDTO(@RequestParam String poiId){
+        systemDictDataService.updateDouyinPoiIdDTO(poiId);
+        return ApiResponse.success(null);
+    }
 }
diff --git a/server/platform/src/main/resources/application.yml b/server/platform/src/main/resources/application.yml
index 495699e..f9ad69d 100644
--- a/server/platform/src/main/resources/application.yml
+++ b/server/platform/src/main/resources/application.yml
@@ -9,7 +9,7 @@
 #  application:
 #    name: parkbike
   profiles:
-    active: pro
+    active: dev
   # JSON杩斿洖閰嶇疆
   jackson:
     # 榛樿鏃跺尯
diff --git a/server/services/src/main/java/com/doumee/core/constants/Constants.java b/server/services/src/main/java/com/doumee/core/constants/Constants.java
index bf2d151..d4ef25c 100644
--- a/server/services/src/main/java/com/doumee/core/constants/Constants.java
+++ b/server/services/src/main/java/com/doumee/core/constants/Constants.java
@@ -33,6 +33,130 @@
     public static final String DINGDING_TOKEN ="DINGDING_TOKEN" ;
 
     public static final String MINI_PROGRAMME ="MINI_PROGRAMME" ;
+
+    // ==================== 鎶栭煶鏍搁攢閰嶇疆(瀛樹簬瀛楀吀 DOUYIN_CONFIG,鍚庡彴鍙敼銆佸厤閲嶅惎)====================
+    /** 瀛楀吀缂栫爜:鎶栭煶鏍搁攢閰嶇疆(system_dict.code) */
+    public static final String DOUYIN_CONFIG ="DOUYIN_CONFIG" ;
+    /** 瀛楀吀椤规爣绛�:鎶栭煶搴旂敤 client_key */
+    public static final String DOUYIN_CLIENT_KEY ="CLIENT_KEY" ;
+    /** 瀛楀吀椤规爣绛�:鎶栭煶搴旂敤 client_secret */
+    public static final String DOUYIN_CLIENT_SECRET ="CLIENT_SECRET" ;
+    /** 瀛楀吀椤规爣绛�:鏉ュ鍟嗘埛鏍硅处鎴稩D */
+    public static final String DOUYIN_ACCOUNT_ID ="ACCOUNT_ID" ;
+    /** 瀛楀吀椤规爣绛�:鏍搁攢闂ㄥ簵ID(鍗曢棬搴�) */
+    public static final String DOUYIN_POI_ID ="POI_ID" ;
+
+    // ==================== 鎶栭煶鏍搁攢鏋氫妇(瀵瑰簲 douyin_verify_log / douyin_verify_record 琛ㄥ瓧娈靛彇鍊�)====================
+
+    /**
+     * 鎶栭煶鏍搁攢鎿嶄綔鏃ュ織绫诲瀷 鈥斺�� 瀵瑰簲 douyin_verify_log.operate_type:0楠屽埜鍑嗗 1鏍搁攢 2鎾ら攢鏍搁攢
+     */
+    public enum DOUYIN_VERIFY_OPERATE_TYPE {
+        PREPARE(0, "楠屽埜鍑嗗", "鎵爜/杈撶爜鍑嗗鏍搁攢"),
+        VERIFY(1, "鏍搁攢", "楠屽埜鏍搁攢"),
+        CANCEL(2, "鎾ら攢鏍搁攢", "鎾ら攢宸叉牳閿�璁板綍"),
+        ;
+        String name;
+        Integer key;
+        String info;
+
+        DOUYIN_VERIFY_OPERATE_TYPE(Integer key, String name, String info) {
+            this.name = name;
+            this.key = key;
+            this.info = info;
+        }
+        public String getName() {
+            return name;
+        }
+        public Integer getKey() {
+            return key;
+        }
+        public String getInfo() {
+            return info;
+        }
+    }
+
+    /**
+     * 鎶栭煶鏍搁攢鎿嶄綔鏃ュ織缁撴灉 鈥斺�� 瀵瑰簲 douyin_verify_log.result:0鎴愬姛 1澶辫触
+     */
+    public enum DOUYIN_VERIFY_LOG_RESULT {
+        SUCCESS(0, "鎴愬姛", "鎿嶄綔鎴愬姛"),
+        FAIL(1, "澶辫触", "鎿嶄綔澶辫触"),
+        ;
+        String name;
+        Integer key;
+        String info;
+
+        DOUYIN_VERIFY_LOG_RESULT(Integer key, String name, String info) {
+            this.name = name;
+            this.key = key;
+            this.info = info;
+        }
+        public String getName() {
+            return name;
+        }
+        public Integer getKey() {
+            return key;
+        }
+        public String getInfo() {
+            return info;
+        }
+    }
+
+    /**
+     * 鎶栭煶鏍搁攢璁板綍鏍搁攢鐘舵�� 鈥斺�� 瀵瑰簲 douyin_verify_record.verify_status:0鎴愬姛 1澶辫触
+     */
+    public enum DOUYIN_VERIFY_STATUS {
+        SUCCESS(0, "鏍搁攢鎴愬姛", "鏍搁攢鎴愬姛"),
+        FAIL(1, "鏍搁攢澶辫触", "鏍搁攢澶辫触"),
+        ;
+        String name;
+        Integer key;
+        String info;
+
+        DOUYIN_VERIFY_STATUS(Integer key, String name, String info) {
+            this.name = name;
+            this.key = key;
+            this.info = info;
+        }
+        public String getName() {
+            return name;
+        }
+        public Integer getKey() {
+            return key;
+        }
+        public String getInfo() {
+            return info;
+        }
+    }
+
+    /**
+     * 鎶栭煶鏍搁攢璁板綍鎾ら攢鐘舵�� 鈥斺�� 瀵瑰簲 douyin_verify_record.cancel_status:0鏈挙閿� 1宸叉挙閿�
+     */
+    public enum DOUYIN_VERIFY_CANCEL_STATUS {
+        NOT_CANCEL(0, "鏈挙閿�", "鏈挙閿�"),
+        DONE(1, "宸叉挙閿�", "宸叉挙閿�"),
+        ;
+        String name;
+        Integer key;
+        String info;
+
+        DOUYIN_VERIFY_CANCEL_STATUS(Integer key, String name, String info) {
+            this.name = name;
+            this.key = key;
+            this.info = info;
+        }
+        public String getName() {
+            return name;
+        }
+        public Integer getKey() {
+            return key;
+        }
+        public String getInfo() {
+            return info;
+        }
+    }
+
     public static final String FREE_RENT_TIME ="FREE_RENT_TIME" ;
     public static final String LOW_VOLTAGE ="LOW_VOLTAGE" ;
     public static final String ACCESS_TOKEN ="ACCESS_TOKEN" ;
@@ -217,6 +341,8 @@
         public static final String RENT_NOTICE = "RENT_NOTICE";
         //灏忕▼搴忓仠姝㈡湇鍔℃彁绀�
         public static final String STOP_SERVE_TIPS = "STOP_SERVE_TIPS";
+        //鎶栭煶鍒稿厬鎹㈣鏄�(灏忕▼搴忛椤靛睍绀�,鎻愮ず鐢ㄦ埛鎶栭煶鍒稿厬鎹㈣鍒�)
+        public static final String DOUYIN_EXCHANGE_TIPS = "DOUYIN_EXCHANGE_TIPS";
         //灏忕▼搴忔槸鍚﹀仠姝㈡湇鍔� 0鍚� 1鏄�
         public static final String IS_STOP_SERVE = "IS_STOP_SERVE";
         //灏忕▼搴忓仠姝㈠紑濮嬫椂闂�
diff --git a/server/services/src/main/java/com/doumee/core/douyin/DouyinClient.java b/server/services/src/main/java/com/doumee/core/douyin/DouyinClient.java
new file mode 100644
index 0000000..8f7b418
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/core/douyin/DouyinClient.java
@@ -0,0 +1,491 @@
+package com.doumee.core.douyin;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.TypeReference;
+import com.doumee.core.constants.Constants;
+import com.doumee.core.constants.ResponseStatus;
+import com.doumee.core.douyin.dto.DouyinBaseResp;
+import com.doumee.core.douyin.dto.DouyinCancelReq;
+import com.doumee.core.douyin.dto.DouyinCancelResp;
+import com.doumee.core.douyin.dto.DouyinClientTokenReq;
+import com.doumee.core.douyin.dto.DouyinClientTokenResp;
+import com.doumee.core.douyin.dto.DouyinOnlineQueryReq;
+import com.doumee.core.douyin.dto.DouyinOnlineQueryResp;
+import com.doumee.core.douyin.dto.DouyinPrepareReq;
+import com.doumee.core.douyin.dto.DouyinPrepareResp;
+import com.doumee.core.douyin.dto.DouyinShopPoiResp;
+import com.doumee.core.douyin.dto.DouyinVerifyReq;
+import com.doumee.core.douyin.dto.DouyinVerifyResp;
+import com.doumee.core.exception.BusinessException;
+import com.doumee.core.utils.Http;
+import com.doumee.biz.system.SystemDictDataBiz;
+import com.doumee.dao.system.model.SystemDictData;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 鎶栭煶寮�鏀惧钩鍙� HTTP 瀹㈡埛绔�
+ * 灏佽 client_token 鑾峰彇(甯� Redis 缂撳瓨,鎻愬墠鍒锋柊)涓� goodlife/v1 鎺ュ彛璋冪敤銆�
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+@Slf4j
+@Component
+public class DouyinClient {
+
+    @Autowired
+    private DouyinProperties douyinProperties;
+
+    @Autowired
+    private RedisTemplate<String, Object> redisTemplate;
+
+    /** 瀛楀吀鍙栨暟(鍚庡彴鍙敼鎶栭煶閰嶇疆:client_key/client_secret/account_id/poi_id,鍏嶉噸鍚�) */
+    @Autowired
+    private SystemDictDataBiz systemDictDataBiz;
+
+    /**
+     * 浠庡瓧鍏� DOUYIN_CONFIG 鍙栨寚瀹氭爣绛剧殑鍊笺��
+     * 瀛楀吀鏃犲��(杩斿洖绌哄璞�)鏃跺厹搴曡繑鍥� null,鐢辫皟鐢ㄦ柟鏍¢獙銆�
+     *
+     * @param label 瀛楀吀椤规爣绛�(濡� CLIENT_KEY / POI_ID)
+     * @return 閰嶇疆鍊�;鏌ヤ笉鍒拌繑鍥� null
+     */
+    private String getDictValue(String label) {
+        SystemDictData data = systemDictDataBiz.queryByCode(Constants.DOUYIN_CONFIG, label);
+        return data == null ? null : data.getCode();
+    }
+
+    /**
+     * 鍙栨牳閿�闂ㄥ簵ID(鍗曢棬搴�,瀛樺瓧鍏� POI_ID)銆�
+     *
+     * @return 闂ㄥ簵ID
+     */
+    public String getPoiId() {
+        return getDictValue(Constants.DOUYIN_POI_ID);
+    }
+
+    // 瀹樻柟 API 鏂囨。(鐢熸椿鏈嶅姟 / 鍥㈣喘鏍搁攢):
+    // https://partner.open-douyin.com/docs/resource/zh-CN/local-life/develop/OpenAPI/general-capabilities/life.capacity.fulfilment/certificate.prepare
+    /** 銆岀敓鎴� client_token銆嶆帴鍙h矾寰� */
+    private static final String URL_CLIENT_TOKEN = "/oauth/client_token/";
+    /** 銆屾煡璇㈠晢鍝佺嚎涓婃暟鎹垪琛ㄣ�嶆帴鍙h矾寰� */
+    private static final String URL_PRODUCT_ONLINE_QUERY = "/goodlife/v1/goods/product/online/query/";
+    /** 銆屾煡璇㈤棬搴椾俊鎭�嶆帴鍙h矾寰�(鏌ヨ鍟嗘埛涓嬪凡璁ら鐨勯棬搴楀垪琛�) */
+    private static final String URL_SHOP_POI_QUERY = "/goodlife/v1/shop/poi/query/";
+    /** 銆岄獙鍒稿噯澶囥�嶆帴鍙h矾寰� */
+    private static final String URL_PREPARE = "/goodlife/v1/fulfilment/certificate/prepare/";
+    /** 銆岄獙鍒�(鏍搁攢)銆嶆帴鍙h矾寰� */
+    private static final String URL_VERIFY = "/goodlife/v1/fulfilment/certificate/verify/";
+    /** 銆屾挙閿�鏍搁攢銆嶆帴鍙h矾寰� */
+    private static final String URL_CANCEL = "/goodlife/v1/fulfilment/certificate/cancel/";
+
+    /** token 鎻愬墠鍒锋柊浣欓噺(绉�),閬垮厤涓寸晫杩囨湡 */
+    private static final long TOKEN_REFRESH_LEAD_SECONDS = 300L;
+    /** client_token 榛樿鏈夋晥鏈�(绉�),鎺ュ彛鏈繑鍥炴椂鍏滃簳 */
+    private static final long TOKEN_DEFAULT_EXPIRE_SECONDS = 7200L;
+
+    /** access_token 鏃犳晥 / 杩囨湡閿欒鐮� */
+    private static final int ERR_TOKEN_INVALID = 2190002;
+    private static final int ERR_TOKEN_EXPIRED = 2190008;
+
+    // ============================ client_token ============================
+
+    /**
+     * 鑾峰彇 access-token(甯� Redis 缂撳瓨,涓磋繎杩囨湡鑷姩鍒锋柊)
+     */
+    public String getAccessToken() {
+        String key = douyinProperties.getRedisTokenKey();
+        Object cached = redisTemplate.opsForValue().get(key);
+        if (cached instanceof String) {
+            String token = (String) cached;
+            Long ttl = redisTemplate.getExpire(key, TimeUnit.SECONDS);
+            if (StringUtils.isNotBlank(token) && ttl != null && ttl > TOKEN_REFRESH_LEAD_SECONDS) {
+                return token;
+            }
+        }
+        return refreshAccessToken();
+    }
+
+    /**
+     * 寮哄埗鍒锋柊 access-token
+     */
+    public String refreshAccessToken() {
+        DouyinClientTokenReq req = new DouyinClientTokenReq();
+        // client_key / client_secret 浠庡瓧鍏� DOUYIN_CONFIG 瀹炴椂璇诲彇(鍚庡彴鍙敼)
+        req.setClientKey(getDictValue(Constants.DOUYIN_CLIENT_KEY));
+        req.setClientSecret(getDictValue(Constants.DOUYIN_CLIENT_SECRET));
+        req.setGrantType("client_credential");
+        try {
+            Http.HttpWrap wrap = new Http().build(douyinProperties.getHost() + URL_CLIENT_TOKEN);
+            Http.HttpResult result = wrap.setRequestProperty("Content-Type", "application/json")
+                    .postJSON(JSONObject.parseObject(JSON.toJSONString(req)));
+            DouyinClientTokenResp resp = result.toClass(DouyinClientTokenResp.class);
+            if (resp == null || resp.getData() == null
+                    || resp.getData().getErrorCode() == null
+                    || resp.getData().getErrorCode() != 0
+                    || StringUtils.isBlank(resp.getData().getAccessToken())) {
+                log.error("鎶栭煶 client_token 鑾峰彇澶辫触:{}", JSON.toJSONString(resp));
+                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "鎶栭煶 client_token 鑾峰彇澶辫触");
+            }
+            String token = resp.getData().getAccessToken();
+            long expiresIn = resp.getData().getExpiresIn() == null
+                    ? TOKEN_DEFAULT_EXPIRE_SECONDS : resp.getData().getExpiresIn();
+            redisTemplate.opsForValue().set(douyinProperties.getRedisTokenKey(), token, expiresIn, TimeUnit.SECONDS);
+            return token;
+        } catch (BusinessException e) {
+            throw e;
+        } catch (Exception e) {
+            log.error("璋冪敤鎶栭煶 client_token 寮傚父", e);
+            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),
+                    "璋冪敤鎶栭煶 client_token 寮傚父:" + e.getMessage());
+        }
+    }
+
+    /**
+     * client_token 鍓╀綑鏈夋晥鏈�(绉�);鏈紦瀛樿繑鍥� 0
+     */
+    public long getTokenTtlSeconds() {
+        return redisTemplate.getExpire(douyinProperties.getRedisTokenKey(), TimeUnit.SECONDS);
+    }
+
+    /**
+     * 娓呯┖缂撳瓨鐨� access-token銆�
+     * <p>鍚庡彴淇敼浜� client_key/client_secret 鍚�,鏃� token 宸插け鏁�,涓嶆竻浼氳 {@link #getAccessToken()}
+     * 缁х画澶嶇敤,瀵艰嚧鎶栭煶鎺ュ彛鎶ャ�宎ccess-token 鏃犳晥/杩囨湡銆�;娓呮帀鍚�,涓嬫鐪熸璋冪敤鎶栭煶鎺ュ彛鏃�
+     * 鎵嶇敤鏂伴厤缃崲鍙栨柊 token(姣旂珛鍗� refresh 鏇寸ǔ:鏂伴厤缃嫢鏈夎,涓嶄細鍦ㄤ繚瀛樼灛闂村氨鎶ラ敊)銆�
+     */
+    public void clearAccessToken() {
+        redisTemplate.delete(douyinProperties.getRedisTokenKey());
+    }
+
+    // ============================ 鍟嗗搧绾夸笂鏁版嵁鍒楄〃 ============================
+
+    /**
+     * 鏌ヨ鍟嗗搧绾夸笂鏁版嵁鍒楄〃(鍗曢〉),token 澶辨晥鑷姩鍒锋柊閲嶈瘯
+     */
+    public DouyinBaseResp<DouyinOnlineQueryResp> onlineQuery(DouyinOnlineQueryReq req) {
+        if (StringUtils.isBlank(req.getAccountId())) {
+            // account_id 浠庡瓧鍏� DOUYIN_CONFIG 瀹炴椂璇诲彇(鍚庡彴鍙敼)
+            req.setAccountId(getDictValue(Constants.DOUYIN_ACCOUNT_ID));
+        }
+        if (req.getPoiIds() == null || req.getPoiIds().isEmpty()) {
+            // poi_ids 浠庡瓧鍏� POI_ID 瀹炴椂璇诲彇(褰撳墠鍗曢棬搴�,鏀惧叆鍗曞厓绱犲垪琛�);
+            // 鎶栭煶瑙勫垯:poi_ids 浼� 0 瑙嗕负绌哄��(杩斿洖鍟嗘埛涓嬪叏閲忓晢鍝�),鏁呭瓧鍏搁』閰嶇湡瀹為棬搴桰D,
+            // 闈炴暟瀛楁垨 0 鏃舵斁寮冮棬搴楄繃婊�(绛変环杩斿洖鍏ㄩ噺),閬垮厤鎶婅剰鍊煎綋杩囨护鏉′欢鍙戝嚭
+            String poiIdStr = getDictValue(Constants.DOUYIN_POI_ID);
+            if (StringUtils.isNotBlank(poiIdStr)) {
+                try {
+                    long poiId = Long.parseLong(poiIdStr.trim());
+                    if (poiId > 0) {
+                        req.setPoiIds(Collections.singletonList(poiId));
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("瀛楀吀 POI_ID 闈炴暟瀛�,online/query 蹇界暐闂ㄥ簵杩囨护:{}", poiIdStr);
+                }
+            }
+        }
+        // GET 鏂瑰紡:鍏ュ弬鎷兼垚 query 鍙傛暟(浠� prepare);buildQuery 浼氳烦杩� null 鍊�
+        DouyinBaseResp<DouyinOnlineQueryResp> resp = doGet(URL_PRODUCT_ONLINE_QUERY, buildOnlineQueryParams(req),
+                new TypeReference<DouyinBaseResp<DouyinOnlineQueryResp>>() {});
+        if (tokenInvalid(resp)) {
+            refreshAccessToken();
+            resp = doGet(URL_PRODUCT_ONLINE_QUERY, buildOnlineQueryParams(req),
+                    new TypeReference<DouyinBaseResp<DouyinOnlineQueryResp>>() {});
+        }
+        return resp;
+    }
+
+    /**
+     * 鎶� online/query 鍏ュ弬鎷兼垚 GET 鏌ヨ鍙傛暟銆�
+     * <p>瑕嗙洊 account_id / product_id / out_id / status / cursor / count / poi_ids;
+     * buildQuery 浼氳烦杩� null 鍊�;status/count 浠呴潪 null 鏃舵斁鍏�;poi_ids 鏁扮粍鐢ㄩ�楀彿鍒嗛殧浼犲弬銆�
+     *
+     * @param req 鏌ヨ鍟嗗搧绾夸笂鏁版嵁鍒楄〃鍏ュ弬
+     * @return GET 鏌ヨ鍙傛暟 Map(鍊煎彲鑳戒负 null,鐢� buildQuery 缁熶竴璺宠繃)
+     */
+    private Map<String, String> buildOnlineQueryParams(DouyinOnlineQueryReq req) {
+        Map<String, String> params = new LinkedHashMap<>();
+        params.put("account_id", req.getAccountId());
+        params.put("product_id", req.getProductId());
+        params.put("goods_query_type", "2");
+        params.put("out_id", req.getOutId());
+        if (req.getStatus() != null) {
+            params.put("status", String.valueOf(req.getStatus()));
+        }
+        params.put("cursor", req.getCursor());
+        if (req.getCount() != null) {
+            params.put("count", String.valueOf(req.getCount()));
+        }
+        // poi_ids 涓� Array<Int64>,GET 鐢ㄩ�楀彿鍒嗛殧浼犲弬(濡� poi_ids=123,456)
+        if (req.getPoiIds() != null && !req.getPoiIds().isEmpty()) {
+            params.put("poi_ids", StringUtils.join(req.getPoiIds(), ","));
+        }
+        return params;
+    }
+
+    // ============================ 鏌ヨ闂ㄥ簵淇℃伅 ============================
+
+    /**
+     * 鏌ヨ鍟嗘埛涓嬪凡璁ら鐨勯棬搴楀垪琛�(绠$悊绔�夋牳閿�闂ㄥ簵鐢�),token 澶辨晥鑷姩鍒锋柊閲嶈瘯銆�
+     * <p>account_id 鍥哄畾浠庡瓧鍏� DOUYIN_CONFIG 瀹炴椂璇诲彇(鍚庡彴鍙敼),涓嶆帴鏀跺閮ㄥ叆鍙傘��
+     *
+     * @return 闂ㄥ簵淇℃伅鍒楄〃;鎺ュ彛寮傚父鎶� {@link BusinessException}
+     */
+    public DouyinBaseResp<DouyinShopPoiResp> shopPoiQuery() {
+        // account_id 浠庡瓧鍏� DOUYIN_CONFIG 瀹炴椂璇诲彇(鍚庡彴鍙敼)
+        Map<String, String> params = new LinkedHashMap<>();
+        params.put("account_id", getDictValue(Constants.DOUYIN_ACCOUNT_ID));
+        // GET 鏂瑰紡:鍏ュ弬鎷兼垚 query 鍙傛暟;buildQuery 浼氳烦杩� null 鍊�
+        DouyinBaseResp<DouyinShopPoiResp> resp = doGet(URL_SHOP_POI_QUERY, params,
+                new TypeReference<DouyinBaseResp<DouyinShopPoiResp>>() {});
+        if (tokenInvalid(resp)) {
+            refreshAccessToken();
+            resp = doGet(URL_SHOP_POI_QUERY, params,
+                    new TypeReference<DouyinBaseResp<DouyinShopPoiResp>>() {});
+        }
+        return resp;
+    }
+
+    // ============================ 楠屽埜鍑嗗 ============================
+
+    /**
+     * 鎶婃壂鐮佺煭閾�(鎴栧惈 object_id 鐨勯暱閾�)瑙f瀽涓� encrypted_data
+     *
+     * @return encrypted_data(object_id),瑙f瀽澶辫触杩斿洖 null
+     */
+    public String resolveShortLink(String shortUrl) {
+        if (StringUtils.isBlank(shortUrl)) {
+            return null;
+        }
+        String input = shortUrl.trim();
+        String objectId = extractObjectId(input);
+        if (objectId != null) {
+            return objectId;
+        }
+        try {
+            HttpURLConnection conn = (HttpURLConnection) new URL(input).openConnection();
+            conn.setInstanceFollowRedirects(true);
+            conn.setRequestMethod("GET");
+            conn.setConnectTimeout(5000);
+            conn.setReadTimeout(5000);
+            conn.connect();
+            String finalUrl = conn.getURL().toString();
+            conn.disconnect();
+            return extractObjectId(finalUrl);
+        } catch (Exception e) {
+            log.error("瑙f瀽鎶栭煶鐭摼寮傚父:{}", input, e);
+            return null;
+        }
+    }
+
+    /**
+     * 浠� url 涓彁鍙� object_id 鍙傛暟鍊�(鍗� encrypted_data)
+     *
+     * @param url 鍙兘鍚� object_id=xxx 鐨勯摼鎺�
+     * @return 瑙g爜鍚庣殑 object_id,涓嶅惈鍒欒繑鍥� null
+     */
+    private String extractObjectId(String url) {
+        if (url == null) {
+            return null;
+        }
+        int idx = url.indexOf("object_id=");
+        if (idx < 0) {
+            return null;
+        }
+        String tail = url.substring(idx + "object_id=".length());
+        int end = tail.indexOf('&');
+        String val = end < 0 ? tail : tail.substring(0, end);
+        try {
+            return URLDecoder.decode(val, "UTF-8");
+        } catch (Exception e) {
+            return val;
+        }
+    }
+
+    /**
+     * 楠屽埜鍑嗗,token 澶辨晥鑷姩鍒锋柊閲嶈瘯
+     */
+    public DouyinBaseResp<DouyinPrepareResp> prepare(DouyinPrepareReq req) {
+        if (StringUtils.isBlank(req.getAccountId())) {
+            // account_id 浠庡瓧鍏� DOUYIN_CONFIG 瀹炴椂璇诲彇(鍚庡彴鍙敼)
+            req.setAccountId(getDictValue(Constants.DOUYIN_ACCOUNT_ID));
+        }
+        DouyinBaseResp<DouyinPrepareResp> resp = doGet(URL_PREPARE, buildPrepareQuery(req),
+                new TypeReference<DouyinBaseResp<DouyinPrepareResp>>() {});
+        if (tokenInvalid(resp)) {
+            refreshAccessToken();
+            resp = doGet(URL_PREPARE, buildPrepareQuery(req),
+                    new TypeReference<DouyinBaseResp<DouyinPrepareResp>>() {});
+        }
+        return resp;
+    }
+
+    /**
+     * 鎶婇獙鍒稿噯澶囧叆鍙傛嫾鎴� GET 鏌ヨ鍙傛暟(prepare 鎺ュ彛璧� GET)
+     */
+    private Map<String, String> buildPrepareQuery(DouyinPrepareReq req) {
+        Map<String, String> params = new LinkedHashMap<>();
+        params.put("encrypted_data", req.getEncryptedData());
+        params.put("code", req.getCode());
+        params.put("poi_id", req.getPoiId());
+        params.put("account_id", req.getAccountId());
+        if (req.getCanVerify() != null) {
+            params.put("can_verify", String.valueOf(req.getCanVerify()));
+        }
+        return params;
+    }
+
+    // ============================ 楠屽埜 ============================
+
+    /**
+     * 楠屽埜(鏍搁攢),token 澶辨晥鑷姩鍒锋柊閲嶈瘯
+     */
+    public DouyinBaseResp<DouyinVerifyResp> verify(DouyinVerifyReq req) {
+        if (StringUtils.isBlank(req.getAccountId())) {
+            // account_id 浠庡瓧鍏� DOUYIN_CONFIG 瀹炴椂璇诲彇(鍚庡彴鍙敼)
+            req.setAccountId(getDictValue(Constants.DOUYIN_ACCOUNT_ID));
+        }
+        DouyinBaseResp<DouyinVerifyResp> resp = doPost(URL_VERIFY, req,
+                new TypeReference<DouyinBaseResp<DouyinVerifyResp>>() {});
+        if (tokenInvalid(resp)) {
+            refreshAccessToken();
+            resp = doPost(URL_VERIFY, req,
+                    new TypeReference<DouyinBaseResp<DouyinVerifyResp>>() {});
+        }
+        return resp;
+    }
+
+    // ============================ 鎾ら攢鏍搁攢 ============================
+
+    /**
+     * 鎾ら攢鏍搁攢,token 澶辨晥鑷姩鍒锋柊閲嶈瘯
+     */
+    public DouyinBaseResp<DouyinCancelResp> cancel(DouyinCancelReq req) {
+        if (StringUtils.isBlank(req.getAccountId())) {
+            // account_id 浠庡瓧鍏� DOUYIN_CONFIG 瀹炴椂璇诲彇(鍚庡彴鍙敼)
+            req.setAccountId(getDictValue(Constants.DOUYIN_ACCOUNT_ID));
+        }
+        DouyinBaseResp<DouyinCancelResp> resp = doPost(URL_CANCEL, req,
+                new TypeReference<DouyinBaseResp<DouyinCancelResp>>() {});
+        if (tokenInvalid(resp)) {
+            refreshAccessToken();
+            resp = doPost(URL_CANCEL, req,
+                    new TypeReference<DouyinBaseResp<DouyinCancelResp>>() {});
+        }
+        return resp;
+    }
+
+    // ============================ 鍐呴儴宸ュ叿 ============================
+
+    /**
+     * 缁熶竴 POST 璋冪敤:甯� access-token 璇锋眰澶�,搴忓垪鍖栧叆鍙�,鍙嶅簭鍒楀寲 {@link DouyinBaseResp}
+     *
+     * @param path 鎺ュ彛璺緞(涓嶅惈 host)
+     * @param req  涓氬姟鍏ュ弬瀵硅薄
+     * @param type 鍝嶅簲娉涘瀷绫诲瀷寮曠敤
+     * @return 鎶栭煶閫氱敤鍝嶅簲澶栧3;璋冪敤寮傚父缁熶竴鎶� {@link BusinessException}
+     */
+    private <T> DouyinBaseResp<T> doPost(String path, Object req, TypeReference<DouyinBaseResp<T>> type) {
+        try {
+            // 鍏ュ弬鏃ュ織:鍙墦璇锋眰 body,涓嶅惈 access-token 璇锋眰澶�,閬垮厤娉勫瘑
+            log.info("鎶栭煶璇锋眰 {} 鍏ュ弬:{}", path, JSON.toJSONString(req));
+            Http.HttpWrap wrap = new Http().build(douyinProperties.getHost() + path);
+            wrap.setRequestProperty("Content-Type", "application/json");
+            wrap.setRequestProperty("access-token", getAccessToken());
+            Http.HttpResult result = wrap.postJSON(JSONObject.parseObject(JSON.toJSONString(req)));
+            // 鍝嶅簲娴佸彧鑳借涓�娆�(toStringResult 璇诲悗浼氬叧闂簳灞傛祦),鍏堝彇鍑哄瓧绗︿覆澶嶇敤,閬垮厤浜屾璇诲彇鎶� "stream is closed"
+            String body = result.toStringResult();
+            // 鍑哄弬鏃ュ織:鎵撳嵃鎶栭煶鍝嶅簲浣�,渚夸簬鎺掓煡杩斿洖鍐呭
+            log.info("鎶栭煶鍝嶅簲 {} 鍑哄弬:{}", path, body);
+            return JSON.parseObject(body, type);
+        } catch (Exception e) {
+            log.error("璋冪敤鎶栭煶鎺ュ彛寮傚父:path={}", path, e);
+            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),
+                    "璋冪敤鎶栭煶鎺ュ彛寮傚父:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 缁熶竴 GET 璋冪敤:甯� access-token 璇锋眰澶�,鏌ヨ鍙傛暟鎷煎埌 url,鍙嶅簭鍒楀寲 {@link DouyinBaseResp}
+     */
+    private <T> DouyinBaseResp<T> doGet(String path, Map<String, String> params, TypeReference<DouyinBaseResp<T>> type) {
+        String url = douyinProperties.getHost() + path;
+        String query = buildQuery(params);
+        if (StringUtils.isNotBlank(query)) {
+            url = url + "?" + query;
+        }
+        try {
+            // 鍏ュ弬鏃ュ織:GET 鏌ヨ鍙傛暟,涓嶅惈 access-token 璇锋眰澶�
+            log.info("鎶栭煶璇锋眰 {} 鍏ュ弬:{}", path, params);
+            Http.HttpWrap wrap = new Http().build(url);
+            wrap.setRequestProperty("Content-Type", "application/json");
+            wrap.setRequestProperty("access-token", getAccessToken());
+            Http.HttpResult result = wrap.get();
+            // 鍝嶅簲娴佸彧鑳借涓�娆�(toStringResult 璇诲悗浼氬叧闂簳灞傛祦),鍏堝彇鍑哄瓧绗︿覆澶嶇敤,閬垮厤浜屾璇诲彇鎶� "stream is closed"
+            String body = result.toStringResult();
+            // 鍑哄弬鏃ュ織:鎵撳嵃鎶栭煶鍝嶅簲浣�,渚夸簬鎺掓煡杩斿洖鍐呭
+            log.info("鎶栭煶鍝嶅簲 {} 鍑哄弬:{}", path, body);
+            return JSON.parseObject(body, type);
+        } catch (Exception e) {
+            log.error("璋冪敤鎶栭煶鎺ュ彛寮傚父:path={}", path, e);
+            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),
+                    "璋冪敤鎶栭煶鎺ュ彛寮傚父:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 鎶婂弬鏁� Map 鎷兼垚 a=1&b=2 褰㈠紡鐨勬煡璇覆(璺宠繃 null 鍊�)
+     */
+    private String buildQuery(Map<String, String> params) {
+        if (params == null || params.isEmpty()) {
+            return "";
+        }
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry<String, String> e : params.entrySet()) {
+            if (e.getValue() == null) {
+                continue;
+            }
+            if (sb.length() > 0) {
+                sb.append("&");
+            }
+            sb.append(e.getKey()).append("=").append(urlEncode(e.getValue()));
+        }
+        return sb.toString();
+    }
+
+    /**
+     * UTF-8 URL 缂栫爜,缂栫爜寮傚父鏃跺師鏍疯繑鍥�
+     */
+    private String urlEncode(String value) {
+        try {
+            return URLEncoder.encode(value, "UTF-8");
+        } catch (Exception e) {
+            return value;
+        }
+    }
+
+    /**
+     * 鍒ゆ柇鍝嶅簲鏄惁涓� access-token 鏃犳晥/杩囨湡(鍛戒腑鍒欑敱璋冪敤鏂瑰埛鏂板悗閲嶈瘯涓�娆�)
+     */
+    private boolean tokenInvalid(DouyinBaseResp<?> resp) {
+        if (resp == null || resp.getExtra() == null || resp.getExtra().getErrorCode() == null) {
+            return false;
+        }
+        int code = resp.getExtra().getErrorCode();
+        return code == ERR_TOKEN_INVALID || code == ERR_TOKEN_EXPIRED;
+    }
+}
diff --git a/server/services/src/main/java/com/doumee/core/douyin/DouyinProperties.java b/server/services/src/main/java/com/doumee/core/douyin/DouyinProperties.java
new file mode 100644
index 0000000..02e0869
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/core/douyin/DouyinProperties.java
@@ -0,0 +1,26 @@
+package com.doumee.core.douyin;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 鎶栭煶寮�鏀惧钩鍙�(鐢熸椿鏈嶅姟 / 鍥㈣喘鏍搁攢)閰嶇疆銆�
+ * <p>浠呬繚鐣欎笌闂ㄥ簵鏃犲叧鐨勬妧鏈弬鏁�(缃戝叧 host銆丷edis 缂撳瓨 key);
+ * 涓氬姟鐩稿叧涓斿彲鑳藉彉鍔ㄧ殑 client_key / client_secret / account_id / poi_id
+ * 鏀逛负瀛樻暟鎹簱瀛楀吀(DOUYIN_CONFIG),鍚庡彴鍙敼銆佸厤閲嶅惎,鐢� {@link DouyinClient} 瀹炴椂璇诲彇銆�
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+@Component
+@ConfigurationProperties(prefix = "douyin")
+@Data
+public class DouyinProperties {
+
+    /** 寮�鏀惧钩鍙扮綉鍏�,榛樿 https://open.douyin.com */
+    private String host = "https://open.douyin.com";
+
+    /** client_token 鍦� Redis 涓殑缂撳瓨 key */
+    private String redisTokenKey = "douyin:client_token";
+}
diff --git a/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinBaseResp.java b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinBaseResp.java
new file mode 100644
index 0000000..c2c35ba
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinBaseResp.java
@@ -0,0 +1,52 @@
+package com.doumee.core.douyin.dto;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+
+/**
+ * 鎶栭煶鐢熸椿鏈嶅姟鎺ュ彛(goodlife/v1 绯诲垪)閫氱敤鍝嶅簲澶栧3銆�
+ * <p>鎵�鏈夋姈闊崇敓娲绘湇鍔℃帴鍙g殑鍝嶅簲閮藉寘鍦ㄦ缁撴瀯涓�:涓氬姟鏁版嵁鍦� {@link #data},閿欒淇℃伅鍦� {@link #extra}銆�
+ * 鍒ゅ畾鎺ュ彛鏄惁鎴愬姛缁熶竴鐪� {@link Extra#getErrorCode()} == 0銆�
+ *
+ * @author rk
+ * @date 2026/06/22
+ * @param <T> 鍏蜂綋鎺ュ彛鐨勫搷搴旀暟鎹被鍨�
+ */
+@Data
+public class DouyinBaseResp<T> {
+
+    /** 涓氬姟鏁版嵁鑺傜偣(娉涘瀷,鐢卞悇鎺ュ彛鐨勫叿浣撳搷搴旂被鍨嬪~鍏�) */
+    @JSONField(name = "data")
+    private T data;
+
+    /** 鎵╁睍淇℃伅鑺傜偣(閿欒鐮� / 鎻忚堪 / logid 绛�),鐢ㄤ簬鍒ゅ畾鎴愬姛涓庢帓鏌� */
+    @JSONField(name = "extra")
+    private Extra extra;
+
+    /**
+     * 鎵╁睍淇℃伅(error_code / description / logid 绛�)銆�
+     * {@link #errorCode} 涓� 0 琛ㄧず鎴愬姛,闈� 0 鏃� {@link #description} 涓洪敊璇弿杩般��
+     */
+    @Data
+    public static class Extra {
+        /** 閿欒鐮�,0 琛ㄧず鎴愬姛 */
+        @JSONField(name = "error_code")
+        private Integer errorCode;
+
+        /** 閿欒/鎴愬姛鎻忚堪鏂囨 */
+        @JSONField(name = "description")
+        private String description;
+
+        /** 瀛愰敊璇爜(閮ㄥ垎閿欒浼氭湁鏇寸粏鍒嗙殑瀛愮爜) */
+        @JSONField(name = "sub_error_code")
+        private Integer subErrorCode;
+
+        /** 瀛愰敊璇弿杩� */
+        @JSONField(name = "sub_description")
+        private String subDescription;
+
+        /** 鎶栭煶渚ф棩蹇桰D,鎺掓煡闂鏃舵彁渚涚粰鎶栭煶鏂� */
+        @JSONField(name = "logid")
+        private String logid;
+    }
+}
diff --git a/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinCancelParam.java b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinCancelParam.java
new file mode 100644
index 0000000..d6ce3fd
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinCancelParam.java
@@ -0,0 +1,19 @@
+package com.doumee.core.douyin.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 銆屾挙閿�鏍搁攢銆岰ontroller 鍏ュ弬(绠$悊绔� platform /business/douyinVerify/cancel)
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+@Data
+@ApiModel("鎶栭煶鎾ら攢鏍搁攢鍏ュ弬")
+public class DouyinCancelParam {
+
+    @ApiModelProperty(value = "鏍搁攢璁板綍ID(鏈湴)", required = true)
+    private String id;
+}
diff --git a/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinCancelReq.java b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinCancelReq.java
new file mode 100644
index 0000000..b5f4485
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinCancelReq.java
@@ -0,0 +1,31 @@
+package com.doumee.core.douyin.dto;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+
+/**
+ * 銆屾挙閿�鏍搁攢銆嶅叆鍙�
+ * POST https://open.douyin.com/goodlife/v1/fulfilment/certificate/cancel/
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+@Data
+public class DouyinCancelReq {
+
+    /** 鍒告爣璇�(楠屽埜鏃惰繑鍥�,蹇呭~) */
+    @JSONField(name = "certificate_id")
+    private String certificateId;
+
+    /** 涓�娆℃牳閿�鍞竴鏍囪瘑(楠屽埜鏃惰繑鍥�,蹇呭~;娆″崱鎾ら攢澶氭濉� 0) */
+    @JSONField(name = "verify_id")
+    private String verifyId;
+
+    /** 鏍搁攢鍟嗘埛鏍硅处鎴稩D(浜戣繛閿佸繀濉�) */
+    @JSONField(name = "account_id")
+    private String accountId;
+
+    /** 鎾ら攢骞傜瓑鏍囪瘑(娆″崱闃茶秴鏃堕噸澶嶆挙閿�,鏈夋晥鏈�1灏忔椂);鍒嗛棬搴楃粨绠椾笉瑕佷紶 */
+    @JSONField(name = "cancel_token")
+    private String cancelToken;
+}
diff --git a/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinCancelResp.java b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinCancelResp.java
new file mode 100644
index 0000000..7d1b028
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinCancelResp.java
@@ -0,0 +1,51 @@
+package com.doumee.core.douyin.dto;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 銆屾挙閿�鏍搁攢銆嶅嚭鍙�(data 鑺傜偣)
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+@Data
+public class DouyinCancelResp {
+
+    /** 鎶栭煶鎾ら攢浜ゆ槗娴佹按鍙� */
+    @JSONField(name = "transaction_id")
+    private String transactionId;
+
+    /** 閿欒鐮�,0 琛ㄧず鎴愬姛 */
+    @JSONField(name = "error_code")
+    private Integer errorCode;
+
+    /** 閿欒/鎴愬姛鎻忚堪鏂囨 */
+    @JSONField(name = "description")
+    private String description;
+
+    /** 鎾ら攢缁撴灉鍒楄〃(鎸夊埜) */
+    @JSONField(name = "cancel_results")
+    private List<CancelResult> cancelResults;
+
+    @Data
+    public static class CancelResult {
+        /** 瀵瑰簲鐨勪竴娆℃牳閿�鍞竴鏍囪瘑 */
+        @JSONField(name = "verify_id")
+        private String verifyId;
+
+        /** 0 鎾ら攢鎴愬姛 */
+        @JSONField(name = "result_code")
+        private Integer resultCode;
+
+        /** 缁撴灉鎻忚堪 */
+        @JSONField(name = "result_msg")
+        private String resultMsg;
+
+        /** 鎶栭煶渚ц鍗曞彿 */
+        @JSONField(name = "order_id")
+        private String orderId;
+    }
+}
diff --git a/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinClientTokenReq.java b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinClientTokenReq.java
new file mode 100644
index 0000000..a9ba8d2
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinClientTokenReq.java
@@ -0,0 +1,27 @@
+package com.doumee.core.douyin.dto;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+
+/**
+ * 銆岀敓鎴� client_token銆嶅叆鍙�
+ * POST https://open.douyin.com/oauth/client_token/
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+@Data
+public class DouyinClientTokenReq {
+
+    /** 鏈嶅姟鍟�/搴旂敤 client_key(鎶栭煶寮�鏀惧钩鍙伴鍙�) */
+    @JSONField(name = "client_key")
+    private String clientKey;
+
+    /** 鏈嶅姟鍟�/搴旂敤 client_secret(鎶栭煶寮�鏀惧钩鍙伴鍙�,闇�濡ュ杽淇濈) */
+    @JSONField(name = "client_secret")
+    private String clientSecret;
+
+    /** 鍥哄畾鍊� client_credential */
+    @JSONField(name = "grant_type")
+    private String grantType = "client_credential";
+}
diff --git a/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinClientTokenResp.java b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinClientTokenResp.java
new file mode 100644
index 0000000..290d692
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinClientTokenResp.java
@@ -0,0 +1,37 @@
+package com.doumee.core.douyin.dto;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+
+/**
+ * 銆岀敓鎴� client_token銆嶅嚭鍙�
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+@Data
+public class DouyinClientTokenResp {
+
+    /** 鏁版嵁鑺傜偣 */
+    @JSONField(name = "data")
+    private DataBean data;
+
+    @Data
+    public static class DataBean {
+        /** access_token,浠� clt. 寮�澶�,浣滀负鍚庣画鎺ュ彛 access-token 璇锋眰澶� */
+        @JSONField(name = "access_token")
+        private String accessToken;
+
+        /** 杩囨湡鏃堕棿(绉�),榛樿 7200 */
+        @JSONField(name = "expires_in")
+        private Long expiresIn;
+
+        /** 閿欒鐮�,0 琛ㄧず鎴愬姛 */
+        @JSONField(name = "error_code")
+        private Integer errorCode;
+
+        /** 閿欒/鎴愬姛鎻忚堪鏂囨 */
+        @JSONField(name = "description")
+        private String description;
+    }
+}
diff --git a/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinOnlineQueryReq.java b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinOnlineQueryReq.java
new file mode 100644
index 0000000..d187242
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinOnlineQueryReq.java
@@ -0,0 +1,46 @@
+package com.doumee.core.douyin.dto;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 銆屾煡璇㈠晢鍝佺嚎涓婃暟鎹垪琛ㄣ�嶅叆鍙�
+ * POST https://open.douyin.com/goodlife/v1/goods/product/online/query/
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+@Data
+public class DouyinOnlineQueryReq {
+
+    /** 鏉ュ璐︽埛ID(鏈嶅姟鍟嗗満鏅繀濉�,鏈紶鏃剁敱瀹㈡埛绔敤閰嶇疆榛樿鍊艰ˉ榻�) */
+    @JSONField(name = "account_id")
+    private String accountId;
+
+    /** 鎶栭煶渚у晢鍝両D */
+    @JSONField(name = "product_id")
+    private String productId;
+
+    /** 澶栭儴鍟嗗搧ID */
+    @JSONField(name = "out_id")
+    private String outId;
+
+    /** 鍦ㄧ嚎鐘舵�� 1鍦ㄧ嚎 2涓嬬嚎 3灏佺 */
+    @JSONField(name = "status")
+    private Integer status;
+
+    /** 鍒嗛〉娓告爣(棣栭〉涓嶄紶) */
+    @JSONField(name = "cursor")
+    private String cursor;
+
+    /** 姣忛〉鏁伴噺(鎶栭煶瀛楁 count) */
+    @JSONField(name = "count")
+    private Integer count;
+
+    /** 鏍搁攢闂ㄥ簵ID鍒楄〃(鎶栭煶瀛楁 poi_ids,Array<Int64>,鏈�澶�100瀹�;
+     *  娉ㄦ剰:浼� 0 鎶栭煶瑙嗕负绌哄�尖啋杩斿洖鍟嗘埛涓嬪叏閲忓晢鍝�,鏁呭繀椤诲~鐪熷疄闂ㄥ簵ID) */
+    @JSONField(name = "poi_ids")
+    private List<Long> poiIds;
+}
diff --git a/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinOnlineQueryResp.java b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinOnlineQueryResp.java
new file mode 100644
index 0000000..d83e425
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinOnlineQueryResp.java
@@ -0,0 +1,36 @@
+package com.doumee.core.douyin.dto;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 銆屾煡璇㈠晢鍝佺嚎涓婃暟鎹垪琛ㄣ�嶅嚭鍙�(data 鑺傜偣)
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+@Data
+public class DouyinOnlineQueryResp {
+
+    /** 鏄惁杩樻湁涓嬩竴椤� */
+    @JSONField(name = "has_more")
+    private Boolean hasMore;
+
+    /** 涓嬩竴椤垫父鏍�(浣滀负涓嬩竴娆¤姹傜殑 cursor) */
+    @JSONField(name = "next_cursor")
+    private String nextCursor;
+
+    /** 閿欒鐮�,0 琛ㄧず鎴愬姛 */
+    @JSONField(name = "error_code")
+    private Integer errorCode;
+
+    /** 閿欒/鎴愬姛鎻忚堪鏂囨 */
+    @JSONField(name = "description")
+    private String description;
+
+    /** 褰撳墠椤靛晢鍝佸垪琛� */
+    @JSONField(name = "products")
+    private List<DouyinProductDTO> products;
+}
diff --git a/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinPrepareParam.java b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinPrepareParam.java
new file mode 100644
index 0000000..a1ab6d6
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinPrepareParam.java
@@ -0,0 +1,25 @@
+package com.doumee.core.douyin.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 銆岄獙鍒稿噯澶囥�岰ontroller 鍏ュ弬(web 绔皬绋嬪簭:鎵爜 / 杈撶爜)
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+@Data
+@ApiModel("鎶栭煶楠屽埜鍑嗗鍏ュ弬")
+public class DouyinPrepareParam {
+
+    @ApiModelProperty(value = "鎵爜浜岀淮鐮佸唴瀹�(鐭摼鎴栧惈 object_id 鐨勯暱閾�),涓� code 浜岄�変竴")
+    private String qrContent;
+
+    @ApiModelProperty(value = "鍒哥爜鏄庢枃(鎵嬪姩杈撳叆鍦烘櫙),涓� qrContent 浜岄�変竴")
+    private String code;
+
+    @ApiModelProperty(value = "鏍搁攢鎶栭煶闂ㄥ簵ID", required = true)
+    private String poiId;
+}
diff --git a/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinPrepareReq.java b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinPrepareReq.java
new file mode 100644
index 0000000..7787fec
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinPrepareReq.java
@@ -0,0 +1,35 @@
+package com.doumee.core.douyin.dto;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+
+/**
+ * 銆岄獙鍒稿噯澶囥�嶅叆鍙�
+ * GET https://open.douyin.com/goodlife/v1/fulfilment/certificate/prepare/
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+@Data
+public class DouyinPrepareReq {
+
+    /** 浠庝簩缁寸爜瑙f瀽鍑虹殑鏍囪瘑(浼犲弬鍓嶉渶URL缂栫爜);涓� code 浜岄�変竴 */
+    @JSONField(name = "encrypted_data")
+    private String encryptedData;
+
+    /** 鍒哥爜鏄庢枃(鎵嬪姩杈撳叆鍦烘櫙);涓� encrypted_data 浜岄�変竴 */
+    @JSONField(name = "code")
+    private String code;
+
+    /** 鎿嶄綔鏍搁攢鐨勬姈闊抽棬搴桰D(蹇呭~) */
+    @JSONField(name = "poi_id")
+    private String poiId;
+
+    /** 鏍搁攢鍟嗘埛鏍硅处鎴稩D(浜戣繛閿�/鍏辩鍦烘櫙蹇呭~) */
+    @JSONField(name = "account_id")
+    private String accountId;
+
+    /** 鏈嶅姟鍟嗘敹閾剁増鏈彿 */
+    @JSONField(name = "can_verify")
+    private Integer canVerify;
+}
diff --git a/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinPrepareResp.java b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinPrepareResp.java
new file mode 100644
index 0000000..7ffb46e
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinPrepareResp.java
@@ -0,0 +1,93 @@
+package com.doumee.core.douyin.dto;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 銆岄獙鍒稿噯澶囥�嶅嚭鍙�(data 鑺傜偣)
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+@Data
+public class DouyinPrepareResp {
+
+    /** 涓�娆¢獙鍒稿敮涓�鏍囪瘑,浣滀负鍚庣画 verify 鐨� verify_token */
+    @JSONField(name = "verify_token")
+    private String verifyToken;
+
+    /** 鎶栭煶渚ц鍗曞彿 */
+    @JSONField(name = "order_id")
+    private String orderId;
+
+    /** 閿欒鐮�,0 琛ㄧず鎴愬姛 */
+    @JSONField(name = "error_code")
+    private Integer errorCode;
+
+    /** 閿欒/鎴愬姛鎻忚堪鏂囨 */
+    @JSONField(name = "description")
+    private String description;
+
+    /** 鍛戒腑鐨勫埜鍒楄〃(涓�娆″彲鑳藉寮�) */
+    @JSONField(name = "certificates")
+    private List<Certificate> certificates;
+
+    @Data
+    public static class Certificate {
+        /** 鍔犲瘑鍒哥爜(鐢ㄤ簬鍚庣画 verify 鐨� encrypted_codes) */
+        @JSONField(name = "encrypted_code")
+        private String encryptedCode;
+
+        /** 鍒告爣璇�(鎾ら攢鏍搁攢蹇呯敤) */
+        @JSONField(name = "certificate_id")
+        private String certificateId;
+
+        /** 鍘熷鍒哥爜(鏄庢枃) */
+        @JSONField(name = "code")
+        private String code;
+
+        /** 鍒哥姸鎬� 1鍙敤 2宸叉牳閿� 3閫�娆句腑 4宸查��娆� 5鏈埌鍙敤鏃ユ湡 6宸茶繃鏈� */
+        @JSONField(name = "status")
+        private Integer status;
+
+        /** 鏄惁鍙牳閿� 1鍙牳閿� */
+        @JSONField(name = "can_verify_status")
+        private Integer canVerifyStatus;
+
+        /** 鍒稿搴旂殑 SKU 瑙勬牸淇℃伅 */
+        @JSONField(name = "sku")
+        private Sku sku;
+
+        /** 閲戦淇℃伅 */
+        @JSONField(name = "amount")
+        private Amount amount;
+    }
+
+    @Data
+    public static class Sku {
+        /** 鎶栭煶 SKU ID(鏍搁攢寮�濂楅鏃�,鍙嶆煡鏈湴濂楅閾捐矾鐨勮捣鐐�) */
+        @JSONField(name = "sku_id")
+        private String skuId;
+
+        /** SKU 鏍囬(瑙勬牸鍚嶇О) */
+        @JSONField(name = "title")
+        private String title;
+
+        /** 甯傚満浠�(鍒�) */
+        @JSONField(name = "market_price")
+        private Long marketPrice;
+    }
+
+    @Data
+    public static class Amount {
+        /** 瀹炰粯閲戦(鍒�) */
+        @JSONField(name = "pay_amount")
+        private Long payAmount;
+
+        /** 鍘熶环(鍒�) */
+        @JSONField(name = "original_amount")
+        private Long originalAmount;
+    }
+}
diff --git a/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinProductDTO.java b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinProductDTO.java
new file mode 100644
index 0000000..f97c8bc
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinProductDTO.java
@@ -0,0 +1,76 @@
+package com.doumee.core.douyin.dto;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 鎶栭煶鍟嗗搧椤�(online/query 杩斿洖鐨� products 鍏冪礌)銆�
+ * <p>鎶栭煶 online/query 鐨� products 鍏冪礌涓�<strong>宓屽缁撴瀯</strong>:鍦ㄧ嚎鐘舵�併�丼KU 鍒楄〃鍦ㄩ《灞�,
+ * 鍟嗗搧鍩虹淇℃伅(product_id/product_name/product_type/category/out_id 绛�)钘忓湪 {@code product} 瀛愬璞¢噷銆�
+ * 鏁呮湰绫婚《灞傚彧鎸佹湁 onlineStatus / skus / product 涓変釜瀛楁銆�
+ * <p>鏃╂湡鐗堟湰鎶� productId 绛夊钩閾哄湪椤跺眰,涓庢姈闊崇湡瀹炶繑鍥炲眰绾т笉绗�,瀵艰嚧闄� onlineStatus 澶栧瓧娈靛叏绌�,宸叉牎姝c��
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+@Data
+public class DouyinProductDTO {
+
+    /** 鍦ㄧ嚎鐘舵�� 1鍦ㄧ嚎 2涓嬬嚎 3灏佺(椤跺眰瀛楁) */
+    @JSONField(name = "online_status")
+    private Integer onlineStatus;
+
+    /** 澶氳鏍煎晢鍝佺殑 SKU 瑙勬牸鍒楄〃(澶嶆暟鑺傜偣,椤跺眰瀛楁);鍗� SKU 鍥㈣喘姝や负绌烘暟缁� */
+    @JSONField(name = "skus")
+    private List<DouyinSkuDTO> skus;
+
+    /**
+     * 鍗� SKU 鍟嗗搧(濡傚洟璐� product_type=1)鐨� SKU 鏄庣粏(鍗曟暟鑺傜偣,椤跺眰瀛楁)銆�
+     * <p>鎶栭煶 online/query 瀵瑰崟 SKU 鍥㈣喘杩斿洖 {@code sku}(鍗曟暟瀵硅薄)涓� {@code skus}(澶嶆暟)涓虹┖鏁扮粍;
+     * 澶氳鏍煎晢鍝佸垯鐩稿弽(璧� skus)銆傚叆搴撴椂涓よ�呭綊闆嗙粺涓�澶勭悊,瑙� upsertProduct銆�
+     */
+    @JSONField(name = "sku")
+    private DouyinSkuDTO sku;
+
+    /** 鍟嗗搧鍩虹淇℃伅(宓屽瀛愬璞�,鎵胯浇 product_id/product_name/product_type/category/out_id 绛�) */
+    @JSONField(name = "product")
+    private DouyinProductInfoDTO product;
+
+    /**
+     * 鎶栭煶 online/query 鐨� product 瀛愬璞�(鍟嗗搧鍩虹淇℃伅)銆�
+     * <p>瀵瑰簲鎶栭煶杩斿洖 products[].product 鑺傜偣銆�
+     */
+    @Data
+    public static class DouyinProductInfoDTO {
+
+        /** 鎶栭煶渚у晢鍝両D(涓氬姟鍞竴閿�,鐢ㄤ簬鏈湴 upsert 涓庢牳閿�鍖归厤) */
+        @JSONField(name = "product_id")
+        private String productId;
+
+        /** 鍟嗗搧鍚嶇О */
+        @JSONField(name = "product_name")
+        private String productName;
+
+        /** 鍟嗗搧绫诲瀷 */
+        @JSONField(name = "product_type")
+        private Integer productType;
+
+        /** 绫荤洰ID(鏁板瓧,瓒呭嚭 int 鑼冨洿鐢� Long) */
+        @JSONField(name = "category_id")
+        private Long categoryId;
+
+        /** 绫荤洰鍏ㄥ悕(濡�"鏈湴鐢熸椿/椁愰ギ/...",鏂囨湰,灞曠ず鐢�) */
+        @JSONField(name = "category_full_name")
+        private String categoryFullName;
+
+        /** 鎶栭煶鍘熷 out_id;鏈湴 out_id 鐢辩鐞嗙缁戝畾濂楅(discount.id),鍚屾鏃躲�愪笉鍏ュ簱銆� */
+        @JSONField(name = "out_id")
+        private String outId;
+
+        /** 褰掑睘璐︽埛ID(鏉ュ鍟嗘埛鏍硅处鎴�,鏁板瓧杞瓧绗︿覆鍚庤惤搴�) */
+        @JSONField(name = "owner_account_id")
+        private Long ownerAccountId;
+    }
+}
diff --git a/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinShopPoiResp.java b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinShopPoiResp.java
new file mode 100644
index 0000000..8083126
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinShopPoiResp.java
@@ -0,0 +1,51 @@
+package com.doumee.core.douyin.dto;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 銆屾煡璇㈤棬搴椾俊鎭�嶅嚭鍙�(data 鑺傜偣)銆�
+ * <p>GET https://open.douyin.com/goodlife/v1/shop/poi/query/
+ * <p>杩斿洖鍟嗘埛涓嬪凡璁ら鐨勯棬搴楀垪琛�,闂ㄥ簵ID宓屽鍦� pois[].poi.poi_id銆�
+ * 褰撳墠鍙敤闂ㄥ簵ID,鍏朵綑鑺傜偣(account / root_account 绛�)涓嶅仛鏄犲皠銆�
+ *
+ * @author rk
+ * @date 2026/06/26
+ */
+@Data
+public class DouyinShopPoiResp {
+
+    /** 褰撳墠璐︽埛涓嬪凡璁ら鐨勯棬搴楀垪琛� */
+    @JSONField(name = "pois")
+    private List<Poi> pois;
+
+    /** 閿欒鐮�,0 琛ㄧず鎴愬姛 */
+    @JSONField(name = "error_code")
+    private Integer errorCode;
+
+    /** 閿欒/鎴愬姛鎻忚堪鏂囨 */
+    @JSONField(name = "description")
+    private String description;
+
+    /**
+     * 鍗曚釜闂ㄥ簵鏉$洰銆備粎瑙f瀽 poi 鑺傜偣鍙栭棬搴桰D銆�
+     */
+    @Data
+    public static class Poi {
+        /** 闂ㄥ簵鍩烘湰淇℃伅(鍚� poi_id) */
+        @JSONField(name = "poi")
+        private PoiInfo poi;
+    }
+
+    /**
+     * 闂ㄥ簵鍩烘湰淇℃伅銆傚綋鍓嶅搷搴斾粎鐢ㄥ埌 poi_id銆�
+     */
+    @Data
+    public static class PoiInfo {
+        /** 闂ㄥ簵POI ID(鏍搁攢闂ㄥ簵ID,涓庡瓧鍏� POI_ID 鍚屽彛寰�) */
+        @JSONField(name = "poi_id")
+        private String poiId;
+    }
+}
diff --git a/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinSkuDTO.java b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinSkuDTO.java
new file mode 100644
index 0000000..bb12e86
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinSkuDTO.java
@@ -0,0 +1,40 @@
+package com.doumee.core.douyin.dto;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+
+/**
+ * 鎶栭煶鍟嗗搧 SKU 椤�(online/query 杩斿洖 products[].skus / products[].sku 鐨勫厓绱�)銆�
+ * <p>瀛楁鍚嶄弗鏍煎榻愭姈闊� online/query 鐪熷疄杩斿洖鐨� snake_case;
+ * 鏃╂湡鐗堟湰鎸夌寽娴嬪懡鍚�(title/market_price/third_sku_id 绛�)涓庢姈闊宠繑鍥炰笉绗�,宸叉牎姝c��
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+@Data
+public class DouyinSkuDTO {
+
+    /** 鎶栭煶 SKU ID(瑙勬牸鍞竴閿�,鏍搁攢鍖归厤鐢�) */
+    @JSONField(name = "sku_id")
+    private String skuId;
+
+    /** SKU 鏍囬(瑙勬牸鍚嶇О,鎶栭煶瀛楁 sku_name) */
+    @JSONField(name = "sku_name")
+    private String skuName;
+
+    /** 鍘熶环/甯傚満浠�(鍒�,鎶栭煶瀛楁 origin_amount,鍗冲垝绾夸环) */
+    @JSONField(name = "origin_amount")
+    private Long originAmount;
+
+    /** 鍥㈣喘瀹炰粯浠�(鍒�,鎶栭煶瀛楁 actual_amount,鐢ㄦ埛瀹為檯鏀粯閲戦) */
+    @JSONField(name = "actual_amount")
+    private Long actualAmount;
+
+    /** 澶栭儴 SKU ID(鍟嗗鑷畾涔�,鎶栭煶瀛楁 out_sku_id) */
+    @JSONField(name = "out_sku_id")
+    private String skuOutId;
+
+    /** SKU 鐘舵�� 1涓婃灦 2涓嬬嚎 */
+    @JSONField(name = "status")
+    private Integer status;
+}
diff --git a/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinVerifyParam.java b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinVerifyParam.java
new file mode 100644
index 0000000..5e6f59a
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinVerifyParam.java
@@ -0,0 +1,36 @@
+package com.doumee.core.douyin.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 銆岄獙鍒�(鏍搁攢)銆岰ontroller 鍏ュ弬(web 绔皬绋嬪簭)
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+@Data
+@ApiModel("鎶栭煶楠屽埜鍏ュ弬")
+public class DouyinVerifyParam {
+
+    @ApiModelProperty(value = "prepare 杩斿洖鐨� verifyToken", required = true)
+    private String verifyToken;
+
+    @ApiModelProperty(value = "鏍搁攢鎶栭煶闂ㄥ簵ID", required = true)
+    private String poiId;
+
+    @ApiModelProperty(value = "prepare 杩斿洖鐨勫姞瀵嗗埜鐮佸垪琛�", required = true)
+    private List<String> encryptedCodes;
+
+    @ApiModelProperty(value = "鏍搁攢鍟嗘埛鏍硅处鎴稩D(鍏辩/浜戣繛閿佸満鏅紶)")
+    private String accountId;
+
+    @ApiModelProperty(value = "prepare 杩斿洖鐨勫埜瀵瑰簲 SKU ID(certificate.sku.skuId),鏍搁攢鎴愬姛鍚庢嵁姝ゅ弽鏌ユ湰鍦板椁愬紑閫�", required = true)
+    private String skuId;
+
+    @ApiModelProperty(value = "瀹炰粯閲戦(鍒�,鏉ヨ嚜 prepare 鐨� certificate.amount.payAmount,浠呯敤浜庢牳閿�璁板綍蹇収)")
+    private Long payAmount;
+}
diff --git a/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinVerifyReq.java b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinVerifyReq.java
new file mode 100644
index 0000000..5126c49
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinVerifyReq.java
@@ -0,0 +1,41 @@
+package com.doumee.core.douyin.dto;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 銆岄獙鍒搞�嶅叆鍙�
+ * POST https://open.douyin.com/goodlife/v1/fulfilment/certificate/verify/
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+@Data
+public class DouyinVerifyReq {
+
+    /** prepare 杩斿洖鐨勪竴娆¢獙鍒告爣璇�(蹇呭~) */
+    @JSONField(name = "verify_token")
+    private String verifyToken;
+
+    /** 鏍搁攢鎶栭煶闂ㄥ簵ID(蹇呭~) */
+    @JSONField(name = "poi_id")
+    private String poiId;
+
+    /** prepare 杩斿洖鐨勫姞瀵嗗埜鐮�(鎶栭煶鐮佸満鏅�,澶氭鍗″彲浼犲涓浉鍚屽��) */
+    @JSONField(name = "encrypted_codes")
+    private List<String> encryptedCodes;
+
+    /** 涓夋柟鍘熷鍒哥爜(涓夋柟鐮佸満鏅�) */
+    @JSONField(name = "codes")
+    private List<String> codes;
+
+    /** 鏍搁攢鍟嗘埛鏍硅处鎴稩D(鍏辩/浜戣繛閿佸繀濉�) */
+    @JSONField(name = "account_id")
+    private String accountId;
+
+    /** 鎶栭煶渚ц鍗曞彿(涓夋柟鐮侀潪棰勫妯″紡蹇呭~) */
+    @JSONField(name = "order_id")
+    private String orderId;
+}
diff --git a/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinVerifyResp.java b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinVerifyResp.java
new file mode 100644
index 0000000..0fef607
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/core/douyin/dto/DouyinVerifyResp.java
@@ -0,0 +1,59 @@
+package com.doumee.core.douyin.dto;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 銆岄獙鍒搞�嶅嚭鍙�(data 鑺傜偣)
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+@Data
+public class DouyinVerifyResp {
+
+    /** 閿欒鐮�,0 琛ㄧず鎴愬姛 */
+    @JSONField(name = "error_code")
+    private Integer errorCode;
+
+    /** 閿欒/鎴愬姛鎻忚堪鏂囨 */
+    @JSONField(name = "description")
+    private String description;
+
+    /** 鏍搁攢缁撴灉鍒楄〃(鎸夊埜) */
+    @JSONField(name = "verify_results")
+    private List<VerifyResult> verifyResults;
+
+    @Data
+    public static class VerifyResult {
+        /** 0 楠屽埜鎴愬姛 */
+        @JSONField(name = "result")
+        private Integer result;
+
+        /** 缁撴灉鎻忚堪 */
+        @JSONField(name = "msg")
+        private String msg;
+
+        /** 涓�娆℃牳閿�鍞竴鏍囪瘑(鎾ら攢鏍搁攢蹇呯敤) */
+        @JSONField(name = "verify_id")
+        private String verifyId;
+
+        /** 鍒告爣璇�(鎾ら攢鏍搁攢蹇呯敤) */
+        @JSONField(name = "certificate_id")
+        private String certificateId;
+
+        /** 鍘熷鍒哥爜(鏄庢枃) */
+        @JSONField(name = "origin_code")
+        private String originCode;
+
+        /** 鎶栭煶渚ц鍗曞彿 */
+        @JSONField(name = "order_id")
+        private String orderId;
+
+        /** 鏍搁攢鍟嗘埛鏍硅处鎴稩D */
+        @JSONField(name = "account_id")
+        private String accountId;
+    }
+}
diff --git a/server/services/src/main/java/com/doumee/core/track/RideActiveCache.java b/server/services/src/main/java/com/doumee/core/track/RideActiveCache.java
new file mode 100644
index 0000000..3c8a3c2
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/core/track/RideActiveCache.java
@@ -0,0 +1,92 @@
+package com.doumee.core.track;
+
+import com.alibaba.fastjson.JSON;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 鐢佃溅銆岃溅杈� 鈫� 娲昏穬楠戣璁㈠崟銆峈edis 缂撳瓨,鏀跺彛璇诲啓鍒�,渚涘紑閿� / 杩樿溅 / 浣嶇疆涓婃姤涓夊缁熶竴璋冪敤銆�
+ * <p>鐢ㄩ��:浣嶇疆涓婃姤(JT/T 808,绉掔骇楂橀)鏃�,鐢� bikeCode O(1) 鍙栨椿璺冭鍗�,
+ * 鏇夸唬姣忔鏌� member_rides,閬垮厤楂橀 DB 鏌ヨ銆�
+ * <p>鐢熷懡鍛ㄦ湡:
+ * <ul>
+ *   <li>鐢佃溅寮�閿佹垚鍔�(鈫� 楠戣涓�):{@link #set} 鍐欏叆,甯� 24h TTL</li>
+ *   <li>鐢佃溅杩樿溅鎴愬姛(鈫� 宸茶繕杞�):{@link #remove} 鍒犻櫎(瑕嗙洊 backBike / autoBackBike / forceBack 澶氬叆鍙�)</li>
+ *   <li>涓村仠(鈫� 涓村仠涓�):涓嶅垹,鏈熼棿鐓у父鍐欒建杩�</li>
+ * </ul>
+ * <p>TTL 浠呬负鍏滃簳:姝e父杩樿溅浼氫富鍔ㄥ垹;闃茶繕杞﹀紓甯稿垎鏀紡鍒犲鑷存槧灏勬硠婕� 鈫� 宸茶繕杞︾殑杞︾户缁褰撴椿璺冨啓鑴忚建杩广��
+ * <p>闄嶇骇:鏈被鏂规硶涓嶅悶寮傚父,鐢辫皟鐢ㄦ柟 try-catch 闄嶇骇(缂撳瓨鏄紭鍖栥�侀潪涓氬姟姝g‘鎬ф潵婧�,澶辫触涓嶅緱闃绘柇涓绘祦绋�)銆�
+ * <p>value 浠� JSON 瀛楃涓插瓨鍙�(涓庢姈闊� token 缂撳瓨涓�鑷�,閬垮紑 RedisTemplate 搴忓垪鍖栧櫒瀵� POJO 鐨勪緷璧�)銆�
+ *
+ * @author rk
+ * @date 2026/06/25
+ */
+@Slf4j
+@Component
+public class RideActiveCache {
+
+    /** Redis key 鍓嶇紑:杞﹁締缁村害娲昏穬璁㈠崟鏄犲皠 */
+    private static final String KEY_PREFIX = "ride:active:";
+
+    /** 缂撳瓨 TTL(绉�):24 灏忔椂,瑕嗙洊鏈�闀块獞琛�;鍏滃簳闃茶繕杞︽紡鍒犲鑷存槧灏勬硠婕� */
+    private static final long TTL_SECONDS = 24L * 60L * 60L;
+
+    @Autowired
+    private RedisTemplate<String, Object> redisTemplate;
+
+    /** 鎷� Redis key:ride:active:{bikeCode} */
+    private static String key(String bikeCode) {
+        return KEY_PREFIX + bikeCode;
+    }
+
+    /**
+     * 寮�閿佹垚鍔熸椂鍐欏叆娲昏穬璁㈠崟鏄犲皠(甯� 24h TTL)銆�
+     *
+     * @param bikeCode 杞﹁締缂栫爜(缂撳瓨缁村害)
+     * @param ridesId  楠戣璁㈠崟涓婚敭 member_rides.id
+     * @param orderId  鏀粯璁㈠崟涓婚敭 member_rides.ordre_id 鈫� goodsorder.id(鍙� null)
+     */
+    public void set(String bikeCode, String ridesId, String orderId) {
+        if (StringUtils.isBlank(bikeCode)) {
+            return;
+        }
+        RideActiveInfo info = new RideActiveInfo();
+        info.setRidesId(ridesId);
+        info.setOrderId(orderId);
+        redisTemplate.opsForValue().set(key(bikeCode), JSON.toJSONString(info), TTL_SECONDS, TimeUnit.SECONDS);
+    }
+
+    /**
+     * 浣嶇疆涓婃姤鏃惰鍙栨椿璺冭鍗曟槧灏勩��
+     *
+     * @param bikeCode 杞﹁締缂栫爜
+     * @return 娲昏穬璁㈠崟杞借嵎;鏃犳槧灏� / 闈� String 鍊艰繑鍥� null(璋冪敤鏂规嵁姝よ烦杩囪建杩瑰啓鍏�)
+     */
+    public RideActiveInfo get(String bikeCode) {
+        if (StringUtils.isBlank(bikeCode)) {
+            return null;
+        }
+        Object cached = redisTemplate.opsForValue().get(key(bikeCode));
+        if (cached instanceof String) {
+            return JSON.parseObject((String) cached, RideActiveInfo.class);
+        }
+        return null;
+    }
+
+    /**
+     * 杩樿溅鎴愬姛鏃跺垹闄ゆ椿璺冭鍗曟槧灏勩��
+     *
+     * @param bikeCode 杞﹁締缂栫爜
+     */
+    public void remove(String bikeCode) {
+        if (StringUtils.isBlank(bikeCode)) {
+            return;
+        }
+        redisTemplate.delete(key(bikeCode));
+    }
+}
diff --git a/server/services/src/main/java/com/doumee/core/track/RideActiveInfo.java b/server/services/src/main/java/com/doumee/core/track/RideActiveInfo.java
new file mode 100644
index 0000000..73991e3
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/core/track/RideActiveInfo.java
@@ -0,0 +1,21 @@
+package com.doumee.core.track;
+
+import lombok.Data;
+
+/**
+ * 娲昏穬璁㈠崟缂撳瓨鍊�:杞﹁締 鈫� 褰撳墠娲昏穬楠戣璁㈠崟鐨勬槧灏勮浇鑽枫��
+ * <p>搴忓垪鍖栦负 JSON 瀛樺叆 Redis(key = {@code ride:active:{bikeCode}});
+ * 鐢� {@link RideActiveCache} 璇诲啓,杞ㄨ抗涓婃姤鏃舵嵁姝ゅ垽瀹氭槸鍚﹀啓杞ㄨ抗銆�
+ *
+ * @author rk
+ * @date 2026/06/25
+ */
+@Data
+public class RideActiveInfo {
+
+    /** 楠戣璁㈠崟涓婚敭 member_rides.id */
+    private String ridesId;
+
+    /** 鏀粯璁㈠崟涓婚敭 member_rides.ordre_id 鈫� goodsorder.id(寮�閿佹椂鏈粦瀹氬垯涓� null) */
+    private String orderId;
+}
diff --git a/server/services/src/main/java/com/doumee/dao/business/DouyinProductMapper.java b/server/services/src/main/java/com/doumee/dao/business/DouyinProductMapper.java
new file mode 100644
index 0000000..ff17259
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/dao/business/DouyinProductMapper.java
@@ -0,0 +1,14 @@
+package com.doumee.dao.business;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.doumee.dao.business.model.DouyinProduct;
+import com.github.yulichang.base.mapper.MPJJoinMapper;
+
+/**
+ * 鎶栭煶鍟嗗搧(鍥㈣喘) MyBatis-Plus Mapper,瀵瑰簲 douyin_product 琛�
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+public interface DouyinProductMapper extends MPJJoinMapper<DouyinProduct> {
+}
diff --git a/server/services/src/main/java/com/doumee/dao/business/DouyinProductSkuMapper.java b/server/services/src/main/java/com/doumee/dao/business/DouyinProductSkuMapper.java
new file mode 100644
index 0000000..2d3c166
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/dao/business/DouyinProductSkuMapper.java
@@ -0,0 +1,13 @@
+package com.doumee.dao.business;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.doumee.dao.business.model.DouyinProductSku;
+
+/**
+ * 鎶栭煶鍟嗗搧 SKU 瑙勬牸 MyBatis-Plus Mapper,瀵瑰簲 douyin_product_sku 琛�
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+public interface DouyinProductSkuMapper extends BaseMapper<DouyinProductSku> {
+}
diff --git a/server/services/src/main/java/com/doumee/dao/business/DouyinVerifyLogMapper.java b/server/services/src/main/java/com/doumee/dao/business/DouyinVerifyLogMapper.java
new file mode 100644
index 0000000..2e6528f
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/dao/business/DouyinVerifyLogMapper.java
@@ -0,0 +1,13 @@
+package com.doumee.dao.business;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.doumee.dao.business.model.DouyinVerifyLog;
+
+/**
+ * 鎶栭煶楠屽埜鎿嶄綔鏃ュ織 MyBatis-Plus Mapper,瀵瑰簲 douyin_verify_log 琛�
+ *
+ * @author rk
+ * @date 2026/06/25
+ */
+public interface DouyinVerifyLogMapper extends BaseMapper<DouyinVerifyLog> {
+}
diff --git a/server/services/src/main/java/com/doumee/dao/business/DouyinVerifyRecordMapper.java b/server/services/src/main/java/com/doumee/dao/business/DouyinVerifyRecordMapper.java
new file mode 100644
index 0000000..de25e67
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/dao/business/DouyinVerifyRecordMapper.java
@@ -0,0 +1,14 @@
+package com.doumee.dao.business;
+
+import com.doumee.dao.business.model.DouyinVerifyRecord;
+import com.github.yulichang.base.mapper.MPJJoinMapper;
+
+/**
+ * 鎶栭煶鍒告牳閿�璁板綍 MyBatis-Plus Join Mapper,瀵瑰簲 douyin_verify_record 琛�
+ * <p>缁ф壙 MPJJoinMapper 浠ユ敮鎸� selectJoinPage(鍒嗛〉 leftJoin discount_member/member/douyin_product)
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+public interface DouyinVerifyRecordMapper extends MPJJoinMapper<DouyinVerifyRecord> {
+}
diff --git a/server/services/src/main/java/com/doumee/dao/business/MemberRidesTrackMapper.java b/server/services/src/main/java/com/doumee/dao/business/MemberRidesTrackMapper.java
new file mode 100644
index 0000000..22544b2
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/dao/business/MemberRidesTrackMapper.java
@@ -0,0 +1,14 @@
+package com.doumee.dao.business;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.doumee.dao.business.model.MemberRidesTrack;
+
+/**
+ * 鐢佃溅楠戣杞ㄨ抗 Mapper
+ *
+ * @author rk
+ * @date 2026/06/25
+ */
+public interface MemberRidesTrackMapper extends BaseMapper<MemberRidesTrack> {
+
+}
diff --git a/server/services/src/main/java/com/doumee/dao/business/model/DouyinProduct.java b/server/services/src/main/java/com/doumee/dao/business/model/DouyinProduct.java
new file mode 100644
index 0000000..0686843
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/dao/business/model/DouyinProduct.java
@@ -0,0 +1,83 @@
+package com.doumee.dao.business.model;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 鎶栭煶鍟嗗搧(鍥㈣喘)绾夸笂鏁版嵁
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+@Data
+@ApiModel("鎶栭煶鍟嗗搧(鍥㈣喘)绾夸笂鏁版嵁")
+@TableName("\"douyin_product\"")
+public class DouyinProduct {
+
+    @ApiModelProperty(value = "缂栫爜")
+    private String id;
+
+    @ApiModelProperty(value = "鎶栭煶鍟嗗搧ID(涓氬姟鍞竴閿�)")
+    private String productId;
+
+    @ApiModelProperty(value = "缁戝畾鏈湴濂楅ID(discount.id,绠$悊绔淮鎶�,鎶栭煶鍚屾涓嶅啓鍏�)")
+    private String outId;
+
+    @ApiModelProperty(value = "鍟嗗搧鍚嶇О")
+    private String productName;
+
+    @ApiModelProperty(value = "绫荤洰")
+    private String category;
+
+    @ApiModelProperty(value = "鍟嗗搧绫诲瀷")
+    private Integer productType;
+
+    @ApiModelProperty(value = "鍦ㄧ嚎鐘舵�� 1鍦ㄧ嚎 2涓嬬嚎 3灏佺")
+    private Integer onlineStatus;
+
+    @ApiModelProperty(value = "鏉ュ鍟嗘埛鏍硅处鎴稩D")
+    private String accountId;
+
+    @ApiModelProperty(value = "鏈�杩戝悓姝ユ椂闂�")
+    private Date syncDate;
+
+    @ApiModelProperty(value = "鎶栭煶鍘熷鍝嶅簲蹇収")
+    private String rawContent;
+
+    @ApiModelProperty(value = "鍒涘缓鏃堕棿")
+    private Date createDate;
+
+    @ApiModelProperty(value = "鍒涘缓浜�")
+    private String creator;
+
+    @ApiModelProperty(value = "缂栬緫鏃堕棿")
+    private Date editDate;
+
+    @ApiModelProperty(value = "缂栬緫浜�")
+    private String editor;
+
+    @ApiModelProperty(value = "鏄惁宸插垹闄� 0鏈垹闄� 1宸插垹闄�")
+    private Integer isdeleted;
+
+    @ApiModelProperty(value = "缁戝畾濂楅鍚嶇О(鍒嗛〉鏃朵綔濂楅鍚嶆ā绯婃煡璇㈠叆鍙�;璇︽儏/鍒嗛〉鍥炲~瀹為檯濂楅鍚�;涓嶅叆搴�)")
+    @TableField(exist = false)
+    private String discountName;
+
+    @ApiModelProperty(value = "鏈�浣庝环(鍒�;鍙栨湭鍒犻櫎 SKU 鐨勬渶灏� market_price;鏃� SKU 涓� null;涓嶅叆搴�)")
+    @TableField(exist = false)
+    private Long price;
+
+    @ApiModelProperty(value = "宸插厬鎹㈡暟閲�(鏈夋晥鏍搁攢:verify_status=0鎴愬姛 涓� cancel_status=0鏈挙閿�;涓嶅叆搴�)")
+    @TableField(exist = false)
+    private Long exchangedCount;
+
+    @ApiModelProperty(value = "SKU 鍒楄〃")
+    @TableField(exist = false)
+    private List<DouyinProductSku> skus;
+}
diff --git a/server/services/src/main/java/com/doumee/dao/business/model/DouyinProductSku.java b/server/services/src/main/java/com/doumee/dao/business/model/DouyinProductSku.java
new file mode 100644
index 0000000..ae13d87
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/dao/business/model/DouyinProductSku.java
@@ -0,0 +1,56 @@
+package com.doumee.dao.business.model;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 鎶栭煶鍟嗗搧SKU
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+@Data
+@ApiModel("鎶栭煶鍟嗗搧SKU")
+@TableName("\"douyin_product_sku\"")
+public class DouyinProductSku {
+
+    @ApiModelProperty(value = "缂栫爜")
+    private String id;
+
+    @ApiModelProperty(value = "鍏宠仈 douyin_product.product_id(鎶栭煶鍟嗗搧ID)")
+    private String productId;
+
+    @ApiModelProperty(value = "鎶栭煶 SKU ID")
+    private String skuId;
+
+    @ApiModelProperty(value = "SKU 鏍囬")
+    private String title;
+
+    @ApiModelProperty(value = "涓夋柟 SKU ID")
+    private String thirdSkuId;
+
+    @ApiModelProperty(value = "澶栭儴 SKU ID")
+    private String skuOutId;
+
+    @ApiModelProperty(value = "甯傚満浠�(鍒�)")
+    private Long marketPrice;
+
+    @ApiModelProperty(value = "鍥㈣喘绫诲瀷")
+    private Integer grouponType;
+
+    @ApiModelProperty(value = "鍒哥被鍨�")
+    private Integer voucherType;
+
+    @ApiModelProperty(value = "鍒涘缓鏃堕棿")
+    private Date createDate;
+
+    @ApiModelProperty(value = "缂栬緫鏃堕棿")
+    private Date editDate;
+
+    @ApiModelProperty(value = "鏄惁宸插垹闄� 0鏈垹闄� 1宸插垹闄�")
+    private Integer isdeleted;
+}
diff --git a/server/services/src/main/java/com/doumee/dao/business/model/DouyinVerifyLog.java b/server/services/src/main/java/com/doumee/dao/business/model/DouyinVerifyLog.java
new file mode 100644
index 0000000..58207d2
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/dao/business/model/DouyinVerifyLog.java
@@ -0,0 +1,65 @@
+package com.doumee.dao.business.model;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 鎶栭煶楠屽埜鎿嶄綔鏃ュ織(web绔帴鍙f搷浣滄祦姘�)
+ *
+ * @author rk
+ * @date 2026/06/25
+ */
+@Data
+@ApiModel("鎶栭煶楠屽埜鎿嶄綔鏃ュ織")
+@TableName("\"douyin_verify_log\"")
+public class DouyinVerifyLog {
+
+    @ApiModelProperty(value = "缂栫爜")
+    private String id;
+
+    @ApiModelProperty(value = "鎿嶄綔绫诲瀷 0楠屽埜鍑嗗 1鏍搁攢 2鎾ら攢鏍搁攢")
+    private Integer operateType;
+
+    @ApiModelProperty(value = "鎺ュ彛璺緞")
+    private String apiPath;
+
+    @ApiModelProperty(value = "鎿嶄綔浜轰細鍛業D")
+    private String memberId;
+
+    @ApiModelProperty(value = "鍏宠仈鏍搁攢璁板綍ID")
+    private String verifyRecordId;
+
+    @ApiModelProperty(value = "鏍搁攢闂ㄥ簵")
+    private String poiId;
+
+    @ApiModelProperty(value = "鍒哥爜蹇収")
+    private String originCode;
+
+    @ApiModelProperty(value = "鎿嶄綔缁撴灉 0鎴愬姛 1澶辫触")
+    private Integer result;
+
+    @ApiModelProperty(value = "澶辫触鎻忚堪")
+    private String errorMsg;
+
+    @ApiModelProperty(value = "璇锋眰鍏ュ弬蹇収(JSON)")
+    private String rawRequest;
+
+    @ApiModelProperty(value = "鎶栭煶鍝嶅簲鍘熸枃蹇収")
+    private String rawResponse;
+
+    @ApiModelProperty(value = "璇锋眰IP")
+    private String ip;
+
+    @ApiModelProperty(value = "鑰楁椂(姣)")
+    private Integer costMs;
+
+    @ApiModelProperty(value = "鎿嶄綔鏃堕棿")
+    private Date createDate;
+
+    @ApiModelProperty(value = "鏄惁宸插垹闄� 0鏈垹闄� 1宸插垹闄�")
+    private Integer isdeleted;
+}
diff --git a/server/services/src/main/java/com/doumee/dao/business/model/DouyinVerifyRecord.java b/server/services/src/main/java/com/doumee/dao/business/model/DouyinVerifyRecord.java
new file mode 100644
index 0000000..a1be3d0
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/dao/business/model/DouyinVerifyRecord.java
@@ -0,0 +1,101 @@
+package com.doumee.dao.business.model;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 鎶栭煶鍒告牳閿�璁板綍
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+@Data
+@ApiModel("鎶栭煶鍒告牳閿�璁板綍")
+@TableName("\"douyin_verify_record\"")
+public class DouyinVerifyRecord {
+
+    @ApiModelProperty(value = "缂栫爜")
+    private String id;
+
+    @ApiModelProperty(value = "楠屽埜杩斿洖鐨勪竴娆℃牳閿�鍞竴鏍囪瘑,鎾ら攢蹇呯敤")
+    private String verifyId;
+
+    @ApiModelProperty(value = "鍒告爣璇�,鎾ら攢蹇呯敤")
+    private String certificateId;
+
+    @ApiModelProperty(value = "鎶栭煶璁㈠崟鍙�")
+    private String orderId;
+
+    @ApiModelProperty(value = "鍘熷鍒哥爜")
+    private String originCode;
+
+    @ApiModelProperty(value = "鍔犲瘑鍒哥爜(prepare杩斿洖,verify鍏ュ弬)")
+    private String encryptedCode;
+
+    @ApiModelProperty(value = "鏍搁攢闂ㄥ簵")
+    private String poiId;
+
+    @ApiModelProperty(value = "鍟嗘埛鏍硅处鎴稩D")
+    private String accountId;
+
+    @ApiModelProperty(value = "鍟嗗搧ID(蹇収)")
+    private String productId;
+
+    @ApiModelProperty(value = "鍟嗗搧鍚嶇О(蹇収)")
+    private String productName;
+
+    @ApiModelProperty(value = "瀹炰粯閲戦(鍒�)")
+    private Long payAmount;
+
+    @ApiModelProperty(value = "鏍搁攢缁撴灉 0鎴愬姛 1澶辫触")
+    private Integer verifyStatus;
+
+    @ApiModelProperty(value = "鏍搁攢鏃堕棿")
+    private Date verifyTime;
+
+    @ApiModelProperty(value = "鏍搁攢鎿嶄綔浜�")
+    private String verifyUserId;
+
+    @ApiModelProperty(value = "鏍搁攢缁撴灉鎻忚堪")
+    private String verifyMsg;
+
+    @ApiModelProperty(value = "鎾ら攢鐘舵�� 0鏈挙閿� 1宸叉挙閿�")
+    private Integer cancelStatus;
+
+    @ApiModelProperty(value = "鎾ら攢鏃堕棿")
+    private Date cancelTime;
+
+    @ApiModelProperty(value = "鎾ら攢鎿嶄綔浜�")
+    private String cancelUserId;
+
+    @ApiModelProperty(value = "鎾ら攢缁撴灉鎻忚堪")
+    private String cancelMsg;
+
+    @ApiModelProperty(value = "璇锋眰蹇収")
+    private String rawRequest;
+
+    @ApiModelProperty(value = "鍝嶅簲蹇収")
+    private String rawResponse;
+
+    @ApiModelProperty(value = "鍒涘缓鏃堕棿")
+    private Date createDate;
+
+    @ApiModelProperty(value = "鍒涘缓浜�")
+    private String creator;
+
+    @ApiModelProperty(value = "缂栬緫鏃堕棿")
+    private Date editDate;
+
+    @ApiModelProperty(value = "缂栬緫浜�")
+    private String editor;
+
+    @ApiModelProperty(value = "鏍搁攢鎴愬姛寮�閫氱殑濂楅鍗D(discount_member.id)")
+    private String discountMemberId;
+
+    @ApiModelProperty(value = "鏄惁宸插垹闄� 0鏈垹闄� 1宸插垹闄�")
+    private Integer isdeleted;
+}
diff --git a/server/services/src/main/java/com/doumee/dao/business/model/MemberRidesTrack.java b/server/services/src/main/java/com/doumee/dao/business/model/MemberRidesTrack.java
new file mode 100644
index 0000000..7c94668
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/dao/business/model/MemberRidesTrack.java
@@ -0,0 +1,53 @@
+package com.doumee.dao.business.model;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 鐢佃溅楠戣杞ㄨ抗琛�
+ * <p>浠呯數杞�(type=1,璧� JT/T 808)楠戣涓殑浣嶇疆涓婃姤浜х敓;鑷杞﹁蛋 MQTT 鏃� GPS 涓婃姤,涓嶄骇鐢熻建杩广��
+ * 鐢� {@code Jtt808Service.updateBikesInfo} 鍦� 0200 浣嶇疆鎶ユ枃澶勭悊鏃跺啓鍏ャ��
+ *
+ * @author rk
+ * @date 2026/06/25
+ */
+@Data
+@ApiModel("鐢佃溅楠戣杞ㄨ抗琛�")
+@TableName("\"member_rides_track\"")
+public class MemberRidesTrack {
+
+    @ApiModelProperty(value = "涓婚敭")
+    private String id;
+
+    @ApiModelProperty(value = "楠戣璁㈠崟涓婚敭鈶� member_rides.id")
+    private String ridesId;
+
+    @ApiModelProperty(value = "鏀粯璁㈠崟涓婚敭鈶� member_rides.ordre_id 鈫� goodsorder.id(鍙兘涓虹┖)")
+    private String orderId;
+
+    @ApiModelProperty(value = "杞﹁締涓婚敭 bikes.id")
+    private String bikeId;
+
+    @ApiModelProperty(value = "杞﹁締缂栫爜 bikes.code")
+    private String bikeCode;
+
+    @ApiModelProperty(value = "缁忓害(楂樺痉 GCJ02,WGS84 杞崲鍚�,鍗曚綅:搴�)")
+    private BigDecimal longitude;
+
+    @ApiModelProperty(value = "绾害(楂樺痉 GCJ02,WGS84 杞崲鍚�,鍗曚綅:搴�)")
+    private BigDecimal latitude;
+
+    @ApiModelProperty(value = "璁惧涓婃姤鏃堕棿 deviceTime")
+    private Date reportTime;
+
+    @ApiModelProperty(value = "钀藉簱鏃堕棿")
+    private Date createDate;
+
+    @ApiModelProperty(value = "鏄惁宸插垹闄� 0鏈垹闄� 1宸插垹闄�")
+    private Integer isdeleted;
+}
diff --git a/server/services/src/main/java/com/doumee/dao/business/vo/BikeIncomeStatVO.java b/server/services/src/main/java/com/doumee/dao/business/vo/BikeIncomeStatVO.java
new file mode 100644
index 0000000..ac2b4e7
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/dao/business/vo/BikeIncomeStatVO.java
@@ -0,0 +1,31 @@
+package com.doumee.dao.business.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 杞﹀瀷鏀跺叆鍒嗘瀽椤�(web 绔暟鎹姤琛� - 鏀跺叆杞﹀瀷鍒嗘瀽鎺ュ彛鍑哄弬鍏冪礌)銆�
+ * <p>鎸夎溅鍨嬪瀷鍙�(goodsorder.param_id / base_param)姹囨�诲凡缁撶畻璁㈠崟鏀跺叆閲戦,骞舵爣娉ㄦ墍灞炲ぇ绫�(鑷杞�/鐢靛姩杞�)銆�
+ *
+ * @author rk
+ * @date 2026/06/26
+ */
+@Data
+@ApiModel("杞﹀瀷鏀跺叆鍒嗘瀽椤�")
+public class BikeIncomeStatVO {
+
+    @ApiModelProperty(value = "杞﹀瀷涓婚敭 base_param.id")
+    private String paramId;
+
+    @ApiModelProperty(value = "杞﹀瀷鍚嶇О")
+    private String paramName;
+
+    @ApiModelProperty(value = "杞﹁締澶х被:鑷杞� / 鐢靛姩杞�")
+    private String category;
+
+    @ApiModelProperty(value = "鏀跺叆閲戦(鍏�)")
+    private BigDecimal income;
+}
diff --git a/server/services/src/main/java/com/doumee/dao/business/vo/DouyinVerifyRecordPageVO.java b/server/services/src/main/java/com/doumee/dao/business/vo/DouyinVerifyRecordPageVO.java
new file mode 100644
index 0000000..0499e70
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/dao/business/vo/DouyinVerifyRecordPageVO.java
@@ -0,0 +1,68 @@
+package com.doumee.dao.business.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 鎶栭煶鍒告牳閿�璁板綍鍒嗛〉 VO(绠$悊绔� /business/douyinVerify/page 涓撶敤)銆�
+ * <p>涓昏〃 {@code douyin_verify_record},LEFT JOIN discount_member / member / douyin_product 甯﹀嚭鍏宠仈瀛楁銆�
+ * <p>鏌ヨ鍏ュ弬(originCode / verifyStatus / cancelStatus)鍚屾椂鎵胯浇涓昏〃杩斿洖鍊尖�斺��
+ * PageWrap.model 涓庡垎椤电粨鏋滃厓绱犱负涓嶅悓瀹炰緥,鏌ヨ鐢� model銆佽繑鍥炵敤缁撴灉鍏冪礌,浜掍笉骞叉壈(鍚� DiscountMember 妯″紡)銆�
+ *
+ * @author rk
+ * @date 2026/06/26
+ */
+@Data
+@ApiModel("鎶栭煶鍒告牳閿�璁板綍鍒嗛〉")
+public class DouyinVerifyRecordPageVO {
+
+    // ---------------- 鏌ヨ鍏ュ弬 + 涓昏〃杩斿洖(鍚屽悕瀛楁) ----------------
+
+    @ApiModelProperty(value = "鎶栭煶鍒哥爜(鍘熷鍒哥爜;鏌ヨ绮剧‘鍖归厤,杩斿洖涓轰富琛ㄥ��)")
+    private String originCode;
+
+    @ApiModelProperty(value = "楠屽埜鐘舵�� 0鎴愬姛 1澶辫触(鏌ヨ绮剧‘鍖归厤,杩斿洖涓轰富琛ㄥ��)")
+    private Integer verifyStatus;
+
+    @ApiModelProperty(value = "鎾ら攢鐘舵�� 0鏈挙閿� 1宸叉挙閿�(鏌ヨ绮剧‘鍖归厤,杩斿洖涓轰富琛ㄥ��)")
+    private Integer cancelStatus;
+
+    // ---------------- 涓昏〃瀛楁 ----------------
+
+    @ApiModelProperty(value = "鏍搁攢璁板綍ID")
+    private String id;
+
+    @ApiModelProperty(value = "鏍搁攢鏃堕棿")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date verifyTime;
+
+    // ---------------- 鍏宠仈娓叉煋瀛楁 ----------------
+
+    @ApiModelProperty(value = "璁㈠崟缂栧彿(discount_member.goodsorder_id,鏍搁攢鏃惰嚜鍔ㄥ缓鐨� goodsorder 璁㈠崟缂栫爜)")
+    private String orderCode;
+
+    @ApiModelProperty(value = "浼氬憳openid(member.openid)")
+    private String memberOpenid;
+
+    @ApiModelProperty(value = "浼氬憳鎵嬫満鍙�(鑴辨晱 138****1234)")
+    private String memberPhone;
+
+    @ApiModelProperty(value = "鍥㈣喘鍟嗗搧鍚嶇О(douyin_product.product_name,瀹炴椂鍏宠仈;闈炴牳閿�蹇収)")
+    private String productName;
+
+    @ApiModelProperty(value = "鎶栭煶鍒稿悕绉�(discount_member.name,鏈湴寮�閫氱殑濂楅鍚�)")
+    private String couponName;
+
+    @ApiModelProperty(value = "鎶栭煶鍒哥被鍨�/绫荤洰(douyin_product.category)")
+    private String category;
+
+    @ApiModelProperty(value = "鍏戞崲浜�(member.name,鏍搁攢鎿嶄綔浜�=璐拱浼氬憳鏈汉)")
+    private String exchangerName;
+
+    @ApiModelProperty(value = "鐘舵�佹枃妗�(宸插厬鎹�/宸叉挙閿�/鏍搁攢澶辫触)")
+    private String statusName;
+}
diff --git a/server/services/src/main/java/com/doumee/dao/business/vo/IncomeDailyVO.java b/server/services/src/main/java/com/doumee/dao/business/vo/IncomeDailyVO.java
new file mode 100644
index 0000000..37da1be
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/dao/business/vo/IncomeDailyVO.java
@@ -0,0 +1,27 @@
+package com.doumee.dao.business.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 鏀跺叆缁熻姣忔棩鏄庣粏(web 绔暟鎹姤琛� - 鏀跺叆缁熻鎺ュ彛鍑哄弬鍏冪礌,鐢ㄤ簬鏌辩姸鍥�)銆�
+ * <p>鎸夋棩褰掗泦鐨勫凡缁撶畻璁㈠崟鏀跺叆:缁熻 goodsorder.type=0(绉熻溅鎶奸噾)銆乻tatus=4(宸茬粨绠�)
+ * 璁㈠崟鐨� closeMoney(缁撶畻閲戦,鍗曚綅鍒�)涔嬪拰,鎸夋敮浠樻棩 payDate 褰掓棩,骞舵崲绠椾负鍏冦��
+ * 鍖洪棿鍐呮棤璁㈠崟鐨勬棩鏈熶篃浼氳繑鍥�,閲戦涓� 0,浠ヤ繚璇佹煴鐘跺浘妯酱杩炵画銆�
+ *
+ * @author rk
+ * @date 2026/06/26
+ */
+@Data
+@ApiModel("鏀跺叆缁熻姣忔棩鏄庣粏")
+public class IncomeDailyVO {
+
+    @ApiModelProperty(value = "鏃ユ湡 yyyy-MM-dd")
+    private String date;
+
+    @ApiModelProperty(value = "褰撴棩鏀跺叆閲戦(鍏�)")
+    private BigDecimal income;
+}
diff --git a/server/services/src/main/java/com/doumee/dao/business/vo/IncomeStatVO.java b/server/services/src/main/java/com/doumee/dao/business/vo/IncomeStatVO.java
new file mode 100644
index 0000000..54c37f2
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/dao/business/vo/IncomeStatVO.java
@@ -0,0 +1,39 @@
+package com.doumee.dao.business.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 鏀跺叆缁熻缁撴灉(web 绔暟鎹姤琛� - 鏀跺叆缁熻鎺ュ彛鍑哄弬)銆�
+ * <p>鍖呭惈:鍖洪棿鍐呮瘡鏃ユ敹鍏ユ槑缁�(鏌辩姸鍥�)銆佸尯闂寸疮璁℃敹鍏�,浠ュ強鐜瘮 / 鍚屾瘮瀵规瘮鏁版嵁銆�
+ * <p>鏀跺叆鍙e緞缁熶竴涓� goodsorder.type=0(绉熻溅鎶奸噾) 涓� status=4(宸茬粨绠�) 鐨� closeMoney(缁撶畻閲戦)銆�
+ *
+ * @author rk
+ * @date 2026/06/26
+ */
+@Data
+@ApiModel("鏀跺叆缁熻缁撴灉")
+public class IncomeStatVO {
+
+    @ApiModelProperty(value = "姣忔棩鏀跺叆鏄庣粏(鏌辩姸鍥�,鎸夋棩鏈熷崌搴�,鏃犳暟鎹棩琛�0)")
+    private List<IncomeDailyVO> dailyList;
+
+    @ApiModelProperty(value = "鍖洪棿绱鏀跺叆(鍏�)")
+    private BigDecimal totalIncome;
+
+    @ApiModelProperty(value = "鐜瘮涓婃湡鏀跺叆(鍏�):绱ч偦鍓嶄竴绛夐暱鍖洪棿")
+    private BigDecimal chainAmount;
+
+    @ApiModelProperty(value = "鐜瘮澧為暱鐜�(%);涓婃湡涓�0鏃惰繑鍥� null(鏃犳硶璁$畻)")
+    private BigDecimal chainRate;
+
+    @ApiModelProperty(value = "鍚屾瘮鍘诲勾鍚屾湡鏀跺叆(鍏�):骞崇Щ1骞寸殑绛夐暱鍖洪棿")
+    private BigDecimal yearOnYearAmount;
+
+    @ApiModelProperty(value = "鍚屾瘮澧為暱鐜�(%);鍘诲勾鍚屾湡涓�0鏃惰繑鍥� null(鏃犳硶璁$畻)")
+    private BigDecimal yearOnYearRate;
+}
diff --git a/server/services/src/main/java/com/doumee/dao/business/vo/OperationCenterVO.java b/server/services/src/main/java/com/doumee/dao/business/vo/OperationCenterVO.java
new file mode 100644
index 0000000..7feddce
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/dao/business/vo/OperationCenterVO.java
@@ -0,0 +1,41 @@
+package com.doumee.dao.business.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 杩愯惀涓績鏁版嵁(绠$悊绔姤琛�:浠婃棩姒傝 + 鐧诲綍淇℃伅)銆�
+ * <p>鐢ㄤ簬绠$悊绔繍钀ヤ腑蹇冮椤电湅鏉�:浠婃棩璁㈠崟 / 杩涜涓鍗� / 浠婃棩濂楅鏀跺叆 / 浠婃棩鎬绘敹鍏�,
+ * 澶栧姞褰撳墠鐧诲綍浜哄鍚嶃�佷粖鏃ユ棩鏈熴�佹槦鏈熷嚑銆�
+ *
+ * @author rk
+ * @date 2026/06/26
+ */
+@Data
+@ApiModel("杩愯惀涓績鏁版嵁")
+public class OperationCenterVO {
+
+    @ApiModelProperty(value = "褰撳墠鐧诲綍鐢ㄦ埛濮撳悕(鐢� Controller 浠庣櫥褰曟�佹敞鍏�)")
+    private String realName;
+
+    @ApiModelProperty(value = "浠婃棩鏃ユ湡 yyyy-MM-dd")
+    private String today;
+
+    @ApiModelProperty(value = "鏄熸湡鍑�(鏄熸湡X)")
+    private String weekDay;
+
+    @ApiModelProperty(value = "浠婃棩璁㈠崟鎬绘暟:浠婃棩宸叉敮浠樿鍗�(鍚獞琛屾娂閲� type=0 涓庡椁愬崱 type=1)")
+    private Long todayOrderCount;
+
+    @ApiModelProperty(value = "杩涜涓鍗曟暟閲�:楠戣涓�(type=0銆佸凡鏀粯鏈粨绠� status=1),瀹炴椂鍦ㄩ��,涓嶉檺鏃ユ湡")
+    private Long ongoingOrderCount;
+
+    @ApiModelProperty(value = "浠婃棩濂楅鏀跺叆(鍏�):浠婃棩濂楅鍗¤喘涔�(type=1銆佸凡鏀粯)鐨� money 涔嬪拰")
+    private BigDecimal packageIncome;
+
+    @ApiModelProperty(value = "浠婃棩鎬绘敹鍏�(鍏�):涓庢敹鍏ョ粺璁″悓鍙e緞(type=0 鎶奸噾 + status=4 宸茬粨绠� 鐨� closeMoney)")
+    private BigDecimal totalIncome;
+}
diff --git a/server/services/src/main/java/com/doumee/dao/business/vo/OperationOrderVO.java b/server/services/src/main/java/com/doumee/dao/business/vo/OperationOrderVO.java
new file mode 100644
index 0000000..d83bfe8
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/dao/business/vo/OperationOrderVO.java
@@ -0,0 +1,63 @@
+package com.doumee.dao.business.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 杩愯惀涓績璁㈠崟鍒楄〃椤�(绠$悊绔姤琛� - 杩愯惀涓績璁㈠崟鏌ヨ鍑哄弬鍏冪礌)銆�
+ * <p>杞﹀瀷鐩稿叧瀛楁鎸夎鍗曠姸鎬佸尯鍒嗗彇鏁�:
+ * <ul>
+ *   <li>杩涜涓�(status=1):bikeType/rentDate 鍙栬璁㈠崟"楠戣涓�"楠戣璁板綍(member_rides.type 涓� 0鑷杞�/1鐢佃溅),
+ *       paramName 鍙栭獞琛岃褰曠殑杞﹀瀷(member_rides.param_id鈫抌ase_param.name)</li>
+ *   <li>宸茬粨绠�(status=4):bikeType/rentDate 鍙栬璁㈠崟鍏宠仈楠戣璁板綍,
+ *       paramName 鍙栬鍗曠粨绠楄溅鍨�(goodsorder.param_id鈫抌ase_param.name)</li>
+ * </ul>
+ *
+ * @author rk
+ * @date 2026/06/26
+ */
+@Data
+@ApiModel("杩愯惀涓績璁㈠崟鍒楄〃椤�")
+public class OperationOrderVO {
+
+    @ApiModelProperty(value = "璁㈠崟涓婚敭 goodsorder.id")
+    private String id;
+
+    @ApiModelProperty(value = "璁㈠崟缂栧彿 goodsorder.code")
+    private String code;
+
+    @ApiModelProperty(value = "璁㈠崟绫诲瀷 0鑷杞� 1鐢佃溅(鏉ヨ嚜楠戣璁板綍 member_rides.type)")
+    private Integer bikeType;
+
+    @ApiModelProperty(value = "鐢ㄦ埛鎵嬫満鍙� member.phone")
+    private String phone;
+
+    @ApiModelProperty(value = "楠戣寮�濮嬫椂闂�(member_rides.rent_date)")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date rentDate;
+
+    @ApiModelProperty(value = "缁撶畻鏃堕棿(goodsorder.close_date;杩涜涓负 null)")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date closeDate;
+
+    @ApiModelProperty(value = "缁撶畻杞﹀瀷(base_param.name;杩涜涓彇楠戣杞﹀瀷,宸茬粨绠楀彇璁㈠崟缁撶畻杞﹀瀷)")
+    private String paramName;
+
+    @ApiModelProperty(value = "杞﹁締缂栧彿(member_rides.bike_code,鏈�杩戜竴鏉¢獞琛岃褰�;=bikes.code;杩涜涓嵆褰撳墠杞�,宸插畬缁撳嵆鏈�鍚庤溅)")
+    private String bikeCode;
+
+    /** 璁㈠崟鐘舵��(鍐呴儴鎵胯浇,浠呯敤浜庡彇鏁板垎鏀垽鏂�,涓嶈繑鍥炲墠绔�) */
+    @JsonIgnore
+    @ApiModelProperty(value = "璁㈠崟鐘舵��(鍐呴儴鎵胯浇,涓嶈繑鍥炲墠绔�;1杩涜涓�/4宸插畬缁�,鐢ㄤ簬杞﹀瀷鍚嶅彇鏁板垎鏀垽鏂�)", hidden = true)
+    private Integer orderStatus;
+
+    /** 缁撶畻杞﹀瀷鍚�(鍐呴儴鎵胯浇,鍒嗛〉 left join base_param 鍙栧緱,涓嶈繑鍥炲墠绔�) */
+    @JsonIgnore
+    @ApiModelProperty(value = "缁撶畻杞﹀瀷鍚�(鍐呴儴鎵胯浇,涓嶈繑鍥炲墠绔�;鍒嗛〉 left join base_param 鍙栧緱,宸茬粨绠楄鍗曞洖濉� paramName 鐢�)", hidden = true)
+    private String settleParamName;
+}
diff --git a/server/services/src/main/java/com/doumee/dao/business/vo/OrderRideItemVO.java b/server/services/src/main/java/com/doumee/dao/business/vo/OrderRideItemVO.java
new file mode 100644
index 0000000..161a2e0
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/dao/business/vo/OrderRideItemVO.java
@@ -0,0 +1,50 @@
+package com.doumee.dao.business.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 璁㈠崟涓嬪崟鏉¢獞琛岃褰�(绠$悊绔姤琛� - 璁㈠崟楠戣璁板綍鍏冪礌)銆�
+ * <p>瀛楁鍙栬嚜 member_rides;鐢佃溅楠戣涓嬫寕杞ㄨ抗鐐瑰垪琛�(tracks),鑷杞﹁鍗曡建杩逛负绌烘暟缁勩��
+ *
+ * @author rk
+ * @date 2026/06/26
+ */
+@Data
+@ApiModel("璁㈠崟楠戣璁板綍椤�")
+public class OrderRideItemVO {
+
+    @ApiModelProperty(value = "楠戣璁板綍涓婚敭 member_rides.id")
+    private String ridesId;
+
+    @ApiModelProperty(value = "楠戣鐘舵�佸師鏂�(member_rides.status:0璇锋眰寮�閿佷腑 1楠戣涓� 2宸茶繕杞� 3寮�閿佸け璐� 4涓存椂閿佽溅)")
+    private Integer status;
+
+    @ApiModelProperty(value = "楠戣鐘舵�佷腑鏂囧悕(楠戣涓�/宸茶繕杞�/涓存椂閿佽溅/璇锋眰寮�閿佷腑/寮�閿佸け璐�)")
+    private String statusName;
+
+    @ApiModelProperty(value = "楠戣寮�濮嬫椂闂�(member_rides.rent_date)")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date rentDate;
+
+    @ApiModelProperty(value = "楠戣缁撴潫鏃堕棿(member_rides.back_date 杩樿溅鏃堕棿;楠戣涓负 null)")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date backDate;
+
+    @ApiModelProperty(value = "杞﹁締缂栧彿(member_rides.bike_code = bikes.code)")
+    private String bikeCode;
+
+    @ApiModelProperty(value = "楠戣璁¤垂鏃堕暱(鍒嗛挓;member_rides.duration,杩樿溅鏃惰绠�;楠戣涓负 null)")
+    private Integer duration;
+
+    @ApiModelProperty(value = "杞﹁締绫诲瀷(鑷杞�/鐢靛姩杞�,鎸� member_rides.type)")
+    private String bikeTypeName;
+
+    @ApiModelProperty(value = "杞ㄨ抗鐐瑰垪琛�(鐢佃溅:鎸変笂鎶ユ椂闂村崌搴�;鑷杞�:绌烘暟缁�)")
+    private List<OrderRideTrackVO> tracks;
+}
diff --git a/server/services/src/main/java/com/doumee/dao/business/vo/OrderRideTrackVO.java b/server/services/src/main/java/com/doumee/dao/business/vo/OrderRideTrackVO.java
new file mode 100644
index 0000000..241dbdc
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/dao/business/vo/OrderRideTrackVO.java
@@ -0,0 +1,31 @@
+package com.doumee.dao.business.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 楠戣杞ㄨ抗鐐�(绠$悊绔姤琛� - 璁㈠崟楠戣璁板綍涓嬫寕鐨勮建杩瑰厓绱�)銆�
+ * <p>鏉ヨ嚜鐢佃溅 JT/T 808 浣嶇疆涓婃姤(member_rides_track,楂樺痉 GCJ02 鍧愭爣);鑷杞﹁蛋 MQTT 鏃� GPS 涓婃姤,涓嶄骇鐢熻建杩广��
+ *
+ * @author rk
+ * @date 2026/06/26
+ */
+@Data
+@ApiModel("楠戣杞ㄨ抗鐐�")
+public class OrderRideTrackVO {
+
+    @ApiModelProperty(value = "缁忓害(楂樺痉 GCJ02,WGS84 杞崲鍚�,鍗曚綅:搴�)")
+    private BigDecimal longitude;
+
+    @ApiModelProperty(value = "绾害(楂樺痉 GCJ02,WGS84 杞崲鍚�,鍗曚綅:搴�)")
+    private BigDecimal latitude;
+
+    @ApiModelProperty(value = "璁惧涓婃姤鏃堕棿(member_rides_track.report_time)")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date reportTime;
+}
diff --git a/server/services/src/main/java/com/doumee/dao/business/vo/OrderRidesDetailVO.java b/server/services/src/main/java/com/doumee/dao/business/vo/OrderRidesDetailVO.java
new file mode 100644
index 0000000..0d43c04
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/dao/business/vo/OrderRidesDetailVO.java
@@ -0,0 +1,35 @@
+package com.doumee.dao.business.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 璁㈠崟楠戣璁板綍 + 杞ㄨ抗(绠$悊绔姤琛� - 鏍规嵁璁㈠崟鏌ヨ鍏朵笅鍏ㄩ儴楠戣璁板綍鍙婅建杩�)銆�
+ * <p>鑷杞﹁鍗�(member_rides.type=0,璧� MQTT 鏃� GPS)鏃犺溅杈嗚建杩�:hasTrack=false 骞剁粰鍑烘彁绀�;
+ * 鐢佃溅璁㈠崟(type=1,璧� JT/T 808)鏈夎建杩�,姣忔潯楠戣涓嬫寕鎸夋椂闂村崌搴忕殑杞ㄨ抗鐐广��
+ *
+ * @author rk
+ * @date 2026/06/26
+ */
+@Data
+@ApiModel("璁㈠崟楠戣璁板綍涓庤建杩�")
+public class OrderRidesDetailVO {
+
+    @ApiModelProperty(value = "璁㈠崟杞﹁締绫诲瀷 0鑷杞� 1鐢佃溅(鍙栭鏉¢獞琛岃褰� type)")
+    private Integer bikeType;
+
+    @ApiModelProperty(value = "杞﹁締绫诲瀷涓枃鍚�(鑷杞�/鐢靛姩杞�)")
+    private String bikeTypeName;
+
+    @ApiModelProperty(value = "鏄惁鏈夎溅杈嗚建杩�(鐢佃溅 true / 鑷杞� false)")
+    private Boolean hasTrack;
+
+    @ApiModelProperty(value = "鏃犺建杩规彁绀�(鑷杞�:璇ヨ鍗曚负鑷杞﹁鍗�,鏃犺溅杈嗚建杩�;鐢佃溅涓� null)")
+    private String noTrackMessage;
+
+    @ApiModelProperty(value = "楠戣璁板綍鍒楄〃(鎸夐獞琛屽垱寤烘椂闂村厛鍚�)")
+    private List<OrderRideItemVO> rides;
+}
diff --git a/server/services/src/main/java/com/doumee/dao/business/vo/OverviewStatVO.java b/server/services/src/main/java/com/doumee/dao/business/vo/OverviewStatVO.java
new file mode 100644
index 0000000..28c2f87
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/dao/business/vo/OverviewStatVO.java
@@ -0,0 +1,29 @@
+package com.doumee.dao.business.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 姒傝缁熻 VO(web 绔暟鎹姤琛� - 姒傝鎺ュ彛鍑哄弬)銆�
+ * <p>鑱氬悎鍥涢」鎸囨爣:鎬绘敞鍐岀敤鎴枫�佷粖鏃ユ柊澧炵敤鎴枫�佽嚜琛岃溅鏁伴噺銆佺數鍔ㄨ溅鏁伴噺銆�
+ *
+ * @author rk
+ * @date 2026/06/26
+ */
+@Data
+@ApiModel("姒傝缁熻")
+public class OverviewStatVO {
+
+    @ApiModelProperty(value = "鎬绘敞鍐岀敤鎴锋暟")
+    private Long totalMembers;
+
+    @ApiModelProperty(value = "浠婃棩鏂板鐢ㄦ埛鏁�")
+    private Long todayMembers;
+
+    @ApiModelProperty(value = "鑷杞︽暟閲�")
+    private Long bikeCount;
+
+    @ApiModelProperty(value = "鐢靛姩杞︽暟閲�")
+    private Long eleBikeCount;
+}
diff --git a/server/services/src/main/java/com/doumee/dao/business/web/request/BikeIncomeQueryDTO.java b/server/services/src/main/java/com/doumee/dao/business/web/request/BikeIncomeQueryDTO.java
new file mode 100644
index 0000000..0c8ac30
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/dao/business/web/request/BikeIncomeQueryDTO.java
@@ -0,0 +1,35 @@
+package com.doumee.dao.business.web.request;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 杞﹀瀷鏀跺叆鍒嗘瀽鏌ヨ鍏ュ弬(web 绔暟鎹姤琛�)銆�
+ * <p>鏃舵鏀寔涓ょ鏂瑰紡:
+ * <ul>
+ *   <li>蹇嵎:dateType=1 杩�7澶� / 2 杩�15澶� / 3 杩�30澶�(鍚粖澶�,璧锋鐢卞悗绔帹绠�,蹇界暐璧锋瀛楁)</li>
+ *   <li>鑷畾涔�:dateType=4,鐢卞墠绔紶 startDate / endDate(鍧囧惈)</li>
+ * </ul>
+ *
+ * @author rk
+ * @date 2026/06/26
+ */
+@Data
+@ApiModel("杞﹀瀷鏀跺叆鍒嗘瀽鏌ヨ")
+public class BikeIncomeQueryDTO {
+
+    @ApiModelProperty(value = "鏃舵绫诲瀷 1杩�7澶� 2杩�15澶� 3杩�30澶� 4鑷畾涔�", example = "1")
+    private Integer dateType;
+
+    @ApiModelProperty(value = "鑷畾涔夊紑濮嬫椂闂�(鍚�),dateType=4 鏃跺繀濉�", example = "2026-05-01 00:00:00")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date startDate;
+
+    @ApiModelProperty(value = "鑷畾涔夌粨鏉熸椂闂�(鍚�),dateType=4 鏃跺繀濉�", example = "2026-06-26 23:59:59")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date endDate;
+}
diff --git a/server/services/src/main/java/com/doumee/dao/business/web/request/OperationOrderQueryDTO.java b/server/services/src/main/java/com/doumee/dao/business/web/request/OperationOrderQueryDTO.java
new file mode 100644
index 0000000..0c0ce1c
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/dao/business/web/request/OperationOrderQueryDTO.java
@@ -0,0 +1,26 @@
+package com.doumee.dao.business.web.request;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 杩愯惀涓績璁㈠崟鏌ヨ鍏ュ弬(绠$悊绔姤琛�,浣滀负 PageWrap.model 浼犲叆)銆�
+ * <p>鐢ㄤ簬杩愯惀涓績璁㈠崟鍒楄〃鍒嗛〉鏌ヨ銆傞粯璁や粎鏌ヨ鎶奸噾璁㈠崟(goodsorder.type=0,鍚庣鍥哄畾)銆�
+ *
+ * @author rk
+ * @date 2026/06/26
+ */
+@Data
+@ApiModel("杩愯惀涓績璁㈠崟鏌ヨ")
+public class OperationOrderQueryDTO {
+
+    @ApiModelProperty(value = "璁㈠崟绫诲瀷 0鑷杞� 1鐢佃溅(鎸夐獞琛岃褰� member_rides.type 绛涢��)")
+    private Integer bikeType;
+
+    @ApiModelProperty(value = "鐢ㄦ埛鎵嬫満鍙�(妯$硦鍖归厤 member.phone)")
+    private String phone;
+
+    @ApiModelProperty(value = "璁㈠崟鐘舵�� 1杩涜涓�(宸叉敮浠樻湭缁撶畻) 4宸插畬缁�(宸茬粨绠�)")
+    private Integer status;
+}
diff --git a/server/services/src/main/java/com/doumee/dao/business/web/response/DouyinConfigDTO.java b/server/services/src/main/java/com/doumee/dao/business/web/response/DouyinConfigDTO.java
new file mode 100644
index 0000000..bbcbb63
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/dao/business/web/response/DouyinConfigDTO.java
@@ -0,0 +1,31 @@
+package com.doumee.dao.business.web.response;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 鎶栭煶鏍搁攢閰嶇疆(瀛樹簬瀛楀吀 DOUYIN_CONFIG)銆�
+ * <p>瀛楁椹煎嘲鍚嶄笌瀛楀吀 label 澶у啓涓�涓�瀵瑰簲:
+ * clientKey鈫扖LIENT_KEY銆乧lientSecret鈫扖LIENT_SECRET銆乤ccountId鈫扐CCOUNT_ID銆乸oiId鈫扨OI_ID銆�
+ * 澶嶇敤 {@link MiniProgrammeDTO} 鐨� toUnderlineJSONString / toSnakeObject 鍋氶┘宄扳噭涓嬪垝绾胯浆鎹€��
+ *
+ * @author rk
+ * @date 2026/06/26
+ */
+@Data
+@ApiModel("鎶栭煶鏍搁攢閰嶇疆")
+public class DouyinConfigDTO {
+
+    @ApiModelProperty("鎶栭煶搴旂敤 client_key")
+    private String clientKey = "";
+
+    @ApiModelProperty("鎶栭煶搴旂敤 client_secret")
+    private String clientSecret = "";
+
+    @ApiModelProperty("鏉ュ鍟嗘埛鏍硅处鎴稩D")
+    private String accountId = "";
+
+    @ApiModelProperty("鏍搁攢闂ㄥ簵ID(鍗曢棬搴�)")
+    private String poiId = "";
+}
diff --git a/server/services/src/main/java/com/doumee/dao/business/web/response/HomeResponse.java b/server/services/src/main/java/com/doumee/dao/business/web/response/HomeResponse.java
index 6c1fa5d..486d7ab 100644
--- a/server/services/src/main/java/com/doumee/dao/business/web/response/HomeResponse.java
+++ b/server/services/src/main/java/com/doumee/dao/business/web/response/HomeResponse.java
@@ -59,6 +59,9 @@
     @ApiModelProperty(value = "灏忕▼搴忓仠姝㈡湇鍔℃彁绀�")
     private String stopServeTips;
 
+    @ApiModelProperty(value = "鎶栭煶鍒稿厬鎹㈣鏄�")
+    private String douyinExchangeTips;
+
     @ApiModelProperty(value = "楠戣鎯呭喌")
     private MemberRidesResponse memberRidesResponse;
 
diff --git a/server/services/src/main/java/com/doumee/service/business/DouyinProductService.java b/server/services/src/main/java/com/doumee/service/business/DouyinProductService.java
new file mode 100644
index 0000000..07cce86
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/service/business/DouyinProductService.java
@@ -0,0 +1,34 @@
+package com.doumee.service.business;
+
+import com.doumee.core.model.PageData;
+import com.doumee.core.model.PageWrap;
+import com.doumee.dao.business.model.DouyinProduct;
+
+/**
+ * 鎶栭煶鍟嗗搧 Service 瀹氫箟
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+public interface DouyinProductService {
+
+    /**
+     * 浠庢姈闊冲悓姝ュ晢鍝�(娓告爣缈婚〉鍏ㄩ噺鎷夊彇骞惰惤搴�),杩斿洖鍚屾鏉℃暟
+     */
+    int syncFromDouyin();
+
+    /**
+     * 鏈湴鍒嗛〉鏌ヨ
+     */
+    PageData<DouyinProduct> findPage(PageWrap<DouyinProduct> pageWrap);
+
+    /**
+     * 涓婚敭鏌ヨ(鍚� SKU 鍒楄〃,鍥炲~缁戝畾濂楅鍚�)
+     */
+    DouyinProduct findById(String id);
+
+    /**
+     * 缁戝畾/瑙g粦鏈湴濂楅:鎶� out_id 璁句负 discount.id;discountId 涓虹┖鍒欒В缁�
+     */
+    void bindDiscount(String id, String discountId);
+}
diff --git a/server/services/src/main/java/com/doumee/service/business/DouyinVerifyLogService.java b/server/services/src/main/java/com/doumee/service/business/DouyinVerifyLogService.java
new file mode 100644
index 0000000..ddb0bd4
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/service/business/DouyinVerifyLogService.java
@@ -0,0 +1,17 @@
+package com.doumee.service.business;
+
+import com.doumee.dao.business.model.DouyinVerifyLog;
+
+/**
+ * 鎶栭煶楠屽埜鎿嶄綔鏃ュ織 Service
+ *
+ * @author rk
+ * @date 2026/06/25
+ */
+public interface DouyinVerifyLogService {
+
+    /**
+     * 钀戒竴鏉℃搷浣滄棩蹇�
+     */
+    void record(DouyinVerifyLog log);
+}
diff --git a/server/services/src/main/java/com/doumee/service/business/DouyinVerifyService.java b/server/services/src/main/java/com/doumee/service/business/DouyinVerifyService.java
new file mode 100644
index 0000000..d336b75
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/service/business/DouyinVerifyService.java
@@ -0,0 +1,54 @@
+package com.doumee.service.business;
+
+import com.doumee.core.douyin.dto.DouyinBaseResp;
+import com.doumee.core.douyin.dto.DouyinCancelParam;
+import com.doumee.core.douyin.dto.DouyinPrepareParam;
+import com.doumee.core.douyin.dto.DouyinPrepareResp;
+import com.doumee.core.douyin.dto.DouyinVerifyParam;
+import com.doumee.core.model.PageData;
+import com.doumee.core.model.PageWrap;
+import com.doumee.dao.business.model.DouyinVerifyRecord;
+import com.doumee.dao.business.vo.DouyinVerifyRecordPageVO;
+
+/**
+ * 鎶栭煶鍒告牳閿� Service 瀹氫箟
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+public interface DouyinVerifyService {
+
+    /**
+     * 楠屽埜鍑嗗:鎵爜/杈撶爜 鈫� 杩斿洖鍒稿垪琛� + verifyToken
+     */
+    DouyinBaseResp<DouyinPrepareResp> prepare(DouyinPrepareParam param);
+
+    /**
+     * 楠屽埜(鏍搁攢),骞惰惤鏍搁攢璁板綍
+     * @param operator 鎿嶄綔浜篒D(鐢辫皟鐢ㄧ浼犲叆,web 绔彇鐧诲綍浼氬憳ID)
+     */
+    DouyinVerifyRecord verify(DouyinVerifyParam param, String operator);
+
+    /**
+     * 鎾ら攢鏍搁攢(鏍搁攢鍚� 1 灏忔椂鍐�),鏇存柊璁板綍鎾ら攢鐘舵��
+     * @param operator 鎿嶄綔浜篒D(鐢辫皟鐢ㄧ浼犲叆,web 绔彇鐧诲綍浼氬憳ID)
+     */
+    DouyinVerifyRecord cancel(DouyinCancelParam param, String operator);
+
+    /**
+     * 鏍搁攢璁板綍鍒嗛〉(web 绔皬绋嬪簭鑷敤,绠�鍗曞垎椤�)
+     */
+    PageData<DouyinVerifyRecord> findPage(PageWrap<DouyinVerifyRecord> pageWrap);
+
+    /**
+     * 鏍搁攢璁板綍鍒嗛〉(绠$悊绔澶�):LEFT JOIN discount_member/member/douyin_product,
+     * 甯﹀嚭璁㈠崟缂栧彿銆佷細鍛榦penid/鎵嬫満鍙�(鑴辨晱)銆佸洟璐晢鍝佸悕銆佹姈闊冲埜鍚嶃�佺被鐩�佸厬鎹汉銆佺姸鎬佹枃妗堛��
+     * 鏌ヨ鏉′欢:鎶栭煶鍒哥爜銆侀獙鍒哥姸鎬併�佹挙閿�鐘舵�併��
+     */
+    PageData<DouyinVerifyRecordPageVO> findManagePage(PageWrap<DouyinVerifyRecordPageVO> pageWrap);
+
+    /**
+     * 鏍搁攢璁板綍璇︽儏
+     */
+    DouyinVerifyRecord findById(String id);
+}
diff --git a/server/services/src/main/java/com/doumee/service/business/MemberRidesTrackService.java b/server/services/src/main/java/com/doumee/service/business/MemberRidesTrackService.java
new file mode 100644
index 0000000..bf7adf8
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/service/business/MemberRidesTrackService.java
@@ -0,0 +1,28 @@
+package com.doumee.service.business;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 鐢佃溅楠戣杞ㄨ抗 Service 瀹氫箟
+ * <p>杞ㄨ抗钀藉簱鏀跺彛:鐢� {@code Jtt808Service.updateBikesInfo} 鍦ㄤ綅缃笂鎶ャ�佸懡涓椿璺冭鍗曠紦瀛樻椂璋冪敤銆�
+ *
+ * @author rk
+ * @date 2026/06/25
+ */
+public interface MemberRidesTrackService {
+
+    /**
+     * 璁板綍涓�鏉¢獞琛岃建杩圭偣(娲昏穬璁㈠崟涓婃姤鏃惰皟鐢�)銆�
+     *
+     * @param bikeId     杞﹁締涓婚敭 bikes.id
+     * @param bikeCode   杞﹁締缂栫爜 bikes.code
+     * @param ridesId    楠戣璁㈠崟涓婚敭 member_rides.id
+     * @param orderId    鏀粯璁㈠崟涓婚敭 member_rides.ordre_id 鈫� goodsorder.id(鍙兘涓� null)
+     * @param longitude  缁忓害(楂樺痉 GCJ02,杞崲鍚�)
+     * @param latitude   绾害(楂樺痉 GCJ02,杞崲鍚�)
+     * @param reportTime 璁惧涓婃姤鏃堕棿 deviceTime
+     */
+    void record(String bikeId, String bikeCode, String ridesId, String orderId,
+                BigDecimal longitude, BigDecimal latitude, Date reportTime);
+}
diff --git a/server/services/src/main/java/com/doumee/service/business/ReportService.java b/server/services/src/main/java/com/doumee/service/business/ReportService.java
new file mode 100644
index 0000000..4438117
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/service/business/ReportService.java
@@ -0,0 +1,71 @@
+package com.doumee.service.business;
+
+import com.doumee.core.model.PageData;
+import com.doumee.core.model.PageWrap;
+import com.doumee.dao.business.vo.BikeIncomeStatVO;
+import com.doumee.dao.business.vo.IncomeStatVO;
+import com.doumee.dao.business.vo.OperationCenterVO;
+import com.doumee.dao.business.vo.OperationOrderVO;
+import com.doumee.dao.business.vo.OrderRidesDetailVO;
+import com.doumee.dao.business.vo.OverviewStatVO;
+import com.doumee.dao.business.web.request.BikeIncomeQueryDTO;
+import com.doumee.dao.business.web.request.OperationOrderQueryDTO;
+
+import java.util.List;
+
+/**
+ * 鏁版嵁鎶ヨ〃 Service(web 绔�:姒傝缁熻 + 鏀跺叆杞﹀瀷鍒嗘瀽)銆�
+ *
+ * @author rk
+ * @date 2026/06/26
+ */
+public interface ReportService {
+
+    /**
+     * 姒傝缁熻:鎬绘敞鍐岀敤鎴枫�佷粖鏃ユ柊澧炵敤鎴枫�佽嚜琛岃溅鏁伴噺銆佺數鍔ㄨ溅鏁伴噺銆�
+     *
+     * @return 姒傝缁熻
+     */
+    OverviewStatVO overview();
+
+    /**
+     * 鏀跺叆杞﹀瀷鍒嗘瀽:鎸夋椂娈�(杩�7/15/30澶╂垨鑷畾涔�)鎸夎溅鍨嬪瀷鍙锋眹鎬诲凡缁撶畻璁㈠崟鏀跺叆銆�
+     *
+     * @param query 鏃舵鏌ヨ鍏ュ弬(dateType 涓庤嚜瀹氫箟璧锋)
+     * @return 杞﹀瀷鏀跺叆鍒楄〃(鎸夋敹鍏ラ檷搴�)
+     */
+    List<BikeIncomeStatVO> bikeIncome(BikeIncomeQueryDTO query);
+
+    /**
+     * 鏀跺叆缁熻:鎸夋椂娈�(杩�7/15/30澶╂垨鑷畾涔�)鎸夋棩缁熻宸茬粨绠楄鍗曟敹鍏�(鏌辩姸鍥�),
+     * 骞剁粰鍑哄尯闂寸疮璁℃敹鍏ュ強鐜瘮銆佸悓姣斿姣旀暟鎹��
+     *
+     * @param query 鏃舵鏌ヨ鍏ュ弬(dateType 涓庤嚜瀹氫箟璧锋)
+     * @return 鏀跺叆缁熻缁撴灉(姣忔棩鏄庣粏 + 绱鏀跺叆 + 鐜瘮/鍚屾瘮)
+     */
+    IncomeStatVO incomeStat(BikeIncomeQueryDTO query);
+
+    /**
+     * 杩愯惀涓績鏁版嵁:浠婃棩璁㈠崟鎬绘暟銆佽繘琛屼腑璁㈠崟鏁般�佷粖鏃ュ椁愭敹鍏ャ�佷粖鏃ユ�绘敹鍏�,
+     * 浠ュ強浠婃棩鏃ユ湡涓庢槦鏈熷嚑(鐧诲綍浜哄鍚嶇敱 Controller 浠庣櫥褰曟�佹敞鍏�)銆�
+     *
+     * @return 杩愯惀涓績鏁版嵁
+     */
+    OperationCenterVO operationCenter();
+
+    /**
+     * 杩愯惀涓績璁㈠崟鏌ヨ:鎸夎鍗曠被鍨�(楠戣璁板綍绫诲瀷)/鎵嬫満鍙�/璁㈠崟鐘舵�佸垎椤垫煡璇㈡娂閲戣鍗曘��
+     *
+     * @param pageWrap 鍒嗛〉涓庢煡璇㈡潯浠�(bikeType/phone/status)
+     * @return 璁㈠崟鍒嗛〉鏁版嵁
+     */
+    PageData<OperationOrderVO> operationOrderPage(PageWrap<OperationOrderQueryDTO> pageWrap);
+
+    /**
+     * 璁㈠崟楠戣璁板綍 + 杞ㄨ抗:鏌ヨ鎸囧畾璁㈠崟涓嬪叏閮ㄩ獞琛岃褰�,鐢佃溅鎸傝建杩圭偣,鑷杞︽彁绀烘棤杞ㄨ抗銆�
+     *
+     * @param orderId 鏀粯璁㈠崟涓婚敭 goodsorder.id(member_rides.ordre_id)
+     * @return 楠戣璁板綍涓庤建杩硅鎯�(鍚溅杈嗙被鍨嬨�佹槸鍚︽湁杞ㄨ抗銆侀獞琛屽垪琛ㄥ強姣忔潯涓嬫寕鐨勮建杩圭偣)
+     */
+    OrderRidesDetailVO orderRidesDetail(String orderId);
+}
diff --git a/server/services/src/main/java/com/doumee/service/business/impl/DouyinProductServiceImpl.java b/server/services/src/main/java/com/doumee/service/business/impl/DouyinProductServiceImpl.java
new file mode 100644
index 0000000..c18192f
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/service/business/impl/DouyinProductServiceImpl.java
@@ -0,0 +1,293 @@
+package com.doumee.service.business.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.doumee.core.constants.Constants;
+import com.doumee.core.constants.ResponseStatus;
+import com.doumee.core.douyin.DouyinClient;
+import com.doumee.core.douyin.dto.DouyinBaseResp;
+import com.doumee.core.douyin.dto.DouyinOnlineQueryReq;
+import com.doumee.core.douyin.dto.DouyinOnlineQueryResp;
+import com.doumee.core.douyin.dto.DouyinProductDTO;
+import com.doumee.core.douyin.dto.DouyinSkuDTO;
+import com.doumee.core.exception.BusinessException;
+import com.doumee.core.model.LoginUserInfo;
+import com.doumee.core.model.PageData;
+import com.doumee.core.model.PageWrap;
+import com.doumee.core.utils.ID;
+import com.doumee.dao.business.DiscountMapper;
+import com.doumee.dao.business.DouyinProductMapper;
+import com.doumee.dao.business.DouyinProductSkuMapper;
+import com.doumee.dao.business.model.Discount;
+import com.doumee.dao.business.model.DouyinProduct;
+import com.doumee.dao.business.model.DouyinProductSku;
+import com.doumee.service.business.DouyinProductService;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.shiro.SecurityUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 鎶栭煶鍟嗗搧 Service 瀹炵幇
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+@Slf4j
+@Service
+public class DouyinProductServiceImpl implements DouyinProductService {
+
+    @Autowired
+    private DouyinProductMapper douyinProductMapper;
+    @Autowired
+    private DouyinProductSkuMapper douyinProductSkuMapper;
+    @Autowired
+    private DiscountMapper discountMapper;
+    @Autowired
+    private DouyinClient douyinClient;
+
+    /** 姣忛〉鎷夊彇鏉℃暟 */
+    private static final int PAGE_SIZE = 50;
+
+    /**
+     * 浠庢姈闊冲叏閲忓悓姝ュ晢鍝�:娓告爣缈婚〉鎷夊彇 online/query,閫愭潯 upsert 鏈湴鍟嗗搧 + SKU銆�
+     * 鏁翠綋浜嬪姟鍖呰9,浠讳竴鎵规澶辫触鍒欏叏閮ㄥ洖婊氥��
+     *
+     * @return 鏈鍚屾鐨勫晢鍝佹潯鏁�
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int syncFromDouyin() {
+        // 鍙栨搷浣滀汉:platform 绔蛋 Shiro 鐧诲綍鎬�;web 绔�(濡� testQuery 鑱旇皟)鏃� Shiro 鐜浼氭姏寮傚父,姝ゆ椂鐣欑┖,涓嶅奖鍝嶅叆搴�
+        String operator = null;
+        try {
+            LoginUserInfo user = (LoginUserInfo) SecurityUtils.getSubject().getPrincipal();
+            operator = user == null ? null : user.getId();
+        } catch (Exception e) {
+            // 闈� Shiro 鐜(web JWT 绔皟鐢�),operator 鐣欑┖,浠� creator/editor 钀� null
+        }
+        int total = 0;
+        // 鏃� SKU 琚烦杩囩殑鍟嗗搧鏁�(鍐椾綑鏁版嵁,涓嶅瓨鍌�),浠呯敤浜庢棩蹇楃粺璁�
+        int skipped = 0;
+        // 鏈鎶栭煶鍛戒腑鐨勫晢鍝両D闆嗗悎,鐢ㄤ簬鏈熬瀵硅处:鏈懡涓殑鏈湴鍦ㄥ敭鍟嗗搧缃负涓嬫灦
+        Set<String> syncedProductIds = new HashSet<>();
+        String cursor = null;
+        while (true) {
+            DouyinOnlineQueryReq req = new DouyinOnlineQueryReq();
+            req.setCursor(cursor);
+            req.setCount(PAGE_SIZE);
+            DouyinBaseResp<DouyinOnlineQueryResp> resp = douyinClient.onlineQuery(req);
+            Integer errCode = resp == null || resp.getExtra() == null ? null : resp.getExtra().getErrorCode();
+            if (errCode == null || errCode != 0) {
+                String desc = resp == null || resp.getExtra() == null ? "鏃犲搷搴�" : resp.getExtra().getDescription();
+                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "鎶栭煶鍟嗗搧鍚屾澶辫触:" + desc);
+            }
+            DouyinOnlineQueryResp data = resp.getData();
+            List<DouyinProductDTO> products = data == null ? null : data.getProducts();
+            if (products != null && !products.isEmpty()) {
+                for (DouyinProductDTO dto : products) {
+                    // 鏃� SKU 鐨勫晢鍝佷笉瀛樺偍(鍐椾綑鏁版嵁);upsertProduct 杩斿洖 false 鍗宠烦杩�
+                    if (upsertProduct(dto, operator)) {
+                        total++;
+                    } else if (dto.getProduct() != null
+                            && StringUtils.isNotBlank(dto.getProduct().getProductId())) {
+                        // product 鏈夋晥浣嗚璺宠繃(鏃� SKU),浠呯敤浜庢棩蹇楃粺璁�
+                        skipped++;
+                    }
+                    // 鍛戒腑闆嗗悎:product 鏈夋晥鍗崇撼鍏�(鏃犺鏄惁瀛樺偍),淇濇姢鏈湴宸叉湁鐨勫悓 ID 鍟嗗搧涓嶈瀵硅处涓嬫灦
+                    if (dto.getProduct() != null && StringUtils.isNotBlank(dto.getProduct().getProductId())) {
+                        syncedProductIds.add(dto.getProduct().getProductId());
+                    }
+                }
+            }
+            if (data == null || !Boolean.TRUE.equals(data.getHasMore())
+                    || StringUtils.isBlank(data.getNextCursor())) {
+                break;
+            }
+            cursor = data.getNextCursor();
+        }
+
+        // 瀵硅处:鏈鎶栭煶鏈繑鍥炪�佹湰鍦颁粛鏍囪鍦ㄧ嚎鐨勫晢鍝� 鈫� 缃负涓嬫灦(online_status=2)
+        // 浠呬笅鏋躲�屽湪绾�(1)銆嶇殑,涓嶅姩宸蹭笅鏋�(2)/灏佺(3),閬垮厤鎶婃洿涓ラ噸鐨勫皝绂佺姸鎬侀檷绾�
+        // 闆嗗悎闈炵┖鎵嶆墽琛�:绌洪泦鍚堣鏄庢姈闊虫湰娆′竴鏉¢兘娌¤繑鍥�(鎺ュ彛寮傚父/鍟嗘埛鏃犲湪鍞�),涓嶅簲鎹娓呯┖鏈湴
+        if (!syncedProductIds.isEmpty()) {
+            int offlineCount = douyinProductMapper.update(null, new UpdateWrapper<DouyinProduct>().lambda()
+                    .set(DouyinProduct::getOnlineStatus, Constants.TWO)   // 2 涓嬬嚎
+                    .set(DouyinProduct::getEditDate, new Date())
+                    .set(DouyinProduct::getEditor, operator)
+                    .eq(DouyinProduct::getIsdeleted, Constants.ZERO)
+                    .eq(DouyinProduct::getOnlineStatus, Constants.ONE)    // 浠呭綋鍓嶅湪绾跨殑
+                    .notIn(DouyinProduct::getProductId, syncedProductIds));
+            log.info("鎶栭煶鍟嗗搧鍚屾瀵硅处:鏈湴鏈懡涓� {} 鏉�,宸茬疆涓轰笅鏋�", offlineCount);
+        }
+        if (skipped > 0) {
+            log.info("鎶栭煶鍟嗗搧鍚屾:璺宠繃鏃燬KU鍟嗗搧 {} 鏉�(涓嶅瓨鍌�)", skipped);
+        }
+        return total;
+    }
+
+    /**
+     * 鎸� 鎶栭煶鍟嗗搧ID(product_id) upsert 涓昏〃;SKU 閲囩敤銆岄�昏緫鍒犻櫎鏃х殑 + 鎻掑叆鏂扮殑銆嶅叏閲忚鐩栥��
+     * <p>鎶栭煶 online/query 鐨勫晢鍝佸熀纭�淇℃伅钘忓湪 products[].product 瀛愬璞¢噷(瑙� {@link DouyinProductDTO}),
+     * 鏁呭晢鍝佸瓧娈典竴寰嬩粠 dto.product 鍙�;SKU 鍒楄〃鍙栬嚜椤跺眰 dto.skus銆�
+     * <p>鏃� SKU 鐨勫晢鍝�(鍐椾綑鏁版嵁)涓嶄簣瀛樺偍:涓嶆柊澧炰富琛�,涔熶笉鏇存柊鏈湴宸叉湁璁板綍銆�
+     *
+     * @return true=宸插瓨鍌�(鏂板鎴栨洿鏂�);false=璺宠繃(鏃� SKU 鎴栬剰鏁版嵁)
+     */
+    private boolean upsertProduct(DouyinProductDTO dto, String operator) {
+        // product 瀛愬璞$己澶辨垨鏃犲晢鍝両D,璺宠繃(闃茶剰鏁版嵁钀藉簱)
+        if (dto == null || dto.getProduct() == null
+                || StringUtils.isBlank(dto.getProduct().getProductId())) {
+            return false;
+        }
+        // 褰掗泦 SKU:澶氳鏍艰蛋 skus(澶嶆暟鏁扮粍);鍗� SKU 鍥㈣喘(product_type=1)璧� sku(鍗曟暟瀵硅薄)銆�
+        // 涓ょ褰㈡�佺粺涓�褰掗泦鍚庡垽绌�;鏃� SKU 鐨勫晢鍝佽涓哄啑浣欐暟鎹�,涓嶄簣瀛樺偍(涓嶆柊澧�,宸叉湁璁板綍淇濈暀涓嶅姩)
+        List<DouyinSkuDTO> skuList = dto.getSkus();
+        if ((skuList == null || skuList.isEmpty()) && dto.getSku() != null) {
+            skuList = Collections.singletonList(dto.getSku());
+        }
+        if (skuList == null || skuList.isEmpty()) {
+            // 鏃� SKU:涓嶆柊澧炰富琛�,涔熶笉鏇存柊鏈湴宸叉湁璁板綍鍙婂叾 SKU
+            return false;
+        }
+        DouyinProductDTO.DouyinProductInfoDTO info = dto.getProduct();
+        Date now = new Date();
+        DouyinProduct exist = douyinProductMapper.selectOne(new QueryWrapper<DouyinProduct>().lambda()
+                .eq(DouyinProduct::getProductId, info.getProductId())
+                .eq(DouyinProduct::getIsdeleted, Constants.ZERO)
+                .last("limit 1"));
+        DouyinProduct p = exist == null ? new DouyinProduct() : exist;
+        p.setProductId(info.getProductId());
+        // out_id 涓嶅啀鐢辨姈闊冲悓姝ュ啓鍏�,鏀逛负绠$悊绔粦瀹氭湰鍦板椁�(discount.id),瑙� bindDiscount
+        p.setProductName(info.getProductName());
+        // 绫荤洰鍙� category_full_name(鏂囨湰,灞曠ず鍙嬪ソ),鑰岄潪 category_id
+        p.setCategory(info.getCategoryFullName());
+        p.setProductType(info.getProductType());
+        p.setOnlineStatus(dto.getOnlineStatus() == null ? Constants.ONE : dto.getOnlineStatus());
+        // 璐︽埛ID 鍙栧綊灞炶处鎴� owner_account_id(鏁板瓧杞瓧绗︿覆钀藉簱)
+        p.setAccountId(info.getOwnerAccountId() == null ? null : String.valueOf(info.getOwnerAccountId()));
+        p.setSyncDate(now);
+        p.setIsdeleted(Constants.ZERO);
+        if (exist == null) {
+            p.setId(ID.nextGUID());
+            p.setCreateDate(now);
+            p.setCreator(operator);
+            douyinProductMapper.insert(p);
+        } else {
+            p.setEditDate(now);
+            p.setEditor(operator);
+            douyinProductMapper.updateById(p);
+        }
+        // SKU 鍏堥�昏緫鍒犻櫎鏃х殑,鍐嶆彃鍏ユ柊鐨�(鍏ㄩ噺瑕嗙洊)
+        douyinProductSkuMapper.update(null, new UpdateWrapper<DouyinProductSku>().lambda()
+                .set(DouyinProductSku::getIsdeleted, Constants.ONE)
+                .set(DouyinProductSku::getEditDate, now)
+                .eq(DouyinProductSku::getProductId, info.getProductId())
+                .eq(DouyinProductSku::getIsdeleted, Constants.ZERO));
+        if (skuList != null) {
+            for (DouyinSkuDTO sku : skuList) {
+                DouyinProductSku s = new DouyinProductSku();
+                s.setId(ID.nextGUID());
+                s.setProductId(info.getProductId());
+                s.setSkuId(sku.getSkuId());
+                // SKU 鏍囬鍙栨姈闊� sku_name
+                s.setTitle(sku.getSkuName());
+                // 澶栭儴 SKU ID 鍙栨姈闊� out_sku_id
+                s.setSkuOutId(sku.getSkuOutId());
+                // 甯傚満浠峰彇鎶栭煶 origin_amount(鍘熶环/鍒掔嚎浠�,鍒�)
+                s.setMarketPrice(sku.getOriginAmount());
+                // thirdSkuId / grouponType / voucherType 鎶栭煶 online/query 鏃犲搴斿瓧娈�,鍚屾钀� null
+                s.setCreateDate(now);
+                s.setIsdeleted(Constants.ZERO);
+                douyinProductSkuMapper.insert(s);
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public PageData<DouyinProduct> findPage(PageWrap<DouyinProduct> pageWrap) {
+        IPage<DouyinProduct> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
+        MPJLambdaWrapper<DouyinProduct> wrapper = new MPJLambdaWrapper<>();
+        wrapper.selectAll(DouyinProduct.class)
+                // 濂楅鍚�:LEFT JOIN discount ON out_id=discount.id;鏈粦濂楅(out_id 涓虹┖)鈫� discountName 涓� null
+                .selectAs(Discount::getName, DouyinProduct::getDiscountName)
+                // 浠锋牸:鏈垹闄� SKU 鐨勬渶浣� market_price(鍒�),鏃� SKU 涓� null;涓昏〃鍒悕 t
+                .select("(SELECT min(s.market_price) FROM \"douyin_product_sku\" s " +
+                        "WHERE s.product_id = t.product_id AND s.isdeleted = 0)", DouyinProduct::getPrice)
+                // 宸插厬鎹㈡暟閲�:鏈夋晥鏍搁攢(verify_status=0 鎴愬姛 + cancel_status=0 鏈挙閿� + isdeleted=0 鏈垹闄�)
+                .select("(SELECT count(1) FROM \"douyin_verify_record\" v " +
+                        "WHERE v.product_id = t.product_id AND v.verify_status = 0 " +
+                        "AND v.cancel_status = 0 AND v.isdeleted = 0)", DouyinProduct::getExchangedCount)
+                .leftJoin(Discount.class, Discount::getId, DouyinProduct::getOutId)
+                .eq(DouyinProduct::getIsdeleted, Constants.ZERO);
+        DouyinProduct m = pageWrap.getModel();
+        if (m != null) {
+            wrapper.like(StringUtils.isNotBlank(m.getProductName()), DouyinProduct::getProductName, m.getProductName())
+                    // 濂楅鍚嶇瓫閫�:璺� discount 琛� like;LEFT JOIN + 鍙宠〃闈炵┖鏉′欢,浠呰繑鍥炲尮閰嶅椁愬悕鐨勫晢鍝�
+                    .like(StringUtils.isNotBlank(m.getDiscountName()), Discount::getName, m.getDiscountName())
+                    .eq(m.getOnlineStatus() != null, DouyinProduct::getOnlineStatus, m.getOnlineStatus())
+                    .eq(StringUtils.isNotBlank(m.getProductId()), DouyinProduct::getProductId, m.getProductId())
+                    .eq(StringUtils.isNotBlank(m.getOutId()), DouyinProduct::getOutId, m.getOutId())
+                    .eq(StringUtils.isNotBlank(m.getAccountId()), DouyinProduct::getAccountId, m.getAccountId());
+        }
+        wrapper.orderByDesc(DouyinProduct::getSyncDate);
+        return PageData.from(douyinProductMapper.selectJoinPage(page, DouyinProduct.class, wrapper));
+    }
+
+    @Override
+    public DouyinProduct findById(String id) {
+        DouyinProduct p = douyinProductMapper.selectById(id);
+        if (p != null) {
+            List<DouyinProductSku> skus = douyinProductSkuMapper.selectList(new QueryWrapper<DouyinProductSku>().lambda()
+                    .eq(DouyinProductSku::getProductId, p.getProductId())
+                    .eq(DouyinProductSku::getIsdeleted, Constants.ZERO));
+            p.setSkus(skus);
+            if (StringUtils.isNotBlank(p.getOutId())) {
+                Discount discount = discountMapper.selectOne(new QueryWrapper<Discount>().lambda()
+                        .eq(Discount::getId, p.getOutId())
+                        .last("limit 1"));
+                if (discount != null) {
+                    p.setDiscountName(discount.getName());
+                }
+            }
+        }
+        return p;
+    }
+
+    @Override
+    public void bindDiscount(String id, String discountId) {
+        DouyinProduct p = douyinProductMapper.selectById(id);
+        if (p == null || Constants.equalsInteger(p.getIsdeleted(), Constants.ONE)) {
+            throw new BusinessException(ResponseStatus.DATA_EMPTY);
+        }
+        // 闈炵┖鏃舵牎楠屽椁愬瓨鍦ㄤ笖鏈垹闄�;涓虹┖琛ㄧず瑙g粦
+        if (StringUtils.isNotBlank(discountId)) {
+            Discount discount = discountMapper.selectOne(new QueryWrapper<Discount>().lambda()
+                    .eq(Discount::getId, discountId)
+                    .eq(Discount::getIsdeleted, Constants.ZERO)
+                    .last("limit 1"));
+            if (discount == null) {
+                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "濂楅涓嶅瓨鍦ㄦ垨宸插垹闄�");
+            }
+        }
+        LoginUserInfo user = (LoginUserInfo) SecurityUtils.getSubject().getPrincipal();
+        Date now = new Date();
+        douyinProductMapper.update(null, new UpdateWrapper<DouyinProduct>().lambda()
+                .set(DouyinProduct::getOutId, StringUtils.isBlank(discountId) ? null : discountId)
+                .set(DouyinProduct::getEditDate, now)
+                .set(DouyinProduct::getEditor, user == null ? null : user.getId())
+                .eq(DouyinProduct::getId, id));
+    }
+}
diff --git a/server/services/src/main/java/com/doumee/service/business/impl/DouyinVerifyLogServiceImpl.java b/server/services/src/main/java/com/doumee/service/business/impl/DouyinVerifyLogServiceImpl.java
new file mode 100644
index 0000000..d098f19
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/service/business/impl/DouyinVerifyLogServiceImpl.java
@@ -0,0 +1,28 @@
+package com.doumee.service.business.impl;
+
+import com.doumee.dao.business.DouyinVerifyLogMapper;
+import com.doumee.dao.business.model.DouyinVerifyLog;
+import com.doumee.service.business.DouyinVerifyLogService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 鎶栭煶楠屽埜鎿嶄綔鏃ュ織 Service 瀹炵幇
+ *
+ * @author rk
+ * @date 2026/06/25
+ */
+@Service
+public class DouyinVerifyLogServiceImpl implements DouyinVerifyLogService {
+
+    @Autowired
+    private DouyinVerifyLogMapper douyinVerifyLogMapper;
+
+    @Override
+    public void record(DouyinVerifyLog log) {
+        if (log == null) {
+            return;
+        }
+        douyinVerifyLogMapper.insert(log);
+    }
+}
diff --git a/server/services/src/main/java/com/doumee/service/business/impl/DouyinVerifyServiceImpl.java b/server/services/src/main/java/com/doumee/service/business/impl/DouyinVerifyServiceImpl.java
new file mode 100644
index 0000000..fc14eb8
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/service/business/impl/DouyinVerifyServiceImpl.java
@@ -0,0 +1,573 @@
+package com.doumee.service.business.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.doumee.core.constants.Constants;
+import com.doumee.core.constants.ResponseStatus;
+import com.doumee.core.douyin.DouyinClient;
+import com.doumee.core.douyin.dto.DouyinBaseResp;
+import com.doumee.core.douyin.dto.DouyinCancelParam;
+import com.doumee.core.douyin.dto.DouyinCancelReq;
+import com.doumee.core.douyin.dto.DouyinCancelResp;
+import com.doumee.core.douyin.dto.DouyinPrepareParam;
+import com.doumee.core.douyin.dto.DouyinPrepareReq;
+import com.doumee.core.douyin.dto.DouyinPrepareResp;
+import com.doumee.core.douyin.dto.DouyinVerifyParam;
+import com.doumee.core.douyin.dto.DouyinVerifyReq;
+import com.doumee.core.douyin.dto.DouyinVerifyResp;
+import com.doumee.core.exception.BusinessException;
+import com.doumee.core.model.PageData;
+import com.doumee.core.model.PageWrap;
+import com.doumee.core.utils.DateUtil;
+import com.doumee.core.utils.ID;
+import com.doumee.dao.business.DiscountLogMapper;
+import com.doumee.dao.business.DiscountMapper;
+import com.doumee.dao.business.DiscountMemberMapper;
+import com.doumee.dao.business.DouyinProductMapper;
+import com.doumee.dao.business.DouyinProductSkuMapper;
+import com.doumee.dao.business.DouyinVerifyRecordMapper;
+import com.doumee.dao.business.GoodsorderMapper;
+import com.doumee.dao.business.model.Discount;
+import com.doumee.dao.business.model.DiscountLog;
+import com.doumee.dao.business.model.DiscountMember;
+import com.doumee.dao.business.model.DouyinProduct;
+import com.doumee.dao.business.model.DouyinProductSku;
+import com.doumee.dao.business.model.DouyinVerifyRecord;
+import com.doumee.dao.business.model.Goodsorder;
+import com.doumee.dao.business.model.Member;
+import com.doumee.dao.business.vo.DouyinVerifyRecordPageVO;
+import com.doumee.service.business.DouyinVerifyService;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 鎶栭煶鍒告牳閿� Service 瀹炵幇銆�
+ * <p>瑕嗙洊楠屽埜涓夋閾捐矾:prepare(楠屽埜鍑嗗,鎵爜/杈撶爜鎷� verifyToken 涓庡埜鍒楄〃)
+ * 鈫� verify(鏍搁攢,钀芥牳閿�璁板綍 + 寮�閫氬椁�)鈫� cancel(鏍搁攢鍚� 1 灏忔椂鍐呮挙閿�)銆�
+ * 鎿嶄綔浜� operator 鐢辫皟鐢ㄧ浼犲叆(web 绔彇鐧诲綍浼氬憳 id),service 涓嶄緷璧栦换浣曢壌鏉冩鏋躲��
+ *
+ * @author rk
+ * @date 2026/06/22
+ */
+@Slf4j
+@Service
+public class DouyinVerifyServiceImpl implements DouyinVerifyService {
+
+    @Autowired
+    private DouyinClient douyinClient;
+    @Autowired
+    private DouyinVerifyRecordMapper douyinVerifyRecordMapper;
+    @Autowired
+    private DouyinProductSkuMapper douyinProductSkuMapper;
+    @Autowired
+    private DouyinProductMapper douyinProductMapper;
+    @Autowired
+    private DiscountMapper discountMapper;
+    @Autowired
+    private DiscountMemberMapper discountMemberMapper;
+    @Autowired
+    private GoodsorderMapper goodsorderMapper;
+    @Autowired
+    private DiscountLogMapper discountLogMapper;
+
+    /** 鎶栭煶楠屽埜鎺ュ彛杩斿洖鐨勬牳閿�缁撴灉鐮�:0鎴愬姛(闈炴湰鍦拌〃瀛楁,涓嶅苟鍏� Constants 鏋氫妇) */
+    private static final int VERIFY_OK = 0;
+
+    /** goodsorder 浜ゆ槗绫诲瀷:濂楅鍗¤喘涔� */
+    private static final int GOODSORDER_TYPE_DISCOUNT = 1;
+    /** goodsorder 鍏宠仈瀵硅薄绫诲瀷:濂楅鍗� */
+    private static final int GOODSORDER_OBJ_TYPE_DISCOUNT = 0;
+    /** goodsorder 宸叉敮浠樼姸鎬�(璁㈠崟鐘舵�� / 鏀粯鐘舵�佸潎涓� 1) */
+    private static final int GOODSORDER_PAID = 1;
+    /** 鏀粯鏂瑰紡:鎶栭煶鍒告牳閿�(闇�鍓嶇鏀粯鏂瑰紡瀛楀吀閰嶅悎灞曠ず) */
+    private static final int PAY_WAY_DOUYIN = 2;
+    /** discount_log 鎿嶄綔绫诲瀷:骞冲彴璋冩暣 */
+    private static final int DISCOUNT_LOG_TYPE_ADJUST = 2;
+
+    @Override
+    public DouyinBaseResp<DouyinPrepareResp> prepare(DouyinPrepareParam param) {
+        // poiId 涓虹┖鏃跺厹搴曞彇瀛楀吀閰嶇疆(鍗曢棬搴�,鍚庡彴鍙敼)
+        if (param != null && StringUtils.isBlank(param.getPoiId())) {
+            param.setPoiId(douyinClient.getPoiId());
+        }
+        // 鍏ュ弬鏍¢獙:闂ㄥ簵 + (浜岀淮鐮� | 鍒哥爜) 蹇呭~
+        if (param == null || StringUtils.isBlank(param.getPoiId())) {
+            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "poiId 鏈厤缃�(鍓嶇鏈紶涓斿瓧鍏� POI_ID 涓虹┖)");
+        }
+        if (StringUtils.isBlank(param.getQrContent()) && StringUtils.isBlank(param.getCode())) {
+            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "qrContent 涓� code 鑷冲皯浼犱竴涓�");
+        }
+        DouyinPrepareReq req = new DouyinPrepareReq();
+        req.setPoiId(param.getPoiId());
+        // 鍒哥爜鏄庢枃(鎵嬪姩杈撳叆)鍦烘櫙
+        if (StringUtils.isNotBlank(param.getCode())) {
+            req.setCode(param.getCode());
+        }
+        // 鎵爜鍦烘櫙:鍏堟妸鐭摼 / 鍚� object_id 鐨勯暱閾捐В鏋愭垚 encrypted_data
+        if (StringUtils.isNotBlank(param.getQrContent())) {
+            String encryptedData = douyinClient.resolveShortLink(param.getQrContent());
+            // 瑙f瀽涓嶅埌灏辨妸鍘熸枃褰� encrypted_data 鍏滃簳浜ょ粰鎶栭煶
+            req.setEncryptedData(StringUtils.isBlank(encryptedData) ? param.getQrContent() : encryptedData);
+        }
+        return douyinClient.prepare(req);
+    }
+
+    /**
+     * 楠屽埜(鏍搁攢),鎴愬姛鍚庝负褰撳墠鎿嶄綔浜哄紑閫氬椁愬崱(鏁村崟浜嬪姟)銆�
+     * <p>鏂规硶绾т簨鍔� {@code @Transactional}:鎶栭煶鏍搁攢鎺ュ彛澶辫触銆佸埜涓嶅彲鏍搁攢銆佸紑濂楅浠讳竴鐜妭寮傚父,
+     * 鍧囨暣鍗曞洖婊�(鏍搁攢璁板綍 / 濂楅鍗� / 璁㈠崟 / 鏃ュ織鍚岀敓鍏辩伃)銆俢ontroller 灞� {@code douyin_verify_log}
+     * 鍦� finally 鐙珛淇濆瓨,浠嶇暀鐥曚究浜庝簨鍚庡嚟鍒哥爜琛ュ紑銆�
+     *
+     * @param param    鏍搁攢鍏ュ弬(verifyToken/poiId/encryptedCodes + skuId 鍙嶆煡濂楅)
+     * @param operator 鎿嶄綔浜� = 濂楅褰掑睘浜�(web 绔櫥褰曚細鍛� id)
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public DouyinVerifyRecord verify(DouyinVerifyParam param, String operator) {
+        // 鍏ュ弬鏍¢獙
+        if (param == null || StringUtils.isBlank(param.getVerifyToken())) {
+            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "verifyToken 涓嶈兘涓虹┖");
+        }
+        // poiId 涓虹┖鏃跺厹搴曞彇瀛楀吀閰嶇疆(鍗曢棬搴�,鍚庡彴鍙敼)
+        if (StringUtils.isBlank(param.getPoiId())) {
+            param.setPoiId(douyinClient.getPoiId());
+        }
+        if (StringUtils.isBlank(param.getPoiId())) {
+            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "poiId 鏈厤缃�(鍓嶇鏈紶涓斿瓧鍏� POI_ID 涓虹┖)");
+        }
+        if (param.getEncryptedCodes() == null || param.getEncryptedCodes().isEmpty()) {
+            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "encryptedCodes 涓嶈兘涓虹┖");
+        }
+        if (StringUtils.isBlank(param.getSkuId())) {
+            // 鏃� skuId 鍒欐棤娉曞弽鏌ュ椁�(鏍搁攢杩斿洖鏈韩涓嶅惈鍟嗗搧鏍囪瘑),鐩存帴鎷︽埅
+            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "skuId 涓嶈兘涓虹┖");
+        }
+        Date now = new Date();
+
+        // 缁勮鎶栭煶楠屽埜璇锋眰
+        DouyinVerifyReq req = new DouyinVerifyReq();
+        req.setVerifyToken(param.getVerifyToken());
+        req.setPoiId(param.getPoiId());
+        req.setEncryptedCodes(param.getEncryptedCodes());
+        req.setAccountId(param.getAccountId());
+
+        // 璋冪敤鎶栭煶楠屽埜
+        DouyinBaseResp<DouyinVerifyResp> resp = douyinClient.verify(req);
+        String respText = JSON.toJSONString(resp);
+
+        // 鎺ュ彛绾уけ璐�(extra.errorCode 闈� 0):鏁村崟鍥炴粴,鐢� controller 鏃ュ織鐣欑棔
+        Integer extraCode = resp == null || resp.getExtra() == null ? null : resp.getExtra().getErrorCode();
+        if (extraCode == null || extraCode != 0) {
+            String desc = resp == null || resp.getExtra() == null ? "鏃犲搷搴�" : resp.getExtra().getDescription();
+            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "楠屽埜澶辫触:" + desc);
+        }
+
+        // 鎺ュ彛鎴愬姛,鍙栭寮犲埜鐨勬牳閿�缁撴灉(褰撳墠鎸夐寮犲鐞�)
+        DouyinVerifyResp data = resp.getData();
+        List<DouyinVerifyResp.VerifyResult> results = data == null ? null : data.getVerifyResults();
+        DouyinVerifyResp.VerifyResult first = (results == null || results.isEmpty()) ? null : results.get(0);
+        boolean ok = first != null && first.getResult() != null && first.getResult() == VERIFY_OK;
+
+        // 钀芥牳閿�璁板綍(鎴愬姛 / 澶辫触閮藉厛钀�;鍒镐笉鍙牳閿�鏃堕殢浜嬪姟鍥炴粴)
+        DouyinVerifyRecord rec = baseRecord(req, respText, operator, now);
+        rec.setVerifyStatus(ok ? Constants.DOUYIN_VERIFY_STATUS.SUCCESS.getKey() : Constants.DOUYIN_VERIFY_STATUS.FAIL.getKey());
+        rec.setVerifyMsg(first == null ? (data == null ? null : data.getDescription()) : first.getMsg());
+        rec.setPoiId(param.getPoiId());
+        rec.setAccountId(first != null ? first.getAccountId() : param.getAccountId());
+        rec.setEncryptedCode(joinCodes(param.getEncryptedCodes()));
+        if (first != null) {
+            // 蹇収鎶栭煶杩斿洖鐨勬牳閿�鍏抽敭鏍囪瘑,鎾ら攢鏍搁攢鏃惰鐢�
+            rec.setVerifyId(first.getVerifyId());
+            rec.setCertificateId(first.getCertificateId());
+            rec.setOriginCode(first.getOriginCode());
+            rec.setOrderId(first.getOrderId());
+        }
+        rec.setCancelStatus(Constants.ZERO);
+        douyinVerifyRecordMapper.insert(rec);
+
+        // 鎺ュ彛鎴愬姛浣嗗埜鏈韩涓嶅彲鏍搁攢(濡傚凡鏍搁攢 / 宸查��娆�):鎶涘嚭,鏁村崟鍥炴粴
+        if (!ok) {
+            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),
+                    "楠屽埜澶辫触:" + (rec.getVerifyMsg() == null ? "鏈煡鍘熷洜" : rec.getVerifyMsg()));
+        }
+
+        // 鏍搁攢鎴愬姛:涓哄綋鍓嶇櫥褰曚汉寮�閫氬椁�(浠讳竴姝ュけ璐� 鈫� 鏁村崟鍥炴粴)
+        openDiscountForVerify(rec, param, operator);
+        return rec;
+    }
+
+    /**
+     * 鏍搁攢鎴愬姛鍚�,鎸� skuId 鍙嶆煡鏈湴濂楅骞朵负褰撳墠鐧诲綍浜哄紑閫氬椁愬崱(鍚鍗曚笌寮�閫氭棩蹇�)銆�
+     * <p>浠讳竴姝ュけ璐ユ姏寮傚父 鈫� verify 鐨� {@code @Transactional} 鏁村崟鍥炴粴(鏍搁攢璁板綍涓�骞跺洖婊�);
+     * controller 灞� {@code douyin_verify_log} 鍦� finally 鐙珛淇濆瓨,浠嶇暀鐥曚究浜庝簨鍚庤ˉ寮�銆�
+     *
+     * @param rec      鏍搁攢璁板綍(鍚� originCode/certificateId 绛夋姈闊虫爣璇�,寮�閫氬悗鍥炲~濂楅鍗D)
+     * @param param    鏍搁攢鍏ュ弬(鍚� skuId 鍙嶆煡濂楅銆乸ayAmount 蹇収)
+     * @param operator 鎿嶄綔浜� = 濂楅褰掑睘浜�(web 绔櫥褰曚細鍛� id)
+     */
+    private void openDiscountForVerify(DouyinVerifyRecord rec, DouyinVerifyParam param, String operator) {
+        // 鈶� 鍙嶆煡濂楅:skuId 鈫� douyin_product_sku 鈫� product_id 鈫� douyin_product.out_id 鈫� discount
+        DouyinProduct product = resolveProduct(param.getSkuId());
+        Discount discount = resolveDiscount(product);
+
+        Date now = new Date();
+
+        // 鈶� 闃查噸:鍚屼竴鍒哥爜宸蹭负璇ョ敤鎴峰紑杩囧椁愬崱鍒欒烦杩�(閬垮厤閲嶅鏍搁攢閲嶅紑)
+        DiscountMember existCard = discountMemberMapper.selectOne(new QueryWrapper<DiscountMember>().lambda()
+                .eq(DiscountMember::getCode, rec.getOriginCode())
+                .eq(DiscountMember::getMemberId, operator)
+                .eq(DiscountMember::getIsdeleted, Constants.ZERO)
+                .last("limit 1"));
+        if (existCard != null) {
+            log.warn("璇ュ埜鐮佸凡寮�閫氬椁�,璺宠繃閲嶅寮�鍗� originCode={}", rec.getOriginCode());
+            rec.setDiscountMemberId(existCard.getId());
+            douyinVerifyRecordMapper.updateById(rec);
+            return;
+        }
+
+        // 鈶� 涓婚敭
+        String goodsorderId = Constants.getUUID();
+        String discountMemberId = Constants.getUUID();
+
+        // 鈶� 寮� discount_member(澶嶇敤 createDiscountOrderPay 鐨勫紑鍗℃,鐩存帴缃凡鏀粯)
+        DiscountMember dm = new DiscountMember();
+        BeanUtils.copyProperties(discount, dm);
+        dm.setId(discountMemberId);
+        dm.setCreateDate(now);
+        dm.setEditDate(now);
+        dm.setCreator(null);
+        dm.setEditor(null);
+        dm.setMemberId(operator);
+        dm.setCode(rec.getOriginCode());              // 鍘熷鍒哥爜褰撶エ鍙�
+        dm.setGoodsorderId(goodsorderId);
+        dm.setStatus(Constants.ZERO);                 // 姝e父,鏍搁攢鍗宠涓哄凡鏀粯
+        // 鏈夋晥鏈�:useType != 0(闈炲浐瀹氭椂闂存)鏃舵寜璐拱閫昏緫璁$畻
+        if (!Constants.equalsInteger(dm.getUseType(), Constants.ZERO)) {
+            if (Constants.equalsInteger(dm.getUseType(), Constants.ONE)) {
+                // 璐拱鍚庣敓鏁�:浣跨敤寮�濮� = 浠婂ぉ
+                dm.setUseStartDate(DateUtil.StringToDateFormat(DateUtil.getCurrDate(), "yyyy-MM-dd"));
+            }
+            // 浣跨敤缁撴潫 = 浣跨敤寮�濮� + (useDays - 1)
+            dm.setUseEndDate(DateUtil.StringToDateFormat(
+                    DateUtil.getXDaysAfter(dm.getUseStartDate(), dm.getUseDays() - 1), "yyyy-MM-dd"));
+        }
+        discountMemberMapper.insert(dm);
+
+        // 鈶� 寤� goodsorder(瀵归綈鏀粯鍥炶皟,鐩存帴缃凡鏀粯)
+        Goodsorder goodsorder = new Goodsorder();
+        goodsorder.setId(goodsorderId);
+        goodsorder.setCode(goodsorderId);
+        goodsorder.setCreateDate(now);
+        goodsorder.setIsdeleted(Constants.ZERO);
+        goodsorder.setMemberId(operator);
+        goodsorder.setType(GOODSORDER_TYPE_DISCOUNT);       // 1 濂楅鍗¤喘涔�
+        goodsorder.setObjType(GOODSORDER_OBJ_TYPE_DISCOUNT); // 0 濂楅鍗�
+        goodsorder.setObjId(discount.getId());
+        goodsorder.setMoney(BigDecimal.ZERO);               // 鏍搁攢鍏嶈垂鍏戞崲,骞冲彴鏃犲疄鏀�
+        goodsorder.setStatus(GOODSORDER_PAID);              // 1 宸叉敮浠�
+        goodsorder.setPayStatus(GOODSORDER_PAID);           // 1 宸叉敮浠�
+        goodsorder.setPayWay(PAY_WAY_DOUYIN);               // 2 鎶栭煶鍒告牳閿�
+        goodsorder.setPayDate(now);
+        goodsorder.setInfo("鎶栭煶鍒告牳閿�鍏戞崲");
+        goodsorderMapper.insert(goodsorder);
+
+        // 鈶� discount_log 寮�閫氭棩蹇�(骞冲彴璋冩暣)
+        DiscountLog discountLog = new DiscountLog();
+        discountLog.setId(Constants.getUUID());
+        discountLog.setCreateDate(now);
+        discountLog.setCreator(operator);
+        discountLog.setIsdeleted(Constants.ZERO);
+        discountLog.setDiscountMemberId(discountMemberId);
+        discountLog.setGoodsorderId(goodsorderId);
+        discountLog.setType(DISCOUNT_LOG_TYPE_ADJUST);      // 2 骞冲彴璋冩暣
+        discountLog.setInfo("鎶栭煶鍒告牳閿�寮�閫�,鍒哥爜 " + rec.getOriginCode());
+        discountLogMapper.insert(discountLog);
+
+        // 鈶� 鍥炲~鏍搁攢璁板綍(鍟嗗搧蹇収 + 濂楅鍗D)
+        rec.setProductId(product.getProductId());
+        rec.setProductName(product.getProductName());
+        if (param.getPayAmount() != null) {
+            rec.setPayAmount(param.getPayAmount());
+        }
+        rec.setDiscountMemberId(discountMemberId);
+        douyinVerifyRecordMapper.updateById(rec);
+    }
+
+    /**
+     * 鎸� skuId 鍙嶆煡鎶栭煶鍟嗗搧(product_id / out_id 鐨勬潵婧�)銆�
+     * 閾捐矾:skuId 鈫� douyin_product_sku(sku_id,isdeleted=0) 鈫� product_id
+     *
+     * @param skuId 鏍搁攢鍒稿搴旂殑鎶栭煶 SKU ID
+     * @return 鎶栭煶鍟嗗搧;鏌ヤ笉鍒版姏銆屾湭鎵惧埌濂楅銆嶄笟鍔″紓甯�(瑙﹀彂鏁村崟鍥炴粴)
+     */
+    private DouyinProduct resolveProduct(String skuId) {
+        DouyinProductSku sku = douyinProductSkuMapper.selectOne(new QueryWrapper<DouyinProductSku>().lambda()
+                .eq(DouyinProductSku::getSkuId, skuId)
+                .eq(DouyinProductSku::getIsdeleted, Constants.ZERO)
+                .last("limit 1"));
+        if (sku == null) {
+            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "鏈壘鍒拌鍒稿搴旂殑鏈湴濂楅,璇峰厛鍦ㄧ鐞嗙缁戝畾");
+        }
+        DouyinProduct product = douyinProductMapper.selectOne(new QueryWrapper<DouyinProduct>().lambda()
+                .eq(DouyinProduct::getProductId, sku.getProductId())
+                .eq(DouyinProduct::getIsdeleted, Constants.ZERO)
+                .last("limit 1"));
+        if (product == null || StringUtils.isBlank(product.getOutId())) {
+            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "鏈壘鍒拌鍒稿搴旂殑鏈湴濂楅,璇峰厛鍦ㄧ鐞嗙缁戝畾");
+        }
+        return product;
+    }
+
+    /**
+     * 鎸夊凡鏌ュ埌鐨勬姈闊冲晢鍝佸弽鏌ユ湰鍦板椁愩��
+     * 閾捐矾:douyin_product.out_id 鈫� discount(id=out_id, status=0 姝e父, isdeleted=0)
+     *
+     * @param product 宸插弽鏌ュ埌鐨勬姈闊冲晢鍝�(闇�鏈� out_id)
+     * @return 鏈湴濂楅;鏌ヤ笉鍒版姏銆屾湭鎵惧埌濂楅銆嶄笟鍔″紓甯�
+     */
+    private Discount resolveDiscount(DouyinProduct product) {
+        Discount discount = discountMapper.selectOne(new QueryWrapper<Discount>().lambda()
+                .eq(Discount::getId, product.getOutId())
+                .eq(Discount::getStatus, Constants.ZERO)
+                .eq(Discount::getIsdeleted, Constants.ZERO)
+                .last("limit 1"));
+        if (discount == null) {
+            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "鏈壘鍒拌鍒稿搴旂殑鏈湴濂楅,璇峰厛鍦ㄧ鐞嗙缁戝畾");
+        }
+        return discount;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public DouyinVerifyRecord cancel(DouyinCancelParam param, String operator) {
+        if (param == null || StringUtils.isBlank(param.getId())) {
+            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "id 涓嶈兘涓虹┖");
+        }
+        // 鍙栨湰鍦版牳閿�璁板綍
+        DouyinVerifyRecord rec = douyinVerifyRecordMapper.selectById(param.getId());
+        if (rec == null || Constants.equalsInteger(rec.getIsdeleted(), Constants.ONE)) {
+            throw new BusinessException(ResponseStatus.DATA_EMPTY);
+        }
+        // 宸叉挙閿�,闃查噸澶嶆搷浣�
+        if (Constants.equalsInteger(rec.getCancelStatus(), Constants.DOUYIN_VERIFY_CANCEL_STATUS.DONE.getKey())) {
+            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "璇ヨ褰曞凡鎾ら攢,璇峰嬁閲嶅鎿嶄綔");
+        }
+        // 鍙湁鏍搁攢鎴愬姛鐨勮褰曟墠鑳芥挙閿�(绠$悊绔繍钀ユ搷浣�,涓嶅啀鍙�"鏍搁攢鍚�1灏忔椂鍐�"鏃堕棿绐楅檺鍒�)
+        if (!Constants.equalsInteger(rec.getVerifyStatus(), Constants.DOUYIN_VERIFY_STATUS.SUCCESS.getKey())) {
+            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "浠呮垚鍔熸牳閿�鐨勮褰曞彲鎾ら攢");
+        }
+
+        Date now = new Date();
+
+        // 鐢ㄦ牳閿�鏃舵嬁鍒扮殑鏍囪瘑鍘绘姈闊虫挙閿�
+        DouyinCancelReq req = new DouyinCancelReq();
+        req.setCertificateId(rec.getCertificateId());
+        req.setVerifyId(rec.getVerifyId());
+        req.setAccountId(rec.getAccountId());
+
+        DouyinBaseResp<DouyinCancelResp> resp = douyinClient.cancel(req);
+        // 鎴愬姛鍒ゆ嵁:澶栧眰 extra 涓� data 鐨� error_code 閮戒负 0
+        Integer extraCode = resp == null || resp.getExtra() == null ? null : resp.getExtra().getErrorCode();
+        Integer dataCode = resp == null || resp.getData() == null ? null : resp.getData().getErrorCode();
+        boolean ok = extraCode != null && extraCode == 0 && dataCode != null && dataCode == 0;
+
+        String respText = JSON.toJSONString(resp);
+        rec.setEditDate(now);
+        rec.setEditor(operator);
+        rec.setRawResponse(respText);
+        // 鎾ら攢澶辫触:鏇存柊璁板綍鎻忚堪鍚庢姏鍑�(璁板綍淇濈暀鍘熸牳閿�鐘舵��)
+        if (!ok) {
+            String desc = resp == null || resp.getExtra() == null ? "鏃犲搷搴�" : resp.getExtra().getDescription();
+            rec.setCancelMsg("鎾ら攢澶辫触:" + desc);
+            douyinVerifyRecordMapper.updateById(rec);
+            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "鎾ら攢澶辫触:" + desc);
+        }
+        // 鎾ら攢鎴愬姛:缃挙閿�鐘舵�併�佹挙閿�鏃堕棿涓庢挙閿�浜�
+        rec.setCancelStatus(Constants.DOUYIN_VERIFY_CANCEL_STATUS.DONE.getKey());
+        rec.setCancelTime(now);
+        rec.setCancelUserId(operator);
+        rec.setCancelMsg("鎾ら攢鎴愬姛");
+        douyinVerifyRecordMapper.updateById(rec);
+        // 鍚屾浣滃簾鏈湴宸插紑閫氱殑濂楅鍗�(闃叉鎶栭煶鎾ら攢鍚庡椁愪粛琚娇鐢�);鍙傜収 backGoodsorder 閫�鍗�
+        cancelDiscountMember(rec, operator, now);
+        return rec;
+    }
+
+    /**
+     * 鎾ら攢鏍搁攢鍚庝綔搴熷叧鑱旂殑濂楅鍗�:浠呮甯�(status=0)濂楅鍗′綔搴�,宸蹭綔搴熻烦杩�(骞傜瓑);璁� discount_log(骞冲彴浣滃簾)銆�
+     *
+     * @param rec      鏍搁攢璁板綍(鍚� discountMemberId)
+     * @param operator 鎾ら攢鎿嶄綔浜�(绠$悊绔� Shiro 鐧诲綍鐢ㄦ埛 id)
+     * @param now      鎾ら攢鏃堕棿
+     */
+    private void cancelDiscountMember(DouyinVerifyRecord rec, String operator, Date now) {
+        if (StringUtils.isBlank(rec.getDiscountMemberId())) {
+            return;
+        }
+        DiscountMember dm = discountMemberMapper.selectById(rec.getDiscountMemberId());
+        // 鏃犲椁愬崱鎴栧凡浣滃簾,璺宠繃(骞傜瓑)
+        if (dm == null || !Constants.equalsInteger(dm.getStatus(), Constants.ZERO)) {
+            return;
+        }
+        discountMemberMapper.update(null, new UpdateWrapper<DiscountMember>().lambda()
+                .set(DiscountMember::getStatus, Constants.ONE)      // 1 浣滃簾
+                .set(DiscountMember::getEditDate, now)
+                .set(DiscountMember::getEditor, operator)
+                .eq(DiscountMember::getId, dm.getId()));
+        DiscountLog discountLog = new DiscountLog();
+        discountLog.setId(Constants.getUUID());
+        discountLog.setCreateDate(now);
+        discountLog.setCreator(operator);
+        discountLog.setIsdeleted(Constants.ZERO);
+        discountLog.setDiscountMemberId(dm.getId());
+        discountLog.setType(Constants.ONE);                        // 1 骞冲彴浣滃簾
+        discountLog.setEditInfo("鎾ら攢鏍搁攢浣滃簾");
+        discountLog.setGoodsorderId(dm.getGoodsorderId());
+        discountLogMapper.insert(discountLog);
+    }
+
+    @Override
+    public PageData<DouyinVerifyRecord> findPage(PageWrap<DouyinVerifyRecord> pageWrap) {
+        IPage<DouyinVerifyRecord> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
+        QueryWrapper<DouyinVerifyRecord> wrapper = new QueryWrapper<>();
+        // 浠呮煡鏈垹闄�
+        wrapper.lambda().eq(DouyinVerifyRecord::getIsdeleted, Constants.ZERO);
+        DouyinVerifyRecord m = pageWrap.getModel();
+        // 鎸夋煡璇㈡潯浠堕�愰」绮剧‘鍖归厤(闈炵┖鎵嶆嫾鎺�)
+        if (m != null) {
+            if (StringUtils.isNotBlank(m.getVerifyId())) {
+                wrapper.lambda().eq(DouyinVerifyRecord::getVerifyId, m.getVerifyId());
+            }
+            if (StringUtils.isNotBlank(m.getCertificateId())) {
+                wrapper.lambda().eq(DouyinVerifyRecord::getCertificateId, m.getCertificateId());
+            }
+            if (StringUtils.isNotBlank(m.getOriginCode())) {
+                wrapper.lambda().eq(DouyinVerifyRecord::getOriginCode, m.getOriginCode());
+            }
+            if (StringUtils.isNotBlank(m.getOrderId())) {
+                wrapper.lambda().eq(DouyinVerifyRecord::getOrderId, m.getOrderId());
+            }
+            if (StringUtils.isNotBlank(m.getPoiId())) {
+                wrapper.lambda().eq(DouyinVerifyRecord::getPoiId, m.getPoiId());
+            }
+            if (m.getVerifyStatus() != null) {
+                wrapper.lambda().eq(DouyinVerifyRecord::getVerifyStatus, m.getVerifyStatus());
+            }
+            if (m.getCancelStatus() != null) {
+                wrapper.lambda().eq(DouyinVerifyRecord::getCancelStatus, m.getCancelStatus());
+            }
+        }
+        // 榛樿鎸夋牳閿�鏃堕棿鍊掑簭
+        wrapper.lambda().orderByDesc(DouyinVerifyRecord::getVerifyTime);
+        return PageData.from(douyinVerifyRecordMapper.selectPage(page, wrapper));
+    }
+
+    @Override
+    public PageData<DouyinVerifyRecordPageVO> findManagePage(PageWrap<DouyinVerifyRecordPageVO> pageWrap) {
+        IPage<DouyinVerifyRecordPageVO> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
+        MPJLambdaWrapper<DouyinVerifyRecord> wrapper = new MPJLambdaWrapper<>();
+        // 鏄惧紡閫変富琛ㄥ垪(閬垮紑 product_name 蹇収,鍥㈣喘鍟嗗搧鍚嶆敼鐢� join 鐨� douyin_product.product_name)
+        wrapper.select(DouyinVerifyRecord::getId)
+                .select(DouyinVerifyRecord::getOriginCode)
+                .select(DouyinVerifyRecord::getVerifyTime)
+                .select(DouyinVerifyRecord::getVerifyStatus)
+                .select(DouyinVerifyRecord::getCancelStatus)
+                // 璁㈠崟缂栧彿:discount_member.goodsorder_id(鏍搁攢鏃惰嚜鍔ㄥ缓鐨� goodsorder 璁㈠崟)
+                .selectAs(DiscountMember::getGoodsorderId, DouyinVerifyRecordPageVO::getOrderCode)
+                // 浼氬憳 openid/鎵嬫満鍙�/鍏戞崲浜哄鍚�:member
+                .selectAs(Member::getOpenid, DouyinVerifyRecordPageVO::getMemberOpenid)
+                .selectAs(Member::getPhone, DouyinVerifyRecordPageVO::getMemberPhone)
+                .selectAs(Member::getName, DouyinVerifyRecordPageVO::getExchangerName)
+                // 鍥㈣喘鍟嗗搧鍚�/绫荤洰:douyin_product(缁� product_id 鍏宠仈,闈炰富閿瓧娈�)
+                .selectAs(DouyinProduct::getProductName, DouyinVerifyRecordPageVO::getProductName)
+                .selectAs(DouyinProduct::getCategory, DouyinVerifyRecordPageVO::getCategory)
+                // 鎶栭煶鍒稿悕:discount_member.name(鏈湴寮�閫氬椁愬悕)
+                .selectAs(DiscountMember::getName, DouyinVerifyRecordPageVO::getCouponName)
+                // 涓夎〃 leftJoin:discount_member(缁� discount_member_id)鈫� member(缁� member_id);douyin_product(缁� product_id)
+                .leftJoin(DiscountMember.class, DiscountMember::getId, DouyinVerifyRecord::getDiscountMemberId)
+                .leftJoin(Member.class, Member::getId, DiscountMember::getMemberId)
+                .leftJoin(DouyinProduct.class, DouyinProduct::getProductId, DouyinVerifyRecord::getProductId)
+                .eq(DouyinVerifyRecord::getIsdeleted, Constants.ZERO);
+        DouyinVerifyRecordPageVO m = pageWrap.getModel();
+        if (m != null) {
+            // 鏌ヨ鏉′欢:鎶栭煶鍒哥爜(绮剧‘)銆侀獙鍒哥姸鎬併�佹挙閿�鐘舵��
+            wrapper.eq(StringUtils.isNotBlank(m.getOriginCode()), DouyinVerifyRecord::getOriginCode, m.getOriginCode())
+                    .eq(m.getVerifyStatus() != null, DouyinVerifyRecord::getVerifyStatus, m.getVerifyStatus())
+                    .eq(m.getCancelStatus() != null, DouyinVerifyRecord::getCancelStatus, m.getCancelStatus());
+        }
+        wrapper.orderByDesc(DouyinVerifyRecord::getVerifyTime);
+        IPage<DouyinVerifyRecordPageVO> result = douyinVerifyRecordMapper.selectJoinPage(page, DouyinVerifyRecordPageVO.class, wrapper);
+        List<DouyinVerifyRecordPageVO> records = result.getRecords();
+        if (records != null) {
+            for (DouyinVerifyRecordPageVO vo : records) {
+                // 鎵嬫満鍙疯劚鏁� + 鐘舵�佹枃妗�(鍐呭瓨鍥炲~,闈為�愯鏌ヨ)
+                vo.setMemberPhone(maskPhone(vo.getMemberPhone()));
+                vo.setStatusName(statusName(vo.getVerifyStatus(), vo.getCancelStatus()));
+            }
+        }
+        return PageData.from(result);
+    }
+
+    /** 鎵嬫満鍙疯劚鏁�:138****1234(鍓�3鍚�4,涓棿4浣�*);闀垮害 < 7 鍘熸牱杩斿洖 */
+    private String maskPhone(String phone) {
+        if (StringUtils.isBlank(phone) || phone.length() < 7) {
+            return phone;
+        }
+        return phone.substring(0, 3) + "****" + phone.substring(phone.length() - 4);
+    }
+
+    /** 鏍搁攢鐘舵�佹枃妗�:宸叉挙閿� > 鏍搁攢澶辫触 > 宸插厬鎹� */
+    private String statusName(Integer verifyStatus, Integer cancelStatus) {
+        if (Constants.equalsInteger(cancelStatus, Constants.ONE)) {
+            return "宸叉挙閿�";
+        }
+        if (Constants.equalsInteger(verifyStatus, Constants.ONE)) {
+            return "鏍搁攢澶辫触";
+        }
+        return "宸插厬鎹�";
+    }
+
+    @Override
+    public DouyinVerifyRecord findById(String id) {
+        return douyinVerifyRecordMapper.selectById(id);
+    }
+
+    /**
+     * 鏋勯�犱竴鏉℃牳閿�璁板綍鐨勫叕鍏卞瓧娈�(涓婚敭 / 鏃堕棿 / 鎿嶄綔浜� / 璇锋眰鍝嶅簲蹇収 / 鍒犻櫎涓庢挙閿�鍒濆��)
+     */
+    private DouyinVerifyRecord baseRecord(DouyinVerifyReq req, String respText, String operator, Date now) {
+        DouyinVerifyRecord rec = new DouyinVerifyRecord();
+        rec.setId(ID.nextGUID());
+        rec.setVerifyTime(now);
+        rec.setVerifyUserId(operator);
+        rec.setCreateDate(now);
+        rec.setCreator(operator);
+        rec.setIsdeleted(Constants.ZERO);
+        rec.setCancelStatus(Constants.ZERO);
+        rec.setRawRequest(JSON.toJSONString(req));
+        rec.setRawResponse(respText);
+        return rec;
+    }
+
+    /**
+     * 鎶婂姞瀵嗗埜鐮佸垪琛ㄦ嫾鎴愰�楀彿鍒嗛殧瀛楃涓�,渚夸簬鍗曞瓧娈靛瓨鍌�
+     */
+    private String joinCodes(List<String> codes) {
+        if (codes == null || codes.isEmpty()) {
+            return null;
+        }
+        return String.join(",", codes);
+    }
+}
diff --git a/server/services/src/main/java/com/doumee/service/business/impl/GoodsorderServiceImpl.java b/server/services/src/main/java/com/doumee/service/business/impl/GoodsorderServiceImpl.java
index 07b08e1..2846a2b 100644
--- a/server/services/src/main/java/com/doumee/service/business/impl/GoodsorderServiceImpl.java
+++ b/server/services/src/main/java/com/doumee/service/business/impl/GoodsorderServiceImpl.java
@@ -391,6 +391,7 @@
         homeResponse.setTips(systemDictDataBiz.queryByCode(Constants.MINI_PROGRAMME,Constants.RENT_NOTICE).getCode());
         homeResponse.setLeaseVideoUrl(systemDictDataBiz.queryByCode(Constants.MINI_PROGRAMME,Constants.RENT_TIPS_VIDEO).getCode());
         homeResponse.setStopServeTips(systemDictDataBiz.queryByCode(Constants.MINI_PROGRAMME,Constants.STOP_SERVE_TIPS).getCode());
+        homeResponse.setDouyinExchangeTips(systemDictDataBiz.queryByCode(Constants.MINI_PROGRAMME,Constants.DOUYIN_EXCHANGE_TIPS).getCode());
         homeResponse.setIsStopServe(this.checkTemporaryStop()?1:0);
         homeResponse.setIsBusiness(this.checkBusiness()?0:1);
         homeResponse.setUnBusinessTips("钀ヤ笟鏃堕棿涓�"+ systemDictDataBiz.queryByCode(Constants.MINI_PROGRAMME,Constants.BUSINESS_STARTTIME).getCode() +" ~ "+systemDictDataBiz.queryByCode(Constants.MINI_PROGRAMME,Constants.BUSINESS_ENDTIME).getCode()+"锛岃鍦ㄨ惀涓氭椂闂村唴浣跨敤鏈郴缁�");
diff --git a/server/services/src/main/java/com/doumee/service/business/impl/MemberRidesServiceImpl.java b/server/services/src/main/java/com/doumee/service/business/impl/MemberRidesServiceImpl.java
index ec2c3e8..e6eecdd 100644
--- a/server/services/src/main/java/com/doumee/service/business/impl/MemberRidesServiceImpl.java
+++ b/server/services/src/main/java/com/doumee/service/business/impl/MemberRidesServiceImpl.java
@@ -9,6 +9,7 @@
 import com.doumee.core.constants.Constants;
 import com.doumee.core.constants.ResponseStatus;
 import com.doumee.core.exception.BusinessException;
+import com.doumee.core.track.RideActiveCache;
 import com.doumee.core.model.LoginUserInfo;
 import com.doumee.core.model.PageData;
 import com.doumee.core.model.PageWrap;
@@ -35,6 +36,7 @@
 import com.doumee.service.business.MemberRidesService;
 import com.doumee.service.system.SystemDictDataService;
 import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.shiro.SecurityUtils;
 import org.springframework.beans.BeanUtils;
@@ -55,6 +57,7 @@
  * @author 姹熻箘韫�
  * @date 2023/09/27 18:06
  */
+@Slf4j
 @Service
 public class MemberRidesServiceImpl implements MemberRidesService {
 
@@ -93,6 +96,9 @@
 
     @Autowired
     private SystemDictDataMapper systemDictDataMapper;
+    /** 鐢佃溅娲昏穬璁㈠崟缂撳瓨(platform 鍚庡彴寮哄埗杩樿溅鏃跺垹) */
+    @Autowired
+    private RideActiveCache rideActiveCache;
 
     @Override
     public String create(MemberRides memberRides) {
@@ -407,6 +413,12 @@
         update.setDuration( rideTime > freeRentTime  ? rideTime : 0 );
         //update.setDuration( freeRentTime > 0 ? rideTime - freeRentTime : rideTime);
         memberRidesMapper.updateById(update);
+        // 寮哄埗杩樿溅鈫掑凡杩樿溅:鍒犻櫎娲昏穬璁㈠崟缂撳瓨(鐢佃溅鎵嶆湁杞ㄨ抗;鑷杞� key 涓嶅瓨鍦�,鍒犻櫎涓� no-op 鏃犲)
+        try {
+            rideActiveCache.remove(model.getBikeCode());
+        } catch (Exception e) {
+            log.warn("鍒犻櫎娲昏穬璁㈠崟缂撳瓨澶辫触 bikeCode={}", model.getBikeCode(), e);
+        }
         //淇敼鍓�
         String beforeContent = JSONObject.toJSONString(model);
         //淇敼鍚�
diff --git a/server/services/src/main/java/com/doumee/service/business/impl/MemberRidesTrackServiceImpl.java b/server/services/src/main/java/com/doumee/service/business/impl/MemberRidesTrackServiceImpl.java
new file mode 100644
index 0000000..2e1c4f9
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/service/business/impl/MemberRidesTrackServiceImpl.java
@@ -0,0 +1,48 @@
+package com.doumee.service.business.impl;
+
+import com.doumee.core.constants.Constants;
+import com.doumee.core.utils.ID;
+import com.doumee.dao.business.MemberRidesTrackMapper;
+import com.doumee.dao.business.model.MemberRidesTrack;
+import com.doumee.service.business.MemberRidesTrackService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 鐢佃溅楠戣杞ㄨ抗 Service 瀹炵幇
+ *
+ * @author rk
+ * @date 2026/06/25
+ */
+@Service
+public class MemberRidesTrackServiceImpl implements MemberRidesTrackService {
+
+    @Autowired
+    private MemberRidesTrackMapper memberRidesTrackMapper;
+
+    /**
+     * 缁勮骞惰惤搴撲竴鏉¤建杩圭偣銆�
+     * <p>涓婚敭鐢� {@link ID#nextGUID()};閫昏緫鍒犻櫎鏍囪鍒濆鍖栦负鏈垹闄ゃ��
+     */
+    @Override
+    public void record(String bikeId, String bikeCode, String ridesId, String orderId,
+                       BigDecimal longitude, BigDecimal latitude, Date reportTime) {
+        MemberRidesTrack track = new MemberRidesTrack();
+        track.setId(ID.nextGUID());
+        // 楠戣璁㈠崟涓婚敭(蹇呮湁,缂撳瓨鍛戒腑鍗充唬琛ㄦ湁娲昏穬楠戣璁板綍)
+        track.setRidesId(ridesId);
+        // 鏀粯璁㈠崟涓婚敭(寮�閿佹椂鑻ユ湭缁戝畾 goodsorder 鍒欎负 null,杞ㄨ抗鍏佽绌�)
+        track.setOrderId(orderId);
+        track.setBikeId(bikeId);
+        track.setBikeCode(bikeCode);
+        track.setLongitude(longitude);
+        track.setLatitude(latitude);
+        track.setReportTime(reportTime);
+        track.setCreateDate(new Date());
+        track.setIsdeleted(Constants.ZERO);
+        memberRidesTrackMapper.insert(track);
+    }
+}
diff --git a/server/services/src/main/java/com/doumee/service/business/impl/ReportServiceImpl.java b/server/services/src/main/java/com/doumee/service/business/impl/ReportServiceImpl.java
new file mode 100644
index 0000000..da298bd
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/service/business/impl/ReportServiceImpl.java
@@ -0,0 +1,620 @@
+package com.doumee.service.business.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.doumee.core.constants.Constants;
+import com.doumee.core.constants.ResponseStatus;
+import com.doumee.core.exception.BusinessException;
+import com.doumee.core.model.PageData;
+import com.doumee.core.model.PageWrap;
+import com.doumee.core.utils.DateUtil;
+import com.doumee.dao.business.BaseParamMapper;
+import com.doumee.dao.business.BikesMapper;
+import com.doumee.dao.business.GoodsorderMapper;
+import com.doumee.dao.business.MemberMapper;
+import com.doumee.dao.business.MemberRidesTrackMapper;
+import com.doumee.dao.business.join.MemberRidesJoinMapper;
+import com.doumee.dao.business.model.BaseParam;
+import com.doumee.dao.business.model.Bikes;
+import com.doumee.dao.business.model.Goodsorder;
+import com.doumee.dao.business.model.Member;
+import com.doumee.dao.business.model.MemberRides;
+import com.doumee.dao.business.model.MemberRidesTrack;
+import com.doumee.dao.business.vo.BikeIncomeStatVO;
+import com.doumee.dao.business.vo.IncomeDailyVO;
+import com.doumee.dao.business.vo.IncomeStatVO;
+import com.doumee.dao.business.vo.OperationCenterVO;
+import com.doumee.dao.business.vo.OperationOrderVO;
+import com.doumee.dao.business.vo.OrderRideItemVO;
+import com.doumee.dao.business.vo.OrderRideTrackVO;
+import com.doumee.dao.business.vo.OrderRidesDetailVO;
+import com.doumee.dao.business.vo.OverviewStatVO;
+import com.doumee.dao.business.web.request.BikeIncomeQueryDTO;
+import com.doumee.dao.business.web.request.OperationOrderQueryDTO;
+import com.doumee.service.business.ReportService;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * 鏁版嵁鎶ヨ〃 Service 瀹炵幇(web 绔�:姒傝缁熻 + 鏀跺叆杞﹀瀷鍒嗘瀽)銆�
+ *
+ * @author rk
+ * @date 2026/06/26
+ */
+@Slf4j
+@Service
+public class ReportServiceImpl implements ReportService {
+
+    @Autowired
+    private MemberMapper memberMapper;
+    @Autowired
+    private MemberRidesJoinMapper memberRidesJoinMapper;
+    @Autowired
+    private BikesMapper bikesMapper;
+    @Autowired
+    private GoodsorderMapper goodsorderMapper;
+    @Autowired
+    private BaseParamMapper baseParamMapper;
+    /** 鐢佃溅楠戣杞ㄨ抗 Mapper(鑷杞﹁蛋 MQTT 鏃犺建杩�,浠呯數杞︽湁鏁版嵁) */
+    @Autowired
+    private MemberRidesTrackMapper memberRidesTrackMapper;
+
+    /** 鏃舵蹇嵎绫诲瀷 鈫� 杩� N 澶�:dateType 1鈫�7銆�2鈫�15銆�3鈫�30(鍧囧惈浠婂ぉ) */
+    private static final Map<Integer, Integer> RECENT_DAYS = new LinkedHashMap<>();
+
+    static {
+        RECENT_DAYS.put(1, 7);
+        RECENT_DAYS.put(2, 15);
+        RECENT_DAYS.put(3, 30);
+    }
+
+    /** 缁撶畻閲戦鍒嗏啋鍏冩崲绠楅櫎鏁� */
+    private static final BigDecimal CENT_PER_YUAN = new BigDecimal("100");
+
+    /** 鐧惧垎姣斿熀鏁�(澧為暱鐜� = (鏈湡 - 瀵规瘮鏈�) / 瀵规瘮鏈� 脳 100) */
+    private static final BigDecimal PERCENT_BASE = new BigDecimal("100");
+
+    @Override
+    public OverviewStatVO overview() {
+        OverviewStatVO vo = new OverviewStatVO();
+        // 鎬绘敞鍐岀敤鎴�:鏈垹闄ょ殑鍏ㄩ儴鐢ㄦ埛
+        vo.setTotalMembers((long) memberMapper.selectCount(
+                new QueryWrapper<Member>().lambda().eq(Member::getIsdeleted, Constants.ZERO)));
+        // 浠婃棩鏂板:鍒涘缓鏃堕棿 鈮� 浠婃棩0鐐�
+        Date todayStart = DateUtil.getStartOfDay(new Date());
+        vo.setTodayMembers((long) memberMapper.selectCount(
+                new QueryWrapper<Member>().lambda()
+                        .eq(Member::getIsdeleted, Constants.ZERO)
+                        .ge(Member::getCreateDate, todayStart)));
+        // 鑷杞︽暟閲�(type=0),鐢靛姩杞︽暟閲�(type=1);鍧囧惈鍏ㄩ儴鏈垹闄よ溅杈�(鍚鐢�)
+        vo.setBikeCount((long) bikesMapper.selectCount(
+                new QueryWrapper<Bikes>().lambda()
+                        .eq(Bikes::getType, Constants.ZERO)
+                        .eq(Bikes::getIsdeleted, Constants.ZERO)));
+        vo.setEleBikeCount((long) bikesMapper.selectCount(
+                new QueryWrapper<Bikes>().lambda()
+                        .eq(Bikes::getType, Constants.ONE)
+                        .eq(Bikes::getIsdeleted, Constants.ZERO)));
+        return vo;
+    }
+
+    @Override
+    public List<BikeIncomeStatVO> bikeIncome(BikeIncomeQueryDTO query) {
+        // 1. 瑙f瀽鏃舵:1/2/3 杩� N 澶�(鍚粖澶╁叡 N 澶�),4 鑷畾涔�
+        DateRange range = resolveRange(query);
+        Date start = range.start;
+        Date end = range.end;
+
+        // 2. 杞﹀瀷瀛楀吀:base_param type=3 鍗曡溅 / 4 鐢佃溅,渚涜溅鍨嬪悕 + 澶х被褰掔被
+        List<BaseParam> paramList = baseParamMapper.selectList(
+                new QueryWrapper<BaseParam>().lambda()
+                        .eq(BaseParam::getIsdeleted, Constants.ZERO)
+                        .in(BaseParam::getType, Constants.THREE, Constants.FOUR));
+        Map<String, BaseParam> paramMap = paramList.stream()
+                .collect(Collectors.toMap(BaseParam::getId, p -> p, (a, b) -> a));
+
+        // 3. 鏃舵鍐呭凡缁撶畻鐨勭杞︽娂閲戣鍗�(鏌ヨ妯″紡鍙傝�冨悗鍙� getBikeIncomeReportVOList:
+        //    type=0 鎶奸噾绫汇�乻tatus=4 宸茬粨绠椼�乸aramId 闈炵┖銆乸ayDate 钀藉湪鍖洪棿鍐�)
+        List<Goodsorder> orders = goodsorderMapper.selectList(
+                new QueryWrapper<Goodsorder>().lambda()
+                        .eq(Goodsorder::getType, Constants.ZERO)
+                        .eq(Goodsorder::getStatus, Constants.FOUR)
+                        .eq(Goodsorder::getIsdeleted, Constants.ZERO)
+                        .isNotNull(Goodsorder::getParamId)
+                        .ne(Goodsorder::getParamId, StringUtils.EMPTY)
+                        .ge(Goodsorder::getPayDate, start)
+                        .le(Goodsorder::getPayDate, end));
+
+        // 4. 鎸� paramId(杞﹀瀷)鍒嗙粍鍚堣缁撶畻閲戦 closeMoney(鍗曚綅:鍒�)
+        Map<String, BigDecimal> incomeByParam = new LinkedHashMap<>();
+        for (Goodsorder o : orders) {
+            BigDecimal amount = o.getCloseMoney() == null ? BigDecimal.ZERO : o.getCloseMoney();
+            incomeByParam.merge(o.getParamId(), amount, BigDecimal::add);
+        }
+
+        // 5. 缁勮缁撴灉:杞﹀瀷鍚� + 澶х被 + 鏀跺叆(鍒嗏啋鍏�,2浣�),鎸夋敹鍏ラ檷搴�
+        List<BikeIncomeStatVO> result = new ArrayList<>();
+        for (Map.Entry<String, BigDecimal> e : incomeByParam.entrySet()) {
+            BaseParam param = paramMap.get(e.getKey());
+            BikeIncomeStatVO vo = new BikeIncomeStatVO();
+            vo.setParamId(e.getKey());
+            vo.setParamName(param == null ? "鏈煡杞﹀瀷" : param.getName());
+            vo.setCategory(param == null ? "鏈煡" : categoryOf(param.getType()));
+            vo.setIncome(e.getValue().divide(CENT_PER_YUAN, 2, BigDecimal.ROUND_HALF_UP));
+            result.add(vo);
+        }
+        result.sort(Comparator.comparing(BikeIncomeStatVO::getIncome).reversed());
+        return result;
+    }
+
+    /**
+     * 鎸� base_param.type 娲剧敓杞﹁締澶х被:3 鑷杞� / 4 鐢靛姩杞�,鍏朵綑褰�"鏈煡"銆�
+     *
+     * @param type base_param.type(3 鍗曡溅绫诲瀷 / 4 鐢佃溅绫诲瀷)
+     * @return 澶х被涓枃鍚�
+     */
+    private String categoryOf(Integer type) {
+        if (type == null) {
+            return "鏈煡";
+        }
+        // 娉ㄦ剰:Constants.THREE 鏄� Integer銆丆onstants.FOUR 鏄� int(绫诲瀷涓嶄竴鑷�),
+        // 缁熶竴鎷嗘垚 int 姣旇緝,閬垮紑瀵瑰熀鏈被鍨嬭皟鐢� equals / 鍖呰绫诲紩鐢ㄦ瘮杈冪殑鍧�
+        int t = type;
+        if (t == Constants.THREE) {
+            return "鑷杞�";
+        }
+        if (t == Constants.FOUR) {
+            return "鐢靛姩杞�";
+        }
+        return "鏈煡";
+    }
+
+    /**
+     * 瑙f瀽鍚庣殑鏌ヨ鏃舵(鍧囧惈绔偣)銆�
+     */
+    private static final class DateRange {
+        /** 璧峰鏃堕棿(鍚�) */
+        final Date start;
+        /** 缁撴潫鏃堕棿(鍚�) */
+        final Date end;
+
+        DateRange(Date start, Date end) {
+            this.start = start;
+            this.end = end;
+        }
+    }
+
+    /**
+     * 瑙f瀽鏌ヨ鏃舵:dateType 1/2/3 鈫� 杩� N 澶�(鍚粖澶╁叡 N 澶�),4 鈫� 鑷畾涔夎捣姝�(鍧囧惈)銆�
+     * <p>{@link #bikeIncome} 涓� {@link #incomeStat} 鍏辩敤,淇濊瘉涓ょ鏃舵鍙e緞涓�鑷淬��
+     *
+     * @param query 鏃舵鏌ヨ鍏ュ弬
+     * @return 瑙f瀽鍚庣殑 [start, end] 鍖洪棿
+     */
+    private DateRange resolveRange(BikeIncomeQueryDTO query) {
+        if (query == null) {
+            throw new BusinessException(ResponseStatus.BAD_REQUEST);
+        }
+        Integer dateType = query.getDateType();
+        Date start;
+        Date end;
+        if (dateType != null && RECENT_DAYS.containsKey(dateType)) {
+            // 蹇嵎:杩� N 澶�,鍚粖澶╁叡 N 澶�,璧峰 = 浠婂ぉ寰�鍓� N-1 澶╃殑0鐐�
+            int days = RECENT_DAYS.get(dateType);
+            end = DateUtil.getEndOfDay(new Date());
+            start = DateUtil.getStartOfDay(DateUtil.increaseDay(new Date(), -(days - 1)));
+        } else if (dateType != null && dateType == 4) {
+            // 鑷畾涔�:璧锋鍧囧惈,鏍¢獙闈炵┖涓� start<=end
+            if (query.getStartDate() == null || query.getEndDate() == null
+                    || query.getStartDate().getTime() > query.getEndDate().getTime()) {
+                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "鑷畾涔夋椂娈佃捣姝㈡棩鏈熶笉鍚堟硶");
+            }
+            start = query.getStartDate();
+            end = query.getEndDate();
+        } else {
+            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "鏃舵绫诲瀷涓嶅悎娉�");
+        }
+        return new DateRange(start, end);
+    }
+
+    @Override
+    public IncomeStatVO incomeStat(BikeIncomeQueryDTO query) {
+        // 1. 瑙f瀽鏃舵
+        DateRange range = resolveRange(query);
+        Date start = range.start;
+        Date end = range.end;
+
+        // 2. 鏌ヨ鏈湡宸茬粨绠楃殑绉熻溅鎶奸噾璁㈠崟(鏌ヨ妯″紡鍙傝�冨悗鍙� getTotalData / getBikeIncomeReportVOList:
+        //    type=0 鎶奸噾绫汇�乻tatus=4 宸茬粨绠椼�乸ayDate 钀藉湪鍖洪棿鍐�)銆傛敹鍏ョ粺璁′笉闄愯溅鍨�,鏁呬笉绾︽潫 paramId
+        List<Goodsorder> orders = goodsorderMapper.selectList(
+                new QueryWrapper<Goodsorder>().lambda()
+                        .eq(Goodsorder::getType, Constants.ZERO)
+                        .eq(Goodsorder::getStatus, Constants.FOUR)
+                        .eq(Goodsorder::getIsdeleted, Constants.ZERO)
+                        .ge(Goodsorder::getPayDate, start)
+                        .le(Goodsorder::getPayDate, end));
+
+        // 3. 鎸� payDate 鐨勬棩鏈�(yyyy-MM-dd)鍒嗙粍姹囨�� closeMoney(鍗曚綅:鍒�),鍚屾椂绱鍖洪棿鎬婚
+        Map<String, BigDecimal> sumByDay = new LinkedHashMap<>();
+        BigDecimal totalCents = BigDecimal.ZERO;
+        for (Goodsorder o : orders) {
+            BigDecimal amount = o.getCloseMoney() == null ? BigDecimal.ZERO : o.getCloseMoney();
+            sumByDay.merge(DateUtil.getShortDateStr(o.getPayDate()), amount, BigDecimal::add);
+            totalCents = totalCents.add(amount);
+        }
+
+        // 4. 鐢熸垚瀹屾暣鏃ユ湡搴忓垪(鏌辩姸鍥炬í杞磋繛缁�),鏃犳暟鎹棩琛� 0銆�
+        //    娉�:DateUtil.getDateList 鐢� dEnd.after(begin) 姣旇緝,鑻ヤ袱绔椂鍒嗙涓嶄竴鑷翠細澶氱畻涓�澶�,
+        //    鏁呯粺涓�瑙勬暣鍒板綋澶� 0 鐐瑰啀鐢熸垚搴忓垪
+        List<IncomeDailyVO> dailyList = new ArrayList<>();
+        for (Date d : DateUtil.getDateList(DateUtil.getStartOfDay(start), DateUtil.getStartOfDay(end))) {
+            IncomeDailyVO vo = new IncomeDailyVO();
+            vo.setDate(DateUtil.getShortDateStr(d));
+            BigDecimal daySum = sumByDay.getOrDefault(vo.getDate(), BigDecimal.ZERO);
+            // 鍒嗏啋鍏�,2浣嶅皬鏁�
+            vo.setIncome(daySum.divide(CENT_PER_YUAN, 2, BigDecimal.ROUND_HALF_UP));
+            dailyList.add(vo);
+        }
+
+        // 5. 鍖洪棿绱鏀跺叆(澶嶇敤宸叉煡鐨勬湰鏈熸暟鎹�,鍒嗏啋鍏�,閬垮厤閲嶅鏌ヨ)
+        BigDecimal totalIncome = totalCents.divide(CENT_PER_YUAN, 2, BigDecimal.ROUND_HALF_UP);
+        IncomeStatVO result = new IncomeStatVO();
+        result.setDailyList(dailyList);
+        result.setTotalIncome(totalIncome);
+
+        // 6. 鐜瘮:绱ч偦鍓嶄竴绛夐暱鍖洪棿(鏁翠綋骞崇Щ -N 澶�,N=鍖洪棿澶╂暟)銆傚姣旀湡鍙眹鎬婚噾棰�,涓嶉渶姣忔棩鏄庣粏
+        int spanDays = DateUtil.daysBetweenDates(start, end) + 1;
+        BigDecimal chainAmount = sumClosedMoney(
+                DateUtil.increaseDay(start, -spanDays), DateUtil.increaseDay(end, -spanDays));
+        result.setChainAmount(chainAmount);
+        result.setChainRate(growthRate(totalIncome, chainAmount));
+
+        // 7. 鍚屾瘮:鍘诲勾鍚屾湡鍚岄暱搴﹀尯闂�(骞崇Щ -1 骞�,increaseYear 鎸� Calendar 绮剧‘骞崇Щ,闂板勾涓嶅け鐪�)
+        BigDecimal yearOnYearAmount = sumClosedMoney(
+                DateUtil.increaseYear(start, -1), DateUtil.increaseYear(end, -1));
+        result.setYearOnYearAmount(yearOnYearAmount);
+        result.setYearOnYearRate(growthRate(totalIncome, yearOnYearAmount));
+
+        return result;
+    }
+
+    /**
+     * 姹囨�绘寚瀹氭椂娈靛唴宸茬粨绠楃杞︽娂閲戣鍗曠殑缁撶畻鏀跺叆(closeMoney 涔嬪拰,鍒嗏啋鍏�)銆�
+     * <p>渚涚疮璁℃敹鍏ャ�佺幆姣斻�佸悓姣斿鐢�,缁熶竴鏀跺叆鍙e緞;鍙� select closeMoney 鍒椾互鍑忓皯鏁版嵁浼犺緭銆�
+     *
+     * @param start 璧峰鏃堕棿(鍚�)
+     * @param end   缁撴潫鏃堕棿(鍚�)
+     * @return 鍖洪棿缁撶畻鏀跺叆(鍏�,2浣嶅皬鏁�);鏃犳暟鎹繑鍥� 0
+     */
+    private BigDecimal sumClosedMoney(Date start, Date end) {
+        List<Goodsorder> orders = goodsorderMapper.selectList(
+                new QueryWrapper<Goodsorder>().lambda()
+                        .select(Goodsorder::getCloseMoney)
+                        .eq(Goodsorder::getType, Constants.ZERO)
+                        .eq(Goodsorder::getStatus, Constants.FOUR)
+                        .eq(Goodsorder::getIsdeleted, Constants.ZERO)
+                        .ge(Goodsorder::getPayDate, start)
+                        .le(Goodsorder::getPayDate, end));
+        BigDecimal sum = BigDecimal.ZERO;
+        for (Goodsorder o : orders) {
+            if (o.getCloseMoney() != null) {
+                sum = sum.add(o.getCloseMoney());
+            }
+        }
+        // 鍒嗏啋鍏�,2浣嶅皬鏁�
+        return sum.divide(CENT_PER_YUAN, 2, BigDecimal.ROUND_HALF_UP);
+    }
+
+    /**
+     * 璁$畻澧為暱鐜�:(current - base) / base 脳 100,淇濈暀2浣嶅皬鏁般��
+     *
+     * @param current 鏈湡鍊�
+     * @param base    瀵规瘮鏈熷��
+     * @return 澧為暱鐜�(%);base 涓� 0 鎴� null 鏃惰繑鍥� null(鏃犳硶璁$畻,鍓嶇鏄剧ず"-")
+     */
+    private BigDecimal growthRate(BigDecimal current, BigDecimal base) {
+        if (base == null || base.compareTo(BigDecimal.ZERO) == 0) {
+            // 瀵规瘮鏈熸棤鏀跺叆,澧為暱鐜囨棤鎰忎箟
+            return null;
+        }
+        BigDecimal current0 = current == null ? BigDecimal.ZERO : current;
+        return current0.subtract(base)
+                .multiply(PERCENT_BASE)
+                .divide(base, 2, BigDecimal.ROUND_HALF_UP);
+    }
+
+    @Override
+    public OperationCenterVO operationCenter() {
+        Date now = new Date();
+        // 浠婃棩璧锋(鍚鐐�),鐢ㄤ簬"浠婃棩*"绯诲垪缁熻
+        Date todayStart = DateUtil.getStartOfDay(now);
+        Date todayEnd = DateUtil.getEndOfDay(now);
+
+        OperationCenterVO vo = new OperationCenterVO();
+        // 浠婃棩鏃ユ湡 + 鏄熸湡鍑�
+        vo.setToday(DateUtil.getShortDateStr(now));
+        vo.setWeekDay(DateUtil.getWeekOfDate(now));
+
+        // 浠婃棩璁㈠崟鎬绘暟:浠婃棩宸叉敮浠樿鍗�(payStatus=1),鍚獞琛屾娂閲�(type=0)涓庡椁愬崱(type=1)
+        vo.setTodayOrderCount((long) goodsorderMapper.selectCount(
+                new QueryWrapper<Goodsorder>().lambda()
+                        .eq(Goodsorder::getPayStatus, Constants.ONE)
+                        .eq(Goodsorder::getIsdeleted, Constants.ZERO)
+                        .ge(Goodsorder::getPayDate, todayStart)
+                        .le(Goodsorder::getPayDate, todayEnd)));
+
+        // 杩涜涓鍗曟暟閲�:楠戣涓�(type=0 鎶奸噾銆佸凡鏀粯鏈粨绠� status=1),瀹炴椂鍦ㄩ��,涓嶉檺鏃ユ湡
+        vo.setOngoingOrderCount((long) goodsorderMapper.selectCount(
+                new QueryWrapper<Goodsorder>().lambda()
+                        .eq(Goodsorder::getType, Constants.ZERO)
+                        .eq(Goodsorder::getStatus, Constants.ONE)
+                        .eq(Goodsorder::getIsdeleted, Constants.ZERO)));
+
+        // 浠婃棩濂楅鏀跺叆(鍏�):浠婃棩濂楅鍗¤喘涔�(type=1銆佸凡鏀粯)鐨� money 涔嬪拰
+        vo.setPackageIncome(sumMoney(Constants.ONE, todayStart, todayEnd));
+
+        // 浠婃棩鎬绘敹鍏�(鍏�):涓庢敹鍏ョ粺璁″悓鍙e緞(type=0 鎶奸噾 + status=4 宸茬粨绠� 鐨� closeMoney),澶嶇敤
+        vo.setTotalIncome(sumClosedMoney(todayStart, todayEnd));
+
+        return vo;
+    }
+
+    /**
+     * 姹囨�绘寚瀹氳鍗曠被鍨嬪湪鏃舵鍐呭凡鏀粯璁㈠崟鐨勬敮浠橀噾棰�(money 涔嬪拰,鍒嗏啋鍏�)銆�
+     * <p>渚涜繍钀ヤ腑蹇�"浠婃棩濂楅鏀跺叆"绛夋寜 type 缁熻鏀粯閲戦浣跨敤銆�
+     *
+     * @param type  璁㈠崟绫诲瀷(0 绉熻溅鎶奸噾 / 1 濂楅鍗¤喘涔�)
+     * @param start 璧峰鏃堕棿(鍚�)
+     * @param end   缁撴潫鏃堕棿(鍚�)
+     * @return 鍖洪棿鏀粯閲戦(鍏�,2浣嶅皬鏁�);鏃犳暟鎹繑鍥� 0
+     */
+    private BigDecimal sumMoney(Integer type, Date start, Date end) {
+        List<Goodsorder> orders = goodsorderMapper.selectList(
+                new QueryWrapper<Goodsorder>().lambda()
+                        .select(Goodsorder::getMoney)
+                        .eq(Goodsorder::getType, type)
+                        .eq(Goodsorder::getPayStatus, Constants.ONE)
+                        .eq(Goodsorder::getIsdeleted, Constants.ZERO)
+                        .ge(Goodsorder::getPayDate, start)
+                        .le(Goodsorder::getPayDate, end));
+        BigDecimal sum = BigDecimal.ZERO;
+        for (Goodsorder o : orders) {
+            if (o.getMoney() != null) {
+                sum = sum.add(o.getMoney());
+            }
+        }
+        // 鍒嗏啋鍏�,2浣嶅皬鏁�
+        return sum.divide(CENT_PER_YUAN, 2, BigDecimal.ROUND_HALF_UP);
+    }
+
+    @Override
+    public PageData<OperationOrderVO> operationOrderPage(PageWrap<OperationOrderQueryDTO> pageWrap) {
+        // 鍒嗛〉瀵硅薄
+        IPage<OperationOrderVO> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
+        OperationOrderQueryDTO model = pageWrap.getModel() == null
+                ? new OperationOrderQueryDTO() : pageWrap.getModel();
+
+        // 鈹�鈹� 姝ラ1:鍒嗛〉涓绘煡璇� 鈹�鈹�
+        // 涓昏〃 goodsorder left join member(鍙栨墜鏈哄彿)+ left join base_param(鍙栫粨绠楄溅鍨嬪悕);
+        // 璁㈠崟鐘舵��(status)鏄犲皠鍒� VO 鍐呴儴鎵胯浇瀛楁,渚涘洖濉尯鍒嗗彇鏁板垎鏀�;涓嶅啀甯﹂�愯鍙栨暟瀛愭煡璇€��
+        // 娉�:bikeType 绛涢�変粛鐢� inSql 瀛愭煡璇⑩�斺�斿畠鍙綔 WHERE 鏉′欢銆佹暣椤垫墽琛屼竴娆�,涓嶆槸閫愯鎶曞奖,鍙繚鐣欍��
+        MPJLambdaWrapper<Goodsorder> wrapper = new MPJLambdaWrapper<Goodsorder>()
+                // 涓昏〃瀛楁:璁㈠崟涓婚敭銆佺紪鍙枫�佺粨绠楁椂闂�
+                .select(Goodsorder::getId, Goodsorder::getCode, Goodsorder::getCloseDate)
+                // 璁㈠崟鐘舵��:鍐呴儴鎵胯浇瀛楁(@JsonIgnore,涓嶈繑鍥炲墠绔�),鐢ㄤ簬鍥炲~鍒嗘敮鍒ゆ柇
+                .selectAs(Goodsorder::getStatus, OperationOrderVO::getOrderStatus)
+                // 缁撶畻杞﹀瀷鍚�:宸茬粨绠楄鍗曠洿鎺� left join base_param 鍙�(goodsorder.param_id鈫抌ase_param.name)
+                .selectAs(BaseParam::getName, OperationOrderVO::getSettleParamName)
+                // 鐢ㄦ埛鎵嬫満鍙�:left join member
+                .selectAs(Member::getPhone, OperationOrderVO::getPhone)
+                .leftJoin(Member.class, Member::getId, Goodsorder::getMemberId)
+                .leftJoin(BaseParam.class, BaseParam::getId, Goodsorder::getParamId)
+                // 鍥哄畾鏉′欢:榛樿鍙煡鎶奸噾璁㈠崟(type=0)
+                .eq(Goodsorder::getType, Constants.ZERO)
+                .eq(Goodsorder::getIsdeleted, Constants.ZERO)
+                // 璁㈠崟鐘舵��:1杩涜涓� / 4宸插畬缁�(鍙��)
+                .eq(Objects.nonNull(model.getStatus()), Goodsorder::getStatus, model.getStatus())
+                // 鐢ㄦ埛鎵嬫満鍙�:妯$硦鍖归厤(鍙��)
+                .like(StringUtils.isNotBlank(model.getPhone()), Member::getPhone, model.getPhone())
+                // 璁㈠崟绫诲瀷:鎸夐獞琛岃褰� member_rides.type 绛涢��(鍙��)
+                .inSql(Objects.nonNull(model.getBikeType()), Goodsorder::getId,
+                        "select ordre_id from member_rides where isdeleted = 0 and type = " + model.getBikeType())
+                .orderByDesc(Goodsorder::getPayDate);
+        IPage<OperationOrderVO> result = goodsorderMapper.selectJoinPage(page, OperationOrderVO.class, wrapper);
+        List<OperationOrderVO> records = result.getRecords();
+        // 鏃犳暟鎹洿鎺ヨ繑鍥�,閬垮厤绌� in() 鏌ヨ
+        if (records.isEmpty()) {
+            return PageData.from(result);
+        }
+
+        // 鈹�鈹� 姝ラ2:鏀堕泦褰撳墠椤佃鍗� id 鈹�鈹�
+        List<String> orderIds = records.stream().map(OperationOrderVO::getId).collect(Collectors.toList());
+
+        // 鈹�鈹� 姝ラ3:涓�娆℃�ф壒閲忔煡楠戣璁板綍(鍚獞琛岃溅鍨嬪悕) 鈹�鈹�
+        // left join base_param 鐩存帴甯﹀嚭楠戣杞﹀瀷鍚�(member_rides.param_id鈫抌ase_param.name鈫扢emberRides.paramName);
+        // 鎸� create_date desc 鎺掑簭,鍐呭瓨鎸夎鍗曞垎缁勫彇姣忕粍绗竴鏉″嵆"鏈�杩戜竴鏉¢獞琛岃褰�"
+        List<MemberRides> rides = memberRidesJoinMapper.selectJoinList(MemberRides.class,
+                new MPJLambdaWrapper<MemberRides>()
+                        .select(MemberRides::getOrdreId, MemberRides::getType, MemberRides::getRentDate,
+                                MemberRides::getBikeCode)
+                        .selectAs(BaseParam::getName, MemberRides::getParamName)
+                        .leftJoin(BaseParam.class, BaseParam::getId, MemberRides::getParamId)
+                        .eq(MemberRides::getIsdeleted, Constants.ZERO)
+                        .in(MemberRides::getOrdreId, orderIds)
+                        .orderByDesc(MemberRides::getCreateDate)
+                        .orderByDesc(MemberRides::getRentDate));
+        Map<String, MemberRides> latestRideByOrder = new LinkedHashMap<>();
+        for (MemberRides r : rides) {
+            // putIfAbsent:宸叉寜 create_date desc 鎺掑簭,棣栨鍑虹幇鍗宠璁㈠崟鏈�杩戜竴鏉�
+            latestRideByOrder.putIfAbsent(r.getOrdreId(), r);
+        }
+
+        // 鈹�鈹� 鍥炲~ VO(鍏ㄧ▼浠� 2 娆℃煡璇�:鍒嗛〉 / 楠戣;杞﹀瀷鍚嶅潎鐢� join 甯﹀嚭,鏃犻渶鍗曠嫭鏌ヨ溅鍨嬪瓧鍏�) 鈹�鈹�
+        for (OperationOrderVO vo : records) {
+            MemberRides latest = latestRideByOrder.get(vo.getId());
+            // 杞﹀瀷鍚嶅垎鏀�:杩涜涓�(status=1)鍙栨渶杩戦獞琛岀殑杞﹀瀷鍚�,鍚﹀垯(鍚凡缁撶畻)鍙栬鍗曠粨绠楄溅鍨嬪悕
+            Integer orderStatus = vo.getOrderStatus();
+            boolean inProgress = orderStatus != null && orderStatus.equals(Constants.ONE);
+            if (latest != null) {
+                // 璁㈠崟绫诲瀷銆侀獞琛屽紑濮嬫椂闂淬�佽溅杈嗙紪鍙�:缁熶竴鍙栨渶杩戜竴鏉¢獞琛岃褰�
+                // (杩涜涓嵆"褰撳墠楠戣杞﹁締",宸插畬缁撳嵆"鏈�鍚庨獞琛岃溅杈�";bike_code = bikes.code)
+                vo.setBikeType(latest.getType());
+                vo.setRentDate(latest.getRentDate());
+                if (inProgress) {
+                    // 杩涜涓�:楠戣杞﹀瀷鍚�(join 宸插甫鍑�,瀛樹簬 MemberRides.paramName)
+                    vo.setParamName(latest.getParamName());
+                    vo.setBikeCode(latest.getBikeCode());
+                }
+            }
+            if (!inProgress) {
+                // 宸茬粨绠�:璁㈠崟缁撶畻杞﹀瀷鍚�(鍒嗛〉 join 宸插甫鍑�,瀛樹簬 settleParamName)
+                vo.setParamName(vo.getSettleParamName());
+            }
+        }
+
+        return PageData.from(result);
+    }
+
+    @Override
+    public OrderRidesDetailVO orderRidesDetail(String orderId) {
+        OrderRidesDetailVO result = new OrderRidesDetailVO();
+        // 璁㈠崟鍙蜂负绌�:鐩存帴杩斿洖绌虹粨鏋�(涓嶆姏寮傚父,鍓嶇鎸� hasTrack=false 鍏滃簳)
+        if (StringUtils.isBlank(orderId)) {
+            result.setHasTrack(false);
+            result.setRides(Collections.emptyList());
+            return result;
+        }
+
+        // 1. 鏌ヨ璁㈠崟涓嬪叏閮ㄩ獞琛岃褰�(鎸夊垱寤烘椂闂村崌搴�,杩樺師鍚屼竴璁㈠崟澶氭楠戣鐨勫厛鍚�)
+        List<MemberRides> ridesList = memberRidesJoinMapper.selectList(
+                new QueryWrapper<MemberRides>().lambda()
+                        .eq(MemberRides::getOrdreId, orderId)
+                        .eq(MemberRides::getIsdeleted, Constants.ZERO)
+                        .orderByAsc(MemberRides::getCreateDate));
+        if (ridesList.isEmpty()) {
+            // 璁㈠崟涓嬫棤楠戣璁板綍(鐞嗚涓嶅簲鍑虹幇,鍏滃簳杩斿洖绌�)
+            result.setHasTrack(false);
+            result.setRides(Collections.emptyList());
+            return result;
+        }
+
+        // 2. 杞﹁締绫诲瀷鍙栭鏉¢獞琛� type(0鑷杞�/1鐢佃溅):鍐冲畾鏄惁鏌ヨ建杩�
+        Integer bikeType = ridesList.get(0).getType();
+        result.setBikeType(bikeType);
+        result.setBikeTypeName(bikeTypeNameOf(bikeType));
+        boolean isEbike = bikeType != null && bikeType.equals(Constants.ONE);
+
+        // 3. 杞ㄨ抗棰勮浇(浠呯數杞�):涓�娆℃�ф煡鍑鸿璁㈠崟鎵�鏈夐獞琛岃建杩�,鎸� rides_id 鍒嗙粍銆佹寜涓婃姤鏃堕棿鍗囧簭,
+        //    閬垮厤閫愭潯楠戣 N 娆℃煡杞ㄨ抗;鑷杞�(type=0)璧� MQTT 鏃� GPS,璺宠繃銆�
+        Map<String, List<OrderRideTrackVO>> trackByRide = new HashMap<>();
+        if (isEbike) {
+            List<String> ridesIds = ridesList.stream()
+                    .map(MemberRides::getId).collect(Collectors.toList());
+            List<MemberRidesTrack> tracks = memberRidesTrackMapper.selectList(
+                    new QueryWrapper<MemberRidesTrack>().lambda()
+                            .select(MemberRidesTrack::getRidesId, MemberRidesTrack::getLongitude,
+                                    MemberRidesTrack::getLatitude, MemberRidesTrack::getReportTime)
+                            .eq(MemberRidesTrack::getIsdeleted, Constants.ZERO)
+                            .in(MemberRidesTrack::getRidesId, ridesIds)
+                            .orderByAsc(MemberRidesTrack::getReportTime));
+            for (MemberRidesTrack t : tracks) {
+                OrderRideTrackVO tv = new OrderRideTrackVO();
+                tv.setLongitude(t.getLongitude());
+                tv.setLatitude(t.getLatitude());
+                tv.setReportTime(t.getReportTime());
+                trackByRide.computeIfAbsent(t.getRidesId(), k -> new ArrayList<>()).add(tv);
+            }
+        }
+
+        // 4. 缁勮楠戣璁板綍鍒楄〃(姣忔潯鎸傚搴旇建杩圭偣;鑷杞︿竴寰嬬┖杞ㄨ抗)
+        List<OrderRideItemVO> rides = new ArrayList<>(ridesList.size());
+        for (MemberRides r : ridesList) {
+            OrderRideItemVO item = new OrderRideItemVO();
+            item.setRidesId(r.getId());
+            item.setStatus(r.getStatus());
+            item.setStatusName(rideStatusNameOf(r.getStatus()));
+            item.setRentDate(r.getRentDate());
+            item.setBackDate(r.getBackDate());
+            item.setBikeCode(r.getBikeCode());
+            item.setDuration(r.getDuration());
+            item.setBikeTypeName(bikeTypeNameOf(r.getType()));
+            item.setTracks(isEbike
+                    ? trackByRide.getOrDefault(r.getId(), Collections.emptyList())
+                    : Collections.emptyList());
+            rides.add(item);
+        }
+        result.setRides(rides);
+
+        // 5. 杞ㄨ抗鍙敤鎬� + 鑷杞︽棤杞ㄨ抗鎻愮ず
+        if (isEbike) {
+            result.setHasTrack(true);
+        } else {
+            result.setHasTrack(false);
+            result.setNoTrackMessage("璇ヨ鍗曚负鑷杞﹁鍗�,鏃犺溅杈嗚建杩�");
+        }
+        return result;
+    }
+
+    /**
+     * 楠戣鐘舵�� 鈫� 涓枃鍚嶃��
+     * <p>member_rides.status:0璇锋眰寮�閿佷腑 / 1楠戣涓� / 2宸茶繕杞� / 3寮�閿佸け璐� / 4涓存椂閿佽溅銆�
+     *
+     * @param status 楠戣鐘舵�佸師鏂�(鍙兘涓� null)
+     * @return 鐘舵�佷腑鏂囧悕(绌哄��/鏈煡鍙栧�艰繑鍥�"鏈煡")
+     */
+    private String rideStatusNameOf(Integer status) {
+        if (status == null) {
+            return "鏈煡";
+        }
+        switch (status) {
+            case 0:
+                return "璇锋眰寮�閿佷腑";
+            case 1:
+                return "楠戣涓�";
+            case 2:
+                return "宸茶繕杞�";
+            case 3:
+                return "寮�閿佸け璐�";
+            case 4:
+                return "涓存椂閿佽溅";
+            default:
+                return "鏈煡";
+        }
+    }
+
+    /**
+     * 杞﹁締绫诲瀷 鈫� 涓枃鍚嶃��
+     * <p>member_rides.type:0鑷杞� / 1鐢靛姩杞︺��
+     *
+     * @param type 杞﹁締绫诲瀷(鍙兘涓� null)
+     * @return 绫诲瀷涓枃鍚�(鏈煡鍙栧�艰繑鍥�"鏈煡")
+     */
+    private String bikeTypeNameOf(Integer type) {
+        if (type == null) {
+            return "鏈煡";
+        }
+        if (type.equals(Constants.ZERO)) {
+            return "鑷杞�";
+        }
+        if (type.equals(Constants.ONE)) {
+            return "鐢靛姩杞�";
+        }
+        return "鏈煡";
+    }
+}
diff --git a/server/services/src/main/java/com/doumee/service/system/SystemDictDataService.java b/server/services/src/main/java/com/doumee/service/system/SystemDictDataService.java
index bf33cbf..34564c9 100644
--- a/server/services/src/main/java/com/doumee/service/system/SystemDictDataService.java
+++ b/server/services/src/main/java/com/doumee/service/system/SystemDictDataService.java
@@ -2,6 +2,7 @@
 
 import com.doumee.core.model.PageData;
 import com.doumee.core.model.PageWrap;
+import com.doumee.dao.business.web.response.DouyinConfigDTO;
 import com.doumee.dao.business.web.response.MiniProgrammeDTO;
 import com.doumee.dao.system.dto.QuerySystemDictDataDTO;
 import com.doumee.dao.system.model.SystemDictData;
@@ -97,4 +98,22 @@
      * @param miniProgrammeDTO
      */
     void updateMiniProgrammeDTO(MiniProgrammeDTO miniProgrammeDTO);
+
+    /**
+     * 鑾峰彇鎶栭煶鏍搁攢閰嶇疆(client_key/client_secret/account_id/poi_id 鍥涢」瀛楀吀鍊�)
+     * @return 鎶栭煶鏍搁攢閰嶇疆
+     */
+    DouyinConfigDTO getDouyinConfigDTO();
+
+    /**
+     * 淇敼鎶栭煶搴旂敤閰嶇疆(client_key/client_secret/account_id)
+     * @param douyinConfigDTO 鎶栭煶鏍搁攢閰嶇疆
+     */
+    void updateDouyinAppConfigDTO(DouyinConfigDTO douyinConfigDTO);
+
+    /**
+     * 淇敼鏍搁攢闂ㄥ簵ID(鍗曢棬搴� POI_ID)
+     * @param poiId 鏍搁攢闂ㄥ簵ID
+     */
+    void updateDouyinPoiIdDTO(String poiId);
 }
diff --git a/server/services/src/main/java/com/doumee/service/system/impl/SystemDictDataServiceImpl.java b/server/services/src/main/java/com/doumee/service/system/impl/SystemDictDataServiceImpl.java
index 94b3265..07217f0 100644
--- a/server/services/src/main/java/com/doumee/service/system/impl/SystemDictDataServiceImpl.java
+++ b/server/services/src/main/java/com/doumee/service/system/impl/SystemDictDataServiceImpl.java
@@ -5,7 +5,9 @@
 import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.doumee.core.constants.ResponseStatus;
 import com.doumee.core.exception.BusinessException;
+import com.doumee.core.douyin.DouyinClient;
 import com.doumee.dao.business.web.request.LocaltionDTO;
+import com.doumee.dao.business.web.response.DouyinConfigDTO;
 import com.doumee.dao.business.web.response.MiniProgrammeDTO;
 import com.doumee.dao.system.SystemDictMapper;
 import com.doumee.dao.system.model.SystemDict;
@@ -21,6 +23,7 @@
 import com.doumee.service.system.SystemDictDataService;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -40,12 +43,16 @@
  * @date 2022/03/15 09:54
  */
 @Service
+@Slf4j
 public class SystemDictDataServiceImpl implements SystemDictDataService {
 
     @Autowired
     private SystemDictDataMapper systemDictDataMapper;
     @Autowired
     private SystemDictMapper systemDictMapper;
+    /** 鎶栭煶 HTTP 瀹㈡埛绔�:鐢ㄤ簬鏀瑰畬搴旂敤閰嶇疆鍚庢竻绌� client_token 缂撳瓨 */
+    @Autowired
+    private DouyinClient douyinClient;
 
     @Override
     public String create(SystemDictData systemDictData) {
@@ -166,4 +173,69 @@
             throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),"瀛楀吀鍊艰В鏋愭湁璇�");
         }
     }
+
+    @Override
+    public DouyinConfigDTO getDouyinConfigDTO() {
+        try {
+            // 澶嶇敤 MiniProgrammeDTO 鐨勯┘宄扳噭涓嬪垝绾垮伐鍏�:瀵硅薄灞炴�� 鈫� {client_key,client_secret,account_id,poi_id}
+            String jasonStr = MiniProgrammeDTO.toUnderlineJSONString(new DouyinConfigDTO());
+            JSONObject parse = (JSONObject) JSONObject.parse(jasonStr);
+            List<String> collect = parse.entrySet().stream().map(s -> s.getKey().toUpperCase()).collect(Collectors.toList());
+            QueryWrapper<SystemDictData> wrapper = new QueryWrapper<>();
+            wrapper.lambda()
+                    .in(SystemDictData::getLabel,collect);
+            List<SystemDictData> systemDictData = systemDictDataMapper.selectList(wrapper);
+            if (CollectionUtils.isEmpty(systemDictData)){
+                throw new BusinessException(ResponseStatus.DATA_EXISTS.getCode(),"瀛楀吀涓嶅瓨鍦�");
+            }
+            systemDictData.forEach(s->{
+                parse.put(s.getLabel().toLowerCase(),s.getCode());
+            });
+            String s = parse.toJSONString();
+            return MiniProgrammeDTO.toSnakeObject(s, DouyinConfigDTO.class);
+        } catch (BusinessException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(),"瀛楀吀鍊艰В鏋愭湁璇�");
+        }
+    }
+
+    @Transactional(rollbackFor = {Exception.class,BusinessException.class})
+    @Override
+    public void updateDouyinAppConfigDTO(DouyinConfigDTO douyinConfigDTO) {
+        try {
+            String jasonStr = MiniProgrammeDTO.toUnderlineJSONString(douyinConfigDTO);
+            JSONObject parse = (JSONObject) JSONObject.parse(jasonStr);
+            // 浠呮洿鏂版姈闊冲簲鐢ㄤ笁椤�(client_key/client_secret/account_id),poi_id 鐢辩嫭绔嬫帴鍙g淮鎶�
+            parse.entrySet().forEach(s->{
+                UpdateWrapper<SystemDictData> wrapper = new UpdateWrapper<>();
+                wrapper.lambda()
+                        .eq(SystemDictData::getLabel,s.getKey().toUpperCase())
+                        .set(SystemDictData::getCode,s.getValue());
+                systemDictDataMapper.update(null,wrapper);
+            });
+        } catch (JsonProcessingException e) {
+            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),"瀛楀吀鍊艰В鏋愭湁璇�");
+        }
+        // client_key/client_secret 鏀逛簡涔嬪悗,缂撳瓨鐨勬棫 access-token 宸插け鏁�,娓呮帀鍚庝笅娆¤皟鐢ㄦ墠鐢ㄦ柊閰嶇疆鎹㈠彇鏂� token銆�
+        // 娓呯紦瀛樺睘浜庡壇浣滅敤,澶辫触涓嶅洖婊氶厤缃�(閰嶇疆宸叉纭叆搴�,token 涓嬫杩囨湡鑷剤)銆�
+        try {
+            douyinClient.clearAccessToken();
+        } catch (Exception e) {
+            log.warn("鏇存柊鎶栭煶搴旂敤閰嶇疆鍚庢竻绌� access-token 澶辫触", e);
+        }
+    }
+
+    @Transactional(rollbackFor = {Exception.class,BusinessException.class})
+    @Override
+    public void updateDouyinPoiIdDTO(String poiId) {
+        if (StringUtils.isBlank(poiId)){
+            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),"闂ㄥ簵ID涓嶈兘涓虹┖");
+        }
+        UpdateWrapper<SystemDictData> wrapper = new UpdateWrapper<>();
+        wrapper.lambda()
+                .eq(SystemDictData::getLabel, Constants.DOUYIN_POI_ID)
+                .set(SystemDictData::getCode,poiId);
+        systemDictDataMapper.update(null,wrapper);
+    }
 }
diff --git a/server/services/src/main/resources/application-dev.yml b/server/services/src/main/resources/application-dev.yml
index aad4f62..ea92283 100644
--- a/server/services/src/main/resources/application-dev.yml
+++ b/server/services/src/main/resources/application-dev.yml
@@ -90,3 +90,10 @@
     remoteHost: https://apis.map.qq.com
     appKey: 3AYBZ-I5R3V-2BVP3-UWBDQ-ETBM5-B2BBQ
 
+########################鎶栭煶寮�鏀惧钩鍙�(鐢熸椿鏈嶅姟/鍥㈣喘鏍搁攢)閰嶇疆########################
+douyin:
+  host: https://open.douyin.com
+  # client_key / client_secret / account_id / poi_id 鏀逛负瀛樻暟鎹簱瀛楀吀(DOUYIN_CONFIG),
+  # 鍚庡彴 /system/dictData 鍙敼銆佸厤閲嶅惎;姝ゅ浠呬繚鐣欐妧鏈弬鏁般��
+  # client_token 鍦� Redis 涓殑缂撳瓨 key
+  redis-token-key: douyin:client_token
\ No newline at end of file
diff --git a/server/services/src/main/resources/application-pro.yml b/server/services/src/main/resources/application-pro.yml
index acd8368..a373f90 100644
--- a/server/services/src/main/resources/application-pro.yml
+++ b/server/services/src/main/resources/application-pro.yml
@@ -77,3 +77,11 @@
     remoteHost: https://apis.map.qq.com
     appKey: 3AYBZ-I5R3V-2BVP3-UWBDQ-ETBM5-B2BBQ
 
+########################鎶栭煶寮�鏀惧钩鍙�(鐢熸椿鏈嶅姟/鍥㈣喘鏍搁攢)閰嶇疆########################
+douyin:
+  host: https://open.douyin.com
+  # client_key / client_secret / account_id / poi_id 鏀逛负瀛樻暟鎹簱瀛楀吀(DOUYIN_CONFIG),
+  # 鍚庡彴 /system/dictData 鍙敼銆佸厤閲嶅惎;姝ゅ浠呬繚鐣欐妧鏈弬鏁般��
+  # client_token 鍦� Redis 涓殑缂撳瓨 key
+  redis-token-key: douyin:client_token
+
diff --git a/server/web/src/main/java/com/doumee/api/web/DouyinApi.java b/server/web/src/main/java/com/doumee/api/web/DouyinApi.java
new file mode 100644
index 0000000..1559c60
--- /dev/null
+++ b/server/web/src/main/java/com/doumee/api/web/DouyinApi.java
@@ -0,0 +1,226 @@
+package com.doumee.api.web;
+
+import com.alibaba.fastjson.JSON;
+import com.doumee.core.annotation.LoginRequired;
+import com.doumee.core.annotation.pr.PreventRepeat;
+import com.doumee.core.constants.Constants;
+import com.doumee.core.constants.ResponseStatus;
+import com.doumee.core.douyin.DouyinClient;
+import com.doumee.core.douyin.dto.DouyinBaseResp;
+import com.doumee.core.douyin.dto.DouyinPrepareParam;
+import com.doumee.core.douyin.dto.DouyinPrepareResp;
+import com.doumee.core.douyin.dto.DouyinShopPoiResp;
+import com.doumee.core.douyin.dto.DouyinVerifyParam;
+import com.doumee.core.exception.BusinessException;
+import com.doumee.core.model.ApiResponse;
+import com.doumee.core.model.PageData;
+import com.doumee.core.model.PageWrap;
+import com.doumee.core.utils.ID;
+import com.doumee.dao.business.model.DouyinVerifyLog;
+import com.doumee.dao.business.model.DouyinVerifyRecord;
+import com.doumee.service.business.DouyinProductService;
+import com.doumee.service.business.DouyinVerifyLogService;
+import com.doumee.service.business.DouyinVerifyService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 鎶栭煶(灏忕▼搴忕:鍥㈣喘楠屽埜 + 鑱旇皟娴嬭瘯)
+ *
+ * @author rk
+ * @date 2026/06/24
+ */
+@Slf4j
+@Api(tags = "鎶栭煶")
+@RestController
+@RequestMapping("/web/douyin")
+public class DouyinApi extends ApiController {
+
+    @Autowired
+    private DouyinProductService douyinProductService;
+    @Autowired
+    private DouyinVerifyService douyinVerifyService;
+    @Autowired
+    private DouyinVerifyLogService douyinVerifyLogService;
+    /** 鎶栭煶 HTTP 瀹㈡埛绔�:闂ㄥ簵鏌ヨ娴嬭瘯鐢� */
+    @Autowired
+    private DouyinClient douyinClient;
+
+    @ApiOperation(value = "鑱旇皟娴嬭瘯:浠庢姈闊冲叏閲忓悓姝ュ晢鍝佸叆搴�(杩斿洖鍏ュ簱鏉℃暟)", notes = "灏忕▼搴忕")
+    @GetMapping("/testQuery")
+    public ApiResponse<Integer> testQuery() {
+        // 鍏ㄩ噺缈婚〉鎷夊彇 online/query 骞� upsert 鏈湴鍟嗗搧 + SKU,杩斿洖鏈鍏ュ簱鏉℃暟
+        return ApiResponse.success(douyinProductService.syncFromDouyin());
+    }
+
+    @ApiOperation(value = "鑱旇皟娴嬭瘯:鏌ヨ鎶栭煶鍟嗘埛涓嬮棬搴桰D鍒楄〃(楠岃瘉闂ㄥ簵鏌ヨ閰嶇疆)", notes = "account_id 浠庡瓧鍏歌鍙�")
+    @GetMapping("/testPoiList")
+    public ApiResponse<List<String>> testPoiList() {
+        // 闂ㄥ簵鏌ヨ涓烘棤鐘舵�侀�忎紶,鐩存帴璋� DouyinClient;account_id 鐢� Client 浠庡瓧鍏歌鍙�
+        DouyinBaseResp<DouyinShopPoiResp> resp = douyinClient.shopPoiQuery();
+        List<DouyinShopPoiResp.Poi> pois = resp == null || resp.getData() == null ? null : resp.getData().getPois();
+        if (pois == null || pois.isEmpty()) {
+            return ApiResponse.success(Collections.emptyList());
+        }
+        // 浠呮彁鍙栭棬搴桰D,杩囨护 poi 鑺傜偣鎴� poiId 涓虹┖鐨勬潯鐩�
+        List<String> poiIds = pois.stream()
+                .filter(p -> p != null && p.getPoi() != null && StringUtils.isNotBlank(p.getPoi().getPoiId()))
+                .map(p -> p.getPoi().getPoiId())
+                .collect(Collectors.toList());
+        return ApiResponse.success(poiIds);
+    }
+
+    @LoginRequired
+    @PreventRepeat
+    @ApiOperation("鎵爜涓�姝ユ牳閿�(楠屽埜鍑嗗 + 鏍搁攢鍚堝苟;鍓嶇鍙皟姝ゆ帴鍙�)")
+    @PostMapping("/scanVerify")
+    public ApiResponse<DouyinVerifyRecord> scanVerify(@RequestBody DouyinPrepareParam param) {
+        String apiPath = "/web/douyin/scanVerify";
+        String memberId = getMemberId();
+
+        // 鈶� 楠屽埜鍑嗗:鎵爜/杈撶爜 鈫� 鎷� verifyToken 涓庡埜鍒楄〃(鍗曠嫭璁颁竴鏉� PREPARE 鏃ュ織)
+        long prepareStart = System.currentTimeMillis();
+        DouyinVerifyLog prepareLog = baseLog(Constants.DOUYIN_VERIFY_OPERATE_TYPE.PREPARE.getKey(), apiPath, prepareStart);
+        prepareLog.setRawRequest(JSON.toJSONString(param));
+        if (param != null) {
+            prepareLog.setPoiId(param.getPoiId());
+            prepareLog.setOriginCode(StringUtils.firstNonBlank(param.getCode(), param.getQrContent()));
+        }
+        DouyinBaseResp<DouyinPrepareResp> prepareResp;
+        try {
+            prepareResp = douyinVerifyService.prepare(param);
+            prepareLog.setRawResponse(JSON.toJSONString(prepareResp));
+            Integer code = prepareResp == null || prepareResp.getExtra() == null ? null : prepareResp.getExtra().getErrorCode();
+            prepareLog.setResult(code != null && code == 0 ? Constants.DOUYIN_VERIFY_LOG_RESULT.SUCCESS.getKey() : Constants.DOUYIN_VERIFY_LOG_RESULT.FAIL.getKey());
+            if (code == null || code != 0) {
+                String desc = prepareResp == null || prepareResp.getExtra() == null ? "鏃犲搷搴�" : prepareResp.getExtra().getDescription();
+                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "楠屽埜鍑嗗澶辫触:" + desc);
+            }
+        } catch (Throwable e) {
+            prepareLog.setResult(Constants.DOUYIN_VERIFY_LOG_RESULT.FAIL.getKey());
+            prepareLog.setErrorMsg(e.getMessage());
+            throw e;
+        } finally {
+            saveLog(prepareLog);
+        }
+
+        // 鈶� 鍙栭寮犲彲鏍搁攢鍒�(canVerifyStatus=1 浼樺厛,鍚﹀垯棣栧紶),鎻愬彇鏍搁攢鎵�闇�鏍囪瘑
+        DouyinPrepareResp prepareData = prepareResp.getData();
+        List<DouyinPrepareResp.Certificate> certificates = prepareData == null ? null : prepareData.getCertificates();
+        if (certificates == null || certificates.isEmpty()) {
+            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "鏈壘鍒板彲鏍搁攢鐨勫埜");
+        }
+        DouyinPrepareResp.Certificate cert = pickFirstVerifiable(certificates);
+        if (cert == null || cert.getSku() == null || StringUtils.isBlank(cert.getSku().getSkuId())) {
+            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "鍒哥己灏� SKU 淇℃伅,鏃犳硶鏍搁攢");
+        }
+
+        // 鈶� 缁勮鏍搁攢鍏ュ弬:verifyToken / 棣栧紶鍔犲瘑鍒哥爜 / 闂ㄥ簵 / skuId / 瀹炰粯閲戦蹇収
+        DouyinVerifyParam verifyParam = new DouyinVerifyParam();
+        verifyParam.setVerifyToken(prepareData.getVerifyToken());
+        verifyParam.setPoiId(param == null ? null : param.getPoiId());
+        verifyParam.setEncryptedCodes(Collections.singletonList(cert.getEncryptedCode()));
+        verifyParam.setSkuId(cert.getSku().getSkuId());
+        verifyParam.setPayAmount(cert.getAmount() == null ? null : cert.getAmount().getPayAmount());
+
+        // 鈶� 鏍搁攢 + 寮�濂楅(鍗曠嫭璁颁竴鏉� VERIFY 鏃ュ織)
+        long verifyStart = System.currentTimeMillis();
+        DouyinVerifyLog verifyLog = baseLog(Constants.DOUYIN_VERIFY_OPERATE_TYPE.VERIFY.getKey(), apiPath, verifyStart);
+        verifyLog.setRawRequest(JSON.toJSONString(verifyParam));
+        verifyLog.setPoiId(verifyParam.getPoiId());
+        try {
+            DouyinVerifyRecord rec = douyinVerifyService.verify(verifyParam, memberId);
+            fillByRecord(verifyLog, rec);
+            return ApiResponse.success(rec);
+        } catch (Throwable e) {
+            verifyLog.setResult(Constants.DOUYIN_VERIFY_LOG_RESULT.FAIL.getKey());
+            verifyLog.setErrorMsg(e.getMessage());
+            throw e;
+        } finally {
+            saveLog(verifyLog);
+        }
+    }
+
+    /**
+     * 浠庡埜鍒楄〃鎸戦寮犲彲鏍搁攢鐨勫埜(canVerifyStatus==1);閮戒笉婊¤冻鍒欏彇棣栧紶,浜ょ粰鎶栭煶鏍搁攢鎺ュ彛鍒ゅ畾銆�
+     *
+     * @param certificates prepare 杩斿洖鐨勫埜鍒楄〃(宸蹭繚璇侀潪绌�)
+     * @return 棣栧紶鍙牳閿�鍒�;鍧囦笉鍙牳閿�鏃惰繑鍥為寮�
+     */
+    private DouyinPrepareResp.Certificate pickFirstVerifiable(List<DouyinPrepareResp.Certificate> certificates) {
+        for (DouyinPrepareResp.Certificate cert : certificates) {
+            if (cert != null && Constants.equalsInteger(cert.getCanVerifyStatus(), Constants.ONE)) {
+                return cert;
+            }
+        }
+        return certificates.get(0);
+    }
+
+    @LoginRequired
+    @ApiOperation("鏍搁攢璁板綍鍒嗛〉")
+    @PostMapping("/page")
+    public ApiResponse<PageData<DouyinVerifyRecord>> findPage(@RequestBody PageWrap<DouyinVerifyRecord> pageWrap) {
+        return ApiResponse.success(douyinVerifyService.findPage(pageWrap));
+    }
+
+    @LoginRequired
+    @ApiOperation("鏍搁攢璁板綍璇︽儏")
+    @GetMapping("/{id}")
+    public ApiResponse<DouyinVerifyRecord> findById(@PathVariable String id) {
+        return ApiResponse.success(douyinVerifyService.findById(id));
+    }
+
+    // ---------------- 鎿嶄綔鏃ュ織杈呭姪 ----------------
+
+    private DouyinVerifyLog baseLog(int operateType, String apiPath, long start) {
+        DouyinVerifyLog l = new DouyinVerifyLog();
+        l.setId(ID.nextGUID());
+        l.setOperateType(operateType);
+        l.setApiPath(apiPath);
+        l.setMemberId(getMemberId());
+        l.setIp(getRequest().getRemoteAddr());
+        l.setCostMs((int) (System.currentTimeMillis() - start));
+        l.setCreateDate(new Date());
+        l.setIsdeleted(Constants.ZERO);
+        return l;
+    }
+
+    /** verify 鎴愬姛鍚�,鐢ㄦ牳閿�璁板綍鍥炲~鏃ュ織鐨勪笟鍔″瓧娈典笌缁撴灉(鎾ら攢鏍搁攢宸茶縼绉昏嚦绠$悊绔�) */
+    private void fillByRecord(DouyinVerifyLog opLog, DouyinVerifyRecord rec) {
+        if (rec == null) {
+            opLog.setResult(Constants.DOUYIN_VERIFY_LOG_RESULT.FAIL.getKey());
+            return;
+        }
+        opLog.setVerifyRecordId(rec.getId());
+        if (StringUtils.isNotBlank(rec.getPoiId())) {
+            opLog.setPoiId(rec.getPoiId());
+        }
+        opLog.setOriginCode(rec.getOriginCode());
+        opLog.setRawResponse(rec.getRawResponse());
+        opLog.setResult(Constants.equalsInteger(rec.getVerifyStatus(), Constants.ZERO) ? Constants.DOUYIN_VERIFY_LOG_RESULT.SUCCESS.getKey() : Constants.DOUYIN_VERIFY_LOG_RESULT.FAIL.getKey());
+        opLog.setErrorMsg(rec.getVerifyMsg());
+    }
+
+    /** 钀藉簱鎿嶄綔鏃ュ織;鏃ュ織鑷韩寮傚父涓嶆姏鍑�,閬垮厤褰卞搷涓绘祦绋� */
+    private void saveLog(DouyinVerifyLog opLog) {
+        try {
+            douyinVerifyLogService.record(opLog);
+        } catch (Exception e) {
+            log.warn("璁板綍鎶栭煶楠屽埜鎿嶄綔鏃ュ織澶辫触 type={}, recordId={}", opLog.getOperateType(), opLog.getVerifyRecordId(), e);
+        }
+    }
+}
diff --git a/server/web/src/main/java/com/doumee/api/web/ReportController.java b/server/web/src/main/java/com/doumee/api/web/ReportController.java
new file mode 100644
index 0000000..acfda11
--- /dev/null
+++ b/server/web/src/main/java/com/doumee/api/web/ReportController.java
@@ -0,0 +1,116 @@
+package com.doumee.api.web;
+
+import com.doumee.core.annotation.LoginRequired;
+import com.doumee.core.constants.ResponseStatus;
+import com.doumee.core.exception.BusinessException;
+import com.doumee.core.model.ApiResponse;
+import com.doumee.core.model.PageData;
+import com.doumee.core.model.PageWrap;
+import com.doumee.dao.business.vo.BikeIncomeStatVO;
+import com.doumee.dao.business.vo.IncomeStatVO;
+import com.doumee.dao.business.vo.OperationCenterVO;
+import com.doumee.dao.business.vo.OperationOrderVO;
+import com.doumee.dao.business.vo.OrderRidesDetailVO;
+import com.doumee.dao.business.vo.OverviewStatVO;
+import com.doumee.dao.business.web.request.BikeIncomeQueryDTO;
+import com.doumee.dao.business.web.request.OperationOrderQueryDTO;
+import com.doumee.dao.business.web.response.UserResponse;
+import com.doumee.dao.system.model.SystemUser;
+import com.doumee.service.business.ReportService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 鏁版嵁鎶ヨ〃(web 绔繍钀ヤ腑蹇�:姒傝缁熻 / 鏀跺叆杞﹀瀷鍒嗘瀽 / 鏀跺叆缁熻)銆�
+ * <p>閴存潈璧� web 绔� JWT(@LoginRequired),涓旀瘡涓帴鍙f牎楠屽綋鍓嶇櫥褰曚細鍛樺凡缁戝畾绯荤粺绠$悊鍛�(sysuser != null),
+ * 涓� {@link ManagerApi}(/web/manger)鍚屽睘 web 绔繍钀ヤ腑蹇冨満鏅�,闈炵粦瀹氱鐞嗗憳鐨勪細鍛樻棤鏉冭闂��
+ * <p>Service 瀹炵幇澶嶇敤 services 妯″潡鐨� {@link ReportService},鏈浠呮秹鍙� Controller 涓庨壌鏉冩柟寮忋��
+ *
+ * @author rk
+ * @date 2026/06/26
+ */
+@Api(tags = "鏁版嵁鎶ヨ〃")
+@RestController
+@RequestMapping("/web/report")
+public class ReportController extends ApiController {
+
+    /** 鎶ヨ〃缁熻鏈嶅姟(services 妯″潡,web 绔洿鎺ュ鐢�) */
+    @Autowired
+    private ReportService reportService;
+
+    @LoginRequired
+    @ApiOperation("姒傝缁熻:鎬绘敞鍐岀敤鎴�/浠婃棩鏂板/鑷杞�/鐢靛姩杞︽暟閲�")
+    @GetMapping("/overview")
+    public ApiResponse<OverviewStatVO> overview() {
+        checkManager();
+        return ApiResponse.success(reportService.overview());
+    }
+
+    @LoginRequired
+    @ApiOperation("鏀跺叆杞﹀瀷鍒嗘瀽:鎸夎溅鍨嬪瀷鍙锋眹鎬绘敹鍏�,鏀寔杩�7/15/30澶╁強鑷畾涔夋椂娈�")
+    @PostMapping("/bikeIncome")
+    public ApiResponse<List<BikeIncomeStatVO>> bikeIncome(@RequestBody BikeIncomeQueryDTO query) {
+        checkManager();
+        return ApiResponse.success(reportService.bikeIncome(query));
+    }
+
+    @LoginRequired
+    @ApiOperation("鏀跺叆缁熻:鎸夋棩鏀跺叆(鏌辩姸鍥�)+绱鏀跺叆+鐜瘮/鍚屾瘮,鏀寔杩�7/15/30澶╁強鑷畾涔夋椂娈�")
+    @PostMapping("/incomeStat")
+    public ApiResponse<IncomeStatVO> incomeStat(@RequestBody BikeIncomeQueryDTO query) {
+        checkManager();
+        return ApiResponse.success(reportService.incomeStat(query));
+    }
+
+    @LoginRequired
+    @ApiOperation("杩愯惀涓績鏁版嵁:浠婃棩璁㈠崟/杩涜涓�/浠婃棩濂楅鏀跺叆/浠婃棩鎬绘敹鍏� + 鐧诲綍浜�/鏃ユ湡/鏄熸湡")
+    @GetMapping("/operationCenter")
+    public ApiResponse<OperationCenterVO> operationCenter() {
+        // 鐧诲綍浜哄鍚嶅彇缁戝畾鐨勭郴缁熺鐞嗗憳鐪熷疄濮撳悕(涓� ManagerApi 鍚屽彛寰�,闈炵粦瀹氱鐞嗗憳鎶涙潈闄愬紓甯�)
+        SystemUser sysuser = checkManager();
+        OperationCenterVO vo = reportService.operationCenter();
+        vo.setRealName(sysuser == null ? null : sysuser.getRealname());
+        return ApiResponse.success(vo);
+    }
+
+    @LoginRequired
+    @ApiOperation("杩愯惀涓績璁㈠崟鏌ヨ:鎸夎鍗曠被鍨�(楠戣璁板綍绫诲瀷)/鎵嬫満鍙�/鐘舵�佸垎椤垫煡璇㈡娂閲戣鍗�")
+    @PostMapping("/operationOrderPage")
+    public ApiResponse<PageData<OperationOrderVO>> operationOrderPage(
+            @RequestBody PageWrap<OperationOrderQueryDTO> pageWrap) {
+        checkManager();
+        return ApiResponse.success(reportService.operationOrderPage(pageWrap));
+    }
+
+    @LoginRequired
+    @ApiOperation("璁㈠崟楠戣璁板綍+杞ㄨ抗:鎸夎鍗曟煡璇㈠叾涓嬪叏閮ㄩ獞琛岃褰曞強杞ㄨ抗鐐�(鑷杞︽棤杞ㄨ抗)")
+    @GetMapping("/orderRides")
+    public ApiResponse<OrderRidesDetailVO> orderRides(@RequestParam("orderId") String orderId) {
+        checkManager();
+        return ApiResponse.success(reportService.orderRidesDetail(orderId));
+    }
+
+    /**
+     * 鏍¢獙褰撳墠鐧诲綍浼氬憳宸茬粦瀹氱郴缁熺鐞嗗憳;杩斿洖璇ョ鐞嗗憳(渚涜皟鐢ㄦ柟鍙栧鍚嶇瓑)銆�
+     * <p>鎶ヨ〃涓鸿繍钀ユ暟鎹�,浠呯粦瀹氬悗鍙扮鐞嗗憳鐨勪細鍛樺彲璁块棶(瀵归綈 {@link ManagerApi} 鐨� sysuser 鏍¢獙)銆�
+     *
+     * @return 褰撳墠浼氬憳缁戝畾鐨勭郴缁熺鐞嗗憳;鐞嗚涓婂凡淇濊瘉闈炵┖(涓虹┖宸插湪鏂规硶鍐呮姏 NOT_ALLOWED)
+     */
+    private SystemUser checkManager() {
+        UserResponse user = getUserResponse();
+        SystemUser sysuser = user == null ? null : user.getSysuser();
+        if (sysuser == null) {
+            throw new BusinessException(ResponseStatus.NOT_ALLOWED);
+        }
+        return sysuser;
+    }
+}
diff --git a/server/web/src/main/java/com/doumee/jtt808/web/service/Jtt808Service.java b/server/web/src/main/java/com/doumee/jtt808/web/service/Jtt808Service.java
index f4643c6..d361e1b 100644
--- a/server/web/src/main/java/com/doumee/jtt808/web/service/Jtt808Service.java
+++ b/server/web/src/main/java/com/doumee/jtt808/web/service/Jtt808Service.java
@@ -11,6 +11,8 @@
 import com.doumee.core.constants.ResponseStatus;
 import com.doumee.core.dingding.DingDingNotice;
 import com.doumee.core.exception.BusinessException;
+import com.doumee.core.track.RideActiveCache;
+import com.doumee.core.track.RideActiveInfo;
 import com.doumee.core.utils.DateUtil;
 import com.doumee.core.utils.PositionUtil;
 import com.doumee.core.utils.StringTools;
@@ -30,6 +32,7 @@
 import com.doumee.dao.business.web.response.UserResponse;
 import com.doumee.jtt808.web.endpoint.MessageManager;
 import com.doumee.service.business.GoodsorderService;
+import com.doumee.service.business.MemberRidesTrackService;
 import com.doumee.service.business.PricingRuleService;
 import com.github.yulichang.wrapper.MPJLambdaWrapper;
 import lombok.extern.slf4j.Slf4j;
@@ -100,6 +103,12 @@
 
     @Autowired
     PricingRuleService pricingRuleService;
+    /** 鐢佃溅娲昏穬璁㈠崟缂撳瓨:寮�閿佸啓 / 杩樿溅鍒� / 浣嶇疆涓婃姤璇�,鍏嶉珮棰戞煡 member_rides */
+    @Autowired
+    private RideActiveCache rideActiveCache;
+    /** 楠戣杞ㄨ抗钀藉簱(浣嶇疆涓婃姤鍛戒腑娲昏穬璁㈠崟鏃跺啓) */
+    @Autowired
+    private MemberRidesTrackService memberRidesTrackService;
     public  APIResult<T0201_0500>  bikeControl(@RequestBody T8500 request) {
         Mono<APIResult<T0201_0500>>  result = messageManager.requestR(request, T0201_0500.class);
         APIResult<T0201_0500> data = result.block();
@@ -177,6 +186,12 @@
                     rides.setDuration( rideTime > freeRentTime  ? rideTime : 0 );
                     rides.setEditDate(rides.getBackDate());
                     memberRidesJoinMapper.updateById(rides);//鏇存柊楠戣鐘舵�佷负宸茶繕杞�
+                    // 鐢佃溅杩樿溅鈫掑凡杩樿溅:鍒犻櫎娲昏穬璁㈠崟缂撳瓨,鍚庣画涓婃姤涓嶅啀鍐欒璁㈠崟杞ㄨ抗
+                    try {
+                        rideActiveCache.remove(rides.getBikeCode());
+                    } catch (Exception e) {
+                        log.warn("鍒犻櫎娲昏穬璁㈠崟缂撳瓨澶辫触 bikeCode={}", rides.getBikeCode(), e);
+                    }
                 }
             }
         }
@@ -385,6 +400,22 @@
                     .set( Bikes::getHeartDate,date)
                     .eq(Bikes::getId,bikes.getId()));
 
+            // 鐢佃溅浣嶇疆涓婃姤:鏌ユ椿璺冭鍗曠紦瀛�,鍛戒腑鍒欏啓涓�鏉¤建杩�(鍧愭爣鏈夋晥鎵嶅啓);鏁翠綋瀹归敊涓嶉樆鏂綅缃洿鏂颁富娴佺▼
+            if(bike.getLatitude() != null && bike.getLongitude() != null){
+                try {
+                    RideActiveInfo active = rideActiveCache.get(bikes.getCode());
+                    if(active != null){
+                        // 鍛戒腑娲昏穬璁㈠崟:钀藉簱杞ㄨ抗鐐�(缁忕含搴︿负杞崲鍚庣殑楂樺痉 GCJ02,reportTime 涓鸿澶囦笂鎶ユ椂闂�)
+                        memberRidesTrackService.record(bikes.getId(), bikes.getCode(),
+                                active.getRidesId(), active.getOrderId(),
+                                bike.getLongitude(), bike.getLatitude(),
+                                DateUtil.getDateFromLocalDateTime(m.getDeviceTime()));
+                    }
+                } catch (Exception e) {
+                    log.warn("杞ㄨ抗鍐欏叆澶辫触 bikeCode={}", bikes.getCode(), e);
+                }
+            }
+
             if(bikes.getVoltage().compareTo(lowVoltage)>=Constants.ZERO
                     && bike.getVoltage().compareTo(lowVoltage)<Constants.ZERO){
                 //鍙戦�侀拤閽夐�氱煡
@@ -477,6 +508,13 @@
             memberRides.setStatus(Constants.MEMBER_RIDES_STATUS.RIDES_RUNNING.getKey());
             memberRides.setCloseStatus(Constants.ZERO);
             memberRidesJoinMapper.insert(memberRides);
+            // 鐢佃溅寮�閿佹垚鍔熲啋楠戣涓�:鍐欏叆銆岃溅杈嗏啋娲昏穬璁㈠崟銆嶇紦瀛�,渚涘悗缁綅缃笂鎶� O(1) 鍙栬鍗曞啓杞ㄨ抗
+            // 瀹归敊:缂撳瓨鍐欏叆澶辫触涓嶅緱闃绘柇寮�閿佷富娴佺▼
+            try {
+                rideActiveCache.set(memberRides.getBikeCode(), memberRides.getId(), memberRides.getOrdreId());
+            } catch (Exception e) {
+                log.warn("鍐欏叆娲昏穬璁㈠崟缂撳瓨澶辫触 bikeCode={}", memberRides.getBikeCode(), e);
+            }
             BeanUtils.copyProperties(memberRides, memberRidesDetailResponse);
             return memberRidesDetailResponse;
         }catch (BusinessException biz){
@@ -526,6 +564,12 @@
         memberRides.setEditDate(memberRides.getBackDate());
         memberRides.setStatus(Constants.MEMBER_RIDES_STATUS.RIDES_RUNNING.getKey());
         memberRidesJoinMapper.updateById(memberRides);
+        // 涓村仠鎭㈠楠戣:鍒锋柊缂撳瓨(涓村仠鏈熼棿鏈垹,姝ゅ閲嶇疆 TTL 闃蹭复鐣岃繃鏈�)
+        try {
+            rideActiveCache.set(memberRides.getBikeCode(), memberRides.getId(), memberRides.getOrdreId());
+        } catch (Exception e) {
+            log.warn("鍒锋柊娲昏穬璁㈠崟缂撳瓨澶辫触 bikeCode={}", memberRides.getBikeCode(), e);
+        }
     }
 
 
@@ -626,6 +670,12 @@
                         rides.setDuration( rideTime > freeRentTime  ? rideTime : 0 );
                         rides.setEditDate(rides.getBackDate());
                         memberRidesJoinMapper.updateById(rides);//鏇存柊楠戣鐘舵�佷负宸茶繕杞�
+                        // 鐢佃溅杩樿溅鈫掑凡杩樿溅(涓村仠瓒呮椂鑷姩杩樿溅):鍒犻櫎娲昏穬璁㈠崟缂撳瓨
+                        try {
+                            rideActiveCache.remove(rides.getBikeCode());
+                        } catch (Exception e) {
+                            log.warn("鍒犻櫎娲昏穬璁㈠崟缂撳瓨澶辫触 bikeCode={}", rides.getBikeCode(), e);
+                        }
                     }
                 }
             }
diff --git a/server/web/src/main/resources/application.yml b/server/web/src/main/resources/application.yml
index b83b429..a483097 100644
--- a/server/web/src/main/resources/application.yml
+++ b/server/web/src/main/resources/application.yml
@@ -9,7 +9,7 @@
 #  application:
 #    name: parkbike
   profiles:
-    active: pro
+    active: dev
   # JSON杩斿洖閰嶇疆
   jackson:
     # 榛樿鏃跺尯
diff --git "a/server/\346\216\245\345\217\243\345\217\230\346\233\264\350\257\264\346\230\216.txt" "b/server/\346\216\245\345\217\243\345\217\230\346\233\264\350\257\264\346\230\216.txt"
new file mode 100644
index 0000000..958d020
--- /dev/null
+++ "b/server/\346\216\245\345\217\243\345\217\230\346\233\264\350\257\264\346\230\216.txt"
@@ -0,0 +1,69 @@
+============================================================
+鎺ュ彛鍙樻洿璇存槑(鐩稿褰撳墠宸叉彁浜や唬鐮�)
+鏃ユ湡: 2026-06-26   鐗堟湰: v3.0.1   浣滆��: rk
+渚濇嵁: git 宸ヤ綔鍖虹浉瀵� master 宸叉彁浜や唬鐮佺殑瀹為檯宸紓
+============================================================
+
+
+銆愮鐞嗙 platform銆�(Shiro 閴存潈)
+------------------------------------------------------------
+鏂板鎺ュ彛:
+涓�銆佹姈闊虫牳閿�閰嶇疆(鍔犲湪鏃㈡湁 SystemDictDataController,/system/dictData)
+  1. POST /system/dictData/getDouyinConfigDTO
+     鏌ヨ鎶栭煶鏍搁攢閰嶇疆(鍥涢」瀛楀吀鍊�)銆傛棤鍏ュ弬銆�
+  2. POST /system/dictData/updateDouyinAppConfigDTO
+     鏇存柊鎶栭煶搴旂敤閰嶇疆(client_key/client_secret/account_id)銆�
+     鍏ュ弬(body):clientKey / clientSecret / accountId
+  3. POST /system/dictData/updateDouyinPoiIdDTO
+     鏇存柊鏍搁攢闂ㄥ簵ID(鍗曢棬搴�)銆�
+     鍏ュ弬(query):poiId
+
+浜屻�佹姈闊冲埜鏍搁攢(鏂版枃浠� DouyinVerifyController,/business/douyinVerify)
+  4. POST /business/douyinVerify/page        鏍搁攢璁板綍鍒嗛〉(瀵瑰)
+  5. POST /business/douyinVerify/poiList     鏌ヨ鎶栭煶鍟嗘埛涓嬮棬搴桰D鍒楄〃(鏃犲叆鍙�,杩斿洖 List<String>)
+  6. POST /business/douyinVerify/cancel      鎾ら攢鏍搁攢(绠$悊绔�,涓嶅彈1灏忔椂闄愬埗)
+
+涓夈�佹姈闊冲晢鍝�(鏂版枃浠� DouyinProductController,/business/douyinProduct)
+  7. POST /business/douyinProduct/sync       浠庢姈闊冲悓姝ュ晢鍝�(鍏ㄩ噺)
+  8. POST /business/douyinProduct/page       鍒嗛〉鏌ヨ
+  9. GET  /business/douyinProduct/{id}       鏍规嵁ID鏌ヨ(鍚玈KU)
+  10. POST /business/douyinProduct/bindDiscount  缁戝畾/瑙g粦鏈湴濂楅
+
+
+============================================================
+
+銆愭帴鍙g web銆�(JWT 閴存潈)
+------------------------------------------------------------
+鏂板鎺ュ彛:
+涓�銆佹姈闊�(鏂版枃浠� DouyinApi,/web/douyin)
+  1. GET  /web/douyin/testQuery     鑱旇皟娴嬭瘯:浠庢姈闊冲叏閲忓悓姝ュ晢鍝佸叆搴�
+  2. GET  /web/douyin/testPoiList   鑱旇皟娴嬭瘯:鏌ヨ鎶栭煶鍟嗘埛涓嬮棬搴桰D鍒楄〃(杩斿洖 List<String>)
+  3. POST /web/douyin/scanVerify    鎵爜涓�姝ユ牳閿�(楠屽埜鍑嗗+鏍搁攢鍚堝苟)銆侤LoginRequired + @PreventRepeat
+     鍏ュ弬(body):qrContent(鎵爜鍐呭)/ code(鍒哥爜鏄庢枃)浜岄�変竴 + poiId(闂ㄥ簵ID,閫夊~)
+  4. POST /web/douyin/page          鏍搁攢璁板綍鍒嗛〉銆侤LoginRequired
+  5. GET  /web/douyin/{id}          鏍搁攢璁板綍璇︽儏銆侤LoginRequired
+
+浜屻�佹暟鎹姤琛�(鏂版枃浠� ReportController,/web/report,浠庣鐞嗙杩佸洖)
+     閴存潈:JWT + 绠$悊鍛樻牎楠�(闇�鐧诲綍涓� user.sysuser 闈炵┖,鍚﹀垯 NOT_ALLOWED)
+  6. GET  /web/report/overview             姒傝缁熻
+  7. POST /web/report/bikeIncome           鏀跺叆杞﹀瀷鍒嗘瀽
+  8. POST /web/report/incomeStat           鏀跺叆缁熻
+  9. GET  /web/report/operationCenter      杩愯惀涓績鏁版嵁
+  10. POST /web/report/operationOrderPage  杩愯惀涓績璁㈠崟鏌ヨ(鍒嗛〉)
+  11. GET /web/report/orderRides           璁㈠崟楠戣璁板綍+杞ㄨ抗
+
+
+鍙樺寲鎺ュ彛:
+1. GET /web/home/home   杩斿洖鍊兼柊澧炲瓧娈�
+   HomeResponse 鏂板:douyinExchangeTips(鎶栭煶鍒稿厬鎹㈣鏄�,String)
+   鍏ュ弬鏃犲彉鍖�,浠呰繑鍥炰綋澧炲姞涓�涓瓧娈�(鍊兼潵鑷瓧鍏� DOUYIN_EXCHANGE_TIPS)銆�
+
+
+============================================================
+璇存槑:
+- 涓婅堪娓呭崟鍩轰簬 git 宸ヤ綔鍖哄疄闄呭樊寮�(DouyinApi / ReportController /
+  DouyinVerifyController / DouyinProductController 鍧囦负鏂板鏂囦欢,
+  鍦ㄥ凡鎻愪氦浠g爜涓笉瀛樺湪;SystemDictDataController銆丠omeResponse 涓烘棦鏈夋枃浠惰淇敼)銆�
+- 鐩稿叧鏁版嵁搴撳彉鏇磋 db/changeSql.sql(鎶栭煶鍟嗗搧/鏍搁攢璁板綍/鎿嶄綔鏃ュ織/杞ㄨ抗琛� +
+  DOUYIN_CONFIG / DOUYIN_EXCHANGE_TIPS 瀛楀吀椤�)銆�
+============================================================

--
Gitblit v1.9.3