Compare commits

...

577 Commits
main ... pre

Author SHA1 Message Date
李子铭 ed44b84d31 Merge branch 'dev' into pre 2025-09-11 10:16:50 +08:00
李子铭 0a9205ddee order_notify 2025-09-11 10:16:39 +08:00
李子铭 3dd5f2efa5 Merge branch 'dev' into pre 2025-09-11 10:10:42 +08:00
李子铭 f285a948d6 order_notify_2025_09_11 2025-09-11 10:10:22 +08:00
李子铭 f20310775d Merge branch 'dev' into pre 2025-09-11 09:22:25 +08:00
ziming 75de76d1c2 多笔立减 2025-08-27 16:01:06 +08:00
ziming 84a9a09d10 多笔立减 2025-08-25 16:57:15 +08:00
ziming 685067b25d 多笔立减 2025-08-25 15:32:16 +08:00
ziming 4ed2f9b1bb 多笔立减 2025-08-22 10:03:51 +08:00
ziming 470c256c2d 多笔立减 2025-08-21 14:50:37 +08:00
ziming 7066636c2f 多笔立减 2025-08-21 10:14:43 +08:00
ziming 7c530de03e 多笔立减 2025-08-21 10:06:17 +08:00
ziming 4ca489334a 多笔立减 2025-08-21 10:05:34 +08:00
ziming e15b9ef6c3 多笔立减 2025-08-20 10:54:04 +08:00
ziming 99085ec821 多笔立减 2025-08-20 10:23:56 +08:00
ziming b421a943a7 多笔立减 2025-08-20 09:21:36 +08:00
ziming 1c52f0adc6 多笔立减 2025-08-12 15:58:45 +08:00
ziming 4b17fbd9bf 多笔立减 2025-08-12 14:59:57 +08:00
ziming 085d3ddb73 多笔立减 2025-08-12 11:39:29 +08:00
ziming 97a1747424 多笔立减 2025-08-12 11:24:01 +08:00
ziming 0a94a58afa 多笔立减 2025-08-11 17:42:30 +08:00
ziming ca021f71a9 多笔立减 2025-08-11 17:30:30 +08:00
ziming 21163dc79a 多笔立减 2025-08-11 17:06:08 +08:00
ziming 9e39917583 多笔立减 2025-08-11 16:59:35 +08:00
ziming 2cef642b4d 多笔立减 2025-08-11 16:53:05 +08:00
ziming 355340e1c5 多笔立减 2025-08-11 11:37:43 +08:00
ziming 4a65fa66c0 多笔立减 2025-08-11 11:27:28 +08:00
ziming c873859c1a 多笔立减 2025-08-11 10:24:38 +08:00
ziming 90150e36e0 多笔立减 2025-08-11 10:15:39 +08:00
ziming 5be1f63f04 多笔立减 2025-08-11 10:04:46 +08:00
ziming e149fd412c 脚本 2025-08-04 18:16:50 +08:00
ziming 34a947d5a0 脚本 2025-08-01 13:49:39 +08:00
ziming 6bfd3fe7fd 脚本 2025-08-01 11:53:55 +08:00
ziming 0fe1508d9e 脚本 2025-08-01 11:50:34 +08:00
ziming 40b5563852 脚本 2025-08-01 11:43:37 +08:00
ziming ef67a3c666 脚本 2025-08-01 11:38:26 +08:00
ziming 3c5de0b144 脚本 2025-08-01 11:37:42 +08:00
ziming 913de4afe0 脚本 2025-08-01 11:13:18 +08:00
ziming ad380958a0 脚本 2025-08-01 10:49:29 +08:00
ziming 9ff1a88233 脚本 2025-07-04 11:13:44 +08:00
ziming d5278f4594 预警47 2025-07-03 14:14:59 +08:00
ziming b4e56b8303 预警46 2025-07-03 13:51:52 +08:00
ziming e7a362519b 预警45 2025-07-03 13:37:54 +08:00
ziming 018247a7d8 预警44 2025-07-03 08:51:59 +08:00
ziming 2f6fff3644 预警43 2025-07-02 15:56:38 +08:00
ziming b710bd8793 预警42 2025-07-02 15:54:58 +08:00
ziming 2eefff46e2 预警41 2025-07-02 15:54:19 +08:00
ziming a51f5d7834 预警40 2025-07-02 15:51:54 +08:00
ziming f7247315cf 预警39 2025-07-02 15:12:56 +08:00
ziming 0a79405533 预警38 2025-07-02 15:10:36 +08:00
ziming 9959fb0977 预警37 2025-07-02 15:05:49 +08:00
ziming 7b8ae707bf 预警36 2025-07-02 15:02:16 +08:00
ziming a0728d9d23 预警35 2025-07-02 15:00:17 +08:00
ziming 6fac29bc9b 预警34 2025-07-02 14:47:57 +08:00
ziming 7cfaee0413 预警33 2025-07-02 14:43:56 +08:00
ziming 2067410617 预警32 2025-07-02 14:15:10 +08:00
ziming 6c85629a37 预警31 2025-07-02 11:48:39 +08:00
ziming db551ab887 预警30 2025-07-02 11:46:04 +08:00
ziming 388ff8bac4 预警29 2025-07-02 11:32:28 +08:00
ziming 4f625560e3 预警28 2025-07-02 11:30:15 +08:00
ziming b33db300be 预警27 2025-07-02 11:04:26 +08:00
ziming 7e383f6e51 预警26 2025-07-02 11:00:03 +08:00
ziming cb6a810977 预警25 2025-07-02 10:58:07 +08:00
ziming f18d07b003 预警24 2025-07-02 10:57:35 +08:00
ziming 39a6d78f2b 预警23 2025-07-02 10:43:51 +08:00
ziming 890f723716 预警22 2025-07-02 10:36:39 +08:00
ziming f7cb6049df 预警21 2025-07-02 10:29:20 +08:00
ziming 2c4adc9da3 预警20 2025-07-02 10:09:04 +08:00
ziming 72a6da5ce4 预警19 2025-07-02 10:08:01 +08:00
ziming 1b960df589 预警18 2025-07-02 09:57:33 +08:00
ziming 4107f7f1ba 预警17 2025-07-02 09:46:00 +08:00
ziming 9cab8c7aa4 预警15 2025-07-02 09:34:07 +08:00
ziming 6f96e22f5f 预警14 2025-07-02 09:20:04 +08:00
ziming 760549de2a 预警13 2025-07-01 18:25:35 +08:00
ziming 144341501d 预警12 2025-07-01 18:21:36 +08:00
ziming e9c1c63a69 预警11 2025-07-01 18:17:50 +08:00
ziming cb61887221 预警10 2025-07-01 18:15:56 +08:00
ziming d57225b9a9 预警9 2025-07-01 18:12:03 +08:00
ziming ee77d285fa 预警8 2025-07-01 18:08:33 +08:00
ziming f9d5511a91 预警7 2025-07-01 18:04:05 +08:00
ziming 819f92484d 预警6 2025-07-01 17:53:15 +08:00
ziming 2b02cf7bf6 预警5 2025-07-01 17:43:01 +08:00
ziming 8c5396729e 预警4 2025-07-01 17:24:16 +08:00
ziming b7c52ce699 预警3 2025-07-01 16:59:03 +08:00
ziming 31fb6e77da 预警2 2025-07-01 16:45:11 +08:00
ziming d091564610 预警 2025-07-01 11:52:32 +08:00
ziming 3e9bb2b762 券状态查询 2025-06-30 18:31:56 +08:00
ziming e627fdd6a3 券状态查询 2025-06-30 18:30:34 +08:00
ziming 4ca7aec637 Warnf 2025-06-20 10:02:49 +08:00
ziming 5e5c13d9dc Warnf 2025-06-20 08:50:49 +08:00
ziming 854e5a9e7e register tag 2025-06-19 10:30:34 +08:00
ziming adbdc0e4bd register tag 2025-06-19 10:21:35 +08:00
ziming 62cd85166d log 2025-06-18 14:07:56 +08:00
ziming e1fc391984 del cache code 2025-06-18 13:48:03 +08:00
ziming 8f3ca4b199 脚本增加批次号查询 2025-06-18 10:47:33 +08:00
ziming 01b599cc0c log 增加记录当前获取调用栈信息 2025-06-18 10:39:30 +08:00
ziming 3a32575bab log 增加记录当前调用者的调用信息 2025-06-18 10:36:13 +08:00
ziming 5c2253cf92 log 2025-06-18 10:12:36 +08:00
ziming abc720f88b log 2025-06-18 09:38:33 +08:00
ziming b743aca6fa log 2025-06-18 09:09:06 +08:00
ziming 748ca3052b log 2025-06-17 10:37:34 +08:00
ziming 09a8ef9e39 req 副本 2025-06-17 09:29:59 +08:00
ziming 17f3a0216f req 副本 2025-06-17 09:27:38 +08:00
ziming e5e73e70b5 req 副本 2025-06-17 09:23:51 +08:00
ziming 1dc83a8c23 req 副本 2025-06-17 09:21:59 +08:00
ziming 89e0d8d598 timeSliceQuery 8 2025-06-16 10:43:03 +08:00
ziming be358c9984 timeSliceQuery 8 2025-06-16 10:05:23 +08:00
ziming 881600758d timeSliceQuery 8 2025-06-16 10:01:37 +08:00
ziming 87820f6836 timeSliceQuery 8 2025-06-16 09:41:28 +08:00
ziming 792ce23435 timeSliceQuery 8 2025-06-16 08:55:31 +08:00
ziming cb1135e9c8 timeSliceQuery 2025-06-13 18:12:42 +08:00
ziming ba8d349273 timeSliceQuery 2025-06-13 18:11:40 +08:00
ziming cfac0a3be9 timeSliceQuery 2025-06-13 18:10:36 +08:00
ziming 387703f761 timeSliceQuery 2025-06-13 17:50:29 +08:00
ziming 8b85ab5295 timeSliceQuery 2025-06-13 17:36:39 +08:00
ziming 2fc97caa7c timeSliceQuery 2025-06-13 17:32:15 +08:00
ziming 41e0411566 timeSliceQuery 2025-06-13 10:43:55 +08:00
ziming 4099f7eca0 timeSliceQuery 2025-06-13 10:32:55 +08:00
ziming 5e43a7d6d2 timeSliceQuery 2025-06-13 09:46:39 +08:00
ziming ca8f9955cb timeSliceQuery 2025-06-13 09:36:28 +08:00
ziming 6329e6b37f timeSliceQuery 2025-06-13 09:26:04 +08:00
ziming dc152617f3 timeSliceQuery 2025-06-13 09:21:20 +08:00
ziming 8adc2b176b timeSliceQuery 2025-06-13 09:17:44 +08:00
ziming d72757e738 timeSliceQuery 2025-06-12 17:45:09 +08:00
ziming c68c996892 timeSliceQuery 2025-06-12 17:33:07 +08:00
ziming 6b2622b1a3 timeSliceQueryPush 2025-06-12 15:08:08 +08:00
ziming f965bf8b7f timeSliceQueryPush 2025-06-12 14:46:11 +08:00
ziming af64c0eee7 timeSliceQueryPush 2025-06-12 14:41:29 +08:00
ziming 015c62ed60 timeSliceQueryPush 2025-06-12 14:30:58 +08:00
ziming 21bb6f53eb timeSliceQueryPush 2025-06-12 14:30:49 +08:00
ziming 32c3b6c7a8 timeSliceQueryPush 2025-06-12 14:21:55 +08:00
ziming 4e479ffaa8 timeSliceQueryPush 2025-06-12 13:43:00 +08:00
ziming bd18369dca timeSliceQueryPush 2025-06-12 11:36:09 +08:00
ziming 92b319f6c3 timeSliceQueryPush 2025-06-12 11:35:09 +08:00
ziming 1d8862ab7b timeSliceQueryPush 2025-06-12 11:04:07 +08:00
ziming 8996df0279 timeSliceQueryPush 2025-06-12 11:02:34 +08:00
ziming 1b9a4fdd80 timeSliceQueryPush 2025-06-12 10:58:52 +08:00
ziming 48d03c16ce timeSliceQueryPush 2025-06-12 10:57:20 +08:00
ziming 540d873eac timeSliceQueryPush 2025-06-12 10:31:57 +08:00
ziming 4847e4eced timeSliceQueryPush 2025-06-12 10:23:58 +08:00
ziming b366cadb9d timeSliceQueryPush 2025-06-12 10:17:55 +08:00
ziming d348f247fe timeSliceQueryPush 2025-06-12 09:37:25 +08:00
ziming 343df76e71 timeSliceQueryPush 2025-06-12 09:24:21 +08:00
ziming d3be7d733b timeSliceQueryPush 2025-06-11 18:11:45 +08:00
ziming 644075a199 timeSliceQueryPush 2025-06-11 17:45:21 +08:00
ziming d7a1f7ce86 timeSliceQueryPush 2025-06-11 17:31:18 +08:00
ziming 3aa1ad9520 timeSliceQueryPush 2025-06-11 17:03:41 +08:00
ziming 153dd55b4d query 2025-06-11 14:30:00 +08:00
ziming 769af27bf9 query 2025-06-11 14:29:03 +08:00
ziming ac00969597 query 2025-06-11 14:27:10 +08:00
ziming 87222148ec query 2025-06-10 19:01:15 +08:00
ziming f418a277c3 query 2025-06-10 14:07:01 +08:00
ziming 68b95dc87c query 2025-06-10 10:39:17 +08:00
ziming d094dbd5ff query 2025-06-10 10:17:16 +08:00
ziming 4639e6a347 query 2025-06-10 10:04:13 +08:00
ziming 07b62a9725 query 2025-06-10 09:55:37 +08:00
ziming 3410510d81 query 2025-06-10 09:54:04 +08:00
ziming c18f25d55e query 2025-06-10 09:51:55 +08:00
ziming 5ff8c8a0e6 query 2025-06-10 09:50:20 +08:00
ziming 5ce7dfbb88 query 2025-06-10 09:39:05 +08:00
ziming 7ee72814bd query 2025-06-10 09:37:55 +08:00
ziming 8dc9431614 pro order use notify 2025-06-09 17:45:34 +08:00
ziming a1271d1fc8 pro order use notify 2025-06-09 15:01:52 +08:00
ziming 0bd9444026 pro order use notify 2025-06-09 15:00:25 +08:00
ziming 6feb2d479b pro order use notify 2025-06-09 14:56:09 +08:00
ziming 585fe2fb97 pro order use notify 2025-06-09 14:36:52 +08:00
ziming 16fe9ff561 pro order use notify 2025-06-09 14:31:11 +08:00
ziming bd72f853a5 Merge branch 'pro' into pre 2025-06-09 09:30:02 +08:00
ziming 03828585b1 WechatQuery 2025-06-06 17:43:45 +08:00
ziming 7cb8ee4202 WechatQuery 2025-06-06 15:56:27 +08:00
ziming 9325bde2a3 WechatQuery 2025-06-06 11:39:26 +08:00
ziming 11ff1c8540 WechatQuery 2025-06-06 09:31:12 +08:00
ziming c222b06fab Merge branch 'pro' into pre
# Conflicts:
#	internal/service/cmb.go
#	internal/service/mock.go
2025-06-06 09:18:18 +08:00
ziming b70eda503b WechatQuery 2025-06-06 09:16:50 +08:00
ziming 994e47b4d0 WechatQuery 2025-06-06 09:13:42 +08:00
ziming 3362eec301 WechatQuery 2025-06-06 09:07:27 +08:00
ziming 15de8ab277 FinByStockIdInBatches 2025-06-06 09:04:10 +08:00
ziming 7a02c6047c wechat retry 2025-06-05 18:28:14 +08:00
ziming 69c4171a20 wechat retry 2025-06-05 18:27:22 +08:00
ziming 0df46d71ba 微信券查询处理耗时 2025-06-05 15:31:08 +08:00
ziming e440b477f4 微信券查询处理耗时 2025-06-05 15:14:23 +08:00
ziming 5eebfb7b91 领取成功查询核销状态,过期不做通知 2025-06-05 15:13:38 +08:00
ziming 70c2f65dbf 领取成功查询核销状态,过期不做通知 2025-06-05 14:48:23 +08:00
ziming d11ff47f0c 过期不做通知 2025-06-05 13:46:55 +08:00
ziming c556912b1b 增加任务2 2025-06-05 10:30:25 +08:00
ziming c89fb9f27a 增加任务 2025-06-04 11:54:52 +08:00
ziming 607fe3a146 增加任务 2025-06-04 11:51:39 +08:00
ziming 41df5bff23 微信错误处理 2025-05-29 17:39:42 +08:00
ziming 451169b22d 微信错误处理 2025-05-29 17:37:14 +08:00
ziming 73a0321d99 微信回调消费接收消息成功 2025-05-29 17:05:12 +08:00
ziming 216500e4d1 微信回调消费接收消息成功 2025-05-28 22:12:30 +08:00
ziming f4cecc138b 微信回调消费接收消息成功 2025-05-28 22:09:30 +08:00
ziming 5924ee0c11 微信回调消费接收消息成功 2025-05-28 21:29:57 +08:00
ziming e3085019f4 微信回调消费接收消息成功 2025-05-28 21:29:22 +08:00
ziming 28968842ed 代码调整 2025-05-28 21:14:36 +08:00
ziming 84451f8aaa 代码调整 2025-05-28 20:40:53 +08:00
ziming a7e9fc653b 代码调整 2025-05-28 20:35:43 +08:00
ziming 2d34354ef2 代码调整 2025-05-28 20:02:37 +08:00
ziming 08a35b4070 代码调整 2025-05-28 19:51:11 +08:00
ziming 2e7f368e06 代码调整 2025-05-28 19:41:54 +08:00
ziming a8621cf93d 代码调整 2025-05-28 19:39:24 +08:00
ziming 832827bee2 代码调整 2025-05-28 19:38:51 +08:00
ziming 91f7db4caa 重试脚本 2025-05-28 19:28:32 +08:00
ziming b523b035ad 重试脚本 2025-05-28 19:22:17 +08:00
ziming 3b6c9923af 册通知标签 stock.MchId 批次创建商户, stock.BatchNo 商品批次号 2025-05-28 19:14:46 +08:00
ziming b0a1fdaef7 RegisterTag 2025-05-28 19:09:10 +08:00
ziming 3120c34de5 order retry 2025-05-28 18:03:36 +08:00
ziming 9c62904de5 过滤调该类型错误通知 2025-05-16 18:19:45 +08:00
ziming 1a70fa9375 过滤调该类型错误通知 2025-05-16 18:11:52 +08:00
ziming 0add233ebe 过滤调该类型错误通知 2025-05-16 18:10:59 +08:00
ziming fb4b094aed 风险判定 2025-05-16 17:53:40 +08:00
ziming 6b9e51ffed 风险判定 2025-05-16 16:57:43 +08:00
ziming a4e4dbda83 Merge branch 'dev' into pre 2025-05-16 13:41:33 +08:00
ziming 8dfe852318 减少通知 2025-05-16 13:41:05 +08:00
ziming 729a23e410 Merge branch 'pro' into pre 2025-05-12 09:52:37 +08:00
ziming 0c01053615 减少一次消费条数 2025-05-12 09:50:31 +08:00
ziming ae34f4153a Merge branch 'pro' into pre 2025-05-12 09:41:16 +08:00
ziming 83295e7443 request.WithTimeout log 2025-05-12 09:40:44 +08:00
ziming 77e2b82aa9 Merge branch 'pro' into pre 2025-05-12 09:19:21 +08:00
ziming 664dfa5159 request.WithTimeout log 2025-05-12 09:18:35 +08:00
ziming 9cfd31b1e7 Merge branch 'pro' into pre 2025-05-06 14:57:44 +08:00
ziming 2fa0d7f62e request.WithTimeout 2025-05-06 14:14:16 +08:00
ziming f85cf646f9 Merge branch 'dev' into pre 2025-05-06 11:15:40 +08:00
ziming da92e535bb request.WithTimeout 2025-05-06 11:14:06 +08:00
ziming bcd58ef49d request.WithTimeout 2025-05-06 10:06:09 +08:00
ziming fb8330888b request.WithTimeout 2025-05-06 09:44:50 +08:00
ziming c209c645f7 Merge branch 'pro' into pre
# Conflicts:
#	internal/server/http.go
#	internal/service/cmb.go
2025-05-06 09:42:12 +08:00
ziming fb81a548a5 request.WithTimeout 2025-05-06 09:40:15 +08:00
ziming 517559eed1 del code 2025-04-30 10:44:53 +08:00
ziming 21743caae9 order query 2025-04-29 15:54:50 +08:00
ziming b20fa8ad35 order query 2025-04-29 15:54:29 +08:00
ziming a5e48469f1 order query 2025-04-29 13:42:27 +08:00
ziming 465192e6fd order query 2025-04-29 11:31:18 +08:00
ziming 295923cccc order query 2025-04-29 11:21:39 +08:00
ziming bc4c6451e5 order query 2025-04-29 11:15:38 +08:00
ziming ffc56c74de syn notice 2025-04-28 17:45:01 +08:00
ziming 5bc5bf77dd syn notice 2025-04-28 17:43:48 +08:00
ziming 19a3ecc0df test 2025-04-22 17:52:30 +08:00
ziming be5fcdf414 clear log 2025-04-16 14:27:40 +08:00
ziming 23b8bda2a0 test data 2025-04-16 08:57:13 +08:00
ziming a6123f5b1c 订单定时通知,执行失败 2025-04-15 18:34:01 +08:00
ziming 96c60af6a3 "2006-01-02 15:04:05.000" 批次查询接口时间调整 2025-04-14 15:18:58 +08:00
ziming 86ae352bcc "2006-01-02 15:04:05.000" 批次查询接口时间调整 2025-04-14 14:16:10 +08:00
ziming 46e6e8f450 TransDate 核销时间/当前时间 2025-04-09 14:03:39 +08:00
ziming ff2d245133 TransDate 格式yyyy-mm-dd hh:mm:ss.sss 2025-04-09 11:58:11 +08:00
ziming bbf0c0f9e7 TransDate 格式yyyy-mm-dd hh:mm:ss.ss 2025-04-09 11:55:09 +08:00
ziming 48b5d897d6 加锁调整 2025-03-31 11:36:12 +08:00
李子铭 ba25cc46ac mq test 2025-03-27 20:43:20 +08:00
ziming 4cac90f2da 删除,mock数据 2025-03-27 17:50:42 +08:00
ziming 0bea08e91e 删除,mock数据 2025-03-27 17:50:07 +08:00
ziming 3e9376ec56 mock 满足招行第一次失败,二次成功 2025-03-27 17:42:08 +08:00
ziming bb4b857d9c mock 满足招行第一次失败,二次成功 2025-03-27 17:35:52 +08:00
ziming 647fb437f2 timeout 30s 2025-03-26 16:49:21 +08:00
ziming 22626fcc05 timeout 30s 2025-03-26 16:40:43 +08:00
ziming fa4449deb0 ping 2025-03-26 16:39:04 +08:00
ziming 10852fac3b ping 2025-03-26 16:30:27 +08:00
ziming 6dcb6f1b38 test 2025-03-26 16:24:49 +08:00
ziming ba06b05e8f ctx 2025-03-26 15:45:52 +08:00
ziming 51717f223c ctx 2025-03-26 15:12:38 +08:00
ziming c5c045a874 ctx 2025-03-26 15:06:38 +08:00
ziming edee30f156 ctx 2025-03-26 15:06:19 +08:00
ziming 3ccef3d345 ctx 2025-03-26 14:53:14 +08:00
ziming 05d9d6ff6b ctx 2025-03-26 14:47:49 +08:00
ziming 87c22cad47 ctx 2025-03-26 14:29:23 +08:00
ziming 5243d6b063 禁用事务 2025-03-26 14:18:26 +08:00
ziming c6e72948e0 禁用事务 2025-03-26 14:17:15 +08:00
ziming 9bacaf120e 禁用事务 2025-03-26 14:15:26 +08:00
ziming 258344421d 禁用事务 2025-03-26 14:12:59 +08:00
ziming 0d4a5e3528 禁用事物 2025-03-26 13:50:06 +08:00
ziming b2f6021d3d 连接数 2025-03-26 12:03:12 +08:00
ziming 50bb64d6e6 300 2025-03-26 10:43:31 +08:00
ziming 1db4523885 300 2025-03-26 10:37:41 +08:00
ziming 29078f7003 3500 2025-03-26 10:17:41 +08:00
ziming 8c9cd3b345 ctx 2025-03-26 09:48:15 +08:00
ziming e9b5ea3e7d ctx 2025-03-26 09:43:41 +08:00
ziming f278be1cb7 ctx 2025-03-26 09:35:29 +08:00
李子铭 a498b53ce4 休眠400ms 2025-03-25 19:54:20 +08:00
ziming 06c43184e1 mysql 2025-03-25 18:05:10 +08:00
ziming 6113645400 mysql 2025-03-25 17:34:22 +08:00
ziming 5096983c56 register_tag_lock 2025-03-25 17:27:39 +08:00
ziming d90f22a424 脚本调整 2025-03-25 16:54:25 +08:00
ziming 5b85eb954b 增加日志 2025-03-25 16:11:43 +08:00
ziming 0ee4a5781b 增加日志 2025-03-25 16:00:24 +08:00
ziming 8f4d6f8035 增加日志 2025-03-25 15:40:19 +08:00
ziming eba81c7ee0 bizBytes 2025-03-25 15:37:54 +08:00
ziming 35eb9f28f8 bizBytes 2025-03-25 13:58:22 +08:00
ziming 35d3ef1501 bizBytes 2025-03-25 13:58:07 +08:00
ziming f7f4287034 解密处调整 2025-03-25 13:45:40 +08:00
ziming d119955f1d 调整 2025-03-25 10:24:23 +08:00
ziming dfc8341119 调整 2025-03-25 10:03:06 +08:00
李子铭 d9edfb0b71 调整 2025-03-25 09:07:37 +08:00
李子铭 7fb061c9a3 Merge branch 'dev-fb' into dev
# Conflicts:
#	internal/data/mixrepoimpl/cmb.go
2025-03-24 21:02:04 +08:00
李子铭 3f9f83dd94 调整 2025-03-24 20:39:43 +08:00
qiyunfanbo126.com 5b2b5d0ba0 修改加密 2025-03-24 20:25:53 +08:00
李子铭 2af26fb728 加密调整 2025-03-24 19:55:53 +08:00
李子铭 721808e543 加密调整 2025-03-24 18:24:02 +08:00
李子铭 3ac5a26048 发券,过期发券验证 2025-03-24 17:50:43 +08:00
李子铭 a17ead31f1 test 2025-03-24 16:34:41 +08:00
李子铭 33d456b5d3 ctx 2025-03-24 14:12:11 +08:00
李子铭 1a07ff016d 商品数据不存在 2025-03-24 10:54:46 +08:00
李子铭 6c735d38f2 压测 2025-03-24 10:42:29 +08:00
李子铭 1cddb6ae1e 压测 2025-03-24 10:37:19 +08:00
李子铭 6e368dcc15 压测 2025-03-24 10:21:22 +08:00
李子铭 3bec99c982 代码调整 2025-03-24 10:17:56 +08:00
李子铭 f662f3a480 代码调整 2025-03-24 09:20:13 +08:00
李子铭 879c7b09df 代码调整 2025-03-24 09:10:36 +08:00
李子铭 d47f785669 代码调整 2025-03-21 18:31:00 +08:00
李子铭 9d3cea8669 代码调整 2025-03-21 18:25:55 +08:00
李子铭 01320b9e04 代码调整 2025-03-21 18:18:36 +08:00
李子铭 a4390dbb8b 代码调整 2025-03-21 17:51:48 +08:00
李子铭 86b8fb30d8 代码调整 2025-03-21 17:50:37 +08:00
李子铭 41c1be4763 代码调整 2025-03-21 17:47:58 +08:00
李子铭 6183b7cf4d 代码调整 2025-03-21 17:44:50 +08:00
李子铭 7abaf972a4 代码调整 2025-03-21 17:41:36 +08:00
李子铭 4f71228ff1 满足压测处理 2025-03-21 16:19:44 +08:00
李子铭 2cdb04c522 满足压测处理 2025-03-21 15:50:06 +08:00
李子铭 b58a3f20a4 满足压测处理 2025-03-21 15:15:17 +08:00
李子铭 a91114d8ae 满足压测处理 2025-03-21 15:14:00 +08:00
李子铭 bab72020ad 满足压测处理 2025-03-21 15:08:09 +08:00
李子铭 45233650d7 满足压测处理 2025-03-21 15:04:25 +08:00
李子铭 6dd9cfbbee 满足压测处理 2025-03-21 14:44:24 +08:00
李子铭 1a8343cda8 还原代码 2025-03-21 14:35:59 +08:00
李子铭 9d0e5e6f76 还原代码 2025-03-21 13:57:42 +08:00
李子铭 4d07148789 去掉枷锁 2025-03-21 13:47:03 +08:00
李子铭 73a23aef4a 去掉枷锁 2025-03-21 13:38:51 +08:00
李子铭 ab031b6048 去掉枷锁 2025-03-21 10:13:18 +08:00
李子铭 526634aee3 订单 2025-03-21 09:17:16 +08:00
李子铭 90d03a07bc 订单 2025-03-21 09:13:29 +08:00
李子铭 609fe56f75 查询 2025-03-21 08:57:23 +08:00
李子铭 aa2d5a5069 查询 2025-03-20 19:50:41 +08:00
李子铭 a1750d6959 查询 2025-03-20 19:46:15 +08:00
李子铭 a8dae49f45 查询 2025-03-20 19:32:17 +08:00
李子铭 27aff98a72 查询 2025-03-20 19:25:33 +08:00
李子铭 856d0058cb 查询 2025-03-20 19:19:04 +08:00
李子铭 e0e9cc0102 查询 2025-03-20 19:12:56 +08:00
李子铭 07e48bb62b 查询 2025-03-20 19:06:52 +08:00
李子铭 31c2da9027 查询 2025-03-20 19:06:22 +08:00
李子铭 345f7c6da4 add cacke 2025-03-20 18:04:54 +08:00
李子铭 e059a5f64a add cacke 2025-03-20 17:45:12 +08:00
李子铭 413b816a10 add cacke 2025-03-20 17:44:14 +08:00
李子铭 71eb344f0c db fail 当前打开连接数 2025-03-20 17:24:36 +08:00
李子铭 37143f17fa db fail 当前打开连接数 2025-03-20 17:11:48 +08:00
李子铭 2c131e30c8 db fail 当前打开连接数 2025-03-20 17:10:21 +08:00
李子铭 70dbaf72c0 db fail 2025-03-20 16:53:23 +08:00
李子铭 240cd23fb6 db fail 2025-03-20 16:40:59 +08:00
李子铭 17e6902994 db fail 2025-03-20 16:37:40 +08:00
李子铭 e511aae2d4 db fail 2025-03-20 16:17:13 +08:00
李子铭 f469b36833 db fail 2025-03-20 16:06:13 +08:00
李子铭 3eb170f4bd db fail 2025-03-20 15:48:14 +08:00
李子铭 d1b0380c4d redis db 2025-03-20 15:44:36 +08:00
李子铭 bedabb1d17 redis db 2025-03-20 15:30:36 +08:00
李子铭 bbf8875e30 redis db 2025-03-20 15:28:26 +08:00
李子铭 5a438ce485 redis db 2025-03-20 14:56:09 +08:00
李子铭 7569500d48 redis db 2025-03-20 14:52:23 +08:00
李子铭 6bccfe3d82 redis db 2025-03-20 14:30:47 +08:00
李子铭 fa0bd7bc62 lock failed 2025-03-20 14:23:04 +08:00
李子铭 e92f4f4fff lock failed 2025-03-20 14:20:08 +08:00
李子铭 cff1310b2e db fail 2025-03-20 14:09:25 +08:00
李子铭 090663c725 db fail 2025-03-20 11:33:46 +08:00
李子铭 44ca97b7d0 redis lock 2025-03-20 11:27:35 +08:00
李子铭 ec15531eac 异常通知 2025-03-20 10:32:47 +08:00
李子铭 c00d947678 跳过压测的订单数据 2025-03-20 10:18:11 +08:00
李子铭 964f3cd5cf 跳过压测的订单数据 2025-03-20 09:39:22 +08:00
李子铭 508a838f2f 备注 2025-03-19 20:17:07 +08:00
李子铭 17afd86f85 备注 2025-03-19 19:40:06 +08:00
李子铭 1e45fdb222 iv长度 判定 2025-03-19 19:25:36 +08:00
李子铭 17bf37004f 订单生成调整 2025-03-19 18:17:53 +08:00
李子铭 8a389f04b0 订单生成调整 2025-03-19 16:42:07 +08:00
李子铭 fbe1d8712e 订单生成调整 2025-03-19 16:27:03 +08:00
李子铭 5494d7079e 增加错误日志记录报文 2025-03-19 16:22:45 +08:00
李子铭 78d3015aa4 增加错误日志记录报文 2025-03-19 16:14:34 +08:00
李子铭 472e684449 定时任务调整 2025-03-19 15:58:17 +08:00
李子铭 81cc61348a 4.0mq 2025-03-19 15:46:27 +08:00
李子铭 d856e002e3 order no 2025-03-19 15:36:12 +08:00
李子铭 15ca495271 order no 2025-03-19 14:28:25 +08:00
李子铭 0edea515c8 order no 2025-03-19 14:27:49 +08:00
李子铭 457381a85c orderNotice定时任务 2025-03-19 11:19:56 +08:00
李子铭 4f9caac1b0 orderNotice定时任务 2025-03-19 09:46:26 +08:00
李子铭 ccd3157649 orderNotice定时任务 2025-03-19 09:38:23 +08:00
李子铭 3eaffae096 orderNotice定时任务 2025-03-19 09:30:53 +08:00
李子铭 33a9f1516b orderNotice定时任务 2025-03-19 08:56:56 +08:00
李子铭 8a64a2b32e orderNotice定时任务 2025-03-18 20:24:07 +08:00
李子铭 0700044a0e 微信错误返回body解析报错 2025-03-18 19:36:03 +08:00
李子铭 40cbe4e8ba 微信错误返回body解析报错 2025-03-18 18:30:16 +08:00
李子铭 1bcd5c768d 微信错误返回body解析报错 2025-03-18 14:34:53 +08:00
李子铭 06f5dd2d5f 回调通知 2025-03-18 14:06:04 +08:00
李子铭 c2b2d52195 回调通知 2025-03-18 13:49:34 +08:00
李子铭 d21b849bba 回调通知 2025-03-18 13:47:28 +08:00
李子铭 bd83a242c0 回调通知 2025-03-18 11:37:36 +08:00
李子铭 194b508f55 活动批次查询确认 2025-03-18 11:21:35 +08:00
李子铭 1cb90ef6f3 活动批次查询确认 2025-03-18 10:50:15 +08:00
李子铭 580af7f577 活动批次查询确认 2025-03-18 09:37:05 +08:00
李子铭 8ec8f2a980 活动批次查询确认 2025-03-17 19:48:10 +08:00
李子铭 95dbc27dcc 活动批次查询确认 2025-03-17 17:49:24 +08:00
李子铭 572f8db83b 通知查询检索调整 2025-03-17 16:27:50 +08:00
李子铭 418ab72562 通知查询检索调整 2025-03-17 16:08:33 +08:00
李子铭 eb10f82c4c 通知查询检索调整 2025-03-17 15:20:18 +08:00
李子铭 87e3b7a4ca 增加领取成功时间 2025-03-17 15:03:20 +08:00
李子铭 fbd8d0c413 CmbBatchNoticeCacheKey 2025-03-17 14:35:59 +08:00
李子铭 1fecc06a17 mock 2025-03-17 14:30:37 +08:00
李子铭 17ba4bc361 wechatNotify 2025-03-17 14:13:24 +08:00
李子铭 323691d061 wechatNotify 2025-03-17 14:10:58 +08:00
李子铭 70b3b82dcf err 2025-03-17 13:55:28 +08:00
李子铭 0afa6e0c68 err 2025-03-17 13:50:24 +08:00
李子铭 084b6f5874 cmbProductQuery 2025-03-17 12:39:35 +08:00
李子铭 f0910f9ca8 cmbProductQuery 2025-03-17 11:25:54 +08:00
李子铭 77ed8f70cf 拓展参数字段 2025-03-17 11:11:57 +08:00
李子铭 d5ffbbd7d6 拓展参数字段 2025-03-17 10:11:24 +08:00
李子铭 4fadebfea0 拓展参数字段 2025-03-17 09:49:08 +08:00
李子铭 03216c1815 拓展参数字段 2025-03-17 09:14:59 +08:00
李子铭 3b672e7e05 异步通知 2025-03-15 14:26:17 +08:00
李子铭 091e0ef0ce 自定义错误 2025-03-15 13:54:12 +08:00
李子铭 82b3ae0731 自定义错误 2025-03-15 13:52:48 +08:00
李子铭 e5291d07f4 自定义错误 2025-03-14 21:44:41 +08:00
李子铭 564a5b6d82 自定义错误 2025-03-14 21:23:19 +08:00
李子铭 2e648c09c3 自定义错误 2025-03-14 21:11:00 +08:00
李子铭 b29c603199 自定义错误 2025-03-14 20:48:29 +08:00
李子铭 0ed61a0f2c notice 2025-03-14 20:18:10 +08:00
李子铭 bd377b5b81 notice 2025-03-14 16:04:37 +08:00
李子铭 107ed2cae0 notice 2025-03-14 14:36:27 +08:00
李子铭 7b927290bb notice 2025-03-14 13:59:51 +08:00
李子铭 e3f1959582 notice 2025-03-14 13:57:02 +08:00
李子铭 0d1870f5c8 notice 2025-03-14 11:51:49 +08:00
李子铭 a8ccd564c7 notice 2025-03-14 11:43:31 +08:00
李子铭 2ca8e988e7 notice 2025-03-14 11:39:09 +08:00
李子铭 6e2fcf5ca1 test 2025-03-13 20:51:22 +08:00
李子铭 16d16890ce test 2025-03-13 19:41:08 +08:00
李子铭 57ca0180cb test 2025-03-13 19:11:51 +08:00
李子铭 3c9f5c8792 test 2025-03-13 19:06:33 +08:00
李子铭 1588b9b7bd test 2025-03-13 19:04:01 +08:00
李子铭 901ae51019 test 2025-03-13 18:59:30 +08:00
李子铭 f88f4d84b9 test 2025-03-13 18:57:32 +08:00
李子铭 3ad7757cf2 add 查询回调地址接口,设置回调通知i接口 2025-03-13 17:54:04 +08:00
李子铭 c3c8389737 add 查询回调地址接口,设置回调通知i接口 2025-03-13 17:29:00 +08:00
李子铭 0597083258 add 查询回调地址接口,设置回调通知i接口 2025-03-13 16:42:20 +08:00
李子铭 8d294ef297 add 查询回调地址接口 2025-03-13 15:20:00 +08:00
李子铭 514e7bb724 接口调整 2025-03-13 14:33:14 +08:00
李子铭 3e8e4d111d 接口调整 2025-03-13 11:35:06 +08:00
李子铭 6624a5193f 接口调整 2025-03-13 11:31:28 +08:00
李子铭 0de4d12b85 接口调整 2025-03-13 11:21:47 +08:00
李子铭 e9c99d7542 接口调整 2025-03-13 11:14:44 +08:00
李子铭 866459211a 接口调整 2025-03-13 10:16:36 +08:00
李子铭 05b23b3cde 接口调整 2025-03-12 18:50:31 +08:00
李子铭 084635fa6a 接口调整 2025-03-12 18:25:24 +08:00
李子铭 072c8c9fee 接口调整 2025-03-12 18:22:47 +08:00
李子铭 98c3374bfb 接口调整 2025-03-12 17:54:02 +08:00
李子铭 9e101492b2 接口调整 2025-03-12 17:47:31 +08:00
李子铭 e8967efc9d 包装错误信息 2025-03-12 14:58:42 +08:00
李子铭 c9bd4612f2 取消必填多余字段 2025-03-12 14:01:51 +08:00
李子铭 c8a6b557a9 log 2025-03-12 11:19:51 +08:00
李子铭 51f79d2cd4 log 2025-03-12 10:26:30 +08:00
李子铭 77a9782136 log 2025-03-12 10:05:48 +08:00
李子铭 8e964dd8a8 log 2025-03-12 10:01:02 +08:00
李子铭 6a15c47799 log 2025-03-12 09:59:59 +08:00
李子铭 11f07197c0 log 2025-03-12 09:00:28 +08:00
李子铭 c668d614a3 log 2025-03-11 18:29:04 +08:00
李子铭 f6a8cc5cb7 cmb 2025-03-11 18:14:10 +08:00
李子铭 587c3fe2c9 cmb 2025-03-11 18:10:57 +08:00
李子铭 3925ba5bb0 增加预警 2025-03-11 18:06:10 +08:00
李子铭 dc0ab16dca 增加预警 2025-03-11 17:58:30 +08:00
李子铭 61df9c223c cmb 2025-03-11 16:59:40 +08:00
李子铭 d3587c2c72 cmb 2025-03-11 16:43:48 +08:00
李子铭 190d9fc065 cmb 2025-03-11 16:34:13 +08:00
李子铭 5928e407fe cmb 2025-03-11 16:18:37 +08:00
李子铭 c20daf71fa cmb 2025-03-11 14:19:01 +08:00
李子铭 9e895e7114 Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	internal/biz/notify_consume.go
#	internal/biz/notify_retry_consume.go
#	internal/service/consume.go
2025-03-11 14:04:23 +08:00
李子铭 4350b11ec6 cmb 2025-03-11 14:04:12 +08:00
李子铭 97fcb32db9 cmb 2025-03-11 13:57:23 +08:00
李子铭 982e1c3877 cmb 2025-03-10 18:01:01 +08:00
李子铭 4940e90a1e cmb 2025-03-10 16:14:25 +08:00
李子铭 332416bc8f cmb 2025-03-10 15:15:28 +08:00
李子铭 02aeb7db1e cmb 2025-03-10 15:06:44 +08:00
李子铭 e27872a414 cmb 2025-03-10 15:04:46 +08:00
李子铭 ff51cb7c8e cmb 2025-03-10 14:57:11 +08:00
李子铭 daabf7c9fe cmb 2025-03-10 14:52:13 +08:00
李子铭 a603f7d955 cmb 2025-03-10 14:52:01 +08:00
李子铭 3e876ed49a cmb 2025-03-10 13:43:11 +08:00
李子铭 f6e758777f cmb 2025-03-10 11:29:59 +08:00
李子铭 41aa1d379e cmb 2025-03-10 11:27:26 +08:00
李子铭 efd8dd03ec cmb 2025-03-10 11:25:52 +08:00
李子铭 288d5e7005 cmb 2025-03-10 11:11:47 +08:00
李子铭 14b2491d07 cmb 2025-03-10 11:07:55 +08:00
李子铭 6e71d8ae44 cmb 2025-03-10 10:29:56 +08:00
李子铭 6c1dde9834 cmb 2025-03-10 10:24:32 +08:00
李子铭 90a398800b cmb 2025-03-10 10:10:16 +08:00
李子铭 5dfd3c39c3 cmb 2025-03-10 09:55:30 +08:00
李子铭 8c2aff8d35 cmb 2025-03-08 17:18:03 +08:00
李子铭 a0f6e86754 cmb 2025-03-08 16:37:11 +08:00
李子铭 b228b83f5b cmb 2025-03-08 16:36:14 +08:00
李子铭 f5de5b58fd cmb 2025-03-08 16:32:59 +08:00
李子铭 1ae27c1c30 cmb 2025-03-08 16:29:27 +08:00
李子铭 53a323cae4 cmb 2025-03-08 16:23:25 +08:00
李子铭 5256a965d7 cmb 2025-03-08 16:15:00 +08:00
李子铭 22ef786d2e cmb 2025-03-08 16:04:30 +08:00
李子铭 a6bbb6819a cmb 2025-03-08 15:55:16 +08:00
李子铭 527544f989 cmb 2025-03-08 15:54:18 +08:00
李子铭 d8c8ec8f6e cmb 2025-03-08 15:17:07 +08:00
李子铭 874abf205f cmb 2025-03-08 15:01:40 +08:00
李子铭 fcea60ddee cmb 2025-03-08 14:37:04 +08:00
李子铭 9b607a61ed cmb 2025-03-08 14:20:19 +08:00
李子铭 5dbd4762b4 cmb 2025-03-08 14:07:50 +08:00
李子铭 d6a70119ce cmb 2025-03-08 14:04:35 +08:00
李子铭 d61cd68faa cmb 2025-03-08 13:59:25 +08:00
李子铭 8ebf0127de cmb 2025-03-08 13:55:30 +08:00
李子铭 d382acfd2d cmb 2025-03-08 13:39:00 +08:00
李子铭 549f24da52 cmb 2025-03-08 11:10:34 +08:00
李子铭 27743f103a cmb 2025-03-07 18:28:44 +08:00
李子铭 803a8706b1 cmb 2025-03-07 18:12:56 +08:00
李子铭 09bf8b90fc cmb 2025-03-07 17:57:03 +08:00
李子铭 e8e894b8ec cmb 2025-03-07 17:46:08 +08:00
李子铭 da2fe44408 cmb 2025-03-07 16:40:45 +08:00
李子铭 83a23f7431 cmb 2025-03-07 16:38:55 +08:00
李子铭 1ee1fd6e5b cmb 2025-03-07 16:28:59 +08:00
李子铭 9af1f23476 cmb 2025-03-07 14:40:25 +08:00
李子铭 f2ec06e062 cmb 2025-03-07 13:44:27 +08:00
李子铭 0c1243419e cmb 2025-03-07 11:29:26 +08:00
李子铭 50c6f1f995 cmb 2025-03-07 11:25:53 +08:00
李子铭 a3852ede6e cmb 2025-03-07 11:13:54 +08:00
李子铭 5e584c1353 cmb 2025-03-07 10:00:59 +08:00
李子铭 83f007b423 cmb 2025-03-06 18:28:34 +08:00
李子铭 df9d7d0b59 cmb 2025-03-06 18:20:18 +08:00
李子铭 8d421a8adc cmb 2025-03-06 18:09:31 +08:00
李子铭 73ce021b25 cmb 2025-03-06 18:00:56 +08:00
李子铭 159fece2dd cmb 2025-03-06 17:29:57 +08:00
李子铭 1800084933 cmb 2025-03-06 17:25:06 +08:00
李子铭 f04ad17edb cmb 2025-03-06 17:17:10 +08:00
李子铭 9d22d81cce cmb 2025-03-06 17:00:30 +08:00
李子铭 5abc4151ea cmb 2025-03-06 16:15:55 +08:00
李子铭 84927b7d30 cmb 2025-03-06 16:12:33 +08:00
李子铭 f51c470d66 cmb 2025-03-06 14:46:05 +08:00
李子铭 357bab2626 cmb 2025-03-06 11:07:19 +08:00
李子铭 87acc327b8 cmb 2025-03-06 10:42:47 +08:00
李子铭 5d639570a2 cmb 2025-03-06 10:33:29 +08:00
李子铭 9851c1f61b cmb 2025-03-06 09:52:25 +08:00
李子铭 9b8823cc29 cmb 2025-03-05 18:23:24 +08:00
李子铭 26f3d17ec6 cmb 2025-03-05 18:02:39 +08:00
李子铭 6fbd79f3a1 cmb 2025-03-05 17:57:58 +08:00
李子铭 e9e86d3fc5 cmb 2025-03-05 17:51:23 +08:00
李子铭 d551c83294 cmb 2025-03-05 17:29:27 +08:00
李子铭 1ee31eea6f cmb 2025-03-05 17:00:00 +08:00
李子铭 f362b6b573 cmb 2025-03-05 15:41:47 +08:00
李子铭 d5267dede1 cmb 2025-03-05 15:18:57 +08:00
李子铭 f7def09458 cmb 2025-03-05 15:09:23 +08:00
李子铭 6ac3b3a4cd cmb 2025-03-05 14:50:25 +08:00
李子铭 056004a0b2 cmb 2025-03-05 14:32:31 +08:00
李子铭 110fdd5221 cmb 2025-03-05 14:31:08 +08:00
李子铭 d34f77853f cmb 2025-03-05 14:30:53 +08:00
李子铭 eecda8daab cmb 2025-03-05 14:26:43 +08:00
李子铭 a6c258fd29 cmb 2025-03-05 09:44:59 +08:00
李子铭 6b65f24fa5 cmb 2025-03-05 09:41:07 +08:00
李子铭 b4f92d78e6 cmb 2025-03-04 18:33:04 +08:00
李子铭 e18a7ec246 cmb 2025-03-04 18:14:52 +08:00
李子铭 fcb3b028e0 cmb 2025-03-04 18:11:31 +08:00
李子铭 d6bfb1ff1d cmb 2025-03-04 18:09:46 +08:00
李子铭 2f8750b1b1 cmb 2025-03-04 18:09:40 +08:00
李子铭 f7136f5827 cmb 2025-03-04 18:01:09 +08:00
李子铭 0ce8934364 cmb 2025-03-04 17:46:42 +08:00
李子铭 36e6589c49 cmb 2025-03-04 17:00:50 +08:00
李子铭 7808865b32 cmb 2025-03-04 16:12:00 +08:00
李子铭 effe78a62a cmb 2025-03-04 16:08:07 +08:00
李子铭 e96e3dc777 cmb 2025-03-04 16:04:50 +08:00
李子铭 ce81f41693 cmb 2025-03-04 15:55:05 +08:00
李子铭 ec182fabad cmb 2025-03-04 15:42:06 +08:00
李子铭 dd27b4293c cmb 2025-03-04 14:58:13 +08:00
李子铭 f840cbf0ae cmb 2025-03-04 14:54:41 +08:00
李子铭 064d9b10b7 xx 2025-03-04 14:48:42 +08:00
李子铭 84f53cc4fc cmb 2025-03-04 14:19:55 +08:00
李子铭 7c764e53b7 cmb 2025-03-04 12:01:06 +08:00
李子铭 ad6bb8b928 cmb 2025-03-04 11:52:15 +08:00
李子铭 7b8c29f0f8 cmb 2025-03-04 11:41:30 +08:00
李子铭 0e55f5c468 cmb 2025-03-04 09:48:51 +08:00
李子铭 67215ca534 cmb 2025-03-03 18:30:05 +08:00
李子铭 1d61bd84f4 cmb 2025-03-03 18:28:19 +08:00
李子铭 5d875ea1b0 cmb 2025-03-03 18:23:11 +08:00
李子铭 822ab544fe cmb 2025-03-03 18:23:04 +08:00
李子铭 e6f3d59205 cmb 2025-03-03 18:04:09 +08:00
李子铭 b62bf1c793 cmb 2025-03-03 18:03:19 +08:00
233 changed files with 22867 additions and 621 deletions

7
.gitignore vendored
View File

@ -25,8 +25,11 @@ Thumbs.db
bin/
cert/
log
configs_prd
configs/config_pro.yaml
configs/config_pre.yaml
configs/config_pre2.yaml
configs/config_mock.yaml
api/**/*.go
/cmd/server/wire_gen.go
cmd/server/wire_gen.go
/internal/conf/*.go
/third_party/swagger_ui/openapi.yaml

View File

@ -1,20 +1,11 @@
FROM registry.cn-chengdu.aliyuncs.com/lansexiongdi/build:1.22.2 AS builder
ENV GOCACHE /root/.cache/go-build
FROM registry.cn-chengdu.aliyuncs.com/pkgtool/build:1.23.6 AS builder
COPY . /src
WORKDIR /src
RUN make build
RUN make build
FROM registry.cn-chengdu.aliyuncs.com/lansexiongdi/work:v1
RUN echo 'http://mirrors.ustc.edu.cn/alpine/v3.5/main' > /etc/apk/repositories \
&& echo 'http://mirrors.ustc.edu.cn/alpine/v3.5/community' >>/etc/apk/repositories \
&& apk update && apk add tzdata \
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone
FROM registry.cn-chengdu.aliyuncs.com/pkgtool/work:v0.0.1
COPY --from=builder /src/bin /app
#删除config文件线上是通过挂载进来的

View File

@ -27,7 +27,7 @@ init:
go install github.com/envoyproxy/protoc-gen-validate@v1.0.2
.PHONY: config
# generate internal proto
# generate cpn proto
config:
protoc --proto_path=./internal \
--proto_path=./third_party \
@ -76,6 +76,11 @@ all:
make generate;
make wire;
.PHONY: test
# test
test:
sh ./stress_test.sh $(t)
.DEFAULT_GOAL := help
# show help
help:

View File

@ -1,59 +1,5 @@
# <p align="center">营销系统后台API</p>
### 参与开发
[请参阅](https://tvd8jq9lqkp.feishu.cn/wiki/LNWVweZ64iY2UBkkTkZcezy0n5h?from=from_copylink)
# <p align="center">招行立减金券系统</p>
* * *
### 主要工作
+ 后台接口API
* * *
### 规则说明
+ 路由前缀都为 __/admin__ 开始,路由规则全小写+下划线,例如:/admin/v1/demo_1
* * *
### 构建部署
+ 采用多阶段构建,以获得最小体积的容器镜像
````bash
cd /项目根目录 && make deploy folder=./configs_dev marketing=marketing_backend container_name=marketing_backend http_port=8090
````
* * *
### docker环境下开发
+ 一、[下载Docker Desktop安装程序](https://www.docker.com/products/docker-desktop)
+ 二、在项目根目录下执行命令
```shell
docker build -f Dockerfile_win -t 镜像名称 .
docker run --privileged -itd --name 容器名称 --restart=always -v ./:/src 镜像名称
docker ps
docker exec -it 容器名称 sh
make init
make all
```
### windows非docker开发
1 安装插件(配置goproxy,GOPROXY=https://goproxy.cn,direct)
```shell
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install github.com/go-kratos/kratos/cmd/kratos/v2@latest
go install github.com/go-kratos/kratos/cmd/protoc-gen-go-http/v2@latest
go install github.com/google/gnostic/cmd/protoc-gen-openapi@latest
go install github.com/google/wire/cmd/wire@latest
go install github.com/go-kratos/kratos/cmd/protoc-gen-go-errors/v2@latest
go install gorm.io/gen/tools/gentool@latest
```
2生成相应rpo
命令kratos proto client api/helloworld/v1/demo.proto
位置api和internal下面的conf
3 wire生成依赖
cd cmd/server
wire
4 配置编译
![img_1.png](img.png)
5 生成service
kratos proto server api/helloworld/v1/demo.proto -t internal/service
+ 发券API
* * *

30
api/err/cmb.proto Normal file
View File

@ -0,0 +1,30 @@
syntax = "proto3";
package api.err;
import "errors/errors.proto";
option go_package = "voucher/api/err";
enum CmbErr{
option (errors.default_code) = 499;
// ,
CMB_UNKNOWN = 0;
// ,
CMB_PARAM_FAIL = 1 [(errors.code) = 400];
//
CMB_VERIFY_FAIL = 2 [(errors.code) = 401];
//
CMB_BIZ_CONTENT_DECRYPT_FAIL = 3 [(errors.code) = 401];
// ,
CMB_BIZ_CONTENT_CONVERT_FAIL = 4 [(errors.code) = 401];
//
CMB_BIZ_CONTENT_FAIL = 5 [(errors.code) = 401];
//
CMB_ORDER_NOT_EXIST = 6 [(errors.code) = 404];
//
CMB_PRODUCT_NOT_EXIST = 7 [(errors.code) = 404];
//
CMB_PRODUCT_NOT_AUTH = 8 [(errors.code) = 401];
//
CMB_PRODUCT_NOT_SUPPORTED = 9 [(errors.code) = 401];
}

View File

@ -11,15 +11,12 @@ enum Err {
// panic错误
SYSTEM_PANIC = 0 [(errors.code) = 599];
//
NOT_LOGIN = 1 [(errors.code) = 401];
// DB数据未找到
DB_NOT_FOUND = 2 [(errors.code) = 404];
DB_NOT_FOUND = 1 [(errors.code) = 404];
}
//
PARAM = 3 [(errors.code) = 400];
//
COMMON = 4 [(errors.code) = 555];
}
enum NotifyConsumeErr{
option (errors.default_code) = 1;
//
NeedRetryNotify = 0 [(errors.code) = 500];
}

41
api/err/wechat.proto Normal file
View File

@ -0,0 +1,41 @@
syntax = "proto3";
package api.err;
import "errors/errors.proto";
option go_package = "voucher/api/err";
enum WechatErr{
option (errors.default_code) = 1;
WechatFAIL = 0 [(errors.code) = 500];
WechatUserIllegal = 1 [(errors.code) = 500];
WechatAppIDMchIDMismatch = 2 [(errors.code) = 500];
WechatOpenIDAppIDMismatch = 3 [(errors.code) = 500];
WechatInvalidMerchantID = 4 [(errors.code) = 500];
WechatHighFrequency = 5 [(errors.code) = 500];
WechatActivityInactive = 6 [(errors.code) = 500];
WechatBatchInfoError = 7 [(errors.code) = 500];
WechatAppIDRequired = 8 [(errors.code) = 500];
WechatOpenIDRequired = 9 [(errors.code) = 500];
WechatBatchIDRequired = 10 [(errors.code) = 500];
WechatMerchantIDRequired = 11 [(errors.code) = 500];
WechatInvalidBatchStatus = 12 [(errors.code) = 500];
WechatMchNotExists = 13 [(errors.code) = 500];
WechatBatchBudgetInsufficient = 14 [(errors.code) = 500];
WechatDailyLimitExceeded = 15 [(errors.code) = 500];
WechatAccountBalanceInsufficient = 16 [(errors.code) = 500];
WechatBatchBudgetDepleted = 17 [(errors.code) = 500];
WechatMerchantNoPermission = 18 [(errors.code) = 500];
WechatCrossMerchantNotSupported = 19 [(errors.code) = 500];
WechatUserReceiveLimit = 20 [(errors.code) = 500];
WechatAPIChannelNotSupported = 21 [(errors.code) = 500];
WechatSpecifiedDenominationNotSupported = 22 [(errors.code) = 500];
WechatOnlyAdvertisingBatch = 23 [(errors.code) = 500];
WechatUserMaxCoupons = 24 [(errors.code) = 500];
WechatNaturalPersonRuleBlocked = 25 [(errors.code) = 500];
WechatResourceNotExists = 26 [(errors.code) = 500];
WechatFrequencyLimited = 27 [(errors.code) = 500];
WechatAccountFail = 28 [(errors.code) = 500];
//
WechatNeedNoticeFail = 29 [(errors.code) = 500];
}

226
api/v1/cmb_cpn.proto Normal file
View File

@ -0,0 +1,226 @@
syntax = "proto3";
package api.v1;
option go_package = "voucher/api/v1;v1";
import "validate/validate.proto";
import "google/api/annotations.proto";
import "v1/common.proto";
service Cmb {
rpc Order (CmbRequest) returns (CmbReply) {
option (google.api.http) = {
post: "/voucher/cmb/v1/order",
body: "*"
};
}
rpc Query (CmbRequest) returns (CmbReply) {
option (google.api.http) = {
post: "/voucher/cmb/v1/query",
body: "*"
};
}
rpc QueryProduct (CmbRequest) returns (CmbReply) {
option (google.api.http) = {
post: "/voucher/cmb/v1/product/query",
body: "*"
};
}
rpc OrderRetry (OrderRetryRequest) returns (Empty) {
option (google.api.http) = {
post: "/voucher/cmb/v1/orderRetry",
body: "*"
};
}
rpc OrderMock (CmbOrderRequest) returns (CmbRequest) {
option (google.api.http) = {
post: "/voucher/cmb/v1/orderMock",
body: "*"
};
}
rpc QueryMock (CmbQueryRequest) returns (CmbRequest) {
option (google.api.http) = {
post: "/voucher/cmb/v1/queryMock",
body: "*"
};
}
rpc QueryProductMock (CmbQueryProductRequest) returns (CmbRequest) {
option (google.api.http) = {
post: "/voucher/cmb/v1/product/queryMock",
body: "*"
};
}
rpc DecryptBody (EncryptBodyRequest) returns (DecryptBodyReply) {
option (google.api.http) = {
post: "/voucher/cmb/v1/decryptBody",
body: "*"
};
}
}
message OrderRetryRequest {
repeated string transactionIds = 1 [json_name = "transactionIds"];
}
message CmbRequest {
//
// ID32
string mid = 1 [json_name = "mid"];
// ID32
string aid = 2 [json_name = "aid"];
// yyyyMMddHHmmss
string date = 3 [json_name = "date"];
// 32
string random = 4 [json_name = "random"];
//
string keyAlias = 5 [json_name = "keyAlias"];
//
string cmbKeyAlias = 6 [json_name = "cmbKeyAlias"];
// API的说明文档
string encryptBody = 7 [json_name = "encryptBody"];
//
string sign = 8 [json_name = "sign"];
}
message CmbReply {
//
// 1000 1001
string respCode = 1 [json_name = "respCode"];
//
string respMsg = 2 [json_name = "respMsg"];
// yyyyMMddHHmmss
string date = 3 [json_name = "date"];
//
string keyAlias = 5 [json_name = "keyAlias"];
//
string cmbKeyAlias = 6 [json_name = "cmbKeyAlias"];
// API的说明文档
string encryptBody = 7 [json_name = "encryptBody"];
//
string sign = 8 [json_name = "sign"];
}
message CmbOrderRequest {
//
// 14
string transactionId = 9 [json_name = "transactionId", (validate.rules).string = {min_len: 1,max_len: 50}];
//
string activityId = 10 [json_name = "activityId", (validate.rules).string = {min_len: 1,max_len: 32}];
// openId
string cmbUid = 11 [json_name = "cmbUid", (validate.rules).string = {min_len: 1,max_len: 50}];
// id
string appId = 14 [json_name = "appId", (validate.rules).string = {min_len: 1,max_len: 20}];
// 0-1-openId
string cmbUidType = 12 [json_name = "cmbUidType", (validate.rules).string = {min_len: 1,max_len: 10}];
// 13
string timestamp = 13 [json_name = "timestamp", (validate.rules).string = {min_len: 1,max_len: 14}];
//
string attach = 15 [json_name = "attach"];
}
message CmbOrderReply {
//
// 1000 1001
string respCode = 1 [json_name = "respCode"];
//
string respMsg = 2 [json_name = "respMsg"];
// 50
string codeNo = 9 [json_name = "codeNo"];
//
string thirdErrCode = 3 [json_name = "thirdErrCode"];
}
message CmbQueryRequest {
//
//
string codeNo = 9 [json_name = "codeNo", (validate.rules).string = {min_len: 1,max_len: 32}];
}
message CmbQueryReply {
// codeNo
string ticket = 9 [json_name = "ticket"];
// 0使1使
string status = 10 [json_name = "status"];
// yyyy-mm-dd hh:mm:ss.sss
string transDate = 11 [json_name = "transDate"];
//
string orgNo = 12 [json_name = "orgNo"];
//
string ext = 13 [json_name = "ext"];
}
message CmbQueryProductRequest {
//
//
string activityId = 9 [json_name = "activityId", (validate.rules).string = {min_len: 1,max_len: 32}];
}
message CmbQueryProductReply {
// 1000 1001
string respCode = 1 [json_name = "respCode"];
//
string respMsg = 2 [json_name = "respMsg"];
//
//
string activityName = 9 [json_name = "activityName"];
//
string activityId = 10 [json_name = "activityId"];
//
string amount = 11 [json_name = "amount"];
//
string minAmount = 12 [json_name = "minAmount"];
// 01
string availableType = 13 [json_name = "availableType"];
// -
string availableDays = 14 [json_name = "availableDays"];
// -
string startTime = 15 [json_name = "startTime"];
// -
string endTime = 16 [json_name = "endTime"];
//
string availableStock = 17 [json_name = "availableStock"];
//
string detail = 18 [json_name = "detail"];
// yyyy-mm-dd hh:mm:ss.sss
string saleStartTime = 19 [json_name = "saleStartTime"];
// yyyy-mm-dd hh:mm:ss.sss
string saleEndTime = 20 [json_name = "saleEndTime"];
//
string thirdErrCode = 21 [json_name = "thirdErrCode"];
}
message CmbNotifyRequest {
// codeNo
string ticket = 9 [json_name = "ticket"];
// 0使1使
string status = 10 [json_name = "status"];
// yyyy-mm-dd hh:mm:ss.sss
string transDate = 11 [json_name = "transDate"];
//
string orgNo = 12 [json_name = "orgNo"];
//
string ext = 13 [json_name = "ext"];
string attach = 14 [json_name = "attach"];
}
message CmbNotifyReply {
// 1000 1001
string respCode = 1 [json_name = "respCode"];
//
string respMsg = 2 [json_name = "respMsg"];
}
message EncryptBodyRequest {
string encryptBody = 1 [json_name = "encryptBody"];
}
message DecryptBodyReply {
string decryptBody = 1 [json_name = "decryptBody"];
}

View File

@ -4,44 +4,4 @@ package api.v1;
option go_package = "voucher/api/v1;v1";
//
message Empty {}
//
message ReqEmpty {}
//
message RespEmpty {}
//
message RespOption {
//
string label = 1;
//
string value = 2;
//
bool select = 3;
//
bool disable = 4;
//
repeated RespOption children = 5;
}
//
message RespOptions {
//
repeated RespOption list = 1;
}
// ReqPage
message RespPage {
//
int32 page = 1;
//
int32 limit = 2;
//
int32 total = 3;
}
message Empty {}

View File

@ -1,40 +0,0 @@
syntax = "proto3";
package api.v1;
option go_package = "voucher/api/v1;v1";
import "google/api/annotations.proto";
import "validate/validate.proto";
import "v1/common.proto";
service Order {
rpc Order (OrderRequest) returns (OrderReply) {
option (google.api.http) = {
post: "/openapi/v1/voucher/order",
body:"*"
};
}
rpc Query (QueryRequest) returns (QueryReply) {
option (google.api.http) = {
post: "/openapi/v1/voucher/query",
body:"*"
};
}
}
message OrderRequest {
}
message OrderReply {
}
message QueryRequest {
}
message QueryReply {
}

View File

@ -12,6 +12,7 @@ import (
"github.com/nacos-group/nacos-sdk-go/common/constant"
_ "go.uber.org/automaxprocs"
"gopkg.in/yaml.v2"
"html/template"
"os"
"voucher/internal/conf"
log2 "voucher/internal/pkg/log"
@ -46,7 +47,7 @@ const (
func init() {
flag.StringVar(&nacosIp, "nacosIp", "120.55.12.245", "nacos ip address")
flag.IntVar(&nacosPort, "nacosPort", 8848, "nacos port")
flag.StringVar(&nacosSpace, "nacosSpace", "voucher", "nacos space")
flag.StringVar(&nacosSpace, "nacosSpace", "voucher-test", "nacos space")
flag.StringVar(&nacosUsername, "nacosUsername", "nacos", "nacos passowrd")
flag.StringVar(&nacosPassword, "nacosPassword", "LsxdNacos@2025", "nacos passowrd")
flag.Parse()
@ -57,6 +58,9 @@ func newApp(
logger log.Logger,
httpServer *http.Server,
consumerServer *server.Consumer,
cronServer *server.CronServer,
wechatNotifyConsumer *server.WechatNotifyConsumer,
rdbConsumer *server.RdbConsumer,
) *kratos.App {
return kratos.New(
kratos.ID(id),
@ -66,6 +70,9 @@ func newApp(
kratos.Server(
httpServer,
consumerServer,
cronServer,
wechatNotifyConsumer,
rdbConsumer,
),
)
}
@ -114,10 +121,14 @@ func loadConfig() *conf.Bootstrap {
func main() {
bc := loadConfig()
businessLogger := log2.NewBusinessLogger(bc.Logs.Business, Name, Name, Version)
accessLogger := log2.NewAccessLogger(bc.Logs.Business, id, Name, Version)
if bc == nil {
panic("配置文件加载失败")
}
app, cleanup, err := wireApp(bc, businessLogger, accessLogger)
businessLogger := log2.NewBusinessLogger(bc.Logs.Business, Name, Name, Version)
accessLogger := log2.NewAccessLogger(bc.Logs.Access, id, Name, Version)
app, cleanup, err := wireApp(bc, temp(), businessLogger, accessLogger)
if err != nil {
panic(err)
}
@ -128,3 +139,17 @@ func main() {
panic(err)
}
}
func temp() *template.Template {
wd, err := os.Getwd()
if err != nil {
panic(fmt.Sprintf("获取当前工作目录失败:", err))
}
templatePath := fmt.Sprintf("%s/templates/index.tmpl", wd)
tmpl, err := template.ParseFiles(templatePath)
if err != nil {
panic(err)
}
return tmpl
}

View File

@ -9,26 +9,35 @@ import (
"github.com/go-kratos/kratos/v2"
"github.com/go-kratos/kratos/v2/log"
"github.com/google/wire"
"github.com/robfig/cron"
"html/template"
"voucher/internal/biz"
"voucher/internal/biz/cmb"
"voucher/internal/biz/timeslicequery"
"voucher/internal/conf"
"voucher/internal/data"
"voucher/internal/data/repositoryimpl"
"voucher/internal/data/thirdrepositoryimpl"
"voucher/internal/data/mixrepoimpl"
"voucher/internal/data/repoimpl"
"voucher/internal/data/wechatrepoimpl"
log2 "voucher/internal/pkg/log"
"voucher/internal/server"
"voucher/internal/service"
)
// wireApp init kratos application.
func wireApp(*conf.Bootstrap, log.Logger, *log2.AccessLogger) (*kratos.App, func(), error) {
func wireApp(*conf.Bootstrap, *template.Template, log.Logger, *log2.AccessLogger) (*kratos.App, func(), error) {
panic(wire.Build(
server.ProviderSetServer,
service.ProviderSetService,
cmb.ProviderSetCmb,
biz.ProviderSetBiz,
repositoryimpl.ProviderRepoImplSet,
thirdrepositoryimpl.ProviderThirdRepositoryImplSet,
data.ProviderDataSet,
repoimpl.ProviderRepoImplSet,
wechatrepoimpl.ProviderWechatReposImplSet,
mixrepoimpl.ProviderMixRepoImplSet,
timeslicequery.ProviderSetTimeSliceQuery,
log2.NewLogHelper,
cron.New,
newApp,
))
}

View File

@ -1,49 +0,0 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package main
import (
"github.com/go-kratos/kratos/v2"
"github.com/go-kratos/kratos/v2/log"
"voucher/internal/biz"
"voucher/internal/conf"
"voucher/internal/data"
"voucher/internal/data/repositoryimpl"
"voucher/internal/data/thirdrepositoryimpl"
log2 "voucher/internal/pkg/log"
"voucher/internal/server"
"voucher/internal/service"
)
import (
_ "go.uber.org/automaxprocs"
)
// Injectors from wire.go:
// wireApp init kratos application.
func wireApp(bootstrap *conf.Bootstrap, logger log.Logger, accessLogger *log2.AccessLogger) (*kratos.App, func(), error) {
helper := log2.NewLogHelper(logger)
gormDb, cleanup := data.NewGormDb(bootstrap)
db := data.NewDb(gormDb)
orderRepo := repositoryimpl.NewOrderRepoImpl(db)
rocketMQ, cleanup2, err := data.NewRocketMQ(bootstrap)
if err != nil {
cleanup()
return nil, nil, err
}
thirdMQSend := thirdrepositoryimpl.NewMQSendImpl(rocketMQ)
voucherBiz := biz.NewVoucherBiz(orderRepo, thirdMQSend)
voucherService := service.NewVoucherService(voucherBiz)
httpServer := server.NewHTTPServer(bootstrap, helper, accessLogger, voucherService)
consumer := server.NewConsumer(helper, bootstrap, voucherService)
app := newApp(logger, httpServer, consumer)
return app, func() {
cleanup2()
cleanup()
}, nil
}

View File

@ -8,20 +8,20 @@ server:
data:
db:
driver: mysql
source: root:lansexiongdi6,@tcp(47.97.27.195:3306)/merketing?parseTime=True&loc=Local
maxIdle: 5 #最大的空闲连接数
maxOpen: 100 #最大连接数0表示不受限制
maxLifetime: 5s #连接复用的最大生命周期
isDebug: true
source: root:lansexiongdi6,@tcp(47.108.53.72:3306)/voucher?parseTime=True&loc=Local
maxIdle: 200 #最大的空闲连接数
maxOpen: 1000 #最大连接数0表示不受限制
maxLifetime: 300s #连接复用的最大生命周期
isDebug: false
redis: #没有则注释此属性
addr: 47.97.27.195:6379
addr: 47.108.53.72:6379
password: lansexiongdi@666
readTimeout: 3s
writeTimeout: 3s
poolSize: 5 #连接池大小不配置或配置为0表示不启用连接池
minIdleConns: 2 #最小空闲连接数
connMaxIdleTime: 30s #每个连接最大空闲时间,如果超过了这个时间会被关闭
db: 1
readTimeout: 5s
writeTimeout: 5s
poolSize: 200 #连接池大小不配置或配置为0表示不启用连接池
minIdleConns: 50 #最小空闲连接数
connMaxIdleTime: 60s #每个连接最大空闲时间,如果超过了这个时间会被关闭
db: 3
rocketMQ:
addr: "http://rmq-cn-nwy3fn4ex09.cn-chengdu.rmq.aliyuncs.com:8080"
@ -29,25 +29,99 @@ rocketMQ:
secretKey: "Z3596KCFA9RAUR6k"
secretToken: ""
eventMap:
order:
topic: voucher_order_create
group: voucher_order_create_group
isOpenConsumer: false #是否启动消费 true/false
PerCoroutineCnt: 5 #协程数量不配置默认为20
RetryCnt: 3 #重试次数,不配置默认38
query:
topic: voucher_order_query
group: voucher_order_query_group
isOpenConsumer: false #是否启动消费 true/false
PerCoroutineCnt: 5 #协程数量不配置默认为20
RetryCnt: 3 #重试次数,不配置默认38
notify:
topic: voucher_order_notify
group: voucher_order_notify_group
isOpenConsumer: false #是否启动消费 true/false
PerCoroutineCnt: 5 #协程数量不配置默认为20
notifyRetry: # 重试延迟队列
topic: voucher_order_notifyRetry
group: voucher_order_notifyRetry_group
isOpenConsumer: true #是否启动消费 true/false
PerCoroutineCnt: 2 #协程数量不配置默认为20
RetryCnt: 3 #重试次数,不配置默认38
wechatNotifyMQ:
accessKeyId: "LTAI5tPyV7FynQNTfEvbEBuX"
accessKeySecret: "tZmTh8cV98xAQgtlRU0soWcb6Tpd4T"
endPoint: "http://1389288909295870.mqrest.cn-hangzhou.aliyuncs.com"
regionId: "hangzhou"
instanceId: "MQ_INST_1389288909295870_BYSoMttI"
topic: "notify"
groupId: "GID_voucher"
tag: "voucher_notify_dev"
isOpenConsumer: true #是否启动消费 true/false
registerTagUrl: "https://wpcallbacks.api.1688sup.com/wechatPay/register_tag"
wechat:
mchID: "1710953361" # 证书所属商户 蓝色兄弟服务商立减金配置
mchCertificateSerialNumber: "6006B8208815DB5EAC5BF2E783CB9D34082C3772" #商户证书序列号
wechatPayPublicKeyID: "PUB_KEY_ID_0117109533612025031800326400002563" #微信支付公钥ID
cmb:
sm2Prk: "8d39ff3d2559258c163f4510f082727f51531e1953ab203d5ab1ea4a6d94fd73"
sm2Puk: "04d827a7dbaaa358ce45b8c7794a7f54819f5c175005a702370e47f135ef6f5f9732758b1474f218419fe9e87f90c28c3b05f08254c651db27df35fae67b77b2e4" # 公钥,给到招行密钥
# cmbSm2Pik: "" # 招行私钥mock使用生产不会有该值
# cmbSm2Puk: "0416445bc16cbf42e47002ad9fe7c7af67d902b48be1eb69b98f6a006b0918630e1127f5f2fff83b2ecb30fc7fd72c34c33f37c7c355dffde3589f66800f0036ca" # 招行公钥
cmbSm2Pik: "f6a8d2f412e289686aba6a0f33cad1a64367d0ba012046ee0fbbefd3ffd675bd" # 招行私钥mock使用生产不会有该值
cmbSm2Puk: "043b2fade30067b6bd8e61b42771b1e953116fc5a0f9ed6939fceb9254b8d7d6989902c913642c3c68c42a2b56364512675ea0b517dd4469e73b73c888a2f4e8e3" # 招行公钥-mock使用
mid: "d6fdd78b6fd13a808818286b9cad9687"
aid: "5efaa21263b94f669a1c90ed0279df20"
keyAlias: "CO_PUB_KEY_SM2"
cmbKeyAlias: "SM2_CMBLIFE"
orgNo: "LANSEXIONGDI" # 发码机构号,固定值,掌上生活优惠券系统提供
notifyUrl: "https://sandbox.cdcc.cmbchina.com/AccessGateway/transIn/updateCodeStatus.json" # 招行测试回调地址
noticeStartDays: 7
noticeEndDays: 1
#告警配置
alarm:
webhookURL: "https://oapi.dingtalk.com/robot/send?access_token=5f10c2213cbf8168985cb2d061ebb1a5f70bd1dd47ec7cef58fa6fe545d52588"
secret: "SEC77b63d70a9e22317144e712b4538ce1e0013db885c65f7f9bae283e8958b39eb"
isAll: false
atMobiles:
- "18666173766"
warningMobiles:
- "13474987525"
cron:
isOpen: true #是否启动,控制全局
commandMap:
orderNotice:
isOpen: true #是否启动 true/false
command: "0 0 1 * * ?" # 每天凌晨1点执行一次
warningBudget:
isOpen: true #是否启动 true/false
command: "0 */5 * * * ?" #cron表达式,每5分钟执行一次
rdsMQ:
wechatQuery:
name: "wechatQuery"
retryNum: 1 #重试次数
numWorkers: 2 #协程数量不配置默认为10
waitTime: 1s #处理完成后等待时间
isOpen: false #是否启动消费 true/false
wechatTimeSliceQuery:
name: "wechatTimeSliceQuery"
retryNum: 1 #重试次数
numWorkers: 3 #协程数量不配置默认为10
waitTime: 1s #处理完成后等待时间
isOpen: false #是否启动消费 true/false
orderRetry:
name: "orderRetry"
retryNum: 1 #重试次数
numWorkers: 2 #协程数量不配置默认为10
waitTime: 1s #处理完成后等待时间
isOpen: false #是否启动消费 true/false
retryNotify:
name: "retryNotify"
retryNum: 1 #重试次数
numWorkers: 1 #协程数量不配置默认为10
waitTime: 1s #处理完成后等待时间
isOpen: false #是否启动消费 true/false
aliYunSms:
accessKeyId:
accessKeySecret:
endpoint: dysmsapi.aliyuncs.com
signName: 蓝色兄弟
templateWarning: "SMS_489660721"
#配置日志
logs:
business: business.log #业务日志路径:如果不写日志,则不配置或配置为空

117
configs/config_test.yaml Normal file
View File

@ -0,0 +1,117 @@
server:
http:
addr: 0.0.0.0:13000
timeout: 30s
isOpenSwagger: true #是否开启api文档仅用于开发、测试线上禁止启用
isResponseReqHeaders: true #是否开启响应请求头,仅用于开发调试,线上禁止启用
data:
db:
driver: mysql
source: root:lansexiongdi6,@tcp(47.97.27.195:3306)/voucher?parseTime=True&loc=Local
maxIdle: 200 #最大的空闲连接数
maxOpen: 1000 #最大连接数0表示不受限制
maxLifetime: 300s #连接复用的最大生命周期
isDebug: false
redis: #没有则注释此属性
addr: 47.97.27.195:6379
password: lansexiongdi@666
readTimeout: 5s
writeTimeout: 5s
poolSize: 200 #连接池大小不配置或配置为0表示不启用连接池
minIdleConns: 50 #最小空闲连接数
connMaxIdleTime: 60s #每个连接最大空闲时间,如果超过了这个时间会被关闭
db: 3
rocketMQ:
addr: "http://rmq-cn-nwy3fn4ex09.cn-chengdu.rmq.aliyuncs.com:8080"
accessKey: "Qecl4cea2IAZPKoD"
secretKey: "Z3596KCFA9RAUR6k"
secretToken: ""
eventMap:
notifyRetry: # 重试延迟队列
topic: voucher_order_notifyRetry
group: voucher_order_notifyRetry_group
isOpenConsumer: true #是否启动消费 true/false
PerCoroutineCnt: 2 #协程数量不配置默认为20
RetryCnt: 3 #重试次数,不配置默认38
wechatNotifyMQ:
accessKeyId: "LTAI5tPyV7FynQNTfEvbEBuX"
accessKeySecret: "tZmTh8cV98xAQgtlRU0soWcb6Tpd4T"
endPoint: "http://1389288909295870.mqrest.cn-hangzhou.aliyuncs.com"
regionId: "hangzhou"
instanceId: "MQ_INST_1389288909295870_BYSoMttI"
topic: "notify"
groupId: "GID_voucher"
tag: "voucher_notify_dev"
isOpenConsumer: true #是否启动消费 true/false
registerTagUrl: "https://wpcallbacks.api.1688sup.com/wechatPay/register_tag"
wechat:
mchID: "1710953361" # 证书所属商户 蓝色兄弟服务商立减金配置
mchCertificateSerialNumber: "6006B8208815DB5EAC5BF2E783CB9D34082C3772" #商户证书序列号
wechatPayPublicKeyID: "PUB_KEY_ID_0117109533612025031800326400002563" #微信支付公钥ID
cmb:
sm2Prk: "8d39ff3d2559258c163f4510f082727f51531e1953ab203d5ab1ea4a6d94fd73"
sm2Puk: "04d827a7dbaaa358ce45b8c7794a7f54819f5c175005a702370e47f135ef6f5f9732758b1474f218419fe9e87f90c28c3b05f08254c651db27df35fae67b77b2e4" # 公钥,给到招行密钥
# cmbSm2Pik: "" # 招行私钥mock使用生产不会有该值
# cmbSm2Puk: "0416445bc16cbf42e47002ad9fe7c7af67d902b48be1eb69b98f6a006b0918630e1127f5f2fff83b2ecb30fc7fd72c34c33f37c7c355dffde3589f66800f0036ca" # 招行公钥
cmbSm2Pik: "f6a8d2f412e289686aba6a0f33cad1a64367d0ba012046ee0fbbefd3ffd675bd" # 招行私钥mock使用生产不会有该值
cmbSm2Puk: "043b2fade30067b6bd8e61b42771b1e953116fc5a0f9ed6939fceb9254b8d7d6989902c913642c3c68c42a2b56364512675ea0b517dd4469e73b73c888a2f4e8e3" # 招行公钥-mock使用
mid: "d6fdd78b6fd13a808818286b9cad9687"
aid: "5efaa21263b94f669a1c90ed0279df20"
keyAlias: "CO_PUB_KEY_SM2"
cmbKeyAlias: "SM2_CMBLIFE"
orgNo: "LANSEXIONGDI" # 发码机构号,固定值,掌上生活优惠券系统提供
notifyUrl: "https://sandbox.cdcc.cmbchina.com/AccessGateway/transIn/updateCodeStatus.json" # 招行测试回调地址
noticeStartDays: 7
noticeEndDays: 1
#告警配置
alarm:
webhookURL: "https://oapi.dingtalk.com/robot/send?access_token=5f10c2213cbf8168985cb2d061ebb1a5f70bd1dd47ec7cef58fa6fe545d52588"
secret: "SEC77b63d70a9e22317144e712b4538ce1e0013db885c65f7f9bae283e8958b39eb"
isAll: false
atMobiles:
- "18666173766"
cron:
isOpen: true #是否启动,控制全局
commandMap:
orderNotice:
isOpen: true #是否启动 true/false
command: "0 0 1 * * ?" # 每天凌晨1点执行一次
rdsMQ:
wechatQuery: #发放结算
name: "wechatQuery"
retryNum: 1 #重试次数
numWorkers: 1 #协程数量不配置默认为10
waitTime: 1s #处理完成后等待时间
isOpen: true #是否启动消费 true/false
wechatTimeSliceQuery:
name: "wechatTimeSliceQuery"
retryNum: 1 #重试次数
numWorkers: 3 #协程数量不配置默认为10
waitTime: 1s #处理完成后等待时间
isOpen: true #是否启动消费 true/false
retryNotify:
name: "retryNotify"
retryNum: 1 #重试次数
numWorkers: 1 #协程数量不配置默认为10
waitTime: 1s #处理完成后等待时间
isOpen: false #是否启动消费 true/false
aliYunSms:
accessKeyId: LTAI5tM1X4HuqUwT8D74qXAH
accessKeySecret: gxsC1QK12NSKH1HkCqKR1EnMdAy3ad
endpoint: dysmsapi.aliyuncs.com
signName: 蓝色兄弟
templateWarning: "SMS_489660721"
#配置日志
logs:
business: business.log #业务日志路径:如果不写日志,则不配置或配置为空
access: access.log #访问日志路径:如果不写日志,则不配置或配置为空

37
deploy.sh Normal file
View File

@ -0,0 +1,37 @@
#!/bin/bash
CI_COMMIT_ID=$1
server="voucher"
image="voucher"
http_port=13000
docker pull registry.cn-chengdu.aliyuncs.com/lsxdjr/$image:"${CI_COMMIT_ID}"
docker stop $server && docker rm $server
# docker run
docker run --network=merketing-network -itd --name $server --restart=always \
-p $http_port:13000 \
-v /var/www/marketing/cert:/app/cert \
-v /var/www/marketing/templates:/app/templates \
registry.cn-chengdu.aliyuncs.com/lsxdjr/$image:"${CI_COMMIT_ID}" \
./server -nacosIp "47.110.74.203" -nacosPort 8848 -nacosSpace "voucher" -nacosUsername "" -nacosPassword ""
docker image prune -f
docker container prune -f
#测试 /var/www/marketing/cert
#压测环境 /var/www/cert
#线上 /var/www/cert
docker run -itd --name "voucher" --restart=always \
-p 13000:13000 \
-v /var/www/cert:/app/cert \
registry.cn-chengdu.aliyuncs.com/lsxdjr/voucher-pre:50bb64d6 \
./server -nacosIp "172.16.0.239" -nacosPort 8848 -nacosSpace "pre" -nacosUsername "nacos" -nacosPassword "nacos"
# mysql
docker run -itd --name "mysql" --restart=always \
-p 3306:3306 \
-v /data/mysql/data:/var/lib/mysql:rw \
-v /data/mysql/mysql/conf:/etc/mysql/conf.d:rw \
-v /data/mysql/mysql/logs:/logs:rw \
mysql:8.0.27

1
doc/ng/ng.sh Normal file
View File

@ -0,0 +1 @@
docker run -d --name nginx -p 80:80 -p 443:443 -p 9200:9200 -v /var/www/web/vhost:/etc/nginx/conf.d -v /var/www/web/sites:/usr/share/nginx/html nginx

39
doc/ng/voucher.conf Normal file
View File

@ -0,0 +1,39 @@
upstream voucherServer{
least_conn;
# 第一个后端服务器
server 172.29.42.89:9200;
# 新增第二个后端服务器
server 172.29.42.90:9200;
}
server {
listen 443;
listen 80;
listen [::]:80;
server_name voucher.86698.cn;
ssl_certificate /etc/nginx/conf/certs/voucher.pem;
ssl_certificate_key /etc/nginx/conf/certs/voucher.key;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
#access_log /var/log/nginx/host.access.log main;
location / {
root /usr/share/nginx/html/web;
index index.html index.html;
try_files $uri $uri/ /index.html;
}
location /voucher {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header referer $http_referer;
proxy_pass http://voucherServer;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

View File

@ -0,0 +1,31 @@
upstream voucher{
# 第一个后端服务器
server 172.29.42.89:13000 backup;
# 新增第二个后端服务器
server 172.29.42.90:13000;
}
server {
listen 9200;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
location / {
root /usr/share/nginx/html/web;
index index.html index.html;
try_files $uri $uri/ /index.html;
}
location /voucher {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header referer $http_referer;
proxy_pass http://voucher;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

View File

@ -0,0 +1,32 @@
upstream voucher{
# 第一个后端服务器
server 172.29.42.89:13000;
# 新增第二个后端服务器
server 172.29.42.90:13000 backup;
}
server {
listen 9200;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
location / {
root /usr/share/nginx/html/web;
index index.html index.html;
try_files $uri $uri/ /index.html;
}
location /voucher {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header referer $http_referer;
proxy_pass http://voucher;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

55
go.mod
View File

@ -3,78 +3,99 @@ module voucher
go 1.22.2
require (
gitea.cdlsxd.cn/self-tools/l_crypt v1.0.0
github.com/ZZMarquis/gm v1.3.2
github.com/aliyunmq/mq-http-go-sdk v1.0.3
github.com/apache/rocketmq-client-go/v2 v2.1.2
github.com/brianvoe/gofakeit/v6 v6.28.0
github.com/bwmarrin/snowflake v0.3.0
github.com/duke-git/lancet/v2 v2.2.8
github.com/emmansun/gmsm v0.29.8
github.com/envoyproxy/protoc-gen-validate v1.0.4
github.com/go-kratos/kratos/contrib/config/nacos/v2 v2.0.0-20241105072421-f8b97f675b32
github.com/go-kratos/kratos/v2 v2.8.2
github.com/go-playground/validator/v10 v10.25.0
github.com/gogap/errors v0.0.0-20210818113853-edfbba0ddea9
github.com/gogf/gf v1.16.9
github.com/google/wire v0.6.0
github.com/gorilla/handlers v1.5.1
github.com/nacos-group/nacos-sdk-go v1.1.4
github.com/pkg/errors v0.9.1
github.com/redis/go-redis/v9 v9.1.0
github.com/robfig/cron v1.2.0
github.com/tjfoc/gmsm v1.4.1
github.com/wechatpay-apiv3/wechatpay-go v0.2.20
go.opentelemetry.io/otel v1.28.0
go.opentelemetry.io/otel/exporters/jaeger v1.17.0
go.opentelemetry.io/otel/sdk v1.28.0
go.opentelemetry.io/otel/trace v1.28.0
go.uber.org/automaxprocs v1.5.1
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094
golang.org/x/time v0.5.0
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917
google.golang.org/protobuf v1.34.2
gopkg.in/yaml.v2 v2.4.0
gorm.io/driver/mysql v1.5.7
gorm.io/gorm v1.25.12
gorm.io/hints v1.1.2
)
require (
dario.cat/mergo v1.0.0 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/emirpasic/gods v1.12.0 // indirect
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
github.com/felixge/httpsnoop v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-kratos/aegis v0.2.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/assert/v2 v2.2.0 // indirect
github.com/go-playground/form/v4 v4.2.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/gogap/stack v0.0.0-20150131034635-fef68dddd4f8 // indirect
github.com/golang/mock v1.3.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/sirupsen/logrus v1.4.2 // indirect
github.com/smartystreets/assertions v1.1.0 // indirect
github.com/sirupsen/logrus v1.4.0 // indirect
github.com/tidwall/gjson v1.13.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.59.0 // indirect
go.opentelemetry.io/otel/metric v1.28.0 // indirect
go.uber.org/atomic v1.6.0 // indirect
go.uber.org/multierr v1.5.0 // indirect
go.uber.org/zap v1.15.0 // indirect
golang.org/x/crypto v0.33.0 // indirect
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a // indirect
golang.org/x/lint v0.0.0-20241112194109-818c5a804067 // indirect
golang.org/x/net v0.32.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.28.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
google.golang.org/grpc v1.64.0 // indirect
gopkg.in/ini.v1 v1.56.0 // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/term v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect
google.golang.org/grpc v1.61.1 // indirect
gopkg.in/ini.v1 v1.42.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
stathat.com/c/consistent v1.0.0 // indirect

157
go.sum
View File

@ -1,10 +1,21 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
gitea.cdlsxd.cn/self-tools/l_crypt v1.0.0 h1:U0SBdxgrWNmbzPjEMuAtA51GeQ8PpRoZgN+SNoyqrQM=
gitea.cdlsxd.cn/self-tools/l_crypt v1.0.0/go.mod h1:Q7sEfyYLXGZ7i97HBJ7OaYLYyc4f9scUJK9Ev3ZGyDM=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/ZZMarquis/gm v1.3.2 h1:lFtpzg5zeeVMZ/gKi0gtYcKLBEo9XTqsZDHDz6s3Gow=
github.com/ZZMarquis/gm v1.3.2/go.mod h1:wWbjZYgruQVd7Bb8UkSN8ujU931kx2XUW6nZLCiDE0Q=
github.com/agiledragon/gomonkey v2.0.2+incompatible h1:eXKi9/piiC3cjJD1658mEE2o3NjkJ5vDLgYjCQu0Xlw=
github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWXGBDaHEjb1idR6+FVlX5T3D9hw=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk=
github.com/aliyunmq/mq-http-go-sdk v1.0.3 h1:/uhH7DUoaw9XTtsPgDp7zdPUyG5FBKj2GmJJph9z+6o=
github.com/aliyunmq/mq-http-go-sdk v1.0.3/go.mod h1:JYfRMQoPexERvnNNBcal0ZQ2TVQ5ialDiW9ScjaadEM=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/apache/rocketmq-client-go/v2 v2.1.2 h1:yt73olKe5N6894Dbm+ojRf/JPiP0cxfDNNffKwhpJVg=
github.com/apache/rocketmq-client-go/v2 v2.1.2/go.mod h1:6I6vgxHR3hzrvn+6n/4mrhS+UTulzK/X9LB2Vk1U5gE=
github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4=
@ -15,6 +26,9 @@ github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@ -23,6 +37,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28 h1:LdXxtjzvZYhhUaonAaAKArG3pyC67kGL3YY+6hGG8G4=
github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -33,8 +49,15 @@ github.com/duke-git/lancet/v2 v2.2.8 h1:wlruXhliDe4zls1e2cYmz4qLc+WtcvrpcCnk1VJd
github.com/duke-git/lancet/v2 v2.2.8/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/emmansun/gmsm v0.29.8 h1:py9RwKHe4sIxjgD9mtWSIF5bmspP7IXvQ70Rx8x22Ow=
github.com/emmansun/gmsm v0.29.8/go.mod h1:7UTFG3GmF8yxyZVB4HgpdeU0JoergL/i2OMGm5w02RA=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
@ -43,6 +66,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-kratos/aegis v0.2.0 h1:dObzCDWn3XVjUkgxyBp6ZeWtx/do0DPZ7LY3yNSJLUQ=
@ -61,17 +86,31 @@ github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lY
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/form/v4 v4.2.0 h1:N1wh+Goz61e6w66vo8vJkQt+uwZSoLz50kZPJWR8eic=
github.com/go-playground/form/v4 v4.2.0/go.mod h1:q1a2BY+AQUUzhl6xA/6hBetay6dEIhMHjgvJiGo6K7U=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gogap/errors v0.0.0-20210818113853-edfbba0ddea9 h1:qvGIRaCYFKkyFK9SgRXJCc/lmQCeeg2cl3mwBKQd5W0=
github.com/gogap/errors v0.0.0-20210818113853-edfbba0ddea9/go.mod h1:tbRYYYC7g/H7QlCeX0Z2zaThWKowF4QQCFIsGgAsqRo=
github.com/gogap/stack v0.0.0-20150131034635-fef68dddd4f8 h1:AuxION6c7in+AsPmFjQTUKT6/o1suT8XEEpfU0pWsHA=
github.com/gogap/stack v0.0.0-20150131034635-fef68dddd4f8/go.mod h1:6q1WEv2BiAO4FSdwLQTJbWQYAn1/qDNJHUGJNXCj9kM=
github.com/gogf/gf v1.16.9 h1:Q803UmmRo59+Ws08sMVFOcd8oNpkSWL9vS33hlo/Cyk=
github.com/gogf/gf v1.16.9/go.mod h1:8Q/kw05nlVRp+4vv7XASBsMe9L1tsVKiGoeP2AHnlkk=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
@ -80,6 +119,8 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/gomodule/redigo v1.8.5 h1:nRAxCa+SVsyjSBrtZmG/cqb6VbTmuRzpg/PoTFlpumc=
github.com/gomodule/redigo v1.8.5/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -93,14 +134,15 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI=
github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
@ -110,16 +152,19 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0=
github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@ -128,6 +173,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@ -135,15 +182,20 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -174,22 +226,27 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY=
github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c=
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/sirupsen/logrus v1.4.0 h1:yKenngtzGh+cUSSh6GWbxW2abRqhYUSR/t/6+2QqNvE=
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.1.0 h1:MkTeG1DMwsrdH7QtLXy5W+fUxWq+vmb6cLmyJ7aRtF0=
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@ -197,6 +254,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tidwall/gjson v1.13.0 h1:3TFY9yxOQShrvmjdM76K+jc66zJeT6D3/VFFYCGQf7M=
@ -205,6 +265,18 @@ github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.59.0 h1:Qu0qYHfXvPk1mSLNqcFtEk6DpxgA26hy6bmydotDpRI=
github.com/valyala/fasthttp v1.59.0/go.mod h1:GTxNb9Bc6r2a9D0TWNSPwDz78UxnTGBViY3xZNEqyYU=
github.com/wechatpay-apiv3/wechatpay-go v0.2.20 h1:gS8oFn1bHGnyapR2Zb4aqTV6l4kJWgbtqjCq6k1L9DQ=
github.com/wechatpay-apiv3/wechatpay-go v0.2.20/go.mod h1:A254AUBVB6R+EqQFo3yTgeh7HtyqRRtN2w9hQSOrd4Q=
github.com/wechatpay-apiv3/wechatpay-go v0.2.21 h1:uIyMpzvcaHA33W/QPtHstccw+X52HO1gFdvVL9O6Lfs=
github.com/wechatpay-apiv3/wechatpay-go v0.2.21/go.mod h1:A254AUBVB6R+EqQFo3yTgeh7HtyqRRtN2w9hQSOrd4Q=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/otel v1.0.0/go.mod h1:AjRVh9A5/5DE7S+mZtTR6t8vpKKryam+0lREnfmS4cg=
@ -235,13 +307,20 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a h1:4iLhBPcpqFmylhnkbY3W0ONLUYYkDAW9xMFLfxgsvCw=
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20241112194109-818c5a804067 h1:adDmSQyFTCiv19j015EGKJBoaa7ElV0Q1Wovb/4G7NA=
golang.org/x/lint v0.0.0-20241112194109-818c5a804067/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
@ -250,13 +329,17 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
@ -266,17 +349,22 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -301,14 +389,17 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -317,12 +408,18 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -333,17 +430,31 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos=
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY=
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM=
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0=
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY=
google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@ -361,8 +472,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.56.0 h1:DPMeDvGTM54DXbPkVIZsp19fp/I2K7zwA/itHYHKo8Y=
gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
@ -379,9 +490,17 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/driver/sqlite v1.5.0 h1:zKYbzRCpBrT1bNijRnxLDJWPjVfImGEn0lSnUY5gZ+c=
gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I=
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
gorm.io/hints v1.1.2 h1:b5j0kwk5p4+3BtDtYqqfY+ATSxjj+6ptPgVveuynn9o=
gorm.io/hints v1.1.2/go.mod h1:/ARdpUHAtyEMCh5NNi3tI7FsGh+Cj/MIUlvNxCNCFWg=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
stathat.com/c/consistent v1.0.0 h1:ezyc51EGcRPJUxfHGSgJjWzJdj3NiMU9pNfLNGiXV0c=

31
gorm.sh
View File

@ -101,7 +101,7 @@ echo "领域实体Bo代码生成完成${bo_file}"
# 生成 CRUD 操作代码部分
echo "正在生成 CRUD 操作代码..."
repo_file="${repo_path}/${table}_repo.go"
repo_file="${repo_path}/${table}.go"
mkdir -p "${repo_path}"
cat > "${repo_file}" <<EOL
package repo
@ -120,7 +120,7 @@ type ${table_capitalized}Repo interface {
EOL
crud_file="${repo_impl_path}/${table}_repoImpl.go"
crud_file="${repo_impl_path}/${table}.go"
mkdir -p "${repo_impl_path}"
# 使用heredoc方式向文件中写入CRUD操作代码定义了针对指定表的各种数据库操作方法包括创建、根据ID获取、更新、删除、列表查询以及按字段查询等功能
@ -130,20 +130,27 @@ package repoimpl
import (
"context"
"voucher/internal/data/model"
"voucher/internal/biz/repository"
"voucher/internal/biz/repo"
"voucher/internal/biz/bo"
"voucher/internal/data"
err2 "voucher/api/err"
)
// ${table_capitalized}RepoImpl 定义了针对 ${table_capitalized} 表的 CRUD 操作
// ${table_capitalized}RepoImpl .
type ${table_capitalized}RepoImpl struct {
Base[model.${table_capitalized}, bo.${table_capitalized}Bo]
db *data.Db
}
// New${table_capitalized}RepoImpl 创建一个新的 ${table_capitalized}RepoImpl 实例
func New${table_capitalized}RepoImpl() repository.${table_capitalized}Repo {
// New${table_capitalized}RepoImpl .
func New${table_capitalized}RepoImpl() repo.${table_capitalized}Repo {
return &${table_capitalized}RepoImpl{}
}
func (r *${table_capitalized}RepoImpl) DB(ctx context.Context) *gorm.DB {
return p.db.DB(ctx).WithContext(ctx).Model(model.${table_capitalized}{})
}
func (r *${table_capitalized}RepoImpl) Create(ctx context.Context, req *bo.${table_capitalized}Bo) (*bo.${table_capitalized}Bo, error) {
// todo 待实现
return nil, nil
@ -152,7 +159,17 @@ func (r *${table_capitalized}RepoImpl) Create(ctx context.Context, req *bo.${tab
// GetByID 根据 ID 获取 ${table_capitalized}
func (r *${table_capitalized}RepoImpl) GetByID(ctx context.Context,id int32) (*bo.${table_capitalized}Bo, error) {
var item model.${table_capitalized}
// todo 待实现
tx := p.DB(ctx).Where(model.${table_capitalized}{ID: id}).First(&item)
if tx.Error != nil {
return nil, fmt.Errorf("b fail %w", tx.Error)
}
if tx.RowsAffected == 0 {
return nil, err2.ErrorDbNotFound("数据不存在")
}
return r.ToBo(&item), nil
}
EOL

79
internal/biz/alarm.go Normal file
View File

@ -0,0 +1,79 @@
package biz
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
"strings"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
"voucher/internal/pkg/lock"
)
func (this *VoucherBiz) alarm(ctx context.Context, order *bo.OrderBo, errMsg string) error {
// 1小时 内 指定的批次号 发放 发生错误 预警
c := vo.OrderConsumeFailAlarmKey.BuildCache([]string{order.ProductNo})
_, err := this.rdb.Rdb.Get(ctx, c.Key).Result()
if err == nil {
// 缓存存在,直接返回
return nil
}
if err != redis.Nil {
return fmt.Errorf(fmt.Sprintf("alarm 获取redis缓存%s异常:%v", c.Key, err))
}
cl := vo.OrderConsumeFailAlarmLockKey.BuildCache([]string{order.ProductNo})
return lock.NewMutex(this.rdb.Rdb, cl.TTL).Lock(ctx, cl.Key, func(ctx context.Context) error {
// 二次获取,判定处理,以免获取锁后又执行了一次
cacheValue, err3 := this.rdb.Rdb.Get(ctx, c.Key).Result()
if err3 != nil && err3 != redis.Nil {
return fmt.Errorf(fmt.Sprintf("alarm 二次获取redis缓存%s异常:%v", c.Key, err))
}
if len(cacheValue) > 0 {
return nil // 有直接返回
}
// 通知
title := fmt.Sprintf("券发放异常")
if err = this.DingMixRepo.SendMarkdownMessage(ctx, title, alarmText(order, errMsg)); err != nil {
return err
}
if err = this.rdb.Rdb.Set(ctx, c.Key, order.ProductNo, c.TTL).Err(); err != nil {
return fmt.Errorf(fmt.Sprintf("设置redis缓存%s异常:%v", c.Key, err))
}
return nil
})
}
func alarmText(order *bo.OrderBo, errMsg string) string {
var card strings.Builder
// 警示信息(简化版)
card.WriteString("⚠️ **券发放异常** ⚠️\n\n")
// 订单信息(通过引用和代码块模拟小字体)
card.WriteString("> **订单信息:**\n\n")
card.WriteString(fmt.Sprintf("> AppID: `%s`\n\n", order.AppID))
card.WriteString(fmt.Sprintf("> OpenID: `%s`\n\n", order.Account))
card.WriteString(fmt.Sprintf("> 批次号: `%s`\n\n", order.BatchNo))
card.WriteString(fmt.Sprintf("> 活动号: `%s`\n\n", order.ProductNo))
card.WriteString(fmt.Sprintf("> 订单号: `%s`\n\n", order.OrderNo))
card.WriteString(fmt.Sprintf("> 招行订单号: `%s`\n", order.OutBizNo))
card.WriteString("\n")
// 报警原因(通过引用和代码块模拟小字体)
card.WriteString("> **❗ 报警原因:**\n\n")
card.WriteString(fmt.Sprintf("> `%s`\n", errMsg))
return card.String()
}

14
internal/biz/bo/cmb_bo.go Normal file
View File

@ -0,0 +1,14 @@
package bo
import "voucher/internal/biz/vo"
type CmbRequestBo struct {
FuncName vo.CmbFuncName
BizContent string
}
type CmbResponseBo struct {
RespCode string
RespMsg string
BizContent string
}

View File

@ -1,18 +1,57 @@
package bo
import "time"
import (
"time"
"voucher/internal/biz/vo"
)
// OrderBo 领域实体Bo结构字段和模型字段保持一致
type OrderBo struct {
ID int32
OrderNo string
ID uint64
OrderNo string
OutBizNo string
VoucherNo string
ProductNo string
BatchNo string
ActivityId string
Account string
Type vo.OrderType
AccountType vo.OrderAccountType
Status vo.OrderStatus
AppID string
MerchantNo string
NotifyUrl string
Channel vo.Channel
Attach string
Remark string
TransactionId string
ReceiveSuccessTime *time.Time
LastUseTime *time.Time
CreateTime *time.Time
UpdateTime *time.Time
}
type OrderCreateReqBo struct {
OutBizNo string
ProductNo string
Account string
AccountType bool
Status bool
AppID string
MerchantNo string
Channel bool
CreateTime time.Time
Type vo.OrderType
AccountType vo.OrderAccountType
NotifyUrl string
Attach string
}
type FindInBatchesUseBo struct {
StartTime *time.Time
EndTime *time.Time
}
type FindInBatchesBo struct {
StartTime string `json:"start_time"`
EndTime string `json:"end_time"`
ProductNo string `json:"product_no"`
OrderNos []string `json:"order_nos"`
OutBizNos []string `json:"out_biz_nos"`
VoucherNos []string `json:"voucher_nos"`
}

View File

@ -0,0 +1,22 @@
package bo
import (
"time"
"voucher/internal/biz/vo"
)
// OrderNotifyBo 领域实体Bo结构字段和模型字段保持一致
type OrderNotifyBo struct {
ID uint64
OrderNo string
Status vo.OrderNotifyStatus
Request string
Event vo.OrderNotifyEvent
Channel vo.Channel
Type vo.OrderType
Responses string
Remark string
NotifyUrl string
CreateTime *time.Time
UpdateTime *time.Time
}

View File

@ -0,0 +1,29 @@
package bo
import (
"time"
"voucher/internal/biz/vo"
)
// ProductBo 领域实体Bo结构字段和模型字段保持一致
type ProductBo struct {
ID int32
Name string
ProductNo string
BatchName string
BatchNo string
ActivityId string
MchId string
Channel vo.Channel
AvailableType vo.AvailableType
AvailableDays uint32
Amount int64
AllBudget int64
AvailableBudget int64
WarningBudget int64
WarningPerson string
StartTime *time.Time
EndTime *time.Time
CreateTime *time.Time
UpdateTime *time.Time
}

View File

@ -0,0 +1,18 @@
package bo
import (
"time"
"voucher/internal/biz/vo"
)
// WechatNotifyRegisterTagBo 领域实体Bo结构字段和模型字段保持一致
type WechatNotifyRegisterTagBo struct {
ID int32
StockID string
StockCreatorMchID string
Tag string
Status vo.WechatNotifyRegisterTagStatus
Remark string
CreateTime *time.Time
UpdateTime *time.Time
}

View File

@ -0,0 +1,36 @@
package bo
import "voucher/internal/biz/vo"
// ConsumeInformation 定义消费信息结构体
type ConsumeInformation struct {
ConsumeTime string `json:"consume_time"`
ConsumeMchid string `json:"consume_mchid"`
TransactionID string `json:"transaction_id"`
}
// PlainText 定义明文数据结构体
type PlainText struct {
StockCreatorMchid string `json:"stock_creator_mchid"`
StockID string `json:"stock_id"`
CouponID string `json:"coupon_id"`
CouponName string `json:"coupon_name"`
Description string `json:"description"`
Status vo.WechatVoucherStatus `json:"status"`
CreateTime string `json:"create_time"`
CouponType string `json:"coupon_type"`
NoCash bool `json:"no_cash"`
Singleitem bool `json:"singleitem"`
ConsumeInformation ConsumeInformation `json:"consume_information,omitempty"`
}
type WechatVoucherNotifyBo struct {
ID string `json:"id"`
CreateTime string `json:"create_time"`
ResourceType string `json:"resource_type"`
EventType string `json:"event_type"`
Summary string `json:"summary"`
OriginalType string `json:"original_type"`
AssociatedData string `json:"associated_data"`
PlainText PlainText `json:"plain_text"`
}

42
internal/biz/cmb/cmb.go Normal file
View File

@ -0,0 +1,42 @@
package cmb
import (
"voucher/internal/biz/mixrepos"
"voucher/internal/biz/repo"
"voucher/internal/biz/wechatrepo"
"voucher/internal/conf"
"voucher/internal/data"
)
// Cmb .
// @link https://open.cmbchina.com/Platform/#/resource/document/approvalAPI
// @link https://open.cmbchina.com/Platform/#/resource/document/guide
type Cmb struct {
bc *conf.Bootstrap
rdb *data.Rdb
OrderRepo repo.OrderRepo
ProductRepo repo.ProductRepo
OrderNotifyRepo repo.OrderNotifyRepo
WechatCpnRepo wechatrepo.WechatCpnRepo
CmbMixRepo mixrepos.CmbMixRepo
}
func NewCmb(
bc *conf.Bootstrap,
rdb *data.Rdb,
orderRepo repo.OrderRepo,
ProductRepo repo.ProductRepo,
OrderNotifyRepo repo.OrderNotifyRepo,
WechatCpnRepo wechatrepo.WechatCpnRepo,
CmbMixRepo mixrepos.CmbMixRepo,
) *Cmb {
return &Cmb{
bc: bc,
rdb: rdb,
OrderRepo: orderRepo,
ProductRepo: ProductRepo,
OrderNotifyRepo: OrderNotifyRepo,
WechatCpnRepo: WechatCpnRepo,
CmbMixRepo: CmbMixRepo,
}
}

148
internal/biz/cmb/notify.go Normal file
View File

@ -0,0 +1,148 @@
package cmb
import (
"context"
"encoding/json"
"fmt"
"github.com/go-kratos/kratos/v2/log"
"time"
err2 "voucher/api/err"
v1 "voucher/api/v1"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
)
func (v *Cmb) Notify(ctx context.Context, order *bo.OrderBo) (*bo.OrderNotifyBo, error) {
if !order.Status.IsCanNotify() {
return nil, fmt.Errorf("订单状态不允许通知,orderNo:%s,orderStatusText:%s", order.OrderNo, order.Status.GetText())
}
event, err := order.Status.GetOrderNotifyEvent()
if err != nil {
return nil, err
}
req := &bo.OrderNotifyBo{
OrderNo: order.OrderNo,
NotifyUrl: order.NotifyUrl,
Channel: order.Channel,
Event: event,
Type: order.Type,
}
request, orderNotify, err := v.notifyCreate(ctx, order, req)
if err != nil {
return nil, err
}
reply, err := v.CmbMixRepo.Request(ctx, request, order.NotifyUrl)
if err != nil {
return orderNotify, v.notifyFail(ctx, orderNotify.ID, err.Error())
}
if err = v.CmbMixRepo.VerifyResponse(ctx, reply); err != nil {
log.Errorf("回调通知招行返回验证结果发生错误,rep:%+v error:%s", reply, err.Error())
return orderNotify, v.notifyFail(ctx, orderNotify.ID, err.Error())
}
if reply.RespCode == vo.CmbResponseStatusSuccess.GetValue() {
replyJson, _ := json.Marshal(reply)
return orderNotify, v.notifySuccess(ctx, orderNotify.ID, string(replyJson))
}
return orderNotify, v.notifyFail(ctx, orderNotify.ID, reply.RespMsg)
}
func (v *Cmb) bizContent(_ context.Context, order *bo.OrderBo, orderNotify *bo.OrderNotifyBo) (string, error) {
cmbStatus, err := orderNotify.Event.GetCmbStatusText()
if err != nil {
return "", err
}
req := &v1.CmbNotifyRequest{
Ticket: orderNotify.OrderNo,
Status: cmbStatus.GetValue(),
TransDate: "", // 格式yyyy-mm-dd hh:mm:ss.sss
OrgNo: v.bc.Cmb.OrgNo,
Attach: order.Attach,
Ext: "",
}
if cmbStatus == vo.CmbStatusUse {
if order.LastUseTime == nil || order.LastUseTime.IsZero() {
req.TransDate = time.Now().Format("2006-01-02 15:04:05.000")
} else {
req.TransDate = order.LastUseTime.Format("2006-01-02 15:04:05.000")
}
} else {
req.TransDate = time.Now().Format("2006-01-02 15:04:05.000")
}
bizJsonBytes, err := json.Marshal(req)
if err != nil {
return "", err
}
return string(bizJsonBytes), nil
}
func (v *Cmb) NotifyRequest(ctx context.Context, order *bo.OrderBo, orderNotify *bo.OrderNotifyBo) (*v1.CmbRequest, error) {
bizContent, err := v.bizContent(ctx, order, orderNotify)
if err != nil {
return nil, err
}
request, err := v.CmbMixRepo.GetRequest(ctx, &bo.CmbRequestBo{
FuncName: vo.CmbNotifyFuncName,
BizContent: bizContent,
})
if err != nil {
return nil, err
}
return request, nil
}
func (v *Cmb) notifyCreate(ctx context.Context, order *bo.OrderBo, req *bo.OrderNotifyBo) (*v1.CmbRequest, *bo.OrderNotifyBo, error) {
request, err := v.NotifyRequest(ctx, order, req)
if err != nil {
return nil, nil, err
}
requestBytes, err := json.Marshal(request)
if err != nil {
return nil, nil, err
}
req.Request = string(requestBytes)
orderNotify, err := v.OrderNotifyRepo.Create(ctx, req)
if err != nil {
return nil, nil, err
}
return request, orderNotify, err
}
func (v *Cmb) notifySuccess(ctx context.Context, notifyId uint64, responses string) error {
if len(responses) == 0 {
responses = "{}"
}
return v.OrderNotifyRepo.Success(ctx, notifyId, responses)
}
func (v *Cmb) notifyFail(ctx context.Context, notifyId uint64, errMsg string) error {
if err := v.OrderNotifyRepo.Fail(ctx, notifyId, errMsg); err != nil {
return err
}
return err2.ErrorNeedRetryNotify(errMsg)
}

View File

@ -0,0 +1,44 @@
package cmb
import (
"context"
"encoding/json"
"github.com/go-kratos/kratos/v2/log"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
)
func (v *Cmb) NotifyRetryConsume(ctx context.Context, order *bo.OrderBo, orderNotify *bo.OrderNotifyBo) (*bo.OrderNotifyBo, error) {
req := &bo.OrderNotifyBo{
OrderNo: orderNotify.OrderNo,
NotifyUrl: order.NotifyUrl,
Channel: order.Channel,
Event: orderNotify.Event,
Type: order.Type,
Request: "",
}
request, orderNotify, err := v.notifyCreate(ctx, order, req)
if err != nil {
return nil, err
}
reply, err := v.CmbMixRepo.Request(ctx, request, order.NotifyUrl)
if err != nil {
return orderNotify, v.notifyFail(ctx, orderNotify.ID, err.Error())
}
if err = v.CmbMixRepo.VerifyResponse(ctx, reply); err != nil {
log.Errorf("NotifyRetryConsume CmbMixRepo.VerifyResponse error:%s", err.Error())
return orderNotify, v.notifyFail(ctx, orderNotify.ID, err.Error())
}
if reply.RespCode != vo.CmbResponseStatusSuccess.GetValue() {
return orderNotify, v.notifyFail(ctx, orderNotify.ID, reply.RespMsg)
}
replyJson, _ := json.Marshal(reply)
return orderNotify, v.notifySuccess(ctx, orderNotify.ID, string(replyJson))
}

View File

@ -0,0 +1,8 @@
package cmb
import (
"github.com/google/wire"
)
// ProviderSetCmb is biz providers.
var ProviderSetCmb = wire.NewSet(NewCmb)

View File

@ -1 +0,0 @@
package consume

View File

@ -1 +0,0 @@
package consume

View File

@ -1 +0,0 @@
package consume

View File

@ -1 +0,0 @@
package consume

241
internal/biz/cron_notice.go Normal file
View File

@ -0,0 +1,241 @@
package biz
import (
"context"
"errors"
"fmt"
"github.com/go-kratos/kratos/v2/log"
"github.com/redis/go-redis/v9"
"golang.org/x/sync/errgroup"
"runtime"
"time"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
"voucher/internal/pkg/lock"
)
func (this *VoucherBiz) isCanNotice(ctx context.Context) error {
if this.bc.Cmb.NoticeStartDays == 0 {
return errors.New("订单定时通知,noticeStartDays eq 0")
}
if this.bc.Cmb.NoticeEndDays == 0 {
return errors.New("订单定时通知,noticeEndDays eq 0")
}
cache := vo.CmbBatchNoticeCacheKey.BuildCache([]string{""})
_, err := this.rdb.Rdb.Get(ctx, cache.Key).Result()
if err == nil {
return fmt.Errorf("订单定时通知,notice 获取redis缓存存在已被执行,本台服务不做执行")
}
if err != redis.Nil {
return fmt.Errorf(fmt.Sprintf("订单定时通知,notice 获取redis缓存%s异常:%v", cache.Key, err))
}
c := vo.CmbBatchNoticeLockKey.BuildCache([]string{""})
return lock.NewMutex(this.rdb.Rdb, c.TTL).Lock(ctx, c.Key, func(ctx context.Context) error {
// 二次获取,判定处理,以免获取锁后又执行了一次
cacheValue, err2 := this.rdb.Rdb.Get(ctx, cache.Key).Result()
if err2 != nil && err2 != redis.Nil {
return fmt.Errorf(fmt.Sprintf("订单定时通知,notice 二次获取redis缓存%s异常:%v", cache.Key, err2))
}
if len(cacheValue) > 0 {
return fmt.Errorf("订单定时通知,notice 二次获取redis缓存存在已被执行,本台服务不做执行")
}
if err = this.rdb.Rdb.Set(ctx, cache.Key, fmt.Sprintf("%d_%d", this.bc.Cmb.NoticeStartDays, this.bc.Cmb.NoticeEndDays), c.TTL).Err(); err != nil {
return fmt.Errorf(fmt.Sprintf("notice 设置redis缓存%s异常:%v", cache.Key, err))
}
log.Warnf("订单定时通知,notice 获取redis缓存,不存在,开始处理")
return nil
})
}
func (this *VoucherBiz) Notice(ctx context.Context) error {
if err := this.isCanNotice(ctx); err != nil {
return err
}
now := time.Now()
// 获取 NoticeStartDays 天前的日期
noticeStartDay := now.AddDate(0, 0, int(-this.bc.Cmb.NoticeStartDays))
// 获取 NoticeStartDays 天前 00:00:00 的时间
startTime := time.Date(noticeStartDay.Year(), noticeStartDay.Month(), noticeStartDay.Day(), 0, 0, 0, 0, noticeStartDay.Location())
// 获取 NoticeEndDays 天前的日期
noticeEndDay := now.AddDate(0, 0, int(-this.bc.Cmb.NoticeEndDays))
// 获取 NoticeEndDays 天 23:59:59 的时间
endTime := time.Date(noticeEndDay.Year(), noticeEndDay.Month(), noticeEndDay.Day(), 23, 59, 59, 0, noticeEndDay.Location())
return this.timeSliceQuery(ctx, startTime, endTime)
}
func (this *VoucherBiz) timeSliceQuery(ctx context.Context, startTime, endTime time.Time) error {
log.Warnf("订单定时通知,开始处理,按每两个小时分片处理,范围:%s到%s", startTime.Format(time.DateTime), endTime.Format(time.DateTime))
duration := 2 * time.Hour
eg := new(errgroup.Group)
eg.SetLimit(10)
for start := startTime; start.Before(endTime); start = start.Add(duration) {
end := start.Add(duration) // 计算每次请求的结束时间
if end.After(endTime) {
end = endTime
} else {
end = end.Add(-1 * time.Second)
}
eg.Go(func() error {
req := &bo.FindInBatchesUseBo{
StartTime: &start,
EndTime: &end,
}
defer func() {
if err := recover(); err != nil {
_, file, line, _ := runtime.Caller(1) // 1 表示获取当前调用者的调用信息
log.Errorf("订单定时通知,发生错误:req:%+v,err:%v,file:%s,line:%d", req, err, file, line)
}
}()
return this.ExecuteNotice(ctx, req)
})
}
return eg.Wait() // 仅返回第一个错误
}
func (this *VoucherBiz) ExecuteNotice(ctx context.Context, req *bo.FindInBatchesUseBo) error {
start := time.Now()
num := 0
useNum := 0
sucNum := 0
err := this.OrderRepo.FindInBatches(ctx, req, func(ctx context.Context, rows []*bo.OrderBo) error {
for _, order := range rows {
num += 1
if err := this.notice(ctx, order, &useNum, &sucNum); err != nil {
log.Errorf("订单定时通知,err:%v", err)
}
}
return nil
})
logFields := map[string]interface{}{
"sTime": req.StartTime.Format(time.DateTime) + "到" + req.EndTime.Format(time.DateTime),
"num": num, // 查询总量
"useNum": useNum, // 核销通知数量
"sucNum": sucNum, // 重置为成功数量
"elapsed": time.Now().Sub(start).String(),
}
log.Warnf("订单定时通知,%+v", logFields)
return err
}
func (this *VoucherBiz) notice(ctx context.Context, order *bo.OrderBo, useNum, sucNum *int) (respErr error) {
// 批量通知不做数据存储,量会很大
status, err := this.WechatCpnRepo.Query(ctx, order)
if err != nil {
return err
}
if order.Status == status {
return nil // 券状态未改变,忽略不处理
}
order.Status = status
event, err := status.GetOrderNotifyEvent()
if err != nil {
return err
}
notify := &bo.OrderNotifyBo{
OrderNo: order.OrderNo,
NotifyUrl: order.NotifyUrl,
Channel: order.Channel,
Event: event,
Type: order.Type,
}
if err = this.request(ctx, order, notify); err != nil {
return err
}
if err = this.UpdateOrderStatus(ctx, order.ID, status); err != nil {
return err
}
if event.IsUsed() {
*useNum += 1
} else if event.IsSendDEd() || event.IsExpired() {
*sucNum += 1
}
return nil
}
func (this *VoucherBiz) request(ctx context.Context, order *bo.OrderBo, notify *bo.OrderNotifyBo) (respErr error) {
defer func() {
if err := recover(); err != nil {
// 打印堆栈信息
stackBuf := make([]byte, 1024)
stackSize := runtime.Stack(stackBuf, false)
// 获取调用栈信息
_, file, line, _ := runtime.Caller(1) // 1 表示获取当前调用者的调用信息
respErr = fmt.Errorf("request panic:%v, orderNo:%s, file:%s, line:%d, stack: %s", err, order.OrderNo, file, line, stackBuf[:stackSize])
}
}()
if order == nil {
return fmt.Errorf("request order is nil")
}
if notify == nil {
return fmt.Errorf("notify is nil")
}
if !notify.Event.CanNotify() {
return nil // 不可通知,忽略
}
request, err := this.Cmb.NotifyRequest(ctx, order, notify)
if err != nil {
return err
}
if request == nil {
return fmt.Errorf("request is nil,orderNo:%s,outBizNo:%s,stockId:%s", order.OrderNo, order.OutBizNo, order.BatchNo)
}
if _, err = this.CmbMixRepo.Request(ctx, request, order.NotifyUrl); err != nil {
return fmt.Errorf("orderNo:%s,outBizNo:%s,stockId:%s,err:%s", order.OrderNo, order.OutBizNo, order.BatchNo, err.Error())
}
return nil
}

View File

@ -0,0 +1,36 @@
package do
import "time"
type WxResp struct {
Amount int64 // 券面额
AllBudget int64 // 总预算
AllStock int64 // 总库存
UsedStock int64 // 已使用库存
UsedBudget int64 // 已使用预算
AvailableStock int64 // 可用库存
AvailableBudget int64 // 可用预算
StockUsageRate float64 // 券使用率
StartTime *time.Time
EndTime *time.Time
}
type WarningPerson struct {
Mobile string `json:"mobile"`
Name string `json:"name"`
Tag string `json:"tag"`
}
type WarningBudget struct {
StockName string // 券名称
StockId string // 券ID
StockNo string // 券编号
Amount int64 // 券面额
AllBudget int64 // 总预算
AllStock int64 // 总库存
UsedStock int64 // 已使用库存
UsedBudget int64 // 已使用预算
AvailableStock int64 // 可用库存
RemainingBudget int64 // 剩余预算
StockUsageRate float64 // 券使用率
}

18
internal/biz/do/rds_mq.go Normal file
View File

@ -0,0 +1,18 @@
package do
type WechatQuery struct {
ProductNo string `json:"product_no"`
BatchNo string `json:"batch_no"`
StartTime string `json:"start_time"`
EndTime string `json:"end_time"`
OrderNo string `json:"order_no"`
}
type RdsWechatQuery struct {
ProductNo string `json:"product_no"`
BatchNo string `json:"batch_no"`
StartTime string `json:"start_time"`
EndTime string `json:"end_time"`
GoNum int `json:"go_num"` // 并发数
TimeSliceHours int64 `json:"time_slice_hours"` // 时间片"小时"
}

View File

@ -0,0 +1,28 @@
package mixrepos
import (
"context"
v1 "voucher/api/v1"
"voucher/internal/biz/bo"
)
type CmbMixRepo interface {
// OrderVerify cmb 券订单参数验证
OrderVerify(ctx context.Context, req *v1.CmbRequest) (*v1.CmbOrderRequest, error)
// QueryVerify cmb 券订单参数验证
QueryVerify(ctx context.Context, req *v1.CmbRequest) (*v1.CmbQueryRequest, error)
// ProductQueryVerify cmb 券产品查询参数验证
ProductQueryVerify(ctx context.Context, req *v1.CmbRequest) (*v1.CmbQueryProductRequest, error)
// GetMockRequest cmb 券参数mock构建
GetMockRequest(ctx context.Context, bizContent string) (*v1.CmbRequest, error)
// GetRequest 我们请求cmb参数构建处理
GetRequest(ctx context.Context, reqBo *bo.CmbRequestBo) (*v1.CmbRequest, error)
// GetResponse cmb 请求我们响应返回结果处理
GetResponse(ctx context.Context, reqBo *bo.CmbResponseBo) (*v1.CmbReply, error)
// VerifyResponse cmb 请求响应返回结果验证
VerifyResponse(ctx context.Context, req *v1.CmbReply) error
// Request cmb 请求
Request(ctx context.Context, req *v1.CmbRequest, uri string) (*v1.CmbReply, error)
// Decrypt cmb 业务参数 解密
Decrypt(ctx context.Context, encryptBody string) (string, error)
}

View File

@ -0,0 +1,8 @@
package mixrepos
import "context"
type DingMixRepo interface {
SendMessage(_ context.Context, title, text string) error
SendMarkdownMessage(ctx context.Context, title, text string) error
}

View File

@ -0,0 +1,9 @@
package mixrepos
import "context"
// GenerateMixRepo interface
type GenerateMixRepo interface {
GeneratorString(ctx context.Context, uid string) string
GeneratorNumber(ctx context.Context, uid string) int64
}

View File

@ -1,11 +1,11 @@
package thirdrepository
package mixrepos
import (
"context"
"voucher/internal/pkg/mq"
)
type ThirdMQSend interface {
type MQSendMixRepo interface {
// SendASync 异步发送消息出错会自动写日志errFn 中可以不用再写失败日志,可以处理一些数据纠正的操作等
SendASync(ctx context.Context, topicName string, body []byte, errFn func(error), sendOptions ...mq.SendOption) error

View File

@ -0,0 +1,7 @@
package mixrepos
import "context"
type SmsMixRepo interface {
Send(ctx context.Context, phoneNumbers []string, templateCode string, params map[string]string) error
}

View File

@ -0,0 +1,96 @@
package biz
import (
"context"
"fmt"
"go.opentelemetry.io/otel/trace"
"strconv"
errPb "voucher/api/err"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
"voucher/internal/pkg/lock"
"voucher/internal/pkg/mq"
)
func (this *VoucherBiz) PushNotifyRetryDelayMQ(ctx context.Context, level int, orderNotifyId uint64) error {
str := strconv.FormatUint(orderNotifyId, 10)
eventMap := this.bc.RocketMQ.EventMap["notifyRetry"]
sendOption := []mq.SendOption{
mq.WithSendShardingKeysOption(str),
mq.WithOpenTelemetryOption(trace.SpanFromContext(ctx).SpanContext().TraceID().String()),
mq.WithSendDelayLevelOption(level),
}
if err := this.MqSendMixRepo.SendSync(ctx, eventMap.Topic, []byte("{}"), sendOption...); err != nil {
return fmt.Errorf("回调通知延迟队列,投递消息出错err=%s", err.Error())
}
return nil
}
func (this *VoucherBiz) NotifyRetryConsume(ctx context.Context, orderNotifyId uint64) error {
var (
err error
orderNotify *bo.OrderNotifyBo
c = vo.NotifyRetryConsume.BuildCacheUint64([]uint64{orderNotifyId})
)
err = lock.NewMutex(this.rdb.Rdb, c.TTL).Lock(ctx, c.Key, func(ctx context.Context) error {
orderNotify, err = this.OrderNotifyRepo.GetByID(ctx, orderNotifyId)
if err != nil {
return err
}
order, err2 := this.OrderRepo.GetByOrderNo(ctx, orderNotify.OrderNo)
if err2 != nil {
return err2
}
if order.Type.IsCmb() {
orderNotify, err2 = this.Cmb.NotifyRetryConsume(ctx, order, orderNotify)
return err2
}
return fmt.Errorf("订单类型错误:%s", order.Type.GetText())
})
if !errPb.IsNeedRetryNotify(err) {
return err
}
level, err2 := this.level(ctx, orderNotify)
if err2 != nil {
return err2
}
return this.PushNotifyRetryDelayMQ(ctx, level, orderNotify.ID)
}
func (this *VoucherBiz) level(ctx context.Context, orderNotify *bo.OrderNotifyBo) (int, error) {
// 状态回调接口失败需要支持重试 重试间隔为1分钟、2分钟、12分钟、60分钟、360分钟
count, err := this.OrderNotifyRepo.GetCountByOrderNoAndEvent(ctx, orderNotify.OrderNo, orderNotify.Event)
if err != nil {
return 0, err
}
switch count {
case 1:
return 60, nil
case 2:
return 120, nil
case 3:
return 720, nil
case 4:
return 3600, nil
case 5:
return 21600, nil
}
return 0, fmt.Errorf("回调通知失败次数超过5次不再重试")
}

168
internal/biz/order.go Normal file
View File

@ -0,0 +1,168 @@
package biz
import (
"context"
"fmt"
err2 "voucher/api/err"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
)
func (this *VoucherBiz) CmbOrder(ctx context.Context, req *bo.OrderCreateReqBo) (orderNo string, err error) {
order, err3 := this.GetByOutBizNo(ctx, req)
if err3 != nil {
return "", err3
}
if order != nil {
if order.Status.IsFail() || order.Status.IsIng() {
if err4 := this.orderRetry(ctx, order); err4 != nil {
return "", err4
}
}
return order.OrderNo, err
}
product, err3 := this.ProductRepo.GetByProductNo(ctx, req.ProductNo)
if err3 != nil {
return "", err3
}
order, err3 = this.order(ctx, req, product)
if err3 != nil {
return "", err3
}
return order.OrderNo, nil
}
func (this *VoucherBiz) order(ctx context.Context, req *bo.OrderCreateReqBo, product *bo.ProductBo) (*bo.OrderBo, error) {
order, err := this.create(ctx, req, product)
if err != nil {
return nil, err
}
voucherNo, err := this.send(ctx, order)
if err != nil {
if err3 := this.fail(ctx, order, err); err3 != nil {
return nil, err3
}
return nil, err
}
if err = this.success(ctx, order, voucherNo); err != nil {
return nil, err
}
return order, nil
}
func (this *VoucherBiz) send(ctx context.Context, order *bo.OrderBo) (string, error) {
if order.ActivityId == "" {
return this.WechatCpnRepo.Order(ctx, order)
}
return this.BankMultiActivityRepo.Order(order)
}
func (this *VoucherBiz) orderRetry(ctx context.Context, order *bo.OrderBo) error {
voucherNo, err := this.send(ctx, order)
if err != nil {
if err3 := this.fail(ctx, order, err); err3 != nil {
return err3
}
return err
}
return this.success(ctx, order, voucherNo)
}
func (this *VoucherBiz) create(ctx context.Context, req *bo.OrderCreateReqBo, product *bo.ProductBo) (*bo.OrderBo, error) {
o := &bo.OrderBo{
OrderNo: this.GenerateMixRepo.GeneratorString(ctx, fmt.Sprintf("%d%s", req.Type, req.OutBizNo)),
OutBizNo: req.OutBizNo,
ProductNo: req.ProductNo,
Account: req.Account,
AppID: req.AppID,
MerchantNo: product.MchId,
Channel: product.Channel,
BatchNo: product.BatchNo,
NotifyUrl: req.NotifyUrl,
AccountType: vo.OrderAccountTypeOpenId,
Type: req.Type,
Status: vo.OrderStatusIng, // 同步发放,状态至为发放中
Attach: req.Attach,
ActivityId: product.ActivityId, // 多笔立减活动
}
return this.OrderRepo.Create(ctx, o)
}
func (this *VoucherBiz) ing(ctx context.Context, id uint64) error {
return this.OrderRepo.Ing(ctx, id)
}
func (this *VoucherBiz) success(ctx context.Context, order *bo.OrderBo, voucherNo string) error {
return this.OrderRepo.Success(ctx, order.ID, voucherNo)
}
func (this *VoucherBiz) fail(ctx context.Context, order *bo.OrderBo, errReq error) error {
errMsg := errReq.Error()
if err := this.OrderRepo.Fail(ctx, order.ID, errMsg); err != nil {
return err
}
if errMsg == `Post "https://api.mch.weixin.qq.com/v3/marketing/favor/users/oW97fjrv_ntYBPjMsaaEMRSj6TPA/coupons": EOF` {
// 微信:不同微信号,领取了很多次,自然人限领,发放频率限制,微信研发排期,后续这个错误信息应该会更新,近期没那么快同步上去
return nil
}
if err2.IsWechatAccountFail(errReq) || err2.IsWechatUserMaxCoupons(errReq) {
return nil // 过滤调该类型错误通知
}
return this.alarm(ctx, order, errReq.Error())
}
func (this *VoucherBiz) GetByOutBizNo(ctx context.Context, req *bo.OrderCreateReqBo) (*bo.OrderBo, error) {
order, err := this.OrderRepo.GetByOutBizNo(ctx, vo.OrderTypeCmb, req.OutBizNo)
if err != nil && !err2.IsDbNotFound(err) {
return nil, err
}
return order, nil
}
func (this *VoucherBiz) UpdateOrderStatus(ctx context.Context, orderId uint64, status vo.OrderStatus) error {
if status.IsSuccess() {
return this.OrderRepo.Available(ctx, orderId)
} else if status.IsUse() {
return this.OrderRepo.Used(ctx, orderId)
} else if status.IsExpired() {
return this.OrderRepo.Expired(ctx, orderId)
}
return fmt.Errorf("notice 未知券状态,orderId:%d,statuText:%s", orderId, status.GetText())
}

117
internal/biz/product.go Normal file
View File

@ -0,0 +1,117 @@
package biz
import (
"context"
"fmt"
"github.com/wechatpay-apiv3/wechatpay-go/services/cashcoupons"
"time"
v1 "voucher/api/v1"
"voucher/internal/biz/do"
"voucher/internal/biz/vo"
"voucher/internal/pkg/lock"
)
func (this *VoucherBiz) CmbProductQuery(ctx context.Context, productNo string) (reps *v1.CmbQueryProductReply, err error) {
c := vo.CmbProductQueryLockKey.BuildCache([]string{productNo})
err = lock.NewMutex(this.rdb.Rdb, c.TTL).Lock(ctx, c.Key, func(ctx context.Context) error {
product, err3 := this.ProductRepo.GetByProductNo(ctx, productNo)
if err3 != nil {
return err3
}
if !product.Channel.IsWeChat() {
return fmt.Errorf("只支持微信")
}
wechatResp, err4 := this.WechatCpnRepo.QueryProduct(ctx, product.MchId, product.BatchNo)
if err4 != nil {
return err4
}
reps = &v1.CmbQueryProductReply{
RespCode: vo.CmbResponseStatusSuccess.GetValue(),
RespMsg: "成功",
ActivityName: product.Name,
ActivityId: product.ProductNo,
Amount: "",
MinAmount: "",
AvailableType: "",
AvailableDays: "", // 动态有效期天数
StartTime: "",
EndTime: "",
AvailableStock: "",
Detail: *wechatResp.Description,
}
inputFormat := time.RFC3339
if wechatResp.AvailableBeginTime != nil {
availableBeginTime, _ := time.Parse(inputFormat, *wechatResp.AvailableBeginTime)
reps.StartTime = availableBeginTime.Format("2006-01-02 15:04:05.000")
reps.SaleStartTime = reps.StartTime
}
if wechatResp.AvailableEndTime != nil {
availableEndTime, _ := time.Parse(inputFormat, *wechatResp.AvailableEndTime)
reps.EndTime = availableEndTime.Format("2006-01-02 15:04:05.000")
reps.SaleEndTime = reps.EndTime
}
reps.Amount = fmt.Sprintf("%d", *wechatResp.StockUseRule.FixedNormalCoupon.CouponAmount)
reps.MinAmount = fmt.Sprintf("%d", *wechatResp.StockUseRule.FixedNormalCoupon.TransactionMinimum)
availableStock := *wechatResp.StockUseRule.MaxCoupons - *wechatResp.DistributedCoupons
reps.AvailableStock = fmt.Sprintf("%d", availableStock)
availableType, err3 := product.AvailableType.GetCmbAvailableType()
if err3 != nil {
return err3
}
reps.AvailableType = availableType.GetValue()
reps.AvailableDays = fmt.Sprintf("%d", product.AvailableDays)
return nil
})
return
}
func (this *VoucherBiz) WxResp(wxResp *cashcoupons.Stock) (reps *do.WxResp) {
availableStock := *wxResp.StockUseRule.MaxCoupons - *wxResp.DistributedCoupons
couponAmount := *wxResp.StockUseRule.FixedNormalCoupon.CouponAmount / 100
remainingBudget := availableStock * couponAmount
stockUsageRate := float64(*wxResp.DistributedCoupons) / float64(*wxResp.StockUseRule.MaxCoupons) * 100
req := &do.WxResp{
Amount: couponAmount,
AllBudget: *wxResp.StockUseRule.MaxAmount / 100,
AllStock: *wxResp.StockUseRule.MaxCoupons,
UsedStock: *wxResp.DistributedCoupons,
UsedBudget: *wxResp.DistributedCoupons * couponAmount,
AvailableStock: availableStock,
AvailableBudget: remainingBudget,
StockUsageRate: stockUsageRate,
}
inputFormat := time.RFC3339
if wxResp.AvailableBeginTime != nil {
availableBeginTime, _ := time.Parse(inputFormat, *wxResp.AvailableBeginTime)
req.StartTime = &availableBeginTime
}
if wxResp.AvailableEndTime != nil {
availableEndTime, _ := time.Parse(inputFormat, *wxResp.AvailableEndTime)
req.EndTime = &availableEndTime
}
return req
}

82
internal/biz/query.go Normal file
View File

@ -0,0 +1,82 @@
package biz
import (
"context"
"fmt"
"github.com/go-kratos/kratos/v2/log"
"time"
v1 "voucher/api/v1"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
"voucher/internal/pkg/lock"
)
func (this *VoucherBiz) CmbQuery(ctx context.Context, orderNo string) (resp *v1.CmbQueryReply, err error) {
c := vo.CmbQueryLockKey.BuildCache([]string{orderNo})
err = lock.NewMutex(this.rdb.Rdb, c.TTL).Lock(ctx, c.Key, func(ctx context.Context) error {
order, err3 := this.OrderRepo.GetByOrderNo(ctx, orderNo)
if err3 != nil {
return err3
}
if err = this.Query(ctx, order); err != nil {
return err
}
status, err3 := order.Status.GetCmbStatusText()
if err3 != nil {
return err3
}
resp = &v1.CmbQueryReply{
Ticket: order.OrderNo,
Status: status.GetValue(),
TransDate: time.Now().Format("20060102150405"),
OrgNo: this.bc.Cmb.OrgNo,
Ext: "",
}
return nil
})
return
}
func (this *VoucherBiz) Query(ctx context.Context, order *bo.OrderBo) error {
status, err := this.WechatCpnRepo.Query(ctx, order)
if err != nil {
return err
}
if order.Status == status {
log.Warnf("券状态未改变:%s忽略不处理,orderNo:%s", order.Status.GetText(), order.OrderNo)
return nil
}
if err = this.UpdateOrderStatus(ctx, order.ID, status); err != nil {
return err
}
order.Status = status
return nil
}
func (this *VoucherBiz) QueryOrder(ctx context.Context, orderNo string) (string, error) {
order, err3 := this.OrderRepo.GetByOrderNo(ctx, orderNo)
if err3 != nil {
return "", err3
}
status, err := this.WechatCpnRepo.Query(ctx, order)
if err != nil {
return "", err
}
return fmt.Sprintf("orderNo:%s,订单状态:%s,微信查询返回状态:%s", orderNo, order.Status.GetText(), status.GetText()), nil
}

View File

@ -0,0 +1,77 @@
package biz
import (
"context"
"fmt"
err2 "voucher/api/err"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
"voucher/internal/pkg/lock"
)
func (this *VoucherBiz) RegisterTag(ctx context.Context, id int32) error {
stock, err := this.ProductRepo.GetById(ctx, id)
if err != nil {
return err
}
wxResp, err := this.WechatCpnRepo.QueryProduct(ctx, stock.MchId, stock.BatchNo)
if err != nil {
return err
}
if err = this.ProductRepo.UpdateByWxResp(ctx, stock.ID, this.WxResp(wxResp)); err != nil {
return err
}
if err = this.registerNotifyTag(ctx, stock.MchId, stock.BatchNo); err != nil {
return err
}
_, err = this.ProductRepo.GetByProductNo(ctx, stock.ProductNo)
return err
}
func (this *VoucherBiz) registerNotifyTag(ctx context.Context, stockCreatorMchID, stockID string) error {
cl := vo.WechatNotifyRegisterTagCacheLockKey.BuildCache([]string{this.bc.WechatNotifyMQ.Tag, stockCreatorMchID, stockID})
return lock.NewMutex(this.rdb.Rdb, cl.TTL).Lock(ctx, cl.Key, func(ctx context.Context) error {
wechatNotifyTag, err3 := this.WechatNotifyRegisterTagRepo.GetByStockIdAndMchId(ctx, stockCreatorMchID, stockID)
if err3 != nil && !err2.IsDbNotFound(err3) {
return err3
}
if wechatNotifyTag != nil {
if wechatNotifyTag.Tag != this.bc.WechatNotifyMQ.Tag {
return fmt.Errorf("tag不一致请检查tag配置:%s", wechatNotifyTag.Tag)
}
if wechatNotifyTag.Status.IsSuccess() {
return nil
}
} else {
wechatNotifyTag, err3 = this.createRegisterTag(ctx, stockCreatorMchID, stockID)
if err3 != nil {
return err3
}
}
if err := this.WechatCpnRepo.RegisterNotifyTag(ctx, stockID); err != nil {
return this.WechatNotifyRegisterTagRepo.Fail(ctx, wechatNotifyTag.ID, err.Error())
}
return this.WechatNotifyRegisterTagRepo.Success(ctx, wechatNotifyTag.ID)
})
}
func (this *VoucherBiz) createRegisterTag(ctx context.Context, stockCreatorMchID, stockID string) (*bo.WechatNotifyRegisterTagBo, error) {
return this.WechatNotifyRegisterTagRepo.Create(ctx, &bo.WechatNotifyRegisterTagBo{
StockID: stockID,
StockCreatorMchID: stockCreatorMchID,
Tag: this.bc.WechatNotifyMQ.Tag,
})
}

View File

@ -0,0 +1,29 @@
package repo
import (
"context"
"voucher/internal/biz/bo"
"voucher/internal/biz/do"
"voucher/internal/biz/vo"
)
type OrderRepo interface {
SpecifyFindInBatches(ctx context.Context, w *bo.FindInBatchesBo, fun func(ctx context.Context, rows []*bo.OrderBo) error) error
FinSucByStockIdInBatches(ctx context.Context, req *do.WechatQuery, fun func(ctx context.Context, rows []*bo.OrderBo) error) error
FinFailByStockIdInBatches(ctx context.Context, batchNo string, fun func(ctx context.Context, rows []*bo.OrderBo) error) error
FindIngInBatches(ctx context.Context, fun func(ctx context.Context, rows []*bo.OrderBo) error) error
FindInBatches(ctx context.Context, w *bo.FindInBatchesUseBo, fun func(ctx context.Context, rows []*bo.OrderBo) error) error
GetByOutBizNo(ctx context.Context, t vo.OrderType, outBizNo string) (*bo.OrderBo, error)
GetByOrderNo(ctx context.Context, orderNo string) (*bo.OrderBo, error)
GetByCouponId(ctx context.Context, merchantNo, batchNo, voucherNo string) (*bo.OrderBo, error)
GetByTransactionId(ctx context.Context, stockCreatorMchId, stockID, transactionId string) (*bo.OrderBo, error)
Create(ctx context.Context, req *bo.OrderBo) (*bo.OrderBo, error)
GetByID(ctx context.Context, id uint64) (*bo.OrderBo, error)
Ing(ctx context.Context, id uint64) error
Success(ctx context.Context, id uint64, voucherNo string) error
Fail(ctx context.Context, id uint64, remark string) error
Used(ctx context.Context, id uint64) error
NotifyUsed(ctx context.Context, id uint64, transactionId string) error
Available(ctx context.Context, id uint64) error
Expired(ctx context.Context, id uint64) error
}

View File

@ -0,0 +1,10 @@
package repo
import (
"context"
"voucher/internal/biz/bo"
)
type OrderBakRepo interface {
SpecifyFindInBatches(ctx context.Context, w *bo.FindInBatchesBo, fun func(ctx context.Context, rows []*bo.OrderBo) error) error
}

View File

@ -0,0 +1,15 @@
package repo
import (
"context"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
)
type OrderNotifyRepo interface {
GetByID(ctx context.Context, id uint64) (*bo.OrderNotifyBo, error)
GetCountByOrderNoAndEvent(ctx context.Context, orderNo string, event vo.OrderNotifyEvent) (int64, error)
Create(ctx context.Context, req *bo.OrderNotifyBo) (*bo.OrderNotifyBo, error)
Success(ctx context.Context, id uint64, responses string) error
Fail(ctx context.Context, id uint64, remark string) error
}

View File

@ -1,13 +0,0 @@
package repo
import (
"context"
"voucher/internal/biz/bo"
)
type OrderRepo interface {
// Create 创建 Order
Create(ctx context.Context, req *bo.OrderBo) (*bo.OrderBo, error)
// GetByID 根据 ID 获取 Order
GetByID(ctx context.Context, id int32) (*bo.OrderBo, error)
}

View File

@ -0,0 +1,15 @@
package repo
import (
"context"
"voucher/internal/biz/bo"
"voucher/internal/biz/do"
)
type ProductRepo interface {
GetById(ctx context.Context, id int32) (*bo.ProductBo, error)
FindWarningBudget(ctx context.Context, fun func(ctx context.Context, rows []*bo.ProductBo) error) error
GetByBatchNo(ctx context.Context, batchNo string) (*bo.ProductBo, error)
GetByProductNo(ctx context.Context, productNo string) (*bo.ProductBo, error)
UpdateByWxResp(ctx context.Context, id int32, req *do.WxResp) error
}

View File

@ -0,0 +1,13 @@
package repo
import (
"context"
"voucher/internal/biz/bo"
)
type WechatNotifyRegisterTagRepo interface {
GetByStockIdAndMchId(ctx context.Context, stockCreatorMchID, stockId string) (*bo.WechatNotifyRegisterTagBo, error)
Create(ctx context.Context, req *bo.WechatNotifyRegisterTagBo) (*bo.WechatNotifyRegisterTagBo, error)
Success(ctx context.Context, id int32) error
Fail(ctx context.Context, id int32, remark string) error
}

View File

@ -1,4 +0,0 @@
package repository
type OrderRepo interface {
}

44
internal/biz/retry.go Normal file
View File

@ -0,0 +1,44 @@
package biz
import (
"context"
)
func (this *VoucherBiz) OrderRetry(ctx context.Context, outBizNos []string) error {
return nil
//if len(outBizNos) > 0 {
//
// for _, outBizNo := range outBizNos {
//
// order, err := this.OrderRepo.GetByOutBizNo(ctx, vo.OrderTypeCmb, outBizNo)
// if err != nil {
// return fmt.Errorf(fmt.Sprintf("获取订单%s异常:%v", outBizNo, err))
// }
//
// if !order.Status.IsIng() {
// return fmt.Errorf(fmt.Sprintf("订单%s状态异常:%s", order.OrderNo, order.Status))
// }
//
// if err4 := this.orderRetry(ctx, order); err4 != nil {
// return err4
// }
// }
//
// return nil
//}
//
//return this.OrderRepo.FindIngInBatches(ctx, func(ctx context.Context, rows []*bo.OrderBo) error {
//
// for _, order := range rows {
//
// if err4 := this.orderRetry(ctx, order); err4 != nil {
// return err4
// }
// }
//
// return nil
//})
}

View File

@ -0,0 +1,122 @@
package biz
import (
"context"
"encoding/json"
"fmt"
"github.com/go-kratos/kratos/v2/log"
"github.com/go-kratos/kratos/v2/transport/http"
"time"
"voucher/internal/biz/bo"
)
func (this *VoucherBiz) PushRetryNotify(ctx http.Context, req *bo.FindInBatchesBo) error {
queue := this.bc.RdsMQ.GetRetryNotify()
if queue == nil {
return fmt.Errorf("队列不存在")
}
msg, err := json.Marshal(req)
if err != nil {
return err
}
strMsg := string(msg)
_, err = this.rdb.Rdb.RPush(ctx, queue.Name, strMsg).Result()
if err != nil {
return fmt.Errorf("添加到队列失败:%v", err)
}
return nil
}
func (this *VoucherBiz) RetryNotify(ctx context.Context, msg string) error {
var req *bo.FindInBatchesBo
if err := json.Unmarshal([]byte(msg), &req); err != nil {
return err
}
if err := this.RetryNotifyOrder(ctx, req); err != nil {
return err
}
return this.RetryNotifyOrderBack(ctx, req)
}
func (this *VoucherBiz) RetryNotifyOrder(ctx context.Context, req *bo.FindInBatchesBo) error {
start := time.Now()
startStr := time.Now().String()
log.Warnf("重试通知处理:%s", startStr)
fmt.Printf("重试通知处理:%s", startStr)
n := 0
eNum := 0
notifyNum := 0
err := this.OrderRepo.SpecifyFindInBatches(ctx, req, func(ctx context.Context, rows []*bo.OrderBo) error {
n += 1
for _, order := range rows {
eNum += 1
if _, err := this.Cmb.Notify(ctx, order); err != nil {
log.Errorf("重试通知处理,发生错误,orderNo:%s,outBizNo:%s,voucherNo:%s,err:%v", order.OrderNo, order.OutBizNo, order.VoucherNo, err)
} else {
notifyNum += 1
}
}
log.Warnf("重试通知处理,第:%d组,已执行条数:%d,通知成功条数:%d,", n, eNum, notifyNum)
return nil
})
endTime := time.Now()
log.Warnf("重试通知处理,耗时:%s,结束时间:%s,处理%d组,处理%d单,通知成功条数:%d", endTime.Sub(start).String(), endTime.String(), n, eNum, notifyNum)
fmt.Printf("重试通知处理,耗时:%s,结束时间%s,处理%d组,处理%d单,通知成功条数:%d", endTime.Sub(start).String(), endTime.String(), n, eNum, notifyNum)
return err
}
func (this *VoucherBiz) RetryNotifyOrderBack(ctx context.Context, req *bo.FindInBatchesBo) error {
start := time.Now()
startStr := time.Now().String()
log.Warnf("重试通知处理Bak:%s", startStr)
fmt.Printf("重试通知处理Bak:%s", startStr)
n := 0
eNum := 0
notifyNum := 0
err := this.OrderBakRepo.SpecifyFindInBatches(ctx, req, func(ctx context.Context, rows []*bo.OrderBo) error {
n += 1
for _, order := range rows {
eNum += 1
if _, err := this.Cmb.Notify(ctx, order); err != nil {
log.Errorf("重试通知处理Bak,发生错误,orderNo:%s,outBizNo:%s,voucherNo:%s,err:%v", order.OrderNo, order.OutBizNo, order.VoucherNo, err)
} else {
notifyNum += 1
}
}
log.Warnf("重试通知处理Bak,第:%d组,已执行条数:%d,通知成功条数:%d,", n, eNum, notifyNum)
return nil
})
endTime := time.Now()
log.Warnf("重试通知处理Bak,耗时:%s,结束时间:%s,处理%d组,处理%d单,通知成功条数:%d", endTime.Sub(start).String(), endTime.String(), n, eNum, notifyNum)
fmt.Printf("重试通知处理Bak,耗时:%s,结束时间%s,处理%d组,处理%d单,通知成功条数:%d", endTime.Sub(start).String(), endTime.String(), n, eNum, notifyNum)
return err
}

View File

@ -0,0 +1,14 @@
# <p align="center">券状态同步</p>
* * *
### 主要工作
+ 券状态查询同步
* * *
### 规则说明
+ 按照时间分片处理按照2小时为一个时间片启用一个协程消费处理
+ 协程最大可同时运行指定数量设置为2
* * *
### 使用方式
+ 消费处理,按照时间范围上报消费
+ 每次请求按照时间片分别启用2个协程消费处理也就是每个请求可以同时启用2个协程消费处理
+ 请不要无休止的访问,请按照时间片进行访问,并且不要重复的时间片访问,增加系统负载,特殊发券日期量较大,建议缩短时间范围上报,多切分上报处理
* * *

View File

@ -0,0 +1,84 @@
package timeslicequery
import (
"github.com/nacos-group/nacos-sdk-go/util"
"sync"
"voucher/internal/biz/cmb"
"voucher/internal/biz/mixrepos"
"voucher/internal/biz/repo"
"voucher/internal/biz/wechatrepo"
"voucher/internal/conf"
"voucher/internal/data"
)
type Query struct {
mu sync.RWMutex
queryMap map[string]bool
bc *conf.Bootstrap
rdb *data.Rdb
cmb *cmb.Cmb
productRepo repo.ProductRepo
orderRepo repo.OrderRepo
wechatCpnRepo wechatrepo.WechatCpnRepo
mqSendMixRepo mixrepos.MQSendMixRepo
}
func NewQuery(
bc *conf.Bootstrap,
rdb *data.Rdb,
cmb *cmb.Cmb,
productRepo repo.ProductRepo,
orderRepo repo.OrderRepo,
wechatCpnRepo wechatrepo.WechatCpnRepo,
mqSendMixRepo mixrepos.MQSendMixRepo) *Query {
return &Query{
queryMap: make(map[string]bool),
bc: bc,
rdb: rdb,
cmb: cmb,
productRepo: productRepo,
orderRepo: orderRepo,
wechatCpnRepo: wechatCpnRepo,
mqSendMixRepo: mqSendMixRepo}
}
func (v *Query) uid(no string) string {
return util.Md5("query" + no)
}
func (this *Query) GetAll() map[string]bool {
return this.queryMap
}
func (this *Query) Get(uid string) bool {
this.mu.Lock()
defer this.mu.Unlock()
if _, ok := this.queryMap[uid]; ok {
return ok
}
return false
}
func (this *Query) Add(uid string) {
this.mu.Lock()
defer this.mu.Unlock()
this.queryMap[uid] = true
}
func (this *Query) Remove(uid string) {
this.mu.Lock()
defer this.mu.Unlock()
if _, ok := this.queryMap[uid]; ok {
delete(this.queryMap, uid)
}
}

View File

@ -0,0 +1,98 @@
package timeslicequery
import (
"context"
"fmt"
"github.com/go-kratos/kratos/v2/log"
"time"
"voucher/internal/biz/bo"
"voucher/internal/biz/do"
"voucher/internal/pkg/timeslice"
)
func (v *Query) execute(ctx context.Context, req *timeslice.Manager) error {
start := time.Now()
managerStartStr := req.StartTime.Format(time.DateTime)
managerEndStr := req.EndTime.Format(time.DateTime)
log.Warnf("微信券查询,%s到%s,开始", managerStartStr, managerEndStr)
fmt.Printf("微信券查询,%s到%s,开始\n", managerStartStr, managerEndStr)
taskCount, err := timeslice.NewManager(v.callbackFunc).Run(ctx, req)
if err != nil {
log.Errorf("微信券查询,%s到%s,失败:%v", managerStartStr, managerEndStr, err)
}
elapsed := time.Now().Sub(start).String()
log.Warnf("微信券查询,%s到%s,总任务数:%d,总耗时:%s", managerStartStr, managerEndStr, taskCount, elapsed)
fmt.Printf("微信券查询,%s到%s,总任务数:%d,总耗时:%s\n", managerStartStr, managerEndStr, taskCount, elapsed)
return nil
}
func (v *Query) callbackFunc(ctx context.Context, req *timeslice.Task) error {
startTimeStr := req.Process.Manager.StartTime.Format(time.DateTime)
endTimeStr := req.Process.Manager.EndTime.Format(time.DateTime)
currentStartTimeStr := req.CurrentStartTime.Format(time.DateTime)
currentEndTimeStr := req.CurrentEndTime.Format(time.DateTime)
start := time.Now()
bReq := &do.WechatQuery{
StartTime: currentStartTimeStr,
EndTime: currentEndTimeStr,
ProductNo: req.Process.Manager.ProductNo,
BatchNo: req.Process.Manager.BatchNo,
}
num := 0
errNum := 0
useNum := 0
err := v.orderRepo.FinSucByStockIdInBatches(ctx, bReq, func(ctx context.Context, rows []*bo.OrderBo) error {
for _, order := range rows {
num += 1
if err := v.wechatQuery(ctx, order, &useNum); err != nil {
errNum += 1
logFields := map[string]string{
"order_no": order.OrderNo,
"coupon_id": order.VoucherNo,
"open_id": order.Account,
"stock_id": order.BatchNo,
"err": err.Error(),
}
log.Errorf("微信券查询,%s到%s,taskId:%d,错误:%+v", startTimeStr, endTimeStr, req.TaskID, logFields)
if errNum > 20 {
return fmt.Errorf("微信券查询,%s到%s,第%d个任务,已经连续发生20次错误%+v", startTimeStr, endTimeStr, req.TaskID, logFields)
}
}
}
return nil
})
end := time.Now()
logFields := map[string]any{
"sTime": currentStartTimeStr + "到" + currentEndTimeStr,
"num": num,
"useNum": useNum,
"errNum": errNum,
"batchNo": req.Process.Manager.BatchNo,
"elapsed": end.Sub(start).String(),
}
log.Warnf("微信券查询,%s到%s,taskId:%d,处理完毕:%+v", startTimeStr, endTimeStr, req.TaskID, logFields)
return err
}

View File

@ -0,0 +1,119 @@
package timeslicequery
import (
"context"
"encoding/json"
"fmt"
"github.com/go-kratos/kratos/v2/log"
"github.com/go-kratos/kratos/v2/transport/http"
"time"
"voucher/internal/biz/do"
"voucher/internal/pkg/timeslice"
)
func (v *Query) Push(ctx http.Context, req *do.RdsWechatQuery) (string, error) {
if req.StartTime == "" || req.EndTime == "" {
return "", fmt.Errorf("时间参数不能为空")
}
queue := v.bc.RdsMQ.GetWechatTimeSliceQuery()
if queue == nil {
return "", fmt.Errorf("队列不存在")
}
if queue.Name == "" {
return "", fmt.Errorf("队列不存在")
}
if queue.IsOpen == false {
return "", fmt.Errorf("队列未开启")
}
if req.ProductNo != "" {
_, err := v.productRepo.GetByProductNo(ctx, req.ProductNo)
if err != nil {
return "", err
}
}
b, err := json.Marshal(req)
if err != nil {
return "", err
}
strMsg := string(b)
uid := v.uid(strMsg)
if v.Get(uid) {
return "", fmt.Errorf("此台服务队列正在处理中,%s-%s,ip:%s", uid, strMsg, ctx.Header().Get("X-Forwarded-For"))
}
v.Add(uid)
_, err = v.rdb.Rdb.RPush(ctx, queue.Name, strMsg).Result()
if err != nil {
v.Remove(uid)
return "", fmt.Errorf("添加到队列失败:%v", err)
}
return strMsg, nil
}
func (v *Query) getManager(msg string) (*timeslice.Manager, error) {
var req *do.RdsWechatQuery
if err := json.Unmarshal([]byte(msg), &req); err != nil {
return nil, err
}
if req.StartTime == "" || req.EndTime == "" {
return nil, fmt.Errorf("时间参数不能为空")
}
start, err := time.Parse(time.DateTime, req.StartTime)
if err != nil {
return nil, err
}
end, err := time.Parse(time.DateTime, req.EndTime)
if err != nil {
return nil, err
}
manager := &timeslice.Manager{
StartTime: start,
EndTime: end,
ProductNo: req.ProductNo,
BatchNo: req.BatchNo,
GoNum: timeslice.DefaultGoNum, // 协程数量
TimeSliceHours: timeslice.DefaultTimeSliceHours, // 时间间隔
}
if req.GoNum > 0 {
manager.GoNum = req.GoNum
}
if req.TimeSliceHours > 0 {
manager.TimeSliceHours = req.TimeSliceHours
}
return manager, nil
}
func (v *Query) Consumer(ctx context.Context, msg string) error {
defer v.Remove(v.uid(msg))
req, err := v.getManager(msg)
if err != nil {
log.Errorf("微信券查询,前置参数处理失败,msg:%s,err:%v", msg, err)
return nil
}
if err = v.execute(ctx, req); err != nil {
log.Errorf("微信券查询,失败,msg:%s,err:%v", msg, err)
}
return nil
}

View File

@ -0,0 +1,7 @@
package timeslicequery
import (
"github.com/google/wire"
)
var ProviderSetTimeSliceQuery = wire.NewSet(NewQuery)

View File

@ -0,0 +1,65 @@
package timeslicequery
import (
"context"
"voucher/internal/biz/bo"
)
func (v *Query) wechatQuery(ctx context.Context, order *bo.OrderBo, useNum *int) error {
status, err := v.wechatCpnRepo.Query(ctx, order)
if err != nil {
return err
}
if status.IsUse() {
if err = v.queryUsed(ctx, order); err != nil {
return err
}
*useNum += 1
} else if status.IsExpired() {
return v.queryExpired(ctx, order)
}
return nil
}
func (v *Query) queryUsed(ctx context.Context, order *bo.OrderBo) error {
if order.Status.IsUse() {
return v.notify(ctx, order)
}
if err := v.orderRepo.Used(ctx, order.ID); err != nil {
return err
}
return v.notify(ctx, order)
}
func (v *Query) queryExpired(ctx context.Context, order *bo.OrderBo) error {
if order.Status.IsExpired() {
return nil
}
if err := v.orderRepo.Expired(ctx, order.ID); err != nil {
return err
}
return v.notify(ctx, order)
}
func (v *Query) notify(ctx context.Context, order *bo.OrderBo) error {
order, err := v.orderRepo.GetByID(ctx, order.ID)
if err != nil {
return err
}
if _, err = v.cmb.Notify(ctx, order); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,46 @@
package vo
import "errors"
type AvailableType uint8
const (
AvailableTypeFixed AvailableType = iota + 1
AvailableTypeDynamic
)
var AvailableTypeMap = map[AvailableType]string{
AvailableTypeFixed: "固定有效期",
AvailableTypeDynamic: "动态有效期",
}
func (s AvailableType) GetText() string {
if t, ok := AvailableTypeMap[s]; ok {
return t
}
return "未知券领取类型"
}
func (s AvailableType) GetValue() uint8 {
return uint8(s)
}
func (s AvailableType) IsFixed() bool {
return s == AvailableTypeFixed
}
func (s AvailableType) IsDynamic() bool {
return s == AvailableTypeDynamic
}
var AvailableCmbTypeMap = map[AvailableType]CmbAvailableType{
AvailableTypeFixed: CmbAvailableTypeFixed,
AvailableTypeDynamic: CmbAvailableTypeDynamic,
}
func (s AvailableType) GetCmbAvailableType() (CmbAvailableType, error) {
if t, ok := AvailableCmbTypeMap[s]; ok {
return t, nil
}
return "", errors.New("未知券领取类型")
}

107
internal/biz/vo/cache.go Normal file
View File

@ -0,0 +1,107 @@
package vo
import (
"time"
"voucher/internal/pkg/helper"
)
type CacheKey string
const (
CmbOrderLockKey CacheKey = "cmb_order"
CmbQueryLockKey CacheKey = "cmb_query"
CmbProductQueryLockKey CacheKey = "cmb_product_query"
CmbBatchNoticeCacheKey CacheKey = "cmb_batch_notice"
CmbBatchNoticeLockKey CacheKey = "cmb_batch_notice_lock"
NotifyRetryConsume CacheKey = "notify_retry_consume"
OrderConsumeFailAlarmKey CacheKey = "order_consume_fail_alarm"
OrderConsumeFailAlarmLockKey CacheKey = "order_consume_fail_alarm_lock"
WechatNotifyRegisterTagCacheLockKey CacheKey = "register_tag_lock"
WechatNotifyConsumeLockKey CacheKey = "wechat_notify_consume"
)
const (
ProductQueryKey CacheKey = "product_query"
ProductQueryLockKey CacheKey = "product_query_lock"
)
var (
WarningBudgetCron CacheKey = "warning_budget_cron"
WarningBudgetSendIncr CacheKey = "warning_budget_incr"
)
var CacheKeyMap = map[CacheKey]time.Duration{
CmbOrderLockKey: 30 * time.Second,
CmbQueryLockKey: 30 * time.Second,
CmbProductQueryLockKey: 30 * time.Second,
CmbBatchNoticeCacheKey: 21600 * time.Second, // 6小时
CmbBatchNoticeLockKey: 300 * time.Second,
OrderConsumeFailAlarmKey: 3 * time.Hour, // 3小时
OrderConsumeFailAlarmLockKey: 60 * time.Second,
NotifyRetryConsume: 60 * time.Second,
WechatNotifyRegisterTagCacheLockKey: 60 * time.Second,
WechatNotifyConsumeLockKey: 30 * time.Second,
ProductQueryKey: 30 * 86400 * time.Second, // 30天
ProductQueryLockKey: 30 * time.Second,
WarningBudgetSendIncr: 3 * time.Hour,
WarningBudgetCron: 5 * time.Minute,
}
type Cache struct {
Key string
TTL time.Duration
}
func (s CacheKey) GetValue() string {
return string(s)
}
func (s CacheKey) BuildCache(strArr []string) *Cache {
k := s.GetValue()
if len(strArr) > 0 {
k = helper.BuildStr(k, strArr)
}
c := &Cache{
Key: k,
}
ttl, ok := CacheKeyMap[s]
if !ok {
c.TTL = 30 // 默认30秒
}
c.TTL = ttl
return c
}
func (s CacheKey) BuildCacheUint64(ids []uint64) *Cache {
k := s.GetValue()
if len(ids) > 0 {
k = helper.BuildStr(k, ids)
}
c := &Cache{
Key: k,
}
ttl, ok := CacheKeyMap[s]
if !ok {
c.TTL = 30 // 默认30秒
}
c.TTL = ttl
return c
}

View File

@ -0,0 +1,32 @@
package vo
type Channel uint8
const (
OrderChannelWechat Channel = iota + 1
OrderChannelAlipay
)
var OrderChannelMap = map[Channel]string{
OrderChannelWechat: "微信",
OrderChannelAlipay: "支付宝",
}
func (s Channel) GetText() string {
if t, ok := OrderChannelMap[s]; ok {
return t
}
return "未知商品渠道类型"
}
func (s Channel) GetValue() uint8 {
return uint8(s)
}
func (s Channel) IsWeChat() bool {
return s == OrderChannelWechat
}
func (s Channel) IsAlipay() bool {
return s == OrderChannelAlipay
}

51
internal/biz/vo/cmb.go Normal file
View File

@ -0,0 +1,51 @@
package vo
// CmbFuncName . 招行接口名称
type CmbFuncName string
const (
// CmbNotifyFuncName . 券状态回调通知方法
CmbNotifyFuncName CmbFuncName = "updateCodeStatus.json"
)
func (s CmbFuncName) GetValue() string {
return string(s)
}
// CmbStatus . 券通知状态
type CmbStatus string
const (
CmbStatusSuccess CmbStatus = "0" // 券可用
CmbStatusUse CmbStatus = "1" // 券使用
//CmbStatusExpired CmbStatus = "2" // 券过期-待确认是否通知
CmbStatusExpired CmbStatus = "0" // 券过期-等于券没有使用
)
func (s CmbStatus) GetValue() string {
return string(s)
}
// CmbResponseStatus . 响应状态
type CmbResponseStatus string
const (
CmbResponseStatusSuccess CmbResponseStatus = "1000" // 响应成功
CmbResponseStatusFail CmbResponseStatus = "1001" // 响应失败
)
func (s CmbResponseStatus) GetValue() string {
return string(s)
}
// CmbAvailableType . 有效期形式0固定有效期1动态有效期
type CmbAvailableType string
const (
CmbAvailableTypeFixed CmbAvailableType = "0" // 固定有效期
CmbAvailableTypeDynamic CmbAvailableType = "1" // 动态有效期
)
func (s CmbAvailableType) GetValue() string {
return string(s)
}

View File

@ -0,0 +1,32 @@
package vo
type OrderAccountType uint8
const (
OrderAccountTypeOpenId OrderAccountType = iota + 1
OrderAccountTypePhone
)
var OrderAccountTypeMap = map[OrderAccountType]string{
OrderAccountTypeOpenId: "openid/userid",
OrderAccountTypePhone: "手机号",
}
func (s OrderAccountType) GetText() string {
if t, ok := OrderAccountTypeMap[s]; ok {
return t
}
return "未知账号类型"
}
func (s OrderAccountType) GetValue() uint8 {
return uint8(s)
}
func (s OrderAccountType) IsOpenId() bool {
return s == OrderAccountTypeOpenId
}
func (s OrderAccountType) IsPhone() bool {
return s == OrderAccountTypePhone
}

View File

@ -0,0 +1,57 @@
package vo
import "fmt"
type OrderNotifyEvent uint8
const (
OrderNotifyEventSendDEd OrderNotifyEvent = iota + 1
OrderNotifyEventUsed
OrderNotifyEventExpired
)
var OrderNotifyEventMap = map[OrderNotifyEvent]string{
OrderNotifyEventSendDEd: "可用",
OrderNotifyEventUsed: "已实扣",
OrderNotifyEventExpired: "已过期",
}
func (s OrderNotifyEvent) GetText() string {
if t, ok := OrderNotifyEventMap[s]; ok {
return t
}
return "未知通知事件"
}
func (s OrderNotifyEvent) GetValue() uint8 {
return uint8(s)
}
func (s OrderNotifyEvent) IsSendDEd() bool {
return s == OrderNotifyEventSendDEd
}
func (s OrderNotifyEvent) IsUsed() bool {
return s == OrderNotifyEventUsed
}
func (s OrderNotifyEvent) IsExpired() bool {
return s == OrderNotifyEventExpired
}
func (s OrderNotifyEvent) CanNotify() bool {
return s.IsSendDEd() || s.IsUsed() || s.IsExpired()
}
var OrderNotifyEventMapCmbStatus = map[OrderNotifyEvent]CmbStatus{
OrderNotifyEventSendDEd: CmbStatusSuccess,
OrderNotifyEventUsed: CmbStatusUse,
OrderNotifyEventExpired: CmbStatusExpired,
}
func (s OrderNotifyEvent) GetCmbStatusText() (CmbStatus, error) {
if t, ok := OrderNotifyEventMapCmbStatus[s]; ok {
return t, nil
}
return "", fmt.Errorf("cmbStatus[%s]未定义", s)
}

View File

@ -0,0 +1,38 @@
package vo
type OrderNotifyStatus uint8
const (
OrderNotifyStatusWait OrderNotifyStatus = iota + 1
OrderNotifyStatusSuccess
OrderNotifyStatusFail
)
var OrderNotifyStatusMap = map[OrderNotifyStatus]string{
OrderNotifyStatusWait: "待请求",
OrderNotifyStatusSuccess: "请求成功",
OrderNotifyStatusFail: "请求失败",
}
func (s OrderNotifyStatus) GetText() string {
if t, ok := OrderNotifyStatusMap[s]; ok {
return t
}
return "未知请求状态"
}
func (s OrderNotifyStatus) GetValue() uint8 {
return uint8(s)
}
func (s OrderNotifyStatus) IsWait() bool {
return s == OrderNotifyStatusWait
}
func (s OrderNotifyStatus) IsSuccess() bool {
return s == OrderNotifyStatusSuccess
}
func (s OrderNotifyStatus) IsFail() bool {
return s == OrderNotifyStatusFail
}

View File

@ -1 +1,88 @@
package vo
import "fmt"
type OrderStatus uint8
const (
OrderStatusWait OrderStatus = iota + 1
OrderStatusIng
OrderStatusSuccess
OrderStatusFail
OrderStatusUse
OrderStatusExpired
)
var OrderStatusMap = map[OrderStatus]string{
OrderStatusWait: "待发放",
OrderStatusIng: "发放中",
OrderStatusSuccess: "发放成功",
OrderStatusFail: "发放失败",
OrderStatusUse: "已使用",
OrderStatusExpired: "已过期",
}
func (s OrderStatus) GetText() string {
if t, ok := OrderStatusMap[s]; ok {
return t
}
return "未知状态"
}
func (s OrderStatus) GetValue() uint8 {
return uint8(s)
}
func (s OrderStatus) IsWait() bool {
return s == OrderStatusWait
}
func (s OrderStatus) IsIng() bool {
return s == OrderStatusIng
}
func (s OrderStatus) IsSuccess() bool {
return s == OrderStatusSuccess
}
func (s OrderStatus) IsFail() bool {
return s == OrderStatusFail
}
func (s OrderStatus) IsUse() bool {
return s == OrderStatusUse
}
func (s OrderStatus) IsExpired() bool {
return s == OrderStatusExpired
}
func (s OrderStatus) IsCanNotify() bool {
return s.IsSuccess() || s.IsUse() || s.IsExpired()
}
var OrderStatusMapOrderNotifyEvent = map[OrderStatus]OrderNotifyEvent{
OrderStatusSuccess: OrderNotifyEventSendDEd,
OrderStatusUse: OrderNotifyEventUsed,
OrderStatusExpired: OrderNotifyEventExpired,
}
func (s OrderStatus) GetOrderNotifyEvent() (OrderNotifyEvent, error) {
if t, ok := OrderStatusMapOrderNotifyEvent[s]; ok {
return t, nil
}
return 0, fmt.Errorf("CmbStatus[%s]未定义", s)
}
var OrderStatusMapCmbStatus = map[OrderStatus]CmbStatus{
OrderStatusSuccess: CmbStatusSuccess,
OrderStatusUse: CmbStatusUse,
OrderStatusExpired: CmbStatusExpired,
}
func (s OrderStatus) GetCmbStatusText() (CmbStatus, error) {
if t, ok := OrderStatusMapCmbStatus[s]; ok {
return t, nil
}
return "", fmt.Errorf("cmbStatus[%s]未定义", s)
}

View File

@ -0,0 +1,34 @@
package vo
import (
"fmt"
)
type OrderType uint8
const (
OrderTypeCmb OrderType = iota + 1
)
var OrderTypeMap = map[OrderType]string{
OrderTypeCmb: "招行",
}
func (s OrderType) GetText() string {
if t, ok := OrderTypeMap[s]; ok {
return t
}
return "未知类型"
}
func (s OrderType) String() string {
return fmt.Sprintf("%d", s)
}
func (s OrderType) GetValue() uint8 {
return uint8(s)
}
func (s OrderType) IsCmb() bool {
return s == OrderTypeCmb
}

View File

@ -0,0 +1,38 @@
package vo
type WechatNotifyRegisterTagStatus uint8
const (
WechatNotifyRegisterTagStatusWait WechatNotifyRegisterTagStatus = iota + 1
WechatNotifyRegisterTagStatusSuccess
WechatNotifyRegisterTagStatusFail
)
var WechatNotifyRegisterTagStatusMap = map[WechatNotifyRegisterTagStatus]string{
WechatNotifyRegisterTagStatusWait: "待请求",
WechatNotifyRegisterTagStatusSuccess: "请求成功",
WechatNotifyRegisterTagStatusFail: "请求失败",
}
func (s WechatNotifyRegisterTagStatus) GetText() string {
if t, ok := WechatNotifyRegisterTagStatusMap[s]; ok {
return t
}
return "未知请求状态"
}
func (s WechatNotifyRegisterTagStatus) GetValue() uint8 {
return uint8(s)
}
func (s WechatNotifyRegisterTagStatus) IsWait() bool {
return s == WechatNotifyRegisterTagStatusWait
}
func (s WechatNotifyRegisterTagStatus) IsSuccess() bool {
return s == WechatNotifyRegisterTagStatusSuccess
}
func (s WechatNotifyRegisterTagStatus) IsFail() bool {
return s == WechatNotifyRegisterTagStatusFail
}

View File

@ -0,0 +1,38 @@
package vo
type WechatVoucherStatus string
const (
WechatVoucherStatusSended WechatVoucherStatus = "SENDED"
WechatVoucherStatusUsed WechatVoucherStatus = "USED"
WechatVoucherStatusExpired WechatVoucherStatus = "EXPIRED"
)
var VoucherStatusMap = map[WechatVoucherStatus]string{
WechatVoucherStatusSended: "可用",
WechatVoucherStatusUsed: "已实扣",
WechatVoucherStatusExpired: "已过期",
}
func (s WechatVoucherStatus) GetText() string {
if t, ok := VoucherStatusMap[s]; ok {
return t
}
return "未知类型"
}
func (s WechatVoucherStatus) GetValue() string {
return string(s)
}
func (s WechatVoucherStatus) IsSended() bool {
return s == WechatVoucherStatusSended
}
func (s WechatVoucherStatus) IsUsed() bool {
return s == WechatVoucherStatusUsed
}
func (s WechatVoucherStatus) IsExpired() bool {
return s == WechatVoucherStatusExpired
}

View File

@ -1,17 +1,98 @@
package biz
import (
"voucher/internal/biz/repository"
"voucher/internal/biz/thirdrepository"
"sync"
"voucher/internal/biz/cmb"
"voucher/internal/biz/mixrepos"
"voucher/internal/biz/repo"
"voucher/internal/biz/wechatrepo"
"voucher/internal/conf"
"voucher/internal/data"
)
type VoucherBiz struct {
OrderRepo repository.OrderRepo
ThirdMQSend thirdrepository.ThirdMQSend
bc *conf.Bootstrap
rdb *data.Rdb
Cmb *cmb.Cmb
ProductRepo repo.ProductRepo
OrderRepo repo.OrderRepo
OrderBakRepo repo.OrderBakRepo
OrderNotifyRepo repo.OrderNotifyRepo
WechatNotifyRegisterTagRepo repo.WechatNotifyRegisterTagRepo
MqSendMixRepo mixrepos.MQSendMixRepo
GenerateMixRepo mixrepos.GenerateMixRepo
WechatCpnRepo wechatrepo.WechatCpnRepo
BankMultiActivityRepo wechatrepo.BankMultiActivityRepo
DingMixRepo mixrepos.DingMixRepo
CmbMixRepo mixrepos.CmbMixRepo
SmsMixRepo mixrepos.SmsMixRepo
mu sync.RWMutex
queryMap map[string]bool
}
func NewVoucherBiz(orderRepo repository.OrderRepo, thirdMQSend thirdrepository.ThirdMQSend) *VoucherBiz {
return &VoucherBiz{OrderRepo: orderRepo, ThirdMQSend: thirdMQSend}
func NewVoucherBiz(
bc *conf.Bootstrap,
rdb *data.Rdb,
Cmb *cmb.Cmb,
ProductRepo repo.ProductRepo,
OrderRepo repo.OrderRepo,
OrderBakRepo repo.OrderBakRepo,
OrderNotifyRepo repo.OrderNotifyRepo,
WechatNotifyRegisterTagRepo repo.WechatNotifyRegisterTagRepo,
MqSendMixRepo mixrepos.MQSendMixRepo,
GenerateMixRepo mixrepos.GenerateMixRepo,
WechatCpnRepo wechatrepo.WechatCpnRepo,
BankMultiActivityRepo wechatrepo.BankMultiActivityRepo,
DingMixRepo mixrepos.DingMixRepo,
CmbMixRepo mixrepos.CmbMixRepo,
SmsMixRepo mixrepos.SmsMixRepo,
) *VoucherBiz {
return &VoucherBiz{
bc: bc,
rdb: rdb,
Cmb: Cmb,
ProductRepo: ProductRepo,
OrderRepo: OrderRepo,
OrderBakRepo: OrderBakRepo,
OrderNotifyRepo: OrderNotifyRepo,
WechatNotifyRegisterTagRepo: WechatNotifyRegisterTagRepo,
MqSendMixRepo: MqSendMixRepo,
GenerateMixRepo: GenerateMixRepo,
WechatCpnRepo: WechatCpnRepo,
BankMultiActivityRepo: BankMultiActivityRepo,
DingMixRepo: DingMixRepo,
CmbMixRepo: CmbMixRepo,
SmsMixRepo: SmsMixRepo,
queryMap: make(map[string]bool),
}
}
// 1:收单
func (this *VoucherBiz) Get(uid string) bool {
this.mu.Lock()
defer this.mu.Unlock()
if _, ok := this.queryMap[uid]; ok {
return ok
}
return false
}
func (this *VoucherBiz) Add(uid string) {
this.mu.Lock()
defer this.mu.Unlock()
this.queryMap[uid] = true
}
func (this *VoucherBiz) Remove(uid string) {
this.mu.Lock()
defer this.mu.Unlock()
if _, ok := this.queryMap[uid]; ok {
delete(this.queryMap, uid)
}
}

View File

@ -0,0 +1,251 @@
package biz
import (
"context"
"encoding/json"
"fmt"
"github.com/go-kratos/kratos/v2/log"
"github.com/wechatpay-apiv3/wechatpay-go/services/cashcoupons"
"runtime"
"strconv"
"strings"
"time"
"voucher/internal/biz/bo"
"voucher/internal/biz/do"
"voucher/internal/biz/vo"
)
func (this *VoucherBiz) Warning(ctx context.Context, id int32) error {
product, err := this.ProductRepo.GetById(ctx, id)
if err != nil {
return err
}
return this.WarningBudget(ctx, product)
}
func (this *VoucherBiz) WarningBudgetIncr(ctx context.Context, key string, ttl time.Duration) (int64, error) {
// 增加发送计数
count, err := this.rdb.Rdb.IncrBy(ctx, key, 1).Result()
if err != nil {
return 0, err
}
// 如果是第一次发送,设置 过期时间
if count == 1 {
if err = this.rdb.Rdb.Expire(ctx, key, ttl).Err(); err != nil {
return 0, fmt.Errorf("设置过期时间失败: %v", err)
}
}
// 如果发送次数超过 “指定” 条,清除再来
if count > 24 { // 大约2小时
return 0, this.WarningBudgetIncrDel(ctx, key)
}
return count, nil
}
func (this *VoucherBiz) WarningBudgetIncrDel(ctx context.Context, key string) error {
// 检查键是否存在
exists, err := this.rdb.Rdb.Exists(ctx, key).Result()
if err != nil {
return fmt.Errorf("检查键存在性失败: %w", err)
}
// 如果键不存在,直接返回成功
if exists == 0 {
return nil
}
if _, err = this.rdb.Rdb.Del(ctx, key).Result(); err != nil {
return err
}
return nil
}
func (this *VoucherBiz) CronWarningBudget(ctx context.Context) {
uid := "warningBudget"
if b := this.Get(uid); b {
log.Warn("预警查询,上波还未执行完毕,此次暂不执行")
return
}
this.Add(uid)
defer this.Remove(uid)
start := time.Now()
log.Warnf("预警查询,执行开始: %s", start.Format(time.DateTime))
if err := this.cronWarningBudget(ctx); err != nil {
log.Errorf("预警查询,执行失败: %s", err)
}
end := time.Now()
elapsed := end.Sub(start)
log.Warnf("预警查询,开始执行时间%s,执行结束时间%s,代码块执行耗时: %s", start.Format(time.DateTime), end.Format(time.DateTime), elapsed)
return
}
func (this *VoucherBiz) cronWarningBudget(ctx context.Context) error {
return this.ProductRepo.FindWarningBudget(ctx, func(ctx context.Context, rows []*bo.ProductBo) error {
for _, row := range rows {
if err := this.WarningBudget(ctx, row); err != nil {
log.Context(ctx).Errorf("预警查询,处理失败: %s", err)
}
time.Sleep(time.Second * 2)
}
return nil
})
}
func (this *VoucherBiz) WarningBudget(ctx context.Context, product *bo.ProductBo) (wErr error) {
defer func() {
if err := recover(); err != nil {
_, file, line, _ := runtime.Caller(1) // 1 表示获取当前调用者的调用信息
log.Errorf("预警查询,发生错误:req:%s,err:%v,file:%s,line:%d", product.BatchNo, err, file, line)
wErr = fmt.Errorf("预警查询,panic: %v", err)
}
}()
if product.WarningBudget == 0 {
return fmt.Errorf("no warning budget")
}
if product.WarningBudget == 0 {
return fmt.Errorf("no warning budget")
}
now := time.Now()
if product.StartTime == nil || product.EndTime == nil {
return fmt.Errorf("no start or end time")
}
if now.Before(*product.StartTime) {
return fmt.Errorf("not start")
}
if now.After(*product.EndTime) {
return fmt.Errorf("expired")
}
wxResp, err := this.WechatCpnRepo.QueryProduct(ctx, product.MchId, product.BatchNo)
if err != nil {
return err
}
return this.Calculate(ctx, product, wxResp)
}
func (this *VoucherBiz) Calculate(ctx context.Context, product *bo.ProductBo, wxResp *cashcoupons.Stock) error {
w := this.WxResp(wxResp)
b := vo.WarningBudgetSendIncr.BuildCache([]string{product.BatchNo})
key := b.Key
ttl := b.TTL
if w.AllBudget > product.AllBudget {
if err := this.WarningBudgetIncrDel(ctx, key); err != nil {
return err
}
}
if err := this.ProductRepo.UpdateByWxResp(ctx, product.ID, w); err != nil {
return err
}
if product.WarningBudget >= w.AvailableBudget {
count, err2 := this.WarningBudgetIncr(ctx, key, ttl)
if err2 != nil {
return err2
}
if count == 1 {
return this.WarningSend(ctx, product, w)
} else {
log.Warnf("预警查询,当前达到预警第[%d]次,暂不做通知", count)
}
}
return nil
}
func (this *VoucherBiz) WarningSend(ctx context.Context, product *bo.ProductBo, wxResp *do.WxResp) error {
title := fmt.Sprintf("券预算预警通知:%s", product.BatchName)
if err := this.DingMixRepo.SendMessage(ctx, title, formatAsCard(product, wxResp)); err != nil {
return err
}
var warningPerson []do.WarningPerson
if err := json.Unmarshal([]byte(product.WarningPerson), &warningPerson); err != nil {
return err
}
var mobileList []string
for _, person := range warningPerson {
mobileList = append(mobileList, person.Mobile)
}
if len(mobileList) > 0 {
return this.SmsMixRepo.Send(ctx, mobileList, this.bc.AliYunSms.TemplateWarning, buildTemplateParams(product, wxResp))
}
return nil
}
func buildTemplateParams(product *bo.ProductBo, req *do.WxResp) map[string]string {
return map[string]string{
"stock_name": product.BatchName,
"stock_no": product.ProductNo,
"amount": strconv.Itoa(int(req.Amount)),
"all_stock": strconv.Itoa(int(req.AllStock)),
"all_budget": strconv.Itoa(int(req.AllBudget)),
"used_stock": strconv.Itoa(int(req.UsedStock)),
"used_budget": strconv.Itoa(int(req.UsedBudget)),
"available_stock": strconv.Itoa(int(req.AvailableStock)),
"available_budget": strconv.Itoa(int(req.AvailableBudget)),
"budget_usage_rate": fmt.Sprintf("%.1f", req.StockUsageRate),
}
}
func formatAsCard(product *bo.ProductBo, req *do.WxResp) string {
var card strings.Builder
card.WriteString("### " + product.BatchName + "\n\n")
// 基本信息
card.WriteString("#### 🎫 基本信息\n")
card.WriteString(fmt.Sprintf("- **批次号**: %s\n", product.BatchNo))
card.WriteString(fmt.Sprintf("- **活动号**: %s\n", product.ProductNo))
card.WriteString(fmt.Sprintf("- **预警值**: %d元\n", product.WarningBudget))
card.WriteString(fmt.Sprintf("- **面额**: %d元\n", req.Amount))
card.WriteString(fmt.Sprintf("- **总预算**: %d元\n", req.AllBudget))
card.WriteString(fmt.Sprintf("- **总库存**: %d张\n", req.AllStock))
card.WriteString("\n")
// 使用情况
card.WriteString("#### 📊 使用情况\n")
card.WriteString(fmt.Sprintf("- **已发券数**: %d张\n", req.UsedStock))
card.WriteString(fmt.Sprintf("- **已发券金额**: %d元\n", req.UsedBudget))
card.WriteString(fmt.Sprintf("- **剩余库存**: %d张\n", req.AvailableStock))
card.WriteString(fmt.Sprintf("- **剩余预算**: %d元\n", req.AvailableBudget))
card.WriteString("\n")
card.WriteString("#### 📈 使用率\n")
card.WriteString(fmt.Sprintf("- **使用率**: %.1f%%\n", req.StockUsageRate))
return card.String()
}

View File

@ -0,0 +1,146 @@
package biz
import (
"context"
"errors"
"fmt"
"gorm.io/gorm"
errPb "voucher/api/err"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
"voucher/internal/pkg/lock"
)
func (this *VoucherBiz) WechatNotifyConsumer(ctx context.Context, tag string, req *bo.WechatVoucherNotifyBo) error {
c := vo.WechatNotifyConsumeLockKey.BuildCache([]string{tag, req.PlainText.StockID, req.PlainText.CouponID})
return lock.NewMutex(this.rdb.Rdb, c.TTL).Lock(ctx, c.Key, func(ctx context.Context) error {
order, err := this.getOrder(ctx, req)
if err != nil {
return err
}
if req.PlainText.Status.IsSended() {
return this.available(ctx, order)
} else if req.PlainText.Status.IsUsed() {
return this.notifyUsed(ctx, order, req)
} else if req.PlainText.Status.IsExpired() {
return this.expired(ctx, order)
}
return fmt.Errorf("未知通知类型:%s", req.PlainText.Status.GetText())
})
}
func (this *VoucherBiz) getOrder(ctx context.Context, req *bo.WechatVoucherNotifyBo) (*bo.OrderBo, error) {
order, err := this.OrderRepo.GetByCouponId(ctx, req.PlainText.StockCreatorMchid, req.PlainText.StockID, req.PlainText.CouponID)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, err
}
order, err = this.OrderRepo.GetByTransactionId(ctx, req.PlainText.StockCreatorMchid, req.PlainText.StockID, req.PlainText.ConsumeInformation.TransactionID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fmt.Errorf("微信回调消费,订单不存在,StockCreatorMchid:%s,StockID:%s,CouponID:%s,CreateTime:%s",
req.PlainText.StockCreatorMchid,
req.PlainText.StockID,
req.PlainText.CouponID,
req.PlainText.CreateTime,
)
}
return nil, err
}
return order, nil
}
return order, nil
}
func (this *VoucherBiz) notifyUsed(ctx context.Context, order *bo.OrderBo, req *bo.WechatVoucherNotifyBo) error {
if order.Status.IsUse() {
// 券状态已是已使用,忽略不处理
return nil
}
if err := this.OrderRepo.NotifyUsed(ctx, order.ID, req.PlainText.ConsumeInformation.TransactionID); err != nil {
return err
}
return this.notify(ctx, order)
}
func (this *VoucherBiz) available(ctx context.Context, order *bo.OrderBo) error {
if order.Status.IsSuccess() {
// 券状态已是可使用,忽略不处理
return nil
}
if err := this.OrderRepo.Available(ctx, order.ID); err != nil {
return err
}
return this.notify(ctx, order)
}
func (this *VoucherBiz) expired(ctx context.Context, order *bo.OrderBo) error {
if order.Status.IsExpired() {
// 券状态已是已过期,忽略不处理
return nil
}
if err := this.OrderRepo.Expired(ctx, order.ID); err != nil {
return err
}
return this.notify(ctx, order)
}
func (this *VoucherBiz) notify(ctx context.Context, order *bo.OrderBo) error {
if order.ActivityId == "" {
return nil // 多笔立减活动,不做通知(?不知道有没有核销通知,多笔核销情况未知)
}
return this.cmbNotify(ctx, order.ID)
}
func (this *VoucherBiz) cmbNotify(ctx context.Context, orderId uint64) error {
order, err := this.OrderRepo.GetByID(ctx, orderId)
if err != nil {
return err
}
if orderNotify, err2 := this.Cmb.Notify(ctx, order); err != nil {
if !errPb.IsNeedRetryNotify(err2) {
return err2
}
// 第一次通知失败重试入队
// 状态回调接口失败需要支持重试 重试间隔为1分钟、2分钟、12分钟、60分钟、360分钟
return this.PushNotifyRetryDelayMQ(ctx, 60, orderNotify.ID)
}
return nil
}

View File

@ -0,0 +1,130 @@
package biz
import (
"context"
"encoding/json"
"fmt"
"github.com/go-kratos/kratos/v2/log"
"github.com/go-kratos/kratos/v2/transport/http"
"github.com/nacos-group/nacos-sdk-go/util"
"time"
"voucher/internal/biz/bo"
"voucher/internal/biz/do"
)
func (this *VoucherBiz) uid(_ context.Context, msg string) string {
return util.Md5(msg)
}
func (this *VoucherBiz) PushWechatQuery(ctx http.Context, req *do.WechatQuery) error {
if req.ProductNo != "" {
_, err := this.ProductRepo.GetByProductNo(ctx, req.ProductNo)
if err != nil {
return err
}
}
queue := this.bc.RdsMQ.GetWechatQuery()
if queue == nil {
return fmt.Errorf("队列不存在")
}
msg, err := json.Marshal(req)
if err != nil {
return err
}
strMsg := string(msg)
uid := this.uid(ctx, strMsg)
if this.Get(uid) {
return fmt.Errorf("此台服务队列正在处理中,key:%s,ip:%s", uid, ctx.Header().Get("X-Forwarded-For"))
}
this.Add(uid)
_, err = this.rdb.Rdb.RPush(ctx, queue.Name, strMsg).Result()
if err != nil {
this.Remove(uid)
return fmt.Errorf("添加到队列失败:%v", err)
}
return nil
}
func (this *VoucherBiz) WechatQuery(ctx context.Context, msg string) error {
defer this.Remove(this.uid(ctx, msg))
var req *do.WechatQuery
if err := json.Unmarshal([]byte(msg), &req); err != nil {
return err
}
start := time.Now()
startStr := time.Now().String()
log.Warnf("微信券查询处理开始:%s,msg:%s", startStr, msg)
fmt.Printf("微信券查询处理开始:%s,msg:%s", startStr, msg)
n := 0
num := 0
notifyNum := 0
err := this.OrderRepo.FinSucByStockIdInBatches(ctx, req, func(ctx context.Context, rows []*bo.OrderBo) error {
n += 1
for _, order := range rows {
num += 1
if err := this.wechatQuery(ctx, order, &notifyNum); err != nil {
log.Errorf("微信查询券订单状态发生错误,msg:%s,orderNo:%s,couponId:%s,appId:%s,openId:%s,stockId:%s,err:%v",
msg, order.OrderNo, order.VoucherNo, order.AppID, order.Account, order.BatchNo, err)
}
}
groupTime := time.Now()
log.Warnf("微信券查询处理第:%d组,已执行条数:%d,核销通知条数:%d,执行开始时间:%s当前时间:%s,已耗时:%s", n, num, notifyNum, startStr, groupTime.String(), groupTime.Sub(start).String())
return nil
})
endTime := time.Now()
log.Warnf("微信券查询处理耗时:%s,结束时间:%s,处理%d组,处理%d单,核销通知条数:%d,msg:%s", endTime.Sub(start).String(), endTime.String(), n, num, notifyNum, msg)
fmt.Printf("微信券查询处理耗时:%s,结束时间%s,处理%d组,处理%d单,核销通知条数:%d,msg:%s", endTime.Sub(start).String(), endTime.String(), n, num, notifyNum, msg)
return err
}
func (this *VoucherBiz) wechatQuery(ctx context.Context, order *bo.OrderBo, notifyNum *int) error {
status, err := this.WechatCpnRepo.Query(ctx, order)
if err != nil {
return err
}
if status.IsUse() {
return this.queryUsed(ctx, order, notifyNum)
} else if status.IsExpired() {
return this.expired(ctx, order)
}
return nil
}
func (this *VoucherBiz) queryUsed(ctx context.Context, order *bo.OrderBo, notifyNum *int) error {
*notifyNum += 1
if order.Status.IsUse() {
return this.notify(ctx, order)
}
if err := this.OrderRepo.Used(ctx, order.ID); err != nil {
return err
}
return this.notify(ctx, order)
}

View File

@ -0,0 +1,62 @@
package biz
import (
"context"
"fmt"
)
func (this *VoucherBiz) PushWechatRetry(ctx context.Context, batchNo string) error {
product, err := this.ProductRepo.GetByBatchNo(ctx, batchNo)
if err != nil {
return err
}
queue := this.bc.RdsMQ.GetWechatRetry()
if queue == nil {
return fmt.Errorf("队列不存在")
}
_, err = this.rdb.Rdb.RPush(ctx, queue.Name, product.BatchNo).Result()
if err != nil {
return fmt.Errorf("添加到队列失败:%v", err)
}
return nil
}
func (this *VoucherBiz) WechatRetry(ctx context.Context, batchNo string) error {
return nil
//start := time.Now()
//log.Warnf("失败订单重试开始:%s,batchNo:%s", start.String(), batchNo)
//fmt.Printf("失败订单重试开始:%s,batchNo:%s", start.String(), batchNo)
//
//num := 0
//err := this.OrderRepo.FinFailByStockIdInBatches(ctx, batchNo, func(ctx context.Context, rows []*bo.OrderBo) error {
//
// if len(rows) == 0 {
// log.Infof("微信查询券订单状态,batchNo[%s],已处理[%d]单,无订单,结束执行", batchNo, num)
// return nil
// }
//
// for _, order := range rows {
//
// num += 1
// if err := this.orderRetry(ctx, order); err != nil {
// log.Errorf("失败订单重试发生错误,batchNo:%s,orderNo:%s,appId:%s,openId:%s,err:%v",
// batchNo, order.OrderNo, order.AppID, order.Account, err)
// }
//
// }
//
// time.Sleep(1 * time.Second)
//
// return nil
//})
//
//log.Warnf("微信券查询处理耗时:%s,batchNo:%s,处理%d单", time.Now().Sub(start).String(), batchNo, num)
//fmt.Printf("微信券查询处理耗时:%s,batchNo:%s,处理%d单", time.Now().Sub(start).String(), batchNo, num)
//
//return err
}

View File

@ -0,0 +1,9 @@
package wechatrepo
import (
"voucher/internal/biz/bo"
)
type BankMultiActivityRepo interface {
Order(order *bo.OrderBo) (couponId string, err error)
}

View File

@ -0,0 +1,18 @@
package wechatrepo
import (
"context"
"github.com/wechatpay-apiv3/wechatpay-go/services/cashcoupons"
"voucher/internal/biz/bo"
"voucher/internal/biz/vo"
)
type WechatCpnRepo interface {
Order(ctx context.Context, order *bo.OrderBo) (couponId string, err error)
Query(ctx context.Context, order *bo.OrderBo) (vo.OrderStatus, error)
QueryCoupon(ctx context.Context, orderWechat *bo.OrderBo) (*cashcoupons.Coupon, error)
QueryProduct(ctx context.Context, stockCreatorMchId, stockId string) (*cashcoupons.Stock, error)
QueryCallback(ctx context.Context) (*cashcoupons.Callback, error)
SetCallback(ctx context.Context, url string) (*cashcoupons.SetCallbackResponse, error)
RegisterNotifyTag(ctx context.Context, stockID string) error
}

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
syntax = "proto3";
package voucher.config;
option go_package = "voucher/internal/conf;conf";
option go_package = "voucher/cpn/conf;conf";
import "google/protobuf/duration.proto";
@ -10,6 +10,13 @@ message Bootstrap {
Logs logs = 2;
Data data = 3;
RocketMQ rocketMQ = 4;
Wechat wechat = 5;
Cmb cmb = 6;
WechatNotifyMQ wechatNotifyMQ = 7;
Alarm alarm = 8;
Cron cron = 9;
RdsMQ rdsMQ = 10;
AliYunSms aliYunSms = 11;
}
message Server {
@ -63,6 +70,80 @@ message EventMap {
bool isOpenConsumer = 4;
}
message Wechat {
string mchID = 1;
string mchCertificateSerialNumber = 2;
string wechatPayPublicKeyID = 3;
string name = 4;
}
message Cmb {
string mid = 1;
string aid = 2;
string sm2Prk = 3;
string sm2Puk = 4;
string cmbSm2Pik = 5;
string cmbSm2Puk = 6;
string keyAlias = 7;
string cmbKeyAlias = 8;
string orgNo = 9;
string notifyUrl = 10;
int64 noticeStartDays = 11;
int64 noticeEndDays = 12;
}
message WechatNotifyMQ {
string accessKeyId = 1;
string accessKeySecret = 2;
string endPoint = 3;
string regionId = 4;
string instanceId = 5;
string topic = 6;
string tag = 7;
string groupId = 8;
string registerTagUrl = 10;
bool isOpenConsumer = 11;
}
message Alarm {
string webhookURL = 1;
string secret = 2;
bool atAll = 3;
repeated string atMobiles = 4;
repeated string warningMobiles = 5;
}
message Cron {
message CommandMap {
bool isOpen = 1;
string command = 2;
}
bool isOpen = 1;
map<string, CommandMap> commandMap = 2;
}
message RdsMQ {
message Queue {
string name = 1;
bool isOpen = 2;
uint32 retryNum = 3;
uint32 numWorkers = 4;
google.protobuf.Duration waitTime = 5;
}
Queue wechatQuery = 1;
Queue wechatTimeSliceQuery = 2;
Queue wechatRetry = 3;
Queue retryNotify = 4;
}
message AliYunSms {
string accessKeyId = 1;
string accessKeySecret = 2;
string endpoint = 3;
string signName = 4;
string templateWarning = 5;
}
message Logs {
string business = 1;
string access = 2;

View File

@ -15,12 +15,6 @@ func NewDb(db *GormDb) *Db {
}
}
type contextTxKey struct{}
func (d *Db) DB(ctx context.Context) *gorm.DB {
tx, ok := ctx.Value(contextTxKey{}).(*gorm.DB)
if ok {
return tx
}
func (d *Db) DB(_ context.Context) *gorm.DB {
return d.db.Client
}

View File

@ -1,12 +1,9 @@
package data
import (
"database/sql"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"time"
"voucher/internal/conf"
)
@ -14,22 +11,20 @@ type GormDb struct {
Client *gorm.DB
}
func NewGormDb(c *conf.Bootstrap) (*GormDb, func()) {
db1, mf := db(c.Data.Db)
cleanup := func() {
mf()
}
func NewGormDb(c *conf.Bootstrap) *GormDb {
return &GormDb{
Client: db1,
}, cleanup
Client: db(c.Data.Db),
}
}
func db(data *conf.Data_Database) (*gorm.DB, func()) {
mysqlConn, err := sql.Open(data.Driver, data.Source)
func db(data *conf.Data_Database) *gorm.DB {
gormDB, err := gorm.Open(
mysql.New(mysql.Config{Conn: mysqlConn}),
&gorm.Config{Logger: logger.Default.LogMode(logger.Info)},
mysql.Open(data.Source),
&gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
SkipDefaultTransaction: true,
},
)
if err != nil {
@ -41,19 +36,8 @@ func db(data *conf.Data_Database) (*gorm.DB, func()) {
panic("failed to gormDB " + err.Error())
}
// SetMaxIdleConns sets the maximum number of connections in the idle connection pool.
sqlDB.SetMaxIdleConns(int(data.MaxIdle))
// SetMaxOpenConns sets the maximum number of openapi connections to the database.
sqlDB.SetMaxOpenConns(int(data.MaxOpen))
// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
sqlDB.SetConnMaxLifetime(time.Hour)
sqlDB.SetMaxIdleConns(100)
sqlDB.SetMaxOpenConns(1000)
return gormDB, func() {
if mysqlConn != nil {
fmt.Println("关闭 db")
if err := mysqlConn.Close(); err != nil {
fmt.Printf("关闭 db 失败:%v", err)
}
}
}
return gormDB
}

216
internal/data/gorm_test.go Normal file
View File

@ -0,0 +1,216 @@
package data
import (
"context"
"errors"
"fmt"
"google.golang.org/protobuf/types/known/durationpb"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/hints"
"sync"
"testing"
"time"
"voucher/internal/biz/vo"
"voucher/internal/conf"
"voucher/internal/data/model"
)
var source = "voucher:Lsxd@2024@tcp(voucher.rwlb.cn-chengdu.rds.aliyuncs.com:3306)/voucher?parseTime=True&loc=Local"
func Test_gorm_db_create(t *testing.T) {
gormDB, err := gorm.Open(
mysql.Open(source),
&gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
SkipDefaultTransaction: true,
},
)
if err != nil {
t.Fatal("failed to connect database " + err.Error())
}
sqlDB, err := gormDB.DB()
if err != nil {
t.Fatal("failed to gormDB " + err.Error())
}
sqlDB.SetMaxIdleConns(20)
sqlDB.SetMaxOpenConns(100)
start2 := time.Now()
concurrency := 1000 // 调整并发数
errCount := 0
stats := sqlDB.Stats()
fmt.Printf("order create 当前打开连接数:%d, 空闲连接数:%d\n", stats.OpenConnections, stats.Idle)
go func() {
ticker := time.NewTicker(50 * time.Millisecond)
defer ticker.Stop()
for range ticker.C {
fmt.Printf("监控: 当前打开连接数:%d, 空闲连接数:%d\n", stats.OpenConnections, stats.Idle)
}
}()
var wg sync.WaitGroup
wg.Add(concurrency)
for i := 0; i < concurrency; i++ {
go func(i int) {
defer wg.Done()
ctx := context.Background()
now := time.Now()
info := &model.Order{
OrderNo: fmt.Sprintf("test_create_10_%d", i),
OutBizNo: fmt.Sprintf("374252390990605ngywYrSAGE8310_%d", i),
ProductNo: "001",
BatchNo: "001",
Account: "oO3vO5K2nE131-9uMoeYymLhlbYk",
AccountType: 1,
Status: 2,
Type: 1,
AppID: "",
MerchantNo: "wx9ed74283ad25bca1",
Channel: 1,
NotifyUrl: "https://sandbox.cdcc.cmbchina.com/AccessGateway/transIn/updateCodeStatus.json",
Attach: "{}",
CreateTime: &now,
UpdateTime: &now,
}
if info.ProductNo == "001" {
info.VoucherNo = info.OrderNo
info.Status = vo.OrderStatusSuccess.GetValue()
}
ddd := gormDB.WithContext(ctx).Model(model.Order{})
tx := ddd.Create(info)
if tx.Error != nil {
fmt.Printf("写入错误: %v\n", tx.Error)
errCount += 1
}
sqlDBx, _ := ddd.DB()
fmt.Printf("order create 当前打开连接数:%d, 空闲连接数:%d\n", sqlDBx.Stats().OpenConnections, sqlDBx.Stats().Idle)
}(i)
}
fmt.Printf("order create 当前打开连接数:%d, 空闲连接数:%d\n", stats.OpenConnections, stats.Idle)
wg.Wait()
t.Logf("\n--------------连接请求,总请求耗时: %v,总数: %d, 失败次数: %d--------------\n", time.Since(start2), concurrency, errCount)
}
func Test_gorm_db(t *testing.T) {
// 镜像mysql
//data := &conf.Data_Database{
// Driver: "mysql",
// Source: "root:lsxddb123.@tcp(47.108.53.72:3306)/voucher?parseTime=True&loc=Local",
// MaxIdle: 20, // 空闲连接池
// MaxOpen: 200, // 最大连接数
// MaxLifetime: durationpb.New(60), // 5分钟
// IsDebug: false,
//}
// 测试
//data := &conf.Data_Database{
// Driver: "mysql",
// Source: "root:lansexiongdi6,@tcp(47.97.27.195:3306)/voucher?parseTime=True&loc=Local",
// MaxIdle: 2, // 空闲连接池
// MaxOpen: 10, // 最大连接数
// MaxLifetime: durationpb.New(60),
// IsDebug: false,
//}
data := &conf.Data_Database{
Driver: "mysql",
Source: "voucher:Lsxd@2024@tcp(voucher.rwlb.cn-chengdu.rds.aliyuncs.com:3306)/voucher?parseTime=True&loc=Local",
MaxIdle: 20, // 空闲连接池
MaxOpen: 200, // 最大连接数
MaxLifetime: durationpb.New(60), // 5分钟
IsDebug: false,
}
gormDb := db(data)
start2 := time.Now()
concurrency := 10000 // 调整并发数
errCount := 0
var wg sync.WaitGroup
wg.Add(concurrency)
for i := 0; i < concurrency; i++ {
go func(i int) {
defer wg.Done()
//ctx := context.Background()
var info model.Order
tx := gormDb.
//WithContext(ctx).
Model(model.Order{}).
//Clauses(hints.UseIndex("udx_out_biz_no_type")).
Where(model.Order{Type: vo.OrderTypeCmb.GetValue(), OutBizNo: fmt.Sprintf("174252390990605ngywYrSAGE83e1%d", i)}).
First(&info)
if tx.Error != nil {
if errors.Is(tx.Error, gorm.ErrRecordNotFound) {
t.Errorf("未找到记录")
} else {
fmt.Printf("请求错误: %v\n", tx.Error)
errCount += 1
}
}
time.Sleep(1 * time.Microsecond)
}(i)
}
wg.Wait()
t.Logf("\n--------------连接请求,总请求耗时: %v,总数: %d, 失败次数: %d--------------\n", time.Since(start2), concurrency, errCount)
}
func Test_db(t *testing.T) {
maxLifetime := durationpb.New(300) // 5分钟
data := &conf.Data_Database{
Driver: "mysql",
Source: "root:lansexiongdi6,@tcp(47.97.27.195:3306)/voucher?parseTime=True&loc=Local",
MaxIdle: 2, // 空闲连接池
MaxOpen: 10, // 最大连接数
MaxLifetime: maxLifetime,
IsDebug: false,
}
gormDb := db(data)
start2 := time.Now()
errCount := 0
const concurrency = 1
for i := 0; i < concurrency; i++ {
ctx := context.Background()
var info model.Order
tx := gormDb.
WithContext(ctx).
Model(model.Order{}).
Clauses(hints.ForceIndex("udx_out_biz_no_type")).
Where(model.Order{Type: vo.OrderTypeCmb.GetValue(), OutBizNo: fmt.Sprintf("174252390990605ngywYrSAGE83e1%d", i)}).
First(&info)
if tx.Error != nil {
if errors.Is(tx.Error, gorm.ErrRecordNotFound) {
t.Errorf("未找到记录")
} else {
fmt.Printf("请求错误: %v\n", tx.Error)
errCount += 1
}
}
}
t.Logf("\n--------------连接请求,总请求耗时: %v,总数: %d, 失败次数: %d--------------\n", time.Since(start2), concurrency, errCount)
}

View File

@ -0,0 +1,322 @@
package mixrepoimpl
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/go-kratos/kratos/v2/log"
http2 "github.com/go-kratos/kratos/v2/transport/http"
"io"
"net/http"
"net/url"
"time"
err2 "voucher/api/err"
v1 "voucher/api/v1"
"voucher/internal/biz/bo"
"voucher/internal/biz/mixrepos"
"voucher/internal/biz/vo"
"voucher/internal/conf"
"voucher/internal/pkg/cmb"
"voucher/internal/pkg/helper"
"voucher/internal/pkg/request"
)
type CmbMixRepoImpl struct {
bc *conf.Bootstrap
// 连接池复用(优化网络开销)
options *request.Options
}
func NewCmbMixRepoImpl(bc *conf.Bootstrap) mixrepos.CmbMixRepo {
h := http.Header{
"Content-Type": []string{"application/json"},
}
hc := &http.Client{
Timeout: 20 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 100, // 最大空闲连接数
MaxIdleConnsPerHost: 20, // 每个主机的最大空闲连接数
IdleConnTimeout: 30 * time.Second, // 空闲连接超时时间
},
}
return &CmbMixRepoImpl{
bc: bc,
options: request.NewOptions(request.WithHeaders(h), request.WithHttpClient(hc)),
}
}
func (c *CmbMixRepoImpl) recordBody(ctx context.Context) {
httpRequest, ok := http2.RequestFromServerContext(ctx)
if !ok {
log.Errorf("read body not ok")
return
}
bodyBytes, err := io.ReadAll(httpRequest.Body)
if err != nil {
log.Errorf("read body not ok, %v", err)
return
}
log.Errorf("body %v", string(bodyBytes))
}
func (s *CmbMixRepoImpl) OrderVerify(ctx context.Context, req *v1.CmbRequest) (*v1.CmbOrderRequest, error) {
bizStr, err := s.Verify(ctx, req)
if err != nil {
return nil, err2.ErrorCmbVerifyFail(err.Error())
}
if len(bizStr) == 0 {
s.recordBody(ctx)
return nil, err2.ErrorCmbBizContentFail("业务参数获取异常,请检查参数是否正确传递")
}
var bizContent *v1.CmbOrderRequest
if err = json.Unmarshal([]byte(bizStr), &bizContent); err != nil {
return nil, err2.ErrorCmbBizContentFail(err.Error())
}
if err = bizContent.Validate(); err != nil {
return nil, err2.ErrorCmbBizContentFail(err.Error())
}
return bizContent, nil
}
func (s *CmbMixRepoImpl) QueryVerify(ctx context.Context, req *v1.CmbRequest) (*v1.CmbQueryRequest, error) {
bizStr, err := s.Verify(ctx, req)
if err != nil {
return nil, err2.ErrorCmbVerifyFail(err.Error())
}
if len(bizStr) == 0 {
s.recordBody(ctx)
return nil, err2.ErrorCmbBizContentFail("业务参数获取异常,请检查参数是否正确传递")
}
var bizContent *v1.CmbQueryRequest
if err = json.Unmarshal([]byte(bizStr), &bizContent); err != nil {
return nil, err2.ErrorCmbBizContentFail(err.Error())
}
if err = bizContent.Validate(); err != nil {
return nil, err2.ErrorCmbBizContentFail(err.Error())
}
return bizContent, nil
}
func (s *CmbMixRepoImpl) ProductQueryVerify(ctx context.Context, req *v1.CmbRequest) (*v1.CmbQueryProductRequest, error) {
bizStr, err := s.Verify(ctx, req)
if err != nil {
return nil, err
}
if len(bizStr) == 0 {
s.recordBody(ctx)
return nil, err2.ErrorCmbBizContentFail("业务参数获取异常,请检查参数是否正确传递")
}
var bizContent *v1.CmbQueryProductRequest
if err = json.Unmarshal([]byte(bizStr), &bizContent); err != nil {
return nil, err2.ErrorCmbBizContentFail(err.Error())
}
if err = bizContent.Validate(); err != nil {
return nil, err2.ErrorCmbBizContentFail(err.Error())
}
return bizContent, nil
}
func (s *CmbMixRepoImpl) Verify(_ context.Context, req *v1.CmbRequest) (string, error) {
str := cmb.SortStructStr(req)
b, err := cmb.VerifyBody(str, req.Sign, s.bc.Cmb.CmbSm2Puk)
if err != nil {
return "", err2.ErrorCmbVerifyFail(err.Error())
}
if !b {
return "", err2.ErrorCmbVerifyFail("签名验证失败")
}
bizBytes, err := cmb.DecryptBody(&cmb.Decrypts{EncryptBody: req.EncryptBody, PrivateKey: s.bc.Cmb.Sm2Prk})
if err != nil {
return "", err2.ErrorCmbBizContentDecryptFail(err.Error())
}
return string(bizBytes), nil
}
func (s *CmbMixRepoImpl) GetRequest(_ context.Context, reqBo *bo.CmbRequestBo) (*v1.CmbRequest, error) {
encryptBody, err := cmb.EncryptBody(&cmb.Encrypts{SoaPubKey: s.bc.Cmb.CmbSm2Puk, JsonParam: reqBo.BizContent})
if err != nil {
return nil, err
}
req := &v1.CmbRequest{
Mid: s.bc.Cmb.Mid,
Aid: s.bc.Cmb.Aid,
Date: time.Now().Format("20060102150405"),
Random: string(cmb.RandomBytes(16)),
KeyAlias: s.bc.Cmb.KeyAlias,
CmbKeyAlias: s.bc.Cmb.CmbKeyAlias,
EncryptBody: encryptBody,
Sign: "",
}
str := fmt.Sprintf("%s?%s", reqBo.FuncName, cmb.SortStructStr(req))
sign, err := cmb.SignBody(str, s.bc.Cmb.Sm2Prk)
if err != nil {
return nil, err
}
req.Sign = sign
return req, nil
}
func (s *CmbMixRepoImpl) GetMockRequest(_ context.Context, bizContent string) (*v1.CmbRequest, error) {
if len(s.bc.Cmb.Sm2Puk) == 0 {
return nil, errors.New("mock sm2 puk is empty")
}
if len(s.bc.Cmb.CmbSm2Pik) == 0 {
return nil, errors.New("mock cmb sm2 pik is empty")
}
encryptBody, err := cmb.EncryptBody(&cmb.Encrypts{SoaPubKey: s.bc.Cmb.Sm2Puk, JsonParam: bizContent})
if err != nil {
return nil, err
}
req := &v1.CmbRequest{
Mid: s.bc.Cmb.Mid,
Aid: s.bc.Cmb.Aid,
Date: time.Now().Format("20060102150405"),
Random: string(cmb.RandomBytes(16)),
KeyAlias: s.bc.Cmb.KeyAlias,
CmbKeyAlias: s.bc.Cmb.CmbKeyAlias,
EncryptBody: encryptBody,
Sign: "",
}
sign, err := cmb.SignBody(cmb.SortStructStr(req), s.bc.Cmb.CmbSm2Pik)
if err != nil {
return nil, err
}
req.Sign = sign
return req, nil
}
func (s *CmbMixRepoImpl) VerifyResponse(_ context.Context, req *v1.CmbReply) error {
str := cmb.SortStructStr(req)
b, err := cmb.VerifyBody(str, req.Sign, s.bc.Cmb.CmbSm2Puk)
if err != nil {
return err
}
if !b {
return errors.New("签名验证失败")
}
return nil
}
func (s *CmbMixRepoImpl) GetResponse(_ context.Context, reqBo *bo.CmbResponseBo) (*v1.CmbReply, error) {
reply := &v1.CmbReply{
RespCode: reqBo.RespCode,
RespMsg: reqBo.RespMsg,
Date: time.Now().Format("20060102150405"),
KeyAlias: s.bc.Cmb.KeyAlias,
CmbKeyAlias: s.bc.Cmb.CmbKeyAlias,
EncryptBody: "",
Sign: "",
}
if len(reqBo.BizContent) > 0 {
encryptBody, err := cmb.EncryptBody(&cmb.Encrypts{SoaPubKey: s.bc.Cmb.CmbSm2Puk, JsonParam: reqBo.BizContent})
if err != nil {
return nil, err
}
reply.EncryptBody = encryptBody
}
sign, err := cmb.SignBody(cmb.SortStructStr(reply), s.bc.Cmb.Sm2Prk)
if err != nil {
return nil, err
}
reply.Sign = sign
return reply, nil
}
func (s *CmbMixRepoImpl) Request(ctx context.Context, req *v1.CmbRequest, uri string) (*v1.CmbReply, error) {
kvRows := helper.SortStructFieldsByKey(req)
uv := url.Values{}
for _, kv := range kvRows {
uv.Set(kv.Key, fmt.Sprintf("%v", kv.Value))
}
h := http.Header{
"Content-Type": []string{"application/x-www-form-urlencoded"},
}
r := uri + "?" + uv.Encode()
_, bodyBytes, err := request.Post(ctx, r, nil, request.WithHeaders(h), request.WithTimeout(time.Second*20))
if err != nil {
log.Errorf("请求掌上生活报错,url:%s,err:%v", r, err)
return nil, err
}
var response *v1.CmbReply
if err = json.Unmarshal(bodyBytes, &response); err != nil {
log.Errorf("请求掌上生活返回数据解析报错:%s,url:%s,bodyBytes:%s", err.Error(), r, string(bodyBytes))
return nil, err
}
if response.RespCode != vo.CmbResponseStatusSuccess.GetValue() {
log.Errorf("请求掌上生活返回报错:msg:%s,url:%s,bodyBytes:%s", response.RespMsg, r, string(bodyBytes))
return nil, fmt.Errorf(response.RespMsg)
}
return response, nil
}
func (s *CmbMixRepoImpl) Decrypt(_ context.Context, encryptBody string) (string, error) {
if len(s.bc.Cmb.CmbSm2Pik) == 0 {
return "", errors.New("mock CmbSm2Pik is empty")
}
rs, err := cmb.DecryptBody(&cmb.Decrypts{EncryptBody: encryptBody, PrivateKey: s.bc.Cmb.CmbSm2Pik})
if err != nil {
return "", err
}
return string(rs), nil
}

View File

@ -0,0 +1,44 @@
package mixrepoimpl
import (
"context"
"fmt"
"voucher/internal/biz/mixrepos"
"voucher/internal/conf"
"voucher/internal/pkg/ding"
)
type DingMixRepoImpl struct {
bc *conf.Bootstrap
client *ding.TalkClient
}
func NewDingMixRepoImpl(bc *conf.Bootstrap) mixrepos.DingMixRepo {
client := ding.NewDingTalkClient(bc.Alarm.WebhookURL, bc.Alarm.Secret)
return &DingMixRepoImpl{bc: bc, client: client}
}
func (s *DingMixRepoImpl) SendMessage(_ context.Context, title, text string) error {
if err := s.client.SendMarkdownMessage(title, text, s.bc.Alarm.WarningMobiles, false); err != nil {
return fmt.Errorf("markdown 消息发送失败: %v", err)
}
return nil
}
func (s *DingMixRepoImpl) SendMarkdownMessage(_ context.Context, title, text string) error {
isAtAll := false
if len(s.bc.Alarm.AtMobiles) == 0 {
isAtAll = true
}
if err := s.client.SendMarkdownMessage(title, text, s.bc.Alarm.AtMobiles, isAtAll); err != nil {
return fmt.Errorf("markdown 消息发送失败: %v", err)
}
return nil
}

View File

@ -0,0 +1,46 @@
package mixrepoimpl
import (
"context"
"fmt"
"github.com/bwmarrin/snowflake"
"os"
"voucher/internal/biz/mixrepos"
"voucher/internal/pkg/helper"
)
type GenerateRepoImpl struct {
node *snowflake.Node
}
func NewGenerateMixRepoImpl() (mixrepos.GenerateMixRepo, error) {
g := &GenerateRepoImpl{}
name, err := os.Hostname()
if err != nil {
return nil, err
}
serverId := helper.HashMod(name)
node, err := snowflake.NewNode(int64(serverId))
if err != nil {
return nil, err
}
g.node = node
return g, nil
}
// GeneratorString 生成字符串
func (s *GenerateRepoImpl) GeneratorString(_ context.Context, uid string) string {
id := helper.HashMod(uid)
return fmt.Sprintf("%s%d", s.node.Generate().String(), id)
}
// GeneratorNumber 生成 int64
func (s *GenerateRepoImpl) GeneratorNumber(_ context.Context, uid string) int64 {
id := helper.HashMod(uid)
return s.node.Generate().Int64() + int64(id)
}

View File

@ -1,28 +1,28 @@
package thirdrepositoryimpl
package mixrepoimpl
import (
"context"
"github.com/go-kratos/kratos/v2/log"
"voucher/internal/biz/thirdrepository"
"voucher/internal/biz/mixrepos"
"voucher/internal/data"
"voucher/internal/pkg/mq"
)
type ThirdMQSendImpl struct {
type MQSendMixRepoImpl struct {
mq *data.RocketMQ
}
func NewMQSendImpl(mq *data.RocketMQ) thirdrepository.ThirdMQSend {
return &ThirdMQSendImpl{
func NewMQSendMixRepoImpl(mq *data.RocketMQ) mixrepos.MQSendMixRepo {
return &MQSendMixRepoImpl{
mq: mq,
}
}
func (s *ThirdMQSendImpl) SendSync(ctx context.Context, topicName string, body []byte, sendOptions ...mq.SendOption) error {
func (s *MQSendMixRepoImpl) SendSync(ctx context.Context, topicName string, body []byte, sendOptions ...mq.SendOption) error {
return s.mq.MqProducer.SendSync(ctx, topicName, body, sendOptions...)
}
func (s *ThirdMQSendImpl) SendASync(ctx context.Context, topicName string, body []byte, errFn func(error), sendOptions ...mq.SendOption) error {
func (s *MQSendMixRepoImpl) SendASync(ctx context.Context, topicName string, body []byte, errFn func(error), sendOptions ...mq.SendOption) error {
err := s.mq.MqProducer.SendAsync(ctx, topicName, body, func(err error) {
if err == nil {
return
@ -33,7 +33,7 @@ func (s *ThirdMQSendImpl) SendASync(ctx context.Context, topicName string, body
return err
}
func (s *ThirdMQSendImpl) SendAsyncNotFn(ctx context.Context, topic string, body []byte, sendOptions ...mq.SendOption) error {
func (s *MQSendMixRepoImpl) SendAsyncNotFn(ctx context.Context, topic string, body []byte, sendOptions ...mq.SendOption) error {
err := s.mq.MqProducer.SendAsync(ctx, topic, body, func(err error) {
if err == nil {
return

View File

@ -0,0 +1,14 @@
package mixrepoimpl
import (
"github.com/google/wire"
)
// ProviderMixRepoImplSet is providers.
var ProviderMixRepoImplSet = wire.NewSet(
NewGenerateMixRepoImpl,
NewMQSendMixRepoImpl,
NewCmbMixRepoImpl,
NewDingMixRepoImpl,
NewSmsMixRepoImpl,
)

View File

@ -0,0 +1,35 @@
package mixrepoimpl
import (
"context"
"voucher/internal/biz/mixrepos"
"voucher/internal/conf"
"voucher/internal/pkg/sms"
)
type SmsMixRepoImpl struct {
smsService sms.Service
}
func NewSmsMixRepoImpl(bc *conf.Bootstrap) (mixrepos.SmsMixRepo, error) {
config := sms.Config{
AccessKeyID: bc.AliYunSms.AccessKeyId,
AccessKeySecret: bc.AliYunSms.AccessKeySecret,
Endpoint: bc.AliYunSms.Endpoint,
SignName: bc.AliYunSms.SignName,
RetryTimes: 1,
Timeout: 15,
}
smsService, err := sms.NewService(config)
if err != nil {
return nil, err
}
return &SmsMixRepoImpl{smsService: smsService}, nil
}
func (s *SmsMixRepoImpl) Send(ctx context.Context, phoneNumbers []string, templateCode string, params map[string]string) error {
return s.smsService.SendSMS(ctx, phoneNumbers, templateCode, params)
}

View File

@ -12,17 +12,28 @@ const TableNameOrder = "order"
// Order mapped from table <order>
type Order struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
OrderNo string `gorm:"column:order_no;not null" json:"order_no"`
OutBizNo string `gorm:"column:out_biz_no;not null;comment:外部交易号" json:"out_biz_no"` // 外部交易号
ProductNo string `gorm:"column:product_no;not null;comment:商品编号" json:"product_no"` // 商品编号
Account string `gorm:"column:account;not null;comment:充值账号" json:"account"` // 充值账号
AccountType uint8 `gorm:"column:account_type;not null;comment:1:oepnid/userid 2:手机号" json:"account_type"` // 1:oepnid/userid 2:手机号
Status uint8 `gorm:"column:status;not null;comment:1:待发放 2:发放中 3:发放成功 4:发放失败" json:"status"` // 1:待发放 2:发放中 3:发放成功 4:发放失败
AppID string `gorm:"column:app_id;not null;comment:批次所属应用" json:"app_id"` // 批次所属应用
MerchantNo string `gorm:"column:merchant_no;not null;comment:创建批次号的商户号" json:"merchant_no"` // 创建批次号的商户号
Channel uint8 `gorm:"column:channel;not null;comment:1:微信 2:支付宝" json:"channel"` // 1:微信 2:支付宝
CreateTime *time.Time `gorm:"column:create_time" json:"create_time"`
ID uint64 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
OrderNo string `gorm:"column:order_no;not null" json:"order_no"`
VoucherNo string `gorm:"column:voucher_no;not null" json:"voucher_no"`
OutBizNo string `gorm:"column:out_biz_no;not null;comment:外部交易号" json:"out_biz_no"` // 外部交易号
ProductNo string `gorm:"column:product_no;not null;comment:商品编号" json:"product_no"` // 商品编号
BatchNo string `gorm:"column:batch_no;not null;comment:立减金批次号" json:"batch_no"` // 立减金批次号
ActivityId string `gorm:"column:activity_id;not null;comment:activity_id" json:"activity_id"` // activity_id
Account string `gorm:"column:account;not null;comment:充值账号" json:"account"` // 充值账号
AccountType uint8 `gorm:"column:account_type;not null;comment:1:oepnid/userid 2:手机号" json:"account_type"` // 1:oepnid/userid 2:手机号
Type uint8 `gorm:"column:type;not null;comment:1:招行" json:"type"`
Status uint8 `gorm:"column:status;not null;comment:1:待发放 2:发放中 3:发放成功 4:发放失败" json:"status"` // 1:待发放 2:发放中 3:发放成功 4:发放失败
AppID string `gorm:"column:app_id;not null;comment:批次所属应用" json:"app_id"` // 批次所属应用
MerchantNo string `gorm:"column:merchant_no;not null;comment:创建批次号的商户号" json:"merchant_no"` // 创建批次号的商户号
NotifyUrl string `gorm:"column:notify_url;not null;comment:回调地址" json:"notify_url"`
Channel uint8 `gorm:"column:channel;not null;comment:1:微信 2:支付宝" json:"channel"` // 1:微信 2:支付宝
Remark string `gorm:"column:remark;not null;comment:remark" json:"remark"`
Attach string `gorm:"column:attach;not null;comment:attach" json:"attach"`
ReceiveSuccessTime *time.Time `gorm:"column:receive_success_time" json:"receive_success_time"`
LastUseTime *time.Time `gorm:"column:last_use_time" json:"last_use_time"`
TransactionId string `gorm:"column:transaction_id;not null" json:"transaction_id"`
CreateTime *time.Time `gorm:"column:create_time" json:"create_time"`
UpdateTime *time.Time `gorm:"column:update_time" json:"update_time"`
}
// TableName Order's table name

View File

@ -0,0 +1,42 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
)
const TableNameOrderBak = "order_bak"
// Order mapped from table <order>
type OrderBak struct {
ID uint64 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
OrderNo string `gorm:"column:order_no;not null" json:"order_no"`
VoucherNo string `gorm:"column:voucher_no;not null" json:"voucher_no"`
OutBizNo string `gorm:"column:out_biz_no;not null;comment:外部交易号" json:"out_biz_no"` // 外部交易号
ProductNo string `gorm:"column:product_no;not null;comment:商品编号" json:"product_no"` // 商品编号
BatchNo string `gorm:"column:batch_no;not null;comment:立减金批次号" json:"batch_no"` // 立减金批次号
ActivityId string `gorm:"column:activity_id;not null;comment:activity_id" json:"activity_id"` // activity_id
Account string `gorm:"column:account;not null;comment:充值账号" json:"account"` // 充值账号
AccountType uint8 `gorm:"column:account_type;not null;comment:1:oepnid/userid 2:手机号" json:"account_type"` // 1:oepnid/userid 2:手机号
Type uint8 `gorm:"column:type;not null;comment:1:招行" json:"type"`
Status uint8 `gorm:"column:status;not null;comment:1:待发放 2:发放中 3:发放成功 4:发放失败" json:"status"` // 1:待发放 2:发放中 3:发放成功 4:发放失败
AppID string `gorm:"column:app_id;not null;comment:批次所属应用" json:"app_id"` // 批次所属应用
MerchantNo string `gorm:"column:merchant_no;not null;comment:创建批次号的商户号" json:"merchant_no"` // 创建批次号的商户号
NotifyUrl string `gorm:"column:notify_url;not null;comment:回调地址" json:"notify_url"`
Channel uint8 `gorm:"column:channel;not null;comment:1:微信 2:支付宝" json:"channel"` // 1:微信 2:支付宝
Remark string `gorm:"column:remark;not null;comment:remark" json:"remark"`
Attach string `gorm:"column:attach;not null;comment:attach" json:"attach"`
ReceiveSuccessTime *time.Time `gorm:"column:receive_success_time" json:"receive_success_time"`
LastUseTime *time.Time `gorm:"column:last_use_time" json:"last_use_time"`
TransactionId string `gorm:"column:transaction_id;not null" json:"transaction_id"`
CreateTime *time.Time `gorm:"column:create_time" json:"create_time"`
UpdateTime *time.Time `gorm:"column:update_time" json:"update_time"`
}
// TableName Order's table name
func (*OrderBak) TableName() string {
return TableNameOrderBak
}

View File

@ -0,0 +1,32 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
)
const TableNameOrderNotify = "order_notify"
// OrderNotify mapped from table <order_notify>
type OrderNotify struct {
ID uint64 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
OrderNo string `gorm:"column:order_no;not null" json:"order_no"`
Status uint8 `gorm:"column:status;not null;comment:状态" json:"status"`
Event uint8 `gorm:"column:event;not null;comment:event" json:"event"`
Channel uint8 `gorm:"column:channel;not null;comment:channel" json:"channel"`
Type uint8 `gorm:"column:type;not null;comment:1:招行" json:"type"`
Request string `gorm:"column:request;not null" json:"request"`
Responses string `gorm:"column:responses" json:"responses"`
Remark string `gorm:"column:remark" json:"remark"`
NotifyUrl string `gorm:"column:notify_url;not null;comment:回调地址" json:"notify_url"`
CreateTime *time.Time `gorm:"column:create_time;not null" json:"create_time"`
UpdateTime *time.Time `gorm:"column:update_time" json:"update_time"`
}
// TableName OrderNotify's table name
func (*OrderNotify) TableName() string {
return TableNameOrderNotify
}

View File

@ -0,0 +1,39 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
)
const TableNameProduct = "product"
// Product mapped from table <product>
type Product struct {
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
Name string `gorm:"column:name;not null;comment:商品名称" json:"name"` // 商品名称
ProductNo string `gorm:"column:product_no;not null;comment:商品编号" json:"product_no"` // 商品编号
BatchName string `gorm:"column:batch_name;not null;comment:批次名称" json:"batch_name"` // 批次名称
BatchNo string `gorm:"column:batch_no;not null;comment:立减金批次号" json:"batch_no"` // 立减金批次号
ActivityId string `gorm:"column:activity_id;not null;comment:activity_id" json:"activity_id"` // activity_id
MchId string `gorm:"column:mch_id;not null;comment:商户号,创建批次的商户号" json:"mch_id"` // 商户号,创建批次的商户号
Channel uint8 `gorm:"column:channel;not null;comment:1:微信 2:支付宝" json:"channel"` // 1:微信 2:支付宝
AvailableType uint8 `gorm:"column:available_type;not null;comment:1:固定有效期 2:动态有效期" json:"available_type"`
AvailableDays uint32 `gorm:"column:available_days;not null;comment:领取后多少天内" json:"available_days"`
Amount int64 `gorm:"column:amount;not null;default:0" json:"amount"`
AllBudget int64 `gorm:"column:all_budget;not null;default:0" json:"all_budget"`
AvailableBudget int64 `gorm:"column:available_budget;not null;default:0" json:"available_budget"`
WarningBudget int64 `gorm:"column:warning_budget;not null;default:0" json:"warning_budget"` // 预警预算=0不做预警
WarningPerson string `gorm:"column:warning_person" json:"warning_person"`
StartTime *time.Time `gorm:"column:start_time;not null" json:"start_time"`
EndTime *time.Time `gorm:"column:end_time;not null" json:"end_time"`
CreateTime *time.Time `gorm:"column:create_time;not null" json:"create_time"`
UpdateTime *time.Time `gorm:"column:update_time" json:"update_time"`
}
// TableName Product's table name
func (*Product) TableName() string {
return TableNameProduct
}

View File

@ -0,0 +1,28 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
)
const TableNameWechatNotifyRegisterTag = "wechat_notify_register_tag"
// WechatNotifyRegisterTag mapped from table <wechat_notify_register_tag>
type WechatNotifyRegisterTag struct {
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
StockID string `gorm:"column:stock_id;not null" json:"stock_id"`
StockCreatorMchID string `gorm:"column:stock_creator_mch_id;not null" json:"stock_creator_mch_id"`
Tag string `gorm:"column:tag;not null" json:"tag"`
Status uint8 `gorm:"column:status;not null" json:"status"`
Remark string `gorm:"column:remark;not null" json:"remark"`
CreateTime *time.Time `gorm:"column:create_time;not null" json:"create_time"`
UpdateTime *time.Time `gorm:"column:update_time" json:"update_time"`
}
// TableName WechatNotifyRegisterTag's table name
func (*WechatNotifyRegisterTag) TableName() string {
return TableNameWechatNotifyRegisterTag
}

Some files were not shown because too many files have changed in this diff Show More