Browse Source

initial commit

LiYi 2 weeks ago
commit
c688eb1ecd
78 changed files with 4978 additions and 0 deletions
  1. 8 0
      .idea/.gitignore
  2. 18 0
      .idea/compiler.xml
  3. 6 0
      .idea/encodings.xml
  4. 20 0
      .idea/jarRepositories.xml
  5. 12 0
      .idea/misc.xml
  6. 4 0
      .idea/vcs.xml
  7. 135 0
      README.md
  8. 68 0
      pom.xml
  9. 17 0
      src/main/java/org/jebot/JEBot.java
  10. 90 0
      src/main/java/org/jebot/config/BotConfig.java
  11. 58 0
      src/main/java/org/jebot/config/JpaJebotConfig.java
  12. 58 0
      src/main/java/org/jebot/config/JpaXxpayConfig.java
  13. 126 0
      src/main/java/org/jebot/constant/Constant.java
  14. 26 0
      src/main/java/org/jebot/handler/AbstractHandler.java
  15. 161 0
      src/main/java/org/jebot/handler/HandlerManager.java
  16. 79 0
      src/main/java/org/jebot/handler/dto/BotMessage.java
  17. 39 0
      src/main/java/org/jebot/handler/impl/AuthHandler.java
  18. 89 0
      src/main/java/org/jebot/handler/impl/HelpHandler.java
  19. 27 0
      src/main/java/org/jebot/handler/impl/IdHandler.java
  20. 39 0
      src/main/java/org/jebot/handler/impl/SqlInjectionFilterHandler.java
  21. 72 0
      src/main/java/org/jebot/handler/impl/admin/AdminHandler.java
  22. 88 0
      src/main/java/org/jebot/handler/impl/calculator/CalculatorHandler.java
  23. 22 0
      src/main/java/org/jebot/handler/impl/change/GroupChangeHandler.java
  24. 131 0
      src/main/java/org/jebot/handler/impl/channel/ChannelAccountBookHandler.java
  25. 82 0
      src/main/java/org/jebot/handler/impl/channel/ChannelAccountBookHistoryHandler.java
  26. 127 0
      src/main/java/org/jebot/handler/impl/channel/ChannelBindGroupOrUnbindGroupHandler.java
  27. 179 0
      src/main/java/org/jebot/handler/impl/channel/ChannelInfoHandler.java
  28. 94 0
      src/main/java/org/jebot/handler/impl/channel/ChannelStausHandler.java
  29. 96 0
      src/main/java/org/jebot/handler/impl/channel/ChannelSuccessRateHandler.java
  30. 301 0
      src/main/java/org/jebot/handler/impl/complaint/ComplaintHandler.java
  31. 134 0
      src/main/java/org/jebot/handler/impl/merchant/MerchantAccountBookHandler.java
  32. 82 0
      src/main/java/org/jebot/handler/impl/merchant/MerchantAccountBookHistoryHandler.java
  33. 154 0
      src/main/java/org/jebot/handler/impl/merchant/MerchantBindGroupOrUnbindGroupHandler.java
  34. 61 0
      src/main/java/org/jebot/handler/impl/merchant/MerchantDropOrderHandler.java
  35. 138 0
      src/main/java/org/jebot/handler/impl/merchant/MerchantInfoHandler.java
  36. 250 0
      src/main/java/org/jebot/handler/impl/merchant/MerchantOneClickSettlementHandler.java
  37. 56 0
      src/main/java/org/jebot/handler/impl/merchant/MerchantStatusHandler.java
  38. 104 0
      src/main/java/org/jebot/handler/impl/merchant/MerchantSuccessRateHandler.java
  39. 73 0
      src/main/java/org/jebot/handler/impl/merchant/MerchantWarnHandler.java
  40. 44 0
      src/main/java/org/jebot/handler/impl/price/PriceQueryHandler.java
  41. 39 0
      src/main/java/org/jebot/models/jebot/BotAccountBook.java
  42. 50 0
      src/main/java/org/jebot/models/jebot/BotAccountBookHistory.java
  43. 33 0
      src/main/java/org/jebot/models/jebot/BotComplaint.java
  44. 46 0
      src/main/java/org/jebot/models/jebot/BotGroup.java
  45. 37 0
      src/main/java/org/jebot/models/jebot/BotModel.java
  46. 23 0
      src/main/java/org/jebot/models/jebot/BotUser.java
  47. 33 0
      src/main/java/org/jebot/models/jebot/BotWarn.java
  48. 35 0
      src/main/java/org/jebot/models/xxpay/MchAccount.java
  49. 27 0
      src/main/java/org/jebot/models/xxpay/MchInfo.java
  50. 244 0
      src/main/java/org/jebot/models/xxpay/PayOrder.java
  51. 27 0
      src/main/java/org/jebot/models/xxpay/PayPassage.java
  52. 38 0
      src/main/java/org/jebot/models/xxpay/TransOrder.java
  53. 17 0
      src/main/java/org/jebot/repository/jebot/BotAccountBookHistoryRepository.java
  54. 24 0
      src/main/java/org/jebot/repository/jebot/BotAccountBookRepository.java
  55. 12 0
      src/main/java/org/jebot/repository/jebot/BotComplaintRepository.java
  56. 46 0
      src/main/java/org/jebot/repository/jebot/BotGroupRepository.java
  57. 23 0
      src/main/java/org/jebot/repository/jebot/BotUserRepository.java
  58. 14 0
      src/main/java/org/jebot/repository/jebot/BotWarnRepository.java
  59. 25 0
      src/main/java/org/jebot/repository/xxpay/MchAccountRepository.java
  60. 19 0
      src/main/java/org/jebot/repository/xxpay/MchInfoRepository.java
  61. 40 0
      src/main/java/org/jebot/repository/xxpay/PayOrderRepository.java
  62. 27 0
      src/main/java/org/jebot/repository/xxpay/PayPassageRepository.java
  63. 16 0
      src/main/java/org/jebot/repository/xxpay/TransOrderRepository.java
  64. 30 0
      src/main/java/org/jebot/rest/BotRest.java
  65. 185 0
      src/main/java/org/jebot/scheduled/MchScheduled.java
  66. 21 0
      src/main/java/org/jebot/service/IAccountBookService.java
  67. 7 0
      src/main/java/org/jebot/service/IMchService.java
  68. 19 0
      src/main/java/org/jebot/service/dto/UpdateBalance.java
  69. 77 0
      src/main/java/org/jebot/service/impl/AccountBookServiceImpl.java
  70. 31 0
      src/main/java/org/jebot/service/impl/MchServiceImpl.java
  71. 26 0
      src/main/java/org/jebot/util/DateUtil.java
  72. 70 0
      src/main/java/org/jebot/util/ImageUtil.java
  73. 73 0
      src/main/java/org/jebot/util/PriceUtil.java
  74. 17 0
      src/main/java/org/jebot/util/QueryType.java
  75. 12 0
      src/main/java/org/jebot/util/SnowflakeId.java
  76. 78 0
      src/main/resources/application.yml
  77. 69 0
      src/main/resources/logback-spring.xml
  78. BIN
      src/main/resources/msyh.ttf

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 18 - 0
.idea/compiler.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <annotationProcessing>
+      <profile name="Maven default annotation processors profile" enabled="true">
+        <sourceOutputDir name="target/generated-sources/annotations" />
+        <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
+        <outputRelativeToContentRoot value="true" />
+        <module name="jebot" />
+      </profile>
+    </annotationProcessing>
+  </component>
+  <component name="JavacSettings">
+    <option name="ADDITIONAL_OPTIONS_OVERRIDE">
+      <module name="jebot" options="-parameters" />
+    </option>
+  </component>
+</project>

+ 6 - 0
.idea/encodings.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding">
+    <file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
+  </component>
+</project>

+ 20 - 0
.idea/jarRepositories.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="RemoteRepositoriesConfiguration">
+    <remote-repository>
+      <option name="id" value="central" />
+      <option name="name" value="Central Repository" />
+      <option name="url" value="https://repo.maven.apache.org/maven2" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="central" />
+      <option name="name" value="Maven Central repository" />
+      <option name="url" value="https://repo1.maven.org/maven2" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="jboss.community" />
+      <option name="name" value="JBoss Community repository" />
+      <option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
+    </remote-repository>
+  </component>
+</project>

+ 12 - 0
.idea/misc.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ExternalStorageConfigurationManager" enabled="true" />
+  <component name="MavenProjectsManager">
+    <option name="originalFiles">
+      <list>
+        <option value="$PROJECT_DIR$/pom.xml" />
+      </list>
+    </option>
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="corretto-1.8" project-jdk-type="JavaSDK" />
+</project>

+ 4 - 0
.idea/vcs.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings" defaultProject="true" />
+</project>

+ 135 - 0
README.md

@@ -0,0 +1,135 @@
+# 机器人命令
+```
+z0: 查询支付宝价格
+w0: 查询微信价格
+b0: 查询银行价格
+10(+-*/)10: 加减乘除
+sh: 群组商户信息
+ye: 群组商户余额
+qd: 群组通道信息
+cgl: 通道查询成功率
+cgl: 商户查询成功率
+---下方命令需管理权限--
+tjgl: 添加管理(tjgl用户名)
+scgl: 删除管理(scgl用户名)
+gbsh: 关闭商户
+kqsh: 开启商户
+jssh: 所有商户出账单
+jssh商户号: “指定商户出账单”(jssh2000000)
+dsyj: 代收预警阀值设置(dsyj10)
+dfyj: 代付预警阀值设置(dfyj10)
+bdsh商户号: "绑定商户"(bdsh2000000)
+jbsh: 解绑商户
+bdqd通道编码: "绑定通道",多个用英文逗号隔开(bdqd1,2,3)
+jbqd通道编号: "解绑通道",多个用英文逗号隔开(jbqd1,2,3)
+gbqd通道编码: "关闭通道",多个用英文逗号隔开(gbqd1,2,3)
+kqqd通道编号: "开启通道",多个用英文逗号隔开(kqqd1,2,3)
+ds: "代收记账"(ds500或ds-500)
+df: "代付记账"(df500或df-500)
+dsjl: 代收记账24小时历史
+dfjl: 代付记账24小时历史
+```
+
+
+
+# 文件简介
+
+```
+/home/luck/Data/workspace/jebot/jebot/src
+├── main
+│   ├── java
+│   │   └── org
+│   │       └── jebot
+│   │           ├── config                          # 配置相关类
+│   │           │   ├── BotConfig.java              # 机器人配置类
+│   │           │   ├── JpaJebotConfig.java        # JEBot 模块的 JPA 配置类
+│   │           │   └── JpaXxpayConfig.java        # Xxpay 模块的 JPA 配置类
+│   │           ├── constant                       # 常量定义类
+│   │           │   └── Constant.java              # 全局常量类
+│   │           ├── handler                        # 消息处理器相关类
+│   │           │   ├── AbstractHandler.java       # 抽象处理器类,定义通用逻辑
+│   │           │   ├── dto
+│   │           │   │   └── BotMessage.java        # 机器人消息的数据传输对象 (DTO)
+│   │           │   ├── HandlerManager.java        # 处理器管理类
+│   │           │   └── impl                       # 具体处理器实现
+│   │           │       ├── admin
+│   │           │       │   └── AdminHandler.java  # 管理员相关消息处理器
+│   │           │       ├── AuthHandler.java       # 认证相关消息处理器
+│   │           │       ├── calculator
+│   │           │       │   └── CalculatorHandler.java # 计算器功能处理器
+│   │           │       ├── change
+│   │           │       │   └── GroupChangeHandler.java # 群组变更处理器
+│   │           │       ├── channel                # 通道相关处理器
+│   │           │       │   ├── ChannelAccountBookHandler.java          # 通道账户账本处理器
+│   │           │       │   ├── ChannelAccountBookHistoryHandler.java   # 通道账户历史记录处理器
+│   │           │       │   ├── ChannelBindGroupOrUnbindGroupHandler.java # 通道绑定/解绑群组处理器
+│   │           │       │   ├── ChannelInfoHandler.java                 # 通道信息处理器
+│   │           │       │   ├── ChannelStausHandler.java                # 通道状态处理器
+│   │           │       │   └── ChannelSuccessRateHandler.java          # 通道成功率处理器
+│   │           │       ├── complaint
+│   │           │       │   └── ComplaintHandler.java   # 投诉相关处理器
+│   │           │       ├── HelpHandler.java            # 帮助信息处理器
+│   │           │       ├── IdHandler.java              # ID 相关处理器
+│   │           │       ├── merchant                    # 商户相关处理器
+│   │           │       │   ├── MerchantAccountBookHandler.java          # 商户账户账本处理器
+│   │           │       │   ├── MerchantAccountBookHistoryHandler.java   # 商户账户历史记录处理器
+│   │           │       │   ├── MerchantBindGroupOrUnbindGroupHandler.java # 商户绑定/解绑群组处理器
+│   │           │       │   ├── MerchantDropOrderHandler.java            # 商户订单丢失处理器
+│   │           │       │   ├── MerchantInfoHandler.java                 # 商户信息处理器
+│   │           │       │   ├── MerchantOneClickSettlementHandler.java   # 商户一键结算处理器
+│   │           │       │   ├── MerchantStatusHandler.java               # 商户状态处理器
+│   │           │       │   ├── MerchantSuccessRateHandler.java          # 商户成功率处理器
+│   │           │       │   └── MerchantWarnHandler.java                 # 商户警告处理器
+│   │           │       └── price
+│   │           │           └── PriceQueryHandler.java   # 价格查询处理器
+│   │           ├── JEBot.java                           # 项目主入口类
+│   │           ├── models                               # 实体类
+│   │           │   ├── jebot
+│   │           │   │   ├── BotAccountBookHistory.java  # 账户账本历史记录实体类
+│   │           │   │   ├── BotAccountBook.java         # 账户账本实体类
+│   │           │   │   ├── BotComplaint.java           # 投诉实体类
+│   │           │   │   ├── BotGroup.java               # 群组实体类
+│   │           │   │   ├── BotModel.java               # 机器人模型实体类
+│   │           │   │   ├── BotUser.java                # 用户实体类
+│   │           │   │   └── BotWarn.java                # 警告实体类
+│   │           │   └── xxpay
+│   │           │       ├── MchAccount.java             # 商户账户实体类
+│   │           │       ├── PayOrder.java               # 支付订单实体类
+│   │           │       ├── PayPassage.java             # 支付通道实体类
+│   │           │       └── TransOrder.java             # 转账订单实体类
+│   │           ├── repository                          # 数据访问层
+│   │           │   ├── jebot
+│   │           │   │   ├── BotAccountBookHistoryRepository.java  # 账户账本历史记录仓库
+│   │           │   │   ├── BotAccountBookRepository.java         # 账户账本仓库
+│   │           │   │   ├── BotComplaintRepository.java           # 投诉仓库
+│   │           │   │   ├── BotGroupRepository.java               # 群组仓库
+│   │           │   │   ├── BotUserRepository.java                # 用户仓库
+│   │           │   │   └── BotWarnRepository.java                # 警告仓库
+│   │           │   └── xxpay
+│   │           │       ├── MchAccountRepository.java   # 商户账户仓库
+│   │           │       ├── PayOrderRepository.java     # 支付订单仓库
+│   │           │       ├── PayPassageRepository.java   # 支付通道仓库
+│   │           │       └── TransOrderRepository.java   # 转账订单仓库
+│   │           ├── rest
+│   │           │   └── BotRest.java                    # 机器人相关的 REST 接口
+│   │           ├── rpc
+│   │           │   └── Rpc.java                        # 远程过程调用接口
+│   │           ├── scheduled
+│   │           │   └── MchScheduled.java              # 商户相关的定时任务
+│   │           ├── service                             # 服务层
+│   │           │   ├── dto
+│   │           │   │   └── UpdateBalance.java         # 更新余额的 DTO 类
+│   │           │   ├── IAccountBookService.java       # 账户账本服务接口
+│   │           │   └── impl
+│   │           │       └── AccountBookServiceImpl.java # 账户账本服务实现类
+│   │           └── util                                # 工具类
+│   │               ├── DateUtil.java                  # 日期工具类
+│   │               ├── PriceUtil.java                 # 价格工具类
+│   │               ├── QueryType.java                 # 查询类型工具类
+│   │               └── SnowflakeId.java               # 唯一 ID 生成工具类
+│   └── resources
+│       ├── application.yml                            # Spring Boot 配置文件
+│       └── logback-spring.xml                         # 日志配置文件
+```
+
+# 部署前请修改配置文件

+ 68 - 0
pom.xml

@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.example</groupId>
+    <artifactId>jebot</artifactId>
+    <version>1.0-SNAPSHOT</version>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.7.18</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.8.38</version>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>8.0.28</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-devtools</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>annotationProcessor</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.github.pengrad</groupId>
+            <artifactId>java-telegram-bot-api</artifactId>
+            <version>8.3.0</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 17 - 0
src/main/java/org/jebot/JEBot.java

@@ -0,0 +1,17 @@
+package org.jebot;
+
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+import java.util.TimeZone;
+
+@EnableScheduling
+@SpringBootApplication
+public class JEBot {
+    public static void main(String[] args) {
+        TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai")); // 设置时区为上海
+        SpringApplication.run(JEBot.class, args);
+    }
+}

+ 90 - 0
src/main/java/org/jebot/config/BotConfig.java

@@ -0,0 +1,90 @@
+package org.jebot.config;
+
+import com.pengrad.telegrambot.TelegramBot;
+import com.pengrad.telegrambot.UpdatesListener;
+import com.pengrad.telegrambot.model.BotCommand;
+import com.pengrad.telegrambot.request.SetMyCommands;
+import com.pengrad.telegrambot.response.BaseResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.jebot.handler.HandlerManager;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.PreDestroy;
+import java.util.concurrent.atomic.AtomicReference;
+
+@Slf4j
+@Configuration
+public class BotConfig {
+
+    private final AtomicReference<TelegramBot> botReference = new AtomicReference<>();
+
+    @Value("${telegram.bot.token}")
+    private String botToken;
+
+    @Bean
+    public TelegramBot telegramBot(@Qualifier("handlerManager") HandlerManager handlerManager) {
+        TelegramBot telegramBot = new TelegramBot(botToken);
+        botReference.set(telegramBot);
+        BotCommand command = new BotCommand("/help", "获取帮助");
+        BaseResponse execute = telegramBot.execute(new SetMyCommands(command));
+        if (!execute.isOk()) {
+            log.error("设置命令失败: {}", execute.description());
+        } else {
+            log.info("设置命令成功");
+        }
+        telegramBot.setUpdatesListener(updates -> {
+            handlerManager.handle(telegramBot, updates);
+            return UpdatesListener.CONFIRMED_UPDATES_ALL;
+        }, e -> {
+            if (!e.response().isOk()) {
+                log.error(e.toString());
+                telegramBot.removeGetUpdatesListener();
+            }
+        });
+        return telegramBot;
+    }
+
+    /// 重载 Bot
+    public void reloadBot(String newToken, HandlerManager handlerManager) {
+        TelegramBot telegramBot = botReference.get();
+        if (telegramBot != null) {
+            telegramBot.shutdown();
+            telegramBot.removeGetUpdatesListener();
+            log.info("TelegramBot 已成功停止");
+        }
+        TelegramBot newBot = new TelegramBot(newToken);
+        BotCommand command = new BotCommand("/help", "获取帮助");
+        BaseResponse execute = telegramBot.execute(new SetMyCommands(command));
+        if (!execute.isOk()) {
+            log.error("设置命令失败: {}", execute.description());
+        } else {
+            log.info("设置命令成功");
+        }
+        botReference.set(newBot);
+        newBot.setUpdatesListener(updates -> {
+            handlerManager.handle(newBot, updates);
+            return UpdatesListener.CONFIRMED_UPDATES_ALL;
+        }, e -> {
+            if (!e.response().isOk()) {
+                log.error("监听器错误: {}", e.toString());
+            }
+        });
+        log.info("TelegramBot 已成功重载");
+    }
+
+    public TelegramBot getCurrentBot() {
+        return botReference.get();
+    }
+
+    @PreDestroy
+    public void destroy() {
+        TelegramBot telegramBot = botReference.get();
+        if (telegramBot != null) {
+            telegramBot.shutdown();
+            log.info("TelegramBot 已成功停止");
+        }
+    }
+}

+ 58 - 0
src/main/java/org/jebot/config/JpaJebotConfig.java

@@ -0,0 +1,58 @@
+package org.jebot.config;
+
+import com.zaxxer.hikari.HikariDataSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.jdbc.DataSourceBuilder;
+import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.orm.jpa.JpaTransactionManager;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import javax.sql.DataSource;
+
+@Configuration
+@EnableTransactionManagement
+@EnableJpaRepositories(basePackages = "org.jebot.repository.jebot",
+        entityManagerFactoryRef = "entityManagerFactoryBeanJebot",
+        transactionManagerRef = "platformTransactionManagerJebot")
+public class JpaJebotConfig {
+
+
+    @Autowired
+    JpaProperties jpaProperties;
+
+    @Bean
+    @Primary
+    @Qualifier(value = "dataSourceJebot")
+    @ConfigurationProperties(prefix = "spring.datasource.jebot")
+    DataSource dataSourceJebot() {
+        return DataSourceBuilder.create().type(HikariDataSource.class).build();
+    }
+
+    @Bean
+    @Primary
+    LocalContainerEntityManagerFactoryBean entityManagerFactoryBeanJebot(
+            EntityManagerFactoryBuilder builder) {
+        return builder.dataSource(dataSourceJebot()) //配置数据源
+                .properties(jpaProperties.getProperties())
+                .packages("org.jebot.models.jebot")
+                .persistenceUnit("jebot")
+                .build();
+    }
+
+    @Bean
+    @Primary
+    PlatformTransactionManager platformTransactionManagerJebot(
+            EntityManagerFactoryBuilder builder) {
+        return new JpaTransactionManager(entityManagerFactoryBeanJebot(builder).getObject());
+    }
+
+}

+ 58 - 0
src/main/java/org/jebot/config/JpaXxpayConfig.java

@@ -0,0 +1,58 @@
+package org.jebot.config;
+
+import com.zaxxer.hikari.HikariDataSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.jdbc.DataSourceBuilder;
+import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.orm.jpa.JpaTransactionManager;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.transaction.PlatformTransactionManager;
+
+import javax.sql.DataSource;
+import java.util.Map;
+
+@Configuration
+@EnableJpaRepositories(basePackages = "org.jebot.repository.xxpay",
+        entityManagerFactoryRef = "entityManagerFactoryBeanXxpay",
+        transactionManagerRef = "platformTransactionManagerXxpay")
+public class JpaXxpayConfig {
+
+
+    @Autowired
+    JpaProperties jpaProperties;
+
+
+    @Bean
+    @Qualifier(value = "dataSourceXxpay")
+    @ConfigurationProperties(prefix = "spring.datasource.xxpay")
+    DataSource dataSourceXxpay() {
+        return DataSourceBuilder.create().type(HikariDataSource.class).build();
+    }
+
+    @Bean
+    LocalContainerEntityManagerFactoryBean entityManagerFactoryBeanXxpay(
+            EntityManagerFactoryBuilder builder) {
+        Map<String, String> properties = jpaProperties.getProperties();
+        properties.put("hibernate.hbm2ddl.auto", "none");
+        return builder.dataSource(dataSourceXxpay()) //配置数据源
+                .properties(jpaProperties.getProperties())//设置 JPA 相关配置
+                .packages("org.jebot.models.xxpay")//设置实体类所在的位置
+                .persistenceUnit("xxpay")//配置持久化单元名。若项目中只有一个 EntityManagerFactory,则 persistenceUnit 可以省略掉,若有多个,则必须明确指定持久化单元名。
+                .build();
+    }
+
+
+    @Bean
+    PlatformTransactionManager platformTransactionManagerXxpay(
+            EntityManagerFactoryBuilder builder) {
+        return new JpaTransactionManager(entityManagerFactoryBeanXxpay(builder).getObject());
+    }
+
+}

+ 126 - 0
src/main/java/org/jebot/constant/Constant.java

@@ -0,0 +1,126 @@
+package org.jebot.constant;
+
+public class Constant {
+
+    public static final String SUCCESS = "success";
+
+    /* ----------------------------群组所属类型及bot数据类型--------------------------------- */
+
+    public static final String DATA_TYPE_CHANNEL = "channel";
+
+    public static final String DATA_TYPE_MERCHANT = "merchant";
+
+    /* ----------------------------记账本数据类型--------------------------------- */
+
+    public static final String AGENT = "agent";
+
+    public static final String PAYMENT = "payment";
+
+    /* ----------------------------支付系统金额分转元--------------------------------- */
+
+    public static final int AMOUNT_MOVE_POINT = 2;
+
+
+    /* ----------------------------查询usdt转cny--------------------------------- */
+
+    public static final String QUERY_USDT_TO_ALIPAY_CNY = "z0";
+
+    public static final String QUERY_USDT_TO_WECAHT_CNY = "w0";
+
+    public static final String QUERY_USDT_TO_BANK_CNY = "k0";
+
+
+    //查询群组ID
+    public static final String QUERY_GROUP_ID = "id";
+
+    /* ----------------------------一键结算--------------------------------- */
+
+    public static final String SETTLE_MCH = "所有账单";
+
+    /* ----------------------------添加管理指令标识--------------------------------- */
+    public static final String ADD_ADMIN = "增加管理";
+    public static final String DEL_ADMIN = "删除管理";
+
+
+
+    /* ----------------------------商户指令标识--------------------------------- */
+
+    //查询商户成功率
+    public static final String QUERY_MCH_TODAY_SUCCESS_RATE = "查成率";
+
+    //查询余额
+    public static final String QUERY_MCH_BALANCE = "查余额";
+
+
+    //设置通道代收警告阈值
+    public static final String UPDATE_MCH_AGENT_WARN_THRESHOLD_FLAG = "代付预警";
+
+    //设置通道代付警告阈值
+    public static final String UPDATE_MCH_PAYMENT_WARN_THRESHOLD_FLAG = "预付预警";
+
+    //查询商户信息
+    public static final String MCH_INFO = "商户信息";
+    public static final String QUERY_MCH_ORDER_FLAG = "cd";
+
+    //通过群组 ID 解绑商户号
+    public static final String UNBIND_MCH_FLAG = "解绑商户";
+    public static final String BIND_MCH_FLAG = "绑定商户";
+    //通过群组 ID 关闭商户
+    public static final String DISABLE_MCH = "关闭商户";
+    //通过群组 ID 开启商户
+    public static final String ENABLE_MCH = "开启商户";
+
+
+    /* ----------------------------通道指令标识--------------------------------- */
+
+    //通道查询成功率
+    public static final String QUERY_CHANNEL_TODAY_SUCCESS_RATE = "查成率";
+
+    //查询群组绑定的通道信息
+    public static final String QUERY_CHANNEL_INFO = "通道信息";
+
+    //查询余额
+    public static final String QUERY_CHANNEL_BALANCE = "查余额";
+
+
+    //设置通道代收警告阈值
+    public static final String UPDATE_CHANNEL_AGENT_WARN_THRESHOLD_FLAG = "预付预警";
+
+    //设置通道代付警告阈值
+    public static final String UPDATE_CHANNEL_PAYMENT_WARN_THRESHOLD_FLAG = "代付预警";
+
+    //通道查单命令标识
+    public static final String QUERY_CHANNEL_ORDER_FLAG = "cd";
+
+    //启用通道命令标识
+    public static final String ENABLE_CHANNEL_FLAG = "开启";
+
+    //禁用通道命令标识
+    public static final String DISABLE_CHANNEL_FLAG = "关闭";
+
+    //绑定通道命令标识
+    public static final String BIND_CHANNEL_FLAG = "绑定通道";
+
+    //解绑通道命令标识
+    public static final String UNBIND_CHANNEL_FLAG = "解绑通道";
+
+
+    /* ----------------------------记账指令标识--------------------------------- */
+    //代付匹配标识
+    public static final String AGENT_ACCOUNT_FLAG = "加钱";
+
+    //代收匹配标识
+    public static final String PAYMENT_ACCOUNT_FLAG = "预付";
+
+    //代付匹配标识
+    public static final String AGENT_ACCOUNT_HISTORY_FLAG = "代付账单";
+
+    //代收匹配标识
+    public static final String PAYMENT_ACCOUNT_HISTORY_FLAG = "预付账单";
+
+
+    public static final int OPEN_CHANNEL = 1;
+    public static final int CLOSE_CHANNEL = 0;
+    public static final int OPEN_MERCHANT = 1;
+    public static final int CLOSE_MERCHANT = 0;
+}

+ 26 - 0
src/main/java/org/jebot/handler/AbstractHandler.java

@@ -0,0 +1,26 @@
+package org.jebot.handler;
+
+import org.jebot.handler.dto.BotMessage;
+
+import java.util.Arrays;
+import java.util.List;
+
+public abstract class AbstractHandler {
+
+    public HandlerManager handlerManager;
+
+    public abstract boolean msgHandler(BotMessage message);
+    private static List<String> specialChars = Arrays.asList("_", "[", "]", "(", ")", "~", ">", "'", "#", "+", "-", "=", "|", "{", "}", ".", "!");
+    public  String strEscape(String value, String... ignore) {
+        List<String> ignoreList = Arrays.asList(ignore);
+
+        for (String charToEscape : specialChars) {
+            if (!ignoreList.contains(charToEscape)) {
+                value = value.replace(charToEscape, "\\" + charToEscape);
+            }
+        }
+        return value;
+    }
+
+
+}

+ 161 - 0
src/main/java/org/jebot/handler/HandlerManager.java

@@ -0,0 +1,161 @@
+package org.jebot.handler;
+
+
+import com.pengrad.telegrambot.TelegramBot;
+import com.pengrad.telegrambot.model.Update;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.jebot.handler.dto.BotMessage;
+import org.jebot.handler.impl.AuthHandler;
+import org.jebot.handler.impl.HelpHandler;
+import org.jebot.handler.impl.IdHandler;
+import org.jebot.handler.impl.SqlInjectionFilterHandler;
+import org.jebot.handler.impl.admin.AdminHandler;
+import org.jebot.handler.impl.calculator.CalculatorHandler;
+import org.jebot.handler.impl.change.GroupChangeHandler;
+import org.jebot.handler.impl.channel.*;
+import org.jebot.handler.impl.complaint.ComplaintHandler;
+import org.jebot.handler.impl.merchant.*;
+import org.jebot.handler.impl.price.PriceQueryHandler;
+import org.jebot.repository.jebot.*;
+import org.jebot.repository.xxpay.MchAccountRepository;
+import org.jebot.repository.xxpay.PayOrderRepository;
+import org.jebot.repository.xxpay.PayPassageRepository;
+import org.jebot.service.IAccountBookService;
+import org.jebot.service.IMchService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.awt.*;
+import java.io.File;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+@Slf4j
+@Component
+@Getter
+public class HandlerManager {
+
+
+    @Resource
+    BotUserRepository userRepository;
+
+
+    @Resource
+    BotGroupRepository groupRepository;
+
+    @Resource
+    BotComplaintRepository complaintRepository;
+
+    @Resource
+    BotAccountBookRepository accountBookRepository;
+
+
+    @Resource
+    BotAccountBookHistoryRepository accountBookHistoryRepository;
+
+
+    @Resource
+    PayOrderRepository payOrderRepository;
+
+    @Resource
+    PayPassageRepository passageRepository;
+
+    @Resource
+    MchAccountRepository mchAccountRepository;
+
+    @Resource
+    IAccountBookService accountBookService;
+
+    @Resource
+    IMchService mchService;
+
+    private final List<AbstractHandler> handlers = new ArrayList<>();
+
+    public HandlerManager() {
+        // 无需认证的处理器
+        addHandler(new SqlInjectionFilterHandler());
+        addHandler(new HelpHandler());
+        addHandler(new IdHandler());
+        addHandler(new CalculatorHandler());
+        addHandler(new PriceQueryHandler());
+        //认证处理器
+        addHandler(new AuthHandler());
+
+        //投诉处理
+        addHandler(new ComplaintHandler());
+
+        //群组变更处理
+        addHandler(new GroupChangeHandler());
+
+        //添加或删除管理
+        addHandler(new AdminHandler());
+
+        //通道相关
+        addHandler(new ChannelInfoHandler());
+        addHandler(new ChannelStausHandler());
+        addHandler(new ChannelSuccessRateHandler());
+        addHandler(new ChannelAccountBookHandler());
+        addHandler(new ChannelAccountBookHistoryHandler());
+        addHandler(new ChannelBindGroupOrUnbindGroupHandler());
+        //商户相关
+        addHandler(new MerchantInfoHandler());
+        addHandler(new MerchantStatusHandler());
+        addHandler(new MerchantWarnHandler());
+        addHandler(new MerchantSuccessRateHandler());
+        addHandler(new MerchantDropOrderHandler());
+        addHandler(new MerchantAccountBookHandler());
+        addHandler(new MerchantAccountBookHistoryHandler());
+        addHandler(new MerchantOneClickSettlementHandler());
+        addHandler(new MerchantBindGroupOrUnbindGroupHandler());
+    }
+
+
+    // 添加处理器
+    public void addHandler(AbstractHandler handler) {
+        handler.handlerManager = this;
+        handlers.add(handler);
+    }
+
+    /**
+     * 处理消息
+     */
+    public void handle(TelegramBot telegramBot, List<Update> updates) {
+        updates.forEach(update -> {
+            if (update.message() == null) return;
+            BotMessage botMessage = new BotMessage(telegramBot, update.message(), update);
+            for (AbstractHandler handler : this.handlers) {
+                try {
+                    if (handler.msgHandler(botMessage)) {
+                        break;
+                    }
+                } catch (Exception e) {
+                    log.error("处理消息失败: {}", e.getMessage());
+                    e.printStackTrace();
+                }
+            }
+        });
+    }
+
+    public Font loadFontFromResources() {
+        try (InputStream fontStream = getClass().getClassLoader().getResourceAsStream("msyh.ttf")) {
+            if (fontStream == null) {
+                log.error("字体文件未找到,请确保 msyh.ttf 存在于 resources 目录下");
+                return null;
+            }
+            Font chineseFont = Font.createFont(Font.TRUETYPE_FONT, fontStream);
+            chineseFont = chineseFont.deriveFont(Font.PLAIN, 16);
+            log.info("成功加载字体: {}", chineseFont.getFontName());
+            return chineseFont;
+        } catch (Exception e) {
+            log.error("加载字体失败: {}", e.getMessage());
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+}

+ 79 - 0
src/main/java/org/jebot/handler/dto/BotMessage.java

@@ -0,0 +1,79 @@
+package org.jebot.handler.dto;
+
+import com.pengrad.telegrambot.TelegramBot;
+import com.pengrad.telegrambot.model.Message;
+import com.pengrad.telegrambot.model.Update;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.apache.logging.log4j.util.Strings;
+import org.jebot.constant.Constant;
+import org.jebot.models.jebot.BotGroup;
+import org.jebot.models.jebot.BotUser;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class BotMessage {
+    private Update update;
+    private BotUser botUser;
+    private BotGroup botGroup;
+    private Message message;
+    private TelegramBot telegramBot;
+
+    public BotMessage(TelegramBot telegramBot, Message message, Update update) {
+        this.telegramBot = telegramBot;
+        this.message = message;
+        this.update = update;
+    }
+
+    //用户认证
+    public boolean isUserAuthenticated() {
+        return this.botUser != null;
+    }
+
+    //群组认证
+    public boolean isGroupAuthenticated() {
+        return this.botGroup != null;
+    }
+
+    //是商户群组
+    public boolean isGroupMch() {
+        if (!this.isGroupAuthenticated()) {
+            return false;
+        }
+        return Constant.DATA_TYPE_MERCHANT.equalsIgnoreCase(this.botGroup.getDataType());
+    }
+
+    //是通道群组
+    public boolean isGroupChannel() {
+        if (!this.isGroupAuthenticated()) {
+            return false;
+        }
+        return Constant.DATA_TYPE_CHANNEL.equalsIgnoreCase(this.botGroup.getDataType());
+    }
+
+    //包含文字和图片
+    public boolean isContainsTextAndPhoto(){
+        // 判断是否包含文字
+        boolean hasText = this.message.caption() != null;
+        // 判断是否包含图片
+        boolean hasPhoto = this.message.photo() != null && this.message.photo().length > 0;
+        return hasText && hasPhoto;
+    }
+
+    // 来自群组的消息
+    public boolean isGroupMessage(){
+        return update.message().chat().type().name().equals("group") || update.message().chat().type().name().equals("supergroup");
+    }
+
+    public String getMessageText() {
+        return this.message.text();
+    }
+
+    public boolean messageTextIsEmpty() {
+        return Strings.isEmpty(this.getMessageText());
+    }
+
+
+}

+ 39 - 0
src/main/java/org/jebot/handler/impl/AuthHandler.java

@@ -0,0 +1,39 @@
+package org.jebot.handler.impl;
+
+import lombok.extern.slf4j.Slf4j;
+import org.jebot.handler.AbstractHandler;
+import org.jebot.handler.dto.BotMessage;
+import org.jebot.models.jebot.BotGroup;
+
+import java.util.List;
+
+@Slf4j
+public class AuthHandler extends AbstractHandler {
+    @Override
+    public boolean msgHandler(BotMessage botMessage) {
+
+        //查询用户是否在白名单中
+        botMessage.setBotUser(handlerManager.getUserRepository().findByUserName(botMessage.getMessage().from().username()));
+
+        //判断是否来自群组消息
+        if (botMessage.isGroupMessage()) {
+            //查询当前群组是否已绑定
+            List<BotGroup> byGroupId = handlerManager.getGroupRepository().findByGroupId((botMessage.getMessage().chat().id()));
+            if (byGroupId!=null && !byGroupId.isEmpty()) {
+                botMessage.setBotGroup(byGroupId.get(0));
+            }
+        }
+
+        //如果用户和群组都不在白名单中,返回true
+        if (!botMessage.isUserAuthenticated() && !botMessage.isGroupAuthenticated()) {
+            log.warn("用户id {},用户名:{} 不在白名单中,无法处理消息", botMessage.getMessage().from().id(), botMessage.getMessage().from().username());
+            return true;
+        }
+
+        //如果用户未设置用户id,更新用户id
+        if (botMessage.isUserAuthenticated() && botMessage.getBotUser().getUserId() == null) {
+            handlerManager.getUserRepository().updateUserIdByUserName(botMessage.getMessage().from().id(), botMessage.getMessage().from().username());
+        }
+        return false;
+    }
+}

+ 89 - 0
src/main/java/org/jebot/handler/impl/HelpHandler.java

@@ -0,0 +1,89 @@
+package org.jebot.handler.impl;
+
+import com.pengrad.telegrambot.model.request.ParseMode;
+import com.pengrad.telegrambot.request.SendMessage;
+import org.jebot.handler.AbstractHandler;
+import org.jebot.handler.dto.BotMessage;
+
+import static org.jebot.constant.Constant.*;
+
+
+public class HelpHandler extends AbstractHandler {
+    @Override
+    public boolean msgHandler(BotMessage botMessage) {
+        if (!botMessage.messageTextIsEmpty() && botMessage.getMessageText().contains("/help")) {
+            String helpMessage = String.format("%s: 查询支付宝价格\n" +
+                            "%s: 查询微信价格\n" +
+                            "%s: 查询银行价格\n" +
+                            "10(+-*/)10: 加减乘除\n" +
+                            "%s: 群组商户信息\n" +
+                            "%s: 群组商户余额\n" +
+                            "%s: 群组通道信息\n" +
+                            "%s: 通道查询成功率\n" +
+                            "%s: 商户查询成功率\n" +
+                            "---下方命令需管理权限--\n" +
+                            "%s: 添加管理(%s用户名)\n" +
+                            "%s: 删除管理(%s用户名)\n" +
+                            "%s: 关闭商户\n" +
+                            "%s: 开启商户\n" +
+                            "%s: 所有商户出账单\n" +
+                            "%s商户号: “指定商户出账单”(%s2000000)\n" +
+                            "%s: 代收预警阀值设置(%s10)\n" +
+                            "%s: 代付预警阀值设置(%s10)\n" +
+                            "%s商户号: \"绑定商户\"(%s2000000)\n" +
+                            "%s: 解绑商户\n" +
+                            "%s通道编码: \"绑定通道\",多个用英文逗号隔开(%s1,2,3)\n" +
+                            "%s通道编号: \"解绑通道\",多个用英文逗号隔开(%s1,2,3)\n" +
+                            "%s通道编码: \"关闭通道\",多个用英文逗号隔开(%s1,2,3)\n" +
+                            "%s通道编号: \"开启通道\",多个用英文逗号隔开(%s1,2,3)\n" +
+                            "%s: \"代收记账\"(%s500或%s-500)\n" +
+                            "%s: \"代付记账\"(%s500或%s-500)\n"+
+                            "%s: 代收记账24小时历史\n"+
+                            "%s: 代付记账24小时历史\n",
+                    QUERY_USDT_TO_ALIPAY_CNY,
+                    QUERY_USDT_TO_WECAHT_CNY,
+                    QUERY_USDT_TO_BANK_CNY,
+                    MCH_INFO,
+                    QUERY_MCH_BALANCE,
+                    QUERY_CHANNEL_INFO,
+                    QUERY_CHANNEL_TODAY_SUCCESS_RATE,
+                    QUERY_MCH_TODAY_SUCCESS_RATE,
+                    ADD_ADMIN,
+                    ADD_ADMIN,
+                    DEL_ADMIN,
+                    DEL_ADMIN,
+                    DISABLE_MCH,
+                    ENABLE_MCH,
+                    SETTLE_MCH,
+                    SETTLE_MCH,
+                    SETTLE_MCH,
+                    UPDATE_MCH_PAYMENT_WARN_THRESHOLD_FLAG,
+                    UPDATE_MCH_PAYMENT_WARN_THRESHOLD_FLAG,
+                    UPDATE_MCH_AGENT_WARN_THRESHOLD_FLAG,
+                    UPDATE_MCH_AGENT_WARN_THRESHOLD_FLAG,
+                    BIND_MCH_FLAG,
+                    BIND_MCH_FLAG,
+                    UNBIND_MCH_FLAG,
+                    BIND_CHANNEL_FLAG,
+                    BIND_CHANNEL_FLAG,
+                    UNBIND_CHANNEL_FLAG,
+                    UNBIND_CHANNEL_FLAG,
+                    DISABLE_CHANNEL_FLAG,
+                    DISABLE_CHANNEL_FLAG,
+                    ENABLE_CHANNEL_FLAG,
+                    ENABLE_CHANNEL_FLAG,
+                    PAYMENT_ACCOUNT_FLAG,
+                    PAYMENT_ACCOUNT_FLAG,
+                    PAYMENT_ACCOUNT_FLAG,
+                    AGENT_ACCOUNT_FLAG,
+                    AGENT_ACCOUNT_FLAG,
+                    AGENT_ACCOUNT_FLAG,
+                    PAYMENT_ACCOUNT_HISTORY_FLAG,
+                    AGENT_ACCOUNT_HISTORY_FLAG);
+            SendMessage sendMessage = new SendMessage(botMessage.getMessage().chat().id(), helpMessage);
+            botMessage.getTelegramBot().execute(sendMessage);
+            return true;
+        }
+        return false;
+    }
+}

+ 27 - 0
src/main/java/org/jebot/handler/impl/IdHandler.java

@@ -0,0 +1,27 @@
+package org.jebot.handler.impl;
+
+import com.pengrad.telegrambot.request.SendMessage;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.logging.log4j.util.Strings;
+import org.jebot.constant.Constant;
+import org.jebot.handler.AbstractHandler;
+import org.jebot.handler.dto.BotMessage;
+
+@Slf4j
+public class IdHandler extends AbstractHandler {
+
+    @Override
+    public boolean msgHandler(BotMessage botMessage) {
+        String text = botMessage.getMessage().text();
+        if (Strings.isNotEmpty(text) && text.equals(Constant.QUERY_GROUP_ID)) {
+            // 获取群组 ID
+            Long chatId = botMessage.getMessage().chat().id();
+
+            // 将群组 ID 发回
+            SendMessage sendMessage = new SendMessage(chatId, "群组 ID: " + chatId);
+            botMessage.getTelegramBot().execute(sendMessage);
+            return true;
+        }
+        return false;
+    }
+}

+ 39 - 0
src/main/java/org/jebot/handler/impl/SqlInjectionFilterHandler.java

@@ -0,0 +1,39 @@
+package org.jebot.handler.impl;
+
+import com.pengrad.telegrambot.model.Message;
+import org.apache.logging.log4j.util.Strings;
+import org.jebot.handler.AbstractHandler;
+import org.jebot.handler.dto.BotMessage;
+
+public class SqlInjectionFilterHandler extends AbstractHandler {
+    @Override
+    public boolean msgHandler(BotMessage botMessage) {
+        if (!botMessage.messageTextIsEmpty()){
+            Message message = botMessage.getMessage();
+            String text = message.text();
+            // 检查是否包含SQL注入的常见关键字
+            String[] sqlInjectionKeywords = {"SELECT", "INSERT", "UPDATE", "DELETE", "DROP", "UNION", "--", ";", "' OR '1'='1"};
+
+            for (String keyword : sqlInjectionKeywords) {
+                if (text.toUpperCase().contains(keyword)|| text.replaceAll(" ", "").toLowerCase().contains(keyword)) {
+                    // 如果检测到SQL注入关键字,返回true表示需要过滤
+                    return true;
+                }
+            }
+        }
+
+        String caption = botMessage.getMessage().caption();
+        if (Strings.isNotEmpty(caption)){
+            // 检查是否包含SQL注入的常见关键字
+            String[] sqlInjectionKeywords = {"SELECT", "INSERT", "UPDATE", "DELETE", "DROP", "UNION", "--", ";", "' OR '1'='1"};
+            for (String keyword : sqlInjectionKeywords) {
+                if (caption.toUpperCase().contains(keyword) || caption.replaceAll(" ", "").toLowerCase().contains(keyword)) {
+                    // 如果检测到SQL注入关键字,返回true表示需要过滤
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+}

+ 72 - 0
src/main/java/org/jebot/handler/impl/admin/AdminHandler.java

@@ -0,0 +1,72 @@
+package org.jebot.handler.impl.admin;
+
+import com.pengrad.telegrambot.request.SendMessage;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.logging.log4j.util.Strings;
+import org.jebot.handler.AbstractHandler;
+import org.jebot.handler.dto.BotMessage;
+import org.jebot.models.jebot.BotUser;
+
+import java.util.regex.Pattern;
+
+import static org.jebot.constant.Constant.ADD_ADMIN;
+import static org.jebot.constant.Constant.DEL_ADMIN;
+
+@Slf4j
+public class AdminHandler extends AbstractHandler {
+
+
+    private final Pattern CREATE_ADMIN = Pattern.compile("^" + ADD_ADMIN + "[a-zA-Z0-9_]*$");
+    private final Pattern DELETE_ADMIN = Pattern.compile("^" + DEL_ADMIN + "[a-zA-Z0-9_]*$");
+
+    @Override
+    public boolean msgHandler(BotMessage botMessage) {
+        if (!botMessage.isUserAuthenticated()) {
+            return false;
+        }
+
+        String text = botMessage.getMessage().text();
+        if (Strings.isNotEmpty(text) && CREATE_ADMIN.matcher(text).find()) {
+            String userName = text.replace(ADD_ADMIN, "");
+            // 检查用户名是否存在
+            BotUser existingUser = this.handlerManager.getUserRepository().findByUserName(userName);
+            if (existingUser != null) {
+                // 将群组 ID 发回
+                SendMessage sendMessage = new SendMessage(botMessage.getMessage().chat().id(), "用户已是管理");
+                botMessage.getTelegramBot().execute(sendMessage);
+                return true;
+            }
+            BotUser botUser = new BotUser();
+            botUser.setUserName(userName);
+            BotUser save = this.handlerManager.getUserRepository().save(botUser);
+            if (save != null) {
+                // 将群组 ID 发回
+                SendMessage sendMessage = new SendMessage(botMessage.getMessage().chat().id(), "添加成功");
+                botMessage.getTelegramBot().execute(sendMessage);
+                return true;
+            } else {
+                // 将群组 ID 发回
+                SendMessage sendMessage = new SendMessage(botMessage.getMessage().chat().id(), "添加失败");
+                botMessage.getTelegramBot().execute(sendMessage);
+                return true;
+            }
+        }
+        if (Strings.isNotEmpty(text) && DELETE_ADMIN.matcher(text).find()) {
+            String userName = text.replace(DEL_ADMIN, "");
+            // 检查用户名是否存在
+            BotUser existingUser = this.handlerManager.getUserRepository().findByUserName(userName);
+            if (existingUser == null) {
+                // 将群组 ID 发回
+                SendMessage sendMessage = new SendMessage(botMessage.getMessage().chat().id(), "用户不存在");
+                botMessage.getTelegramBot().execute(sendMessage);
+                return true;
+            }
+            this.handlerManager.getUserRepository().delete(existingUser);
+            // 将群组 ID 发回
+            SendMessage sendMessage = new SendMessage(botMessage.getMessage().chat().id(), "删除成功");
+            botMessage.getTelegramBot().execute(sendMessage);
+            return true;
+        }
+        return false;
+    }
+}

+ 88 - 0
src/main/java/org/jebot/handler/impl/calculator/CalculatorHandler.java

@@ -0,0 +1,88 @@
+package org.jebot.handler.impl.calculator;
+
+import com.pengrad.telegrambot.model.request.ReplyParameters;
+import com.pengrad.telegrambot.request.SendMessage;
+import org.jebot.handler.AbstractHandler;
+import org.jebot.handler.dto.BotMessage;
+
+import java.util.regex.Pattern;
+
+public class CalculatorHandler extends AbstractHandler {
+
+    private static final Pattern REGEX_ADD = Pattern.compile("^-?\\d+(\\.\\d+)?\\s*\\+\\s*-?\\d+(\\.\\d+)?$");
+    private static final Pattern REGEX_SUB = Pattern.compile("^-?\\d+(\\.\\d+)?\\s*-\\s*-?\\d+(\\.\\d+)?$");
+    private static final Pattern REGEX_MUL = Pattern.compile("^-?\\d+(\\.\\d+)?\\s*\\*\\s*-?\\d+(\\.\\d+)?$");
+    private static final Pattern REGEX_DIV = Pattern.compile("^-?\\d+(\\.\\d+)?\\s*/\\s*-?\\d+(\\.\\d+)?$");
+
+
+    @Override
+    public boolean msgHandler(BotMessage message) {
+        if (message.messageTextIsEmpty()) {
+            return false;
+        }
+        String text = message.getMessageText();
+        if (REGEX_ADD.matcher(text).matches()) {
+            String[] parts = text.split("\\+");
+            if (parts.length != 2) {
+                message.getTelegramBot().execute(new SendMessage(
+                        message.getMessage().chat().id(), "输入格式错误").
+                        replyParameters(new ReplyParameters(message.getMessage().messageId()))
+                );
+                return false;
+            }
+            double result = Double.parseDouble(parts[0].trim()) + Double.parseDouble(parts[1].trim());
+            message.getTelegramBot().execute(
+                    new SendMessage(message.getMessage().chat().id(), String.valueOf(result)).
+                            replyParameters(new ReplyParameters(message.getMessage().messageId()))
+            );
+            return true;
+        } else if (REGEX_SUB.matcher(text).matches()) {
+            String[] parts = text.split("-");
+            if (parts.length != 2) {
+                message.getTelegramBot().execute(
+                        new SendMessage(message.getMessage().chat().id(), "输入格式错误").
+                                replyParameters(new ReplyParameters(message.getMessage().messageId()))
+                );
+                return false;
+            }
+            double result = Double.parseDouble(parts[0].trim()) - Double.parseDouble(parts[1].trim());
+            message.getTelegramBot().execute(
+                    new SendMessage(message.getMessage().chat().id(), String.valueOf(result)).
+                            replyParameters(new ReplyParameters(message.getMessage().messageId()))
+            );
+            return true;
+        } else if (REGEX_MUL.matcher(text).matches()) {
+            String[] parts = text.split("\\*");
+            if (parts.length != 2) {
+                message.getTelegramBot().execute(
+                        new SendMessage(message.getMessage().chat().id(), "输入格式错误").
+                                replyParameters(new ReplyParameters(message.getMessage().messageId()))
+                );
+                return false;
+            }
+            double result = Double.parseDouble(parts[0].trim()) * Double.parseDouble(parts[1].trim());
+            message.getTelegramBot().execute(
+                    new SendMessage(message.getMessage().chat().id(), String.valueOf(result)).
+                            replyParameters(new ReplyParameters(message.getMessage().messageId()))
+            );
+            return true;
+        } else if (REGEX_DIV.matcher(text).matches()) {
+            String[] parts = text.split("/");
+            if (parts.length != 2) {
+                message.getTelegramBot().execute(
+                        new SendMessage(message.getMessage().chat().id(), "输入格式错误").
+                                replyParameters(new ReplyParameters(message.getMessage().messageId()))
+                );
+                return false;
+            }
+            double result = Double.parseDouble(parts[0].trim()) / Double.parseDouble(parts[1].trim());
+            message.getTelegramBot().execute(
+                    new SendMessage(message.getMessage().chat().id(), String.valueOf(result)).
+                            replyParameters(new ReplyParameters(message.getMessage().messageId()))
+            );
+            return true;
+        }
+
+        return false;
+    }
+}

+ 22 - 0
src/main/java/org/jebot/handler/impl/change/GroupChangeHandler.java

@@ -0,0 +1,22 @@
+package org.jebot.handler.impl.change;
+
+import com.pengrad.telegrambot.request.SendMessage;
+import org.jebot.handler.AbstractHandler;
+import org.jebot.handler.dto.BotMessage;
+
+public class GroupChangeHandler extends AbstractHandler {
+    @Override
+    public boolean msgHandler(BotMessage message) {
+        // 群组名称变更
+        if (message.isGroupAuthenticated() && !message.getBotGroup().getGroupName().equals(message.getMessage().chat().title())) {
+            String oldGroupName = message.getBotGroup().getGroupName();
+            message.getBotGroup().setGroupName(message.getMessage().chat().title());
+            this.handlerManager.getGroupRepository().save(message.getBotGroup());
+            String msg = String.format("群组名称[%s]已更新为: %s", oldGroupName, message.getMessage().chat().title());
+            SendMessage sendMessage = new SendMessage(message.getMessage().chat().id(), msg);
+            message.getTelegramBot().execute(sendMessage);
+        }
+
+        return false;
+    }
+}

+ 131 - 0
src/main/java/org/jebot/handler/impl/channel/ChannelAccountBookHandler.java

@@ -0,0 +1,131 @@
+package org.jebot.handler.impl.channel;
+
+import cn.hutool.core.date.DateUtil;
+import com.pengrad.telegrambot.request.SendMessage;
+import lombok.extern.slf4j.Slf4j;
+import org.jebot.constant.Constant;
+import org.jebot.handler.AbstractHandler;
+import org.jebot.handler.dto.BotMessage;
+import org.jebot.models.jebot.BotAccountBook;
+import org.jebot.models.jebot.BotAccountBookHistory;
+import org.jebot.service.dto.UpdateBalance;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import static org.jebot.constant.Constant.*;
+
+@Slf4j
+public class ChannelAccountBookHandler extends AbstractHandler {
+
+
+    //代收正则表达式
+    private static final Pattern paymentAccount = Pattern.compile("^" + PAYMENT_ACCOUNT_FLAG + "-?\\d+(\\.\\d+)?$");
+
+    //代付正则表达式
+    private static final Pattern agentAccount = Pattern.compile("^" + AGENT_ACCOUNT_FLAG + "-?\\d+(\\.\\d+)?$");
+
+
+    @Override
+    public boolean msgHandler(BotMessage message) {
+
+        //只处理群组认证
+        if (!message.isGroupChannel()) {
+            return false;
+        }
+
+        if (message.messageTextIsEmpty()) {
+            return false;
+        }
+
+
+        //只处理来自管理员的消息
+        if (!message.isUserAuthenticated()) {
+            return false;
+        }
+
+
+        String messageText = message.getMessageText();
+        if (!paymentAccount.matcher(messageText).find() && !agentAccount.matcher(messageText).find()) {
+            return false;
+        }
+
+        BotAccountBook accountBook = handlerManager.getAccountBookRepository().findByBelongIdAndType(message.getBotGroup().getGroupId(), DATA_TYPE_CHANNEL);
+        if (accountBook == null) {
+            accountBook = new BotAccountBook();
+            accountBook.setBelongId(message.getBotGroup().getGroupId());
+            accountBook.setBelongName(message.getBotGroup().getGroupName());
+            accountBook.setType(Constant.DATA_TYPE_CHANNEL);
+            accountBook.setAgentBalance(0L);
+            accountBook.setPaymentBalance(0L);
+            accountBook = handlerManager.getAccountBookRepository().save(accountBook);
+        }
+
+        //代收处理
+        if (paymentAccount.matcher(messageText).find()) {
+            double paymentAccountAmount = Double.parseDouble(messageText.replace(PAYMENT_ACCOUNT_FLAG, ""));
+            if (paymentAccountAmount == 0) {
+                return true;
+            }
+            double paymentBalanceOld = accountBook.getPaymentBalance();
+            double paymentBalanceNew = paymentBalanceOld + paymentAccountAmount;
+            SendMessage sendMessage = null;
+            try {
+                UpdateBalance updateBalance = new UpdateBalance();
+                updateBalance.setId(accountBook.getId());
+                updateBalance.setAmount(paymentAccountAmount);
+                updateBalance.setOldBalance(paymentBalanceOld);
+                updateBalance.setNewBalance(paymentBalanceNew);
+                updateBalance.setMessage(message);
+                handlerManager.getAccountBookService().updatePaymentBalanceByIdAndPaymentBalance(updateBalance);
+                sendMessage = new SendMessage(message.getMessage().chat().id(), String.format("入账提醒\n入账金额: %.2f\n入账前: %.2f\n入账后: %.2f", paymentAccountAmount, paymentBalanceOld, paymentBalanceNew));
+            } catch (Exception e) {
+                log.error("更新通道群组账本失败,群组ID:{},群组名称:{},用户ID:{},用户名称:{},金额:{},错误信息:{}", message.getBotGroup().getDataId(), message.getBotGroup().getDataName(), message.getBotUser().getUserId(), message.getBotUser().getUserName(), paymentAccountAmount, e.getMessage());
+                e.printStackTrace();
+                sendMessage = new SendMessage(message.getMessage().chat().id(), "处理失败,请重试");
+            } finally {
+                if (sendMessage != null) {
+                    message.getTelegramBot().execute(sendMessage);
+                }
+            }
+            return true;
+        }
+
+        //代付处理
+        if (agentAccount.matcher(messageText).find()) {
+            double agentAccountAmount = Double.parseDouble(messageText.replace(AGENT_ACCOUNT_FLAG, ""));
+            if (agentAccountAmount == 0) {
+                return true;
+            }
+            double agentBalanceOld = accountBook.getAgentBalance();
+            double agentBalanceNew = agentBalanceOld + agentAccountAmount;
+            SendMessage sendMessage = null;
+            try {
+                UpdateBalance updateBalance = new UpdateBalance();
+                updateBalance.setId(accountBook.getId());
+                updateBalance.setAmount(agentAccountAmount);
+                updateBalance.setOldBalance(agentBalanceOld);
+                updateBalance.setNewBalance(agentBalanceNew);
+                updateBalance.setMessage(message);
+                handlerManager.getAccountBookService().updateAgentBalanceByIdAndAgentBalance(updateBalance);
+                sendMessage = new SendMessage(message.getMessage().chat().id(), String.format("入账提醒\n入账金额: %.2f\n入账前: %.2f\n入账后: %.2f", agentAccountAmount, agentBalanceOld, agentBalanceNew));
+            } catch (Exception e) {
+                log.error("更新通道群组账本失败,群组ID:{},群组名称:{},用户ID:{},用户名称:{},金额:{},错误信息:{}", message.getBotGroup().getDataId(), message.getBotGroup().getDataName(), message.getBotUser().getUserId(), message.getBotUser().getUserName(), agentAccountAmount, e.getMessage());
+                e.printStackTrace();
+                sendMessage = new SendMessage(message.getMessage().chat().id(), "处理失败,请重试");
+            } finally {
+                if (sendMessage != null) {
+                    message.getTelegramBot().execute(sendMessage);
+                }
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+}

+ 82 - 0
src/main/java/org/jebot/handler/impl/channel/ChannelAccountBookHistoryHandler.java

@@ -0,0 +1,82 @@
+package org.jebot.handler.impl.channel;
+
+import cn.hutool.core.date.DateUtil;
+import com.pengrad.telegrambot.request.SendPhoto;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.logging.log4j.util.Strings;
+import org.jebot.handler.AbstractHandler;
+import org.jebot.handler.dto.BotMessage;
+import org.jebot.models.jebot.BotAccountBookHistory;
+import org.jebot.util.ImageUtil;
+import org.jebot.util.SnowflakeId;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.util.Date;
+import java.util.List;
+
+import static org.jebot.constant.Constant.*;
+
+@Slf4j
+public class ChannelAccountBookHistoryHandler extends AbstractHandler {
+    @Override
+    public boolean msgHandler(BotMessage message) {
+
+        //只处理通道群组消息
+        if (!message.isGroupChannel()) {
+            return false;
+        }
+
+        if (message.messageTextIsEmpty()) {
+            return false;
+        }
+
+        String messageText = message.getMessageText();
+        if (!AGENT_ACCOUNT_HISTORY_FLAG.equals(messageText) && !PAYMENT_ACCOUNT_HISTORY_FLAG.equals(messageText)) {
+            return false;
+        }
+
+        if (AGENT_ACCOUNT_HISTORY_FLAG.equals(messageText)) {
+            List<BotAccountBookHistory> bookHistories = handlerManager.getAccountBookHistoryRepository().findByBelongIdAndBelongTypeAndType(message.getBotGroup().getGroupId(), DATA_TYPE_CHANNEL, AGENT, DateUtil.offsetHour(new Date(), -24));
+            String format = String.format("./%d.png", SnowflakeId.getId());
+            File imageFile = null;
+            try {
+                ImageUtil.generateTableImage(handlerManager.loadFontFromResources(), bookHistories, format);
+                imageFile = new File(format);
+                SendPhoto sendPhoto = new SendPhoto(message.getMessage().chat().id(), imageFile);
+                message.getTelegramBot().execute(sendPhoto);
+            } catch (Exception e) {
+                log.error("生成图片失败,错误信息:{}", e.getMessage());
+                e.printStackTrace();
+            } finally {
+                if (imageFile != null && imageFile.exists()) {
+                    imageFile.delete();
+                }
+            }
+            return true;
+        }
+
+        if (PAYMENT_ACCOUNT_HISTORY_FLAG.equals(messageText)) {
+            List<BotAccountBookHistory> bookHistories = handlerManager.getAccountBookHistoryRepository().findByBelongIdAndBelongTypeAndType(message.getBotGroup().getGroupId(), DATA_TYPE_CHANNEL, PAYMENT, DateUtil.offsetHour(new Date(), -24));
+            String format = String.format("./%d.png", SnowflakeId.getId());
+            File imageFile = null;
+            try {
+                ImageUtil.generateTableImage(handlerManager.loadFontFromResources(), bookHistories, format);
+                imageFile = new File(format);
+                SendPhoto sendPhoto = new SendPhoto(message.getMessage().chat().id(), imageFile);
+                message.getTelegramBot().execute(sendPhoto);
+            } catch (Exception e) {
+                log.error("生成图片失败,错误信息:{}", e.getMessage());
+                e.printStackTrace();
+            } finally {
+                if (imageFile != null && imageFile.exists()) {
+                    imageFile.delete();
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+}

+ 127 - 0
src/main/java/org/jebot/handler/impl/channel/ChannelBindGroupOrUnbindGroupHandler.java

@@ -0,0 +1,127 @@
+package org.jebot.handler.impl.channel;
+
+import com.pengrad.telegrambot.model.Chat;
+import com.pengrad.telegrambot.model.request.ReplyParameters;
+import com.pengrad.telegrambot.request.SendMessage;
+import com.pengrad.telegrambot.response.SendResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.jebot.constant.Constant;
+import org.jebot.handler.AbstractHandler;
+import org.jebot.handler.dto.BotMessage;
+import org.jebot.models.jebot.BotGroup;
+import org.jebot.models.xxpay.PayPassage;
+
+import java.util.regex.Pattern;
+
+import static org.jebot.constant.Constant.BIND_CHANNEL_FLAG;
+import static org.jebot.constant.Constant.UNBIND_CHANNEL_FLAG;
+
+@Slf4j
+public class ChannelBindGroupOrUnbindGroupHandler extends AbstractHandler {
+
+    //根据通道编号绑定到群组
+    private static final Pattern BIND_CHANNEL = Pattern.compile("^" + BIND_CHANNEL_FLAG + "[a-zA-Z0-9]+(,[a-zA-Z0-9]+)*$");
+
+
+
+    //根据通道编号解绑群组
+    private static final Pattern UNBIND_CHANNEL = Pattern.compile("^" + UNBIND_CHANNEL_FLAG + "[a-zA-Z0-9]+(,[a-zA-Z0-9]+)*$");
+
+    @Override
+    public boolean msgHandler(BotMessage botMessage) {
+
+        //只处理来自群组的消息
+        if (!botMessage.isGroupMessage()) {
+            return false;
+        }
+
+        //忽略商户群组消息
+        if (botMessage.isGroupMch()) {
+            return false;
+        }
+
+        //需要管理员权限
+        if (!botMessage.isUserAuthenticated()) {
+            return false;
+        }
+
+        if (botMessage.messageTextIsEmpty()) {
+            return false;
+        }
+
+        //群组内管理员发送绑定命令
+        if (BIND_CHANNEL.matcher(botMessage.getMessageText()).find()) {
+            bindChannelByChannelId(botMessage);
+            return true;
+        }
+
+        //判断消息是否包含解绑商户号的指令
+        if (UNBIND_CHANNEL.matcher(botMessage.getMessageText()).find()) {
+            String[] channelIds = getChannelIds(botMessage.getMessageText(), UNBIND_CHANNEL_FLAG);
+            for (String channelId : channelIds) {
+                this.handlerManager.getGroupRepository().deleteByDataIDAndDataType(Long.valueOf(channelId), Constant.DATA_TYPE_CHANNEL);
+            }
+            SendMessage sendMessage = new SendMessage(botMessage.getMessage().chat().id(), "解绑成功");
+            sendMessage.replyParameters(new ReplyParameters(botMessage.getMessage().messageId()));
+            SendResponse execute = botMessage.getTelegramBot().execute(sendMessage);
+            if (!execute.isOk()) {
+                log.error("解绑通道结果发送失败: {}", execute);
+            }
+            return true;
+        }
+        return false;
+    }
+
+
+    //根据通道id绑定到群组
+    private void bindChannelByChannelId(BotMessage botMessage) {
+        StringBuilder sb = new StringBuilder();
+        Chat chat = botMessage.getMessage().chat();
+        for (String channelIdStr : getChannelIds(botMessage.getMessageText(), BIND_CHANNEL_FLAG)) {
+            Long channelId = Long.valueOf(channelIdStr);
+            //发起查询通道信息请求
+            PayPassage payPassage = handlerManager.getPassageRepository().findByPassageId(channelId);
+            if (payPassage==null) {
+                continue;
+            }
+
+            BotGroup botGroup = this.handlerManager.getGroupRepository().findBotGroupByDataIdAndDataType(channelId, Constant.DATA_TYPE_CHANNEL);
+            if (botGroup == null) {
+                //如果通道不存在,保存通道
+                botGroup = new BotGroup();
+                botGroup.setDataId(channelId);
+                botGroup.setDataName(payPassage.getPassageName());
+                botGroup.setGroupId(chat.id());
+                botGroup.setGroupName(chat.title());
+                botGroup.setDataType(Constant.DATA_TYPE_CHANNEL);
+                this.handlerManager.getGroupRepository().save(botGroup);
+                sb.append(String.format("%s:%s\r\n", channelId, "绑定成功"));
+                continue;
+            }
+
+            if (botMessage.getMessage().chat().id().equals(botGroup.getGroupId())) {
+                sb.append(String.format("%s:%s\r\n", channelId, "已绑定到当前群组"));
+                continue;
+            }
+
+            //变更绑定到当前群组
+            botGroup.setGroupId(chat.id());
+            this.handlerManager.getGroupRepository().save(botGroup);
+            sb.append(String.format("%s:%s\r\n", channelId, String.format("从绑定 %s 群组变更为绑定 %s 群组\r\n", payPassage.getPassageName(), chat.title())));
+        }
+        SendMessage sendMessage = new SendMessage(chat.id(), sb.toString());
+        sendMessage.replyParameters(new ReplyParameters(botMessage.getMessage().messageId()));
+        SendResponse execute = botMessage.getTelegramBot().execute(sendMessage);
+        if (!execute.isOk()) {
+            log.error("绑定通道结果发送失败: {}", execute);
+        }
+    }
+
+    private static String[] getChannelIds(String text, String replaceStr) {
+        String channelIdsStr = text.replace(replaceStr, "");
+        if (channelIdsStr.contains(",")) {
+            return channelIdsStr.split(",");
+        }
+        return new String[]{channelIdsStr};
+    }
+}

+ 179 - 0
src/main/java/org/jebot/handler/impl/channel/ChannelInfoHandler.java

@@ -0,0 +1,179 @@
+package org.jebot.handler.impl.channel;
+
+import cn.hutool.core.date.DateUtil;
+import com.pengrad.telegrambot.model.request.ParseMode;
+import com.pengrad.telegrambot.model.request.ReplyParameters;
+import com.pengrad.telegrambot.request.SendMessage;
+import com.pengrad.telegrambot.response.SendResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.logging.log4j.util.Strings;
+import org.jebot.constant.Constant;
+import org.jebot.handler.AbstractHandler;
+import org.jebot.handler.dto.BotMessage;
+import org.jebot.models.jebot.BotAccountBook;
+import org.jebot.models.jebot.BotGroup;
+import org.jebot.models.xxpay.PayOrder;
+import org.jebot.models.xxpay.PayPassage;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static org.jebot.constant.Constant.*;
+
+@Slf4j
+public class ChannelInfoHandler extends AbstractHandler {
+
+//    private static final Pattern UPDATE_CHANNEL_AGENT_WARN_THRESHOLD = Pattern.compile("^" + UPDATE_CHANNEL_AGENT_WARN_THRESHOLD_FLAG + "-?\\d+(\\.\\d+)?$");
+//    private static final Pattern UPDATE_CHANNEL_PAYMENT_WARN_THRESHOLD = Pattern.compile("^" + UPDATE_CHANNEL_PAYMENT_WARN_THRESHOLD_FLAG + "-?\\d+(\\.\\d+)?$");
+
+
+    private static final Pattern QUERY_ORDER = Pattern.compile("^" + QUERY_CHANNEL_ORDER_FLAG + "[a-zA-Z0-9]+$");
+
+
+    @Override
+    public boolean msgHandler(BotMessage botMessage) {
+
+        //只处理来自群组的消息
+        if (!botMessage.isGroupMessage()) {
+            return false;
+        }
+
+        //忽略商户群组消息
+        if (botMessage.isGroupMch()) {
+            return false;
+        }
+
+        if (botMessage.messageTextIsEmpty()) {
+            return false;
+        }
+
+        //查询代收余额
+        if (botMessage.isGroupChannel() && QUERY_CHANNEL_BALANCE.equals(botMessage.getMessage().text())) {
+            Date endDate = new Date();
+            Date startDate = org.jebot.util.DateUtil.getTodayMidnight(endDate);
+            List<BotGroup> botGroups = handlerManager.getGroupRepository().findAllByGroupIdAndDataType(botMessage.getBotGroup().getGroupId(), DATA_TYPE_CHANNEL);
+            if (botGroups == null || botGroups.isEmpty()) {
+                botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), "当前群组未绑定任何通道"));
+                return true;
+            }
+
+            //转化为Long类型
+            List<Long> passageIds = botGroups.stream().map(BotGroup::getDataId).collect(Collectors.toList());
+            //根据通道ID查询通道
+            List<PayPassage> payPassages = handlerManager.getPassageRepository().findByPassageIds(passageIds);
+            //获取通道id
+            List<Long> payPassageIds = payPassages.stream().map(PayPassage::getId).collect(Collectors.toList());
+            //根据通道ID和时间查询订单
+            List<PayOrder> payOrders = handlerManager.getPayOrderRepository().findPayOrderByPassageAndCreateTime(payPassageIds, startDate, endDate);
+            //根据通道id分组
+            Map<Long, List<PayOrder>> payOrderMap = payOrders.stream().collect(Collectors.groupingBy(PayOrder::getPassageId));
+            //定义一个StringBuilder对象
+            StringBuilder stringBuffer = new StringBuilder();
+            BigDecimal channelTotalAfterFeeAmount = BigDecimal.ZERO;
+            BotAccountBook accountBook = handlerManager.getAccountBookRepository().findByBelongIdAndType(botMessage.getBotGroup().getGroupId(), DATA_TYPE_CHANNEL);
+            if (accountBook == null) {
+                accountBook = new BotAccountBook();
+                accountBook.setBelongId(botMessage.getBotGroup().getGroupId());
+                accountBook.setBelongName(botMessage.getBotGroup().getGroupName());
+                accountBook.setType(DATA_TYPE_CHANNEL);
+                accountBook.setAgentBalance(0L);
+                accountBook.setPaymentBalance(0L);
+                accountBook = handlerManager.getAccountBookRepository().save(accountBook);
+            }
+            for (PayPassage payPassage : payPassages) {
+                List<PayOrder> payPassageOrders = payOrderMap.get(payPassage.getId());
+                if (payPassageOrders != null && !payPassageOrders.isEmpty()) {
+                    // 计算未减去手续费的总金额
+                    BigDecimal totalAmount = payPassageOrders.stream()
+                            .map(PayOrder::getAmount)
+                            .reduce(BigDecimal.ZERO, BigDecimal::add);
+                    // 计算减去手续费后的总金额
+                    BigDecimal totalAmountAfterFee = payPassageOrders.stream()
+                            .map(order -> {
+                                BigDecimal amount = order.getAmount();
+                                BigDecimal mchRate = order.getMchRate();
+                                if (mchRate.doubleValue() < 100.00) {
+                                    return amount.subtract(amount.multiply(mchRate.movePointLeft(AMOUNT_MOVE_POINT)));
+                                }
+                                return amount;
+                            })
+                            .reduce(BigDecimal.ZERO, BigDecimal::add);
+                    channelTotalAfterFeeAmount = channelTotalAfterFeeAmount.add(totalAmountAfterFee);
+                    stringBuffer.append("通道名: ").append(payPassage.getPassageName()).append("\n");
+                    stringBuffer.append("通道号: ").append(payPassage.getId()).append("\n");
+                    stringBuffer.append("当日跑量: ").append(totalAmount.movePointLeft(Constant.AMOUNT_MOVE_POINT).doubleValue()).append("\n");
+                    stringBuffer.append("应得余额: ").append(totalAmountAfterFee.movePointLeft(Constant.AMOUNT_MOVE_POINT).doubleValue()).append("\n\n");
+                } else {
+                    stringBuffer.append("通道名: ").append(payPassage.getPassageName()).append("\n");
+                    stringBuffer.append("通道号: ").append(payPassage.getId()).append("\n");
+                    stringBuffer.append("当日跑量: 0.0\n");
+                    stringBuffer.append("应得余额: 0.0\n\n");
+                }
+            }
+            stringBuffer.append("当前预付: ").append(accountBook.getPaymentBalance()).append("\n");
+            stringBuffer.append("剩余预付: ").append(accountBook.getPaymentBalance() - channelTotalAfterFeeAmount.movePointLeft(AMOUNT_MOVE_POINT).doubleValue()).append("\n");
+            stringBuffer.append("统计时间: ").append(DateUtil.formatDateTime(endDate)).append("\n\n");
+            botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), stringBuffer.toString()));
+            return true;
+        }
+
+        //通道查单
+        if (botMessage.isGroupChannel() && !botMessage.messageTextIsEmpty() && QUERY_ORDER.matcher(botMessage.getMessage().text()).find()) {
+            String orderId = botMessage.getMessage().text().replace(QUERY_CHANNEL_ORDER_FLAG, "");
+            PayOrder payOrder = this.handlerManager.getPayOrderRepository().findByPayOrderIdOrChannelOrderNo(orderId);
+            if (payOrder == null) {
+                botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), "订单不存在"));
+                return true;
+            }
+
+            StringBuilder stringBuffer = new StringBuilder();
+            stringBuffer.append("商户号: ").append(payOrder.getMchId()).append("\n");
+            stringBuffer.append("平台单号: ").append("`").append(payOrder.getPayOrderId()).append("`").append("\n");
+            if (Strings.isNotEmpty(payOrder.getChannelOrderNo())) {
+                stringBuffer.append("通道单号: ").append("`").append(payOrder.getChannelOrderNo()).append("`").append("\n");
+            } else {
+                stringBuffer.append("通道单号: 无\n");
+            }
+            stringBuffer.append("支付金额: ").append(payOrder.getAmount().movePointLeft(Constant.AMOUNT_MOVE_POINT)).append("\n");
+            stringBuffer.append("支付状态: ").append(payOrder.getStatusMsg()).append("\n");
+            if (payOrder.getPaySuccTime() != null) {
+                stringBuffer.append("支付时间: ").append(DateUtil.formatDateTime(payOrder.getPaySuccTime())).append("\n");
+            } else {
+                stringBuffer.append("支付时间: 无\n");
+            }
+            stringBuffer.append("创建时间: ").append(payOrder.getCreateTime()).append("\n");
+            SendMessage sendMessage = new SendMessage(botMessage.getMessage().chat().id(), this.strEscape(stringBuffer.toString()));
+            sendMessage.parseMode(ParseMode.MarkdownV2);
+            sendMessage.replyParameters(new ReplyParameters(botMessage.getMessage().messageId()));
+            botMessage.getTelegramBot().execute(sendMessage);
+            return true;
+        }
+
+        if (QUERY_CHANNEL_INFO.equals(botMessage.getMessageText())) {
+            List<BotGroup> botGroups = this.handlerManager.getGroupRepository().findAllByGroupIdAndDataType(botMessage.getMessage().chat().id(), Constant.DATA_TYPE_CHANNEL);
+            StringBuilder sb = new StringBuilder();
+            if (botGroups == null || botGroups.isEmpty()) {
+                sb.append("当前群组未绑定任何通道");
+            } else {
+                for (BotGroup botGroup : botGroups) {
+                    sb.append(String.format("通道ID: %s\r\n", botGroup.getDataId()));
+                    sb.append(String.format("通道名称: %s\r\n", botGroup.getDataName()));
+                    sb.append(String.format("绑定时间: %s\r\n", botGroup.getCreateTime()));
+                }
+            }
+            SendMessage sendMessage = new SendMessage(botMessage.getMessage().chat().id(), sb.toString());
+            sendMessage.replyParameters(new ReplyParameters(botMessage.getMessage().messageId()));
+            SendResponse execute = botMessage.getTelegramBot().execute(sendMessage);
+            if (!execute.isOk()) {
+                log.error("通道信息发送失败: {}", execute);
+            }
+        }
+        return false;
+    }
+
+
+}

+ 94 - 0
src/main/java/org/jebot/handler/impl/channel/ChannelStausHandler.java

@@ -0,0 +1,94 @@
+package org.jebot.handler.impl.channel;
+
+import cn.hutool.json.JSONObject;
+import com.pengrad.telegrambot.model.request.ReplyParameters;
+import com.pengrad.telegrambot.request.SendMessage;
+import com.pengrad.telegrambot.response.SendResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.jebot.constant.Constant;
+import org.jebot.handler.AbstractHandler;
+import org.jebot.handler.dto.BotMessage;
+
+import java.util.regex.Pattern;
+
+import static org.jebot.constant.Constant.DISABLE_CHANNEL_FLAG;
+import static org.jebot.constant.Constant.ENABLE_CHANNEL_FLAG;
+
+@Slf4j
+public class ChannelStausHandler extends AbstractHandler {
+
+
+    //根据通道编号启用通道
+    private static final Pattern ENABLE_CHANNEL = Pattern.compile("^" + ENABLE_CHANNEL_FLAG + "[a-zA-Z0-9]+(,[a-zA-Z0-9]+)*$");
+
+
+    //根据通道编号禁用通道
+    private static final Pattern DISABLE_CHANNEL = Pattern.compile("^" + DISABLE_CHANNEL_FLAG + "[a-zA-Z0-9]+(,[a-zA-Z0-9]+)*$");
+
+
+    @Override
+    public boolean msgHandler(BotMessage botMessage) {
+
+        if (botMessage.messageTextIsEmpty()){
+            return false;
+        }
+
+        if (!botMessage.isUserAuthenticated()) {
+            return false;
+        }
+
+        if (!botMessage.isUserAuthenticated()) {
+            return false;
+        }
+
+        //群组内管理员发送打开通道命令
+        if (ENABLE_CHANNEL.matcher(botMessage.getMessageText()).matches()) {
+            StringBuilder sb = new StringBuilder();
+            for (String channelId : getChannelIds(botMessage.getMessageText(), ENABLE_CHANNEL_FLAG)) {
+                //开启通道
+                if (handlerManager.getPassageRepository().updateStatusByIdI(Constant.OPEN_CHANNEL, Long.valueOf(channelId)) < 1) {
+                    sb.append(channelId).append(":").append("开启失败").append("\r\n");
+                } else {
+                    sb.append(channelId).append(":").append("开启成功\r\n");
+                }
+            }
+            SendMessage sendMessage = new SendMessage(botMessage.getMessage().chat().id(), sb.toString());
+            sendMessage.replyParameters(new ReplyParameters(botMessage.getMessage().messageId()));
+            SendResponse execute = botMessage.getTelegramBot().execute(sendMessage);
+            if (!execute.isOk()) {
+                log.error("开启通道结果发送失败: {}", execute);
+            }
+            return true;
+        }
+
+        //群组内管理员发送关闭通道命令
+        if (DISABLE_CHANNEL.matcher(botMessage.getMessageText()).matches()) {
+            StringBuilder sb = new StringBuilder();
+            for (String channelId : getChannelIds(botMessage.getMessageText(), DISABLE_CHANNEL_FLAG)) {
+                //关闭通道
+                if (handlerManager.getPassageRepository().updateStatusByIdI(Constant.CLOSE_CHANNEL, Long.valueOf(channelId)) < 1) {
+                    sb.append(channelId).append(":").append("关闭失败").append("\r\n");
+                } else {
+                    sb.append(channelId).append(":").append("关闭成功\r\n");
+                }
+            }
+            SendMessage sendMessage = new SendMessage(botMessage.getMessage().chat().id(), sb.toString());
+            sendMessage.replyParameters(new ReplyParameters(botMessage.getMessage().messageId()));
+            SendResponse execute = botMessage.getTelegramBot().execute(sendMessage);
+            if (!execute.isOk()) {
+                log.error("关闭通道结果发送失败: {}", execute);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private static String[] getChannelIds(String text, String replaceStr) {
+        String channelIdsStr = text.replace(replaceStr, "");
+        if (channelIdsStr.contains(",")) {
+            return channelIdsStr.split(",");
+        }
+        return new String[]{channelIdsStr};
+    }
+
+}

+ 96 - 0
src/main/java/org/jebot/handler/impl/channel/ChannelSuccessRateHandler.java

@@ -0,0 +1,96 @@
+package org.jebot.handler.impl.channel;
+
+import cn.hutool.core.date.DateUtil;
+import com.pengrad.telegrambot.request.SendMessage;
+import org.jebot.constant.Constant;
+import org.jebot.handler.AbstractHandler;
+import org.jebot.handler.dto.BotMessage;
+import org.jebot.models.jebot.BotGroup;
+import org.jebot.models.xxpay.PayOrder;
+import org.jebot.models.xxpay.PayPassage;
+import org.jebot.repository.xxpay.PayOrderRepository;
+
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.jebot.constant.Constant.QUERY_CHANNEL_TODAY_SUCCESS_RATE;
+
+public class ChannelSuccessRateHandler extends AbstractHandler {
+    @Override
+    public boolean msgHandler(BotMessage botMessage) {
+
+        //只处理来自群组的消息
+        if (!botMessage.isGroupMessage()) {
+            return false;
+        }
+
+        //忽略商户群组消息
+        if (botMessage.isGroupMch()) {
+            return false;
+        }
+
+        if (botMessage.messageTextIsEmpty()) {
+            return false;
+        }
+
+        //查询群组绑定的通道成功率
+        if (botMessage.isGroupChannel() && QUERY_CHANNEL_TODAY_SUCCESS_RATE.equals(botMessage.getMessage().text())) {
+            Date currentTime = new Date();
+            PayOrderRepository payOrderRepository = handlerManager.getPayOrderRepository();
+            List<BotGroup> botGroups = handlerManager.getGroupRepository().findAllByGroupIdAndDataType(botMessage.getMessage().chat().id(), Constant.DATA_TYPE_CHANNEL);
+            if (botGroups == null || botGroups.isEmpty()) {
+                botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), "当前群组未绑定任何通道"));
+                return true;
+            }
+            //获取当前群组绑定的通道
+            List<Long> passageIds = botGroups.stream().map(BotGroup::getDataId).collect(Collectors.toList());
+
+            List<PayOrder> payOrderList = payOrderRepository.findPayOrderByPassageAndCreateTime(passageIds, org.jebot.util.DateUtil.getTodayMidnight(currentTime), currentTime);
+            if (payOrderList == null || payOrderList.isEmpty()) {
+                botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), "无当日记录"));
+                return true;
+            }
+
+            List<PayPassage> passages = handlerManager.getPassageRepository().findByPassageIds(passageIds);
+            StringBuilder result = new StringBuilder();
+
+
+            // 时间段
+            Date halfHourAgo = DateUtil.offsetMinute(currentTime, -30);
+            Date oneHourAgo = DateUtil.offsetMinute(currentTime, -60);
+            Date threeHoursAgo = DateUtil.offsetMinute(currentTime, -180);
+
+            // 按通道统计
+            for (PayPassage passage : passages) {
+                List<PayOrder> passageOrders = payOrderList.stream().filter(order -> passage.getId().equals(order.getPassageId())).collect(Collectors.toList());
+
+                // 统计不同时间段的成功率
+                double halfHourSuccessRate = calculateSuccessRate(passageOrders, halfHourAgo, currentTime);
+                double oneHourSuccessRate = calculateSuccessRate(passageOrders, oneHourAgo, currentTime);
+                double threeHourSuccessRate = calculateSuccessRate(passageOrders, threeHoursAgo, currentTime);
+                double dailySuccessRate = calculateSuccessRate(passageOrders, DateUtil.beginOfDay(currentTime), currentTime);
+
+                result.append("通道名称: ").append(passage.getPassageName()).append("\n");
+                result.append("   30分钟成功率: ").append(String.format("%.2f", halfHourSuccessRate)).append("%\n");
+                result.append("   1小时成功率: ").append(String.format("%.2f", oneHourSuccessRate)).append("%\n");
+                result.append("   3小时成功率: ").append(String.format("%.2f", threeHourSuccessRate)).append("%\n");
+                result.append("   当日成功率: ").append(String.format("%.2f", dailySuccessRate)).append("%\n");
+            }
+            botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), result.toString()));
+            return true;
+        }
+
+        return false;
+    }
+
+
+    // 计算成功率的方法
+    private static double calculateSuccessRate(List<PayOrder> orders, Date startTime, Date endTime) {
+        List<PayOrder> filteredOrders = orders.stream().filter(order -> startTime.before(order.getCreateTime()) && endTime.after(order.getCreateTime())).collect(Collectors.toList());
+        long successCount = filteredOrders.stream().filter(order -> order.getStatus() == 2 || order.getStatus() == 3) // 假设状态 2 和 3 为成功
+                .count();
+        return filteredOrders.isEmpty() ? 0 : (successCount * 100.0 / filteredOrders.size());
+    }
+
+}

+ 301 - 0
src/main/java/org/jebot/handler/impl/complaint/ComplaintHandler.java

@@ -0,0 +1,301 @@
+package org.jebot.handler.impl.complaint;
+
+import cn.hutool.core.date.DateUtil;
+import com.pengrad.telegrambot.model.Update;
+import com.pengrad.telegrambot.model.request.ReplyParameters;
+import com.pengrad.telegrambot.request.CopyMessage;
+import com.pengrad.telegrambot.request.DeleteMessage;
+import com.pengrad.telegrambot.request.GetFile;
+import com.pengrad.telegrambot.request.SendMessage;
+import com.pengrad.telegrambot.response.MessageIdResponse;
+import com.pengrad.telegrambot.response.SendResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.jebot.constant.Constant;
+import org.jebot.handler.AbstractHandler;
+import org.jebot.handler.dto.BotMessage;
+import org.jebot.models.jebot.BotAccountBook;
+import org.jebot.models.jebot.BotComplaint;
+import org.jebot.models.jebot.BotGroup;
+import org.jebot.models.xxpay.PayOrder;
+import org.jebot.repository.jebot.BotAccountBookRepository;
+import org.jebot.service.dto.UpdateBalance;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Base64;
+import java.util.Date;
+
+
+@Slf4j
+public class ComplaintHandler extends AbstractHandler {
+    @Override
+    public boolean msgHandler(BotMessage botMessage) {
+
+        // 只处理来自用户的消息
+        if (botMessage.isGroupMessage()) {
+            return false;
+        }
+
+        //文字和图片都存在,类似投诉转发
+        if (botMessage.isContainsTextAndPhoto()) {
+            String orderId = botMessage.getMessage().caption().trim();
+
+            BotComplaint botComplaint = this.handlerManager.getComplaintRepository().findByComplaintId(orderId);
+            if (botComplaint != null) {
+                log.warn("投诉单号已存在,跳过处理: {}", orderId);
+                SendMessage sendMessage = new SendMessage(botMessage.getMessage().chat().id(), "订单重复");
+                sendMessage.replyParameters(new ReplyParameters(botMessage.getMessage().messageId()));
+                SendResponse execute = botMessage.getTelegramBot().execute(sendMessage);
+                if (!execute.isOk()) {
+                    log.error("重复投诉结果发送失败: {}", execute);
+                }
+                return true;
+            }
+
+            //查询订单信息
+            PayOrder payOrder = handlerManager.getPayOrderRepository().findByPayOrderIdOrChannelOrderNo(orderId);
+
+            //判断订单是否存在
+            if (payOrder == null) {
+                SendMessage sendMessage = new SendMessage(botMessage.getMessage().chat().id(), "订单未找到");
+                sendMessage.replyParameters(new ReplyParameters(botMessage.getMessage().messageId()));
+                SendResponse execute = botMessage.getTelegramBot().execute(sendMessage);
+                if (!execute.isOk()) {
+                    log.error("查询订单结果发送失败: {}", execute);
+                }
+                return true;
+            }
+
+            Long mchId = payOrder.getMchId();
+            BotGroup merchantGroup = this.handlerManager.getGroupRepository().findBotGroupByDataIdAndDataType(mchId, Constant.DATA_TYPE_MERCHANT);
+            if (merchantGroup == null) {
+                log.info("商户号未绑定群组: {}", mchId);
+                SendMessage sendMessage = new SendMessage(botMessage.getMessage().chat().id(), "商户号未绑定群组");
+                sendMessage.replyParameters(new ReplyParameters(botMessage.getMessage().messageId()));
+                SendResponse execute = botMessage.getTelegramBot().execute(sendMessage);
+                if (!execute.isOk()) {
+                    log.error("查询订单结果发送失败: {}", execute);
+                }
+                return true;
+            }
+
+            BotGroup channelGroup = this.handlerManager.getGroupRepository().findBotGroupByDataIdAndDataType(payOrder.getPassageId(), Constant.DATA_TYPE_CHANNEL);
+            if (channelGroup == null) {
+                log.info("通道号未绑定群组: {}", payOrder.getPassageId());
+                SendMessage sendMessage = new SendMessage(botMessage.getMessage().chat().id(), "通道号未绑定群组");
+                sendMessage.replyParameters(new ReplyParameters(botMessage.getMessage().messageId()));
+                SendResponse execute = botMessage.getTelegramBot().execute(sendMessage);
+                if (!execute.isOk()) {
+                    log.error("查询订单结果发送失败: {}", execute);
+                }
+                return true;
+            }
+
+            botComplaint = new BotComplaint();
+            botComplaint.setComplaintId(orderId);
+            botComplaint.setMchId(merchantGroup.getDataId());
+            botComplaint.setComplaintContent(payOrder.toString());
+            Update update = botMessage.getUpdate();
+            // 获取 file_id
+            String fileId = update.message().photo()[update.message().photo().length - 1].fileId();
+
+            // 获取文件路径
+            String filePath = botMessage.getTelegramBot().execute(new GetFile(fileId)).file().filePath();
+
+
+            //商户加预付金额
+            //查询商户预付记账本
+            BotAccountBookRepository accountBookRepository = handlerManager.getAccountBookRepository();
+            BotAccountBook merchantAccountBook = accountBookRepository.findByBelongIdAndType(merchantGroup.getDataId(), Constant.DATA_TYPE_MERCHANT);
+            if (merchantAccountBook == null) {
+                merchantAccountBook = new BotAccountBook();
+                merchantAccountBook.setBelongId(merchantGroup.getDataId());
+                merchantAccountBook.setBelongName(merchantGroup.getDataName());
+                merchantAccountBook.setType(Constant.DATA_TYPE_MERCHANT);
+                merchantAccountBook.setPaymentBalance(0);
+                merchantAccountBook.setAgentBalance(0);
+                merchantAccountBook = accountBookRepository.save(merchantAccountBook);
+            }
+
+            BotAccountBook channelAccountBook = accountBookRepository.findByBelongIdAndType(channelGroup.getDataId(), Constant.DATA_TYPE_CHANNEL);
+            if (channelAccountBook == null) {
+                channelAccountBook = new BotAccountBook();
+                channelAccountBook.setBelongId(merchantGroup.getDataId());
+                channelAccountBook.setBelongName(merchantGroup.getDataName());
+                channelAccountBook.setType(Constant.DATA_TYPE_MERCHANT);
+                channelAccountBook.setPaymentBalance(0);
+                channelAccountBook.setAgentBalance(0);
+                channelAccountBook = accountBookRepository.save(merchantAccountBook);
+            }
+
+            double oldMerchantPaymentBalance = merchantAccountBook.getPaymentBalance();
+            double oldChannelPaymentBalance = channelAccountBook.getPaymentBalance();
+            double amount = payOrder.getAmount().movePointLeft(Constant.AMOUNT_MOVE_POINT).doubleValue();
+            double newMerchantPaymentBalance = BigDecimal.valueOf(oldMerchantPaymentBalance).movePointRight(Constant.AMOUNT_MOVE_POINT).
+                    add(payOrder.getAmount()).movePointLeft(Constant.AMOUNT_MOVE_POINT).doubleValue();
+            double newChannelPaymentBalance = BigDecimal.valueOf(oldChannelPaymentBalance).movePointRight(Constant.AMOUNT_MOVE_POINT).
+                    add(payOrder.getAmount()).movePointLeft(Constant.AMOUNT_MOVE_POINT).doubleValue();
+
+            //转发消息到商户群组
+            CopyMessage sendMerchantCopyMessage = new CopyMessage(merchantGroup.getGroupId(), update.message().chat().id(), update.message().messageId());
+            sendMerchantCopyMessage.caption(payOrder.getMchOrderNo());
+
+            //更新商户余额
+            UpdateBalance updateMerchantPaymentBalance = new UpdateBalance();
+            updateMerchantPaymentBalance.setId(merchantAccountBook.getId());
+            updateMerchantPaymentBalance.setOldBalance(oldMerchantPaymentBalance);
+            updateMerchantPaymentBalance.setNewBalance(newMerchantPaymentBalance);
+            updateMerchantPaymentBalance.setAmount(amount);
+            BotMessage merchantMessage = new BotMessage();
+            merchantMessage.setBotGroup(merchantGroup);
+            merchantMessage.setBotUser(botMessage.getBotUser());
+            updateMerchantPaymentBalance.setMessage(merchantMessage);
+
+            //更新通道余额
+            UpdateBalance updateChannelPaymentBalance = new UpdateBalance();
+            updateChannelPaymentBalance.setId(channelAccountBook.getId());
+            updateChannelPaymentBalance.setOldBalance(oldChannelPaymentBalance);
+            updateChannelPaymentBalance.setNewBalance(newChannelPaymentBalance);
+            updateChannelPaymentBalance.setAmount(amount);
+            BotMessage channelMessage = new BotMessage();
+            channelMessage.setBotGroup(channelGroup);
+            channelMessage.setBotUser(botMessage.getBotUser());
+            updateChannelPaymentBalance.setMessage(channelMessage);
+            //构建发出投诉单号到上游群组的消息
+            StringBuilder stringBuilderChannel = new StringBuilder();
+            stringBuilderChannel.append("投诉订单: ").append("\n");
+            stringBuilderChannel.append("投诉金额: ").append(amount).append("\n");
+            stringBuilderChannel.append("投诉前: ").append(oldChannelPaymentBalance).append("\n");
+            stringBuilderChannel.append("投诉后: ").append(newChannelPaymentBalance).append("\n");
+            SendMessage sendChannelMessage = new SendMessage(channelGroup.getGroupId(), stringBuilderChannel.toString());
+            StringBuilder stringBuilderChannelOrder = new StringBuilder();
+            stringBuilderChannelOrder.append("投诉订单: ").append("\n");
+            stringBuilderChannelOrder.append("订单号: ").append(payOrder.getPayOrderId()).append("\n");
+            stringBuilderChannelOrder.append("金额: ").append(amount).append("\n");
+            stringBuilderChannelOrder.append("投诉时间: ").append(DateUtil.formatDateTime(new Date())).append("\n");
+            SendMessage sendChannelMessageOrder = new SendMessage(channelGroup.getGroupId(), stringBuilderChannelOrder.toString());
+
+            StringBuilder stringBuilderMerchant = new StringBuilder();
+            stringBuilderMerchant.append("投诉订单: ").append("\n");
+            stringBuilderMerchant.append("投诉金额: ").append(amount).append("\n");
+            stringBuilderMerchant.append("投诉前: ").append(oldMerchantPaymentBalance).append("\n");
+            stringBuilderMerchant.append("投诉后: ").append(newMerchantPaymentBalance).append("\n");
+            SendMessage sendMerchantMessage = new SendMessage(merchantGroup.getGroupId(), stringBuilderMerchant.toString());
+            MessageIdResponse merchantCopyResponse = null;
+            SendResponse merchantMessageResponse = null;
+            SendResponse channelMessageResponse = null;
+            SendResponse channelOrderMessageResponse = null;
+            try {
+                merchantCopyResponse = botMessage.getTelegramBot().execute(sendMerchantCopyMessage);
+                Thread.sleep(300);
+                merchantMessageResponse = botMessage.getTelegramBot().execute(sendMerchantMessage);
+                Thread.sleep(300);
+                channelMessageResponse =   botMessage.getTelegramBot().execute(sendChannelMessage);
+                Thread.sleep(300);
+                channelOrderMessageResponse =   botMessage.getTelegramBot().execute(sendChannelMessageOrder);
+                Thread.sleep(300);
+                if (!merchantCopyResponse.isOk() || !merchantMessageResponse.isOk() || !channelMessageResponse.isOk()){
+                    log.error("消息发送失败: {}", merchantCopyResponse);
+                    throw new Exception("消息发送失败");
+                }
+            } catch (Exception e) {
+                log.error("消息发送失败,开始撤回已发送的消息: {}", e.getMessage());
+                try {
+                    if (merchantCopyResponse != null && merchantCopyResponse.messageId() != null) {
+                        botMessage.getTelegramBot().execute(new DeleteMessage(merchantGroup.getGroupId(), merchantCopyResponse.messageId()));
+                    }
+                    if (merchantMessageResponse != null && merchantMessageResponse.message().messageId() != null) {
+                        botMessage.getTelegramBot().execute(new DeleteMessage(merchantGroup.getGroupId(), merchantMessageResponse.message().messageId()));
+                    }
+                    if (channelMessageResponse != null && channelMessageResponse.message().messageId()  != null) {
+                        botMessage.getTelegramBot().execute(new DeleteMessage(channelGroup.getGroupId(), channelMessageResponse.message().messageId() ));
+                    }
+                    if (channelOrderMessageResponse != null && channelOrderMessageResponse.message().messageId()  != null) {
+                        botMessage.getTelegramBot().execute(new DeleteMessage(channelGroup.getGroupId(), channelOrderMessageResponse.message().messageId() ));
+                    }
+                } catch (Exception rollbackException) {
+                    log.error("消息撤回失败: {}", rollbackException.getMessage());
+                }
+            }
+
+            try {
+                handlerManager.getAccountBookService().updateMutiPaymentBalanceByIdAndPaymentBalance(updateMerchantPaymentBalance, updateChannelPaymentBalance);
+            } catch (Exception e) {
+                log.error("更新商户或通道余额失败: {}", e.getMessage());
+                e.printStackTrace();
+                SendMessage sendMessage = new SendMessage(botMessage.getMessage().chat().id(), "处理失败,请稍后再试");
+                sendMessage.replyParameters(new ReplyParameters(botMessage.getMessage().messageId()));
+                botMessage.getTelegramBot().execute(sendMessage);
+                try {
+                    if (merchantCopyResponse != null) {
+                        botMessage.getTelegramBot().execute(new DeleteMessage(merchantGroup.getGroupId(), merchantCopyResponse.messageId()));
+                    }
+                    if (merchantMessageResponse != null && merchantMessageResponse.message() != null) {
+                        botMessage.getTelegramBot().execute(new DeleteMessage(merchantGroup.getGroupId(), merchantMessageResponse.message().messageId()));
+                    }
+                    if (channelMessageResponse != null && channelMessageResponse.message() != null) {
+                        botMessage.getTelegramBot().execute(new DeleteMessage(channelGroup.getGroupId(), channelMessageResponse.message().messageId()));
+                    }
+                    if (channelOrderMessageResponse != null&& channelOrderMessageResponse.message() !=null) {
+                        botMessage.getTelegramBot().execute(new DeleteMessage(channelGroup.getGroupId(), channelOrderMessageResponse.message().messageId()));
+                    }
+                } catch (Exception rollbackException) {
+                    log.error("消息撤回失败: {}", rollbackException.getMessage());
+                }
+                return true;
+            }
+
+            // 下载图片并转换为 Base64
+            SendMessage sendMessage = null;
+            try {
+                byte[] imageBytes = getImageBytes(botMessage, filePath);
+                String base64Image = Base64.getEncoder().encodeToString(imageBytes);
+                botComplaint.setComplaintPhoto(base64Image);
+                this.handlerManager.getComplaintRepository().save(botComplaint);
+                sendMessage = new SendMessage(botMessage.getMessage().chat().id(), "入库成功");
+            } catch (Exception e) {
+                log.error("图片下载或转换失败: {}", e.getMessage());
+                try {
+                    botComplaint.setComplaintPhoto("图片下载或转换失败:" + e.getMessage());
+                    this.handlerManager.getComplaintRepository().save(botComplaint);
+                    sendMessage = new SendMessage(botMessage.getMessage().chat().id(), "入库成功");
+                } catch (Exception ex) {
+                    log.error("保存投诉数据失败: {}", ex.getMessage());
+                }
+            }
+
+            if (sendMessage == null) {
+                sendMessage = new SendMessage(botMessage.getMessage().chat().id(), "已发送下游,入库失败");
+            }
+            sendMessage.replyParameters(new ReplyParameters(botMessage.getMessage().messageId()));
+            SendResponse sendResponse = botMessage.getTelegramBot().execute(sendMessage);
+            if (!sendResponse.isOk()) {
+                log.error("投诉单号转发结果发送失败: {}", sendResponse);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private static byte[] getImageBytes(BotMessage botMessage, String filePath) throws IOException {
+        String fileUrl = "https://api.telegram.org/file/bot" + botMessage.getTelegramBot().getToken() + "/" + filePath;
+        HttpURLConnection connection = (HttpURLConnection) new URL(fileUrl).openConnection();
+        connection.setConnectTimeout(15000);
+        connection.setRequestMethod("GET");
+        InputStream inputStream = connection.getInputStream();
+        byte[] imageBytes;
+        try (ByteArrayOutputStream buffer = new ByteArrayOutputStream()) {
+            byte[] temp = new byte[1024];
+            int bytesRead;
+            while ((bytesRead = inputStream.read(temp)) != -1) {
+                buffer.write(temp, 0, bytesRead);
+            }
+            imageBytes = buffer.toByteArray();
+        }
+        return imageBytes;
+    }
+}

+ 134 - 0
src/main/java/org/jebot/handler/impl/merchant/MerchantAccountBookHandler.java

@@ -0,0 +1,134 @@
+package org.jebot.handler.impl.merchant;
+
+import cn.hutool.core.date.DateUtil;
+import com.pengrad.telegrambot.request.SendMessage;
+import com.pengrad.telegrambot.request.SendPhoto;
+import lombok.extern.slf4j.Slf4j;
+import org.jebot.constant.Constant;
+import org.jebot.handler.AbstractHandler;
+import org.jebot.handler.dto.BotMessage;
+import org.jebot.models.jebot.BotAccountBook;
+import org.jebot.models.jebot.BotAccountBookHistory;
+import org.jebot.repository.jebot.BotAccountBookRepository;
+import org.jebot.service.dto.UpdateBalance;
+import org.jebot.util.SnowflakeId;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.util.Date;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import static org.jebot.constant.Constant.*;
+
+@Slf4j
+public class MerchantAccountBookHandler extends AbstractHandler {
+
+
+    //代收正则表达式
+    private static final Pattern paymentAccount = Pattern.compile("^" + PAYMENT_ACCOUNT_FLAG + "-?\\d+(\\.\\d+)?$");
+
+    //代付正则表达式
+    private static final Pattern agentAccount = Pattern.compile("^" + AGENT_ACCOUNT_FLAG + "-?\\d+(\\.\\d+)?$");
+
+
+    @Override
+    public boolean msgHandler(BotMessage message) {
+
+        //只处理商户群组消息
+        if (!message.isGroupMch()) {
+            return false;
+        }
+
+        //只处理群组消息
+        if (!message.isUserAuthenticated()) {
+            return false;
+        }
+
+        if (message.messageTextIsEmpty()) {
+            return false;
+        }
+
+        String messageText = message.getMessageText();
+        if (!paymentAccount.matcher(messageText).find() && !agentAccount.matcher(messageText).find()) {
+            return false;
+        }
+
+        //获取账本
+        BotAccountBook accountBook = handlerManager.getAccountBookRepository().findByBelongIdAndType(message.getBotGroup().getDataId(), Constant.DATA_TYPE_MERCHANT);
+        if (accountBook == null) {
+            accountBook = new BotAccountBook();
+            accountBook.setBelongId(message.getBotGroup().getDataId());
+            accountBook.setBelongName(message.getBotGroup().getDataName());
+            accountBook.setType(Constant.DATA_TYPE_MERCHANT);
+            accountBook.setAgentBalance(0L);
+            accountBook.setPaymentBalance(0L);
+            accountBook = handlerManager.getAccountBookRepository().save(accountBook);
+        }
+
+        //代收记账处理
+        if (paymentAccount.matcher(messageText).find()) {
+            double paymentAccountAmount = Double.parseDouble(messageText.replace(PAYMENT_ACCOUNT_FLAG, ""));
+            if (paymentAccountAmount == 0) {
+                return true;
+            }
+            double paymentBalanceOld = accountBook.getPaymentBalance();
+            double paymentBalanceNew = paymentBalanceOld + paymentAccountAmount;
+            SendMessage sendMessage = null;
+            try {
+                UpdateBalance updateBalance = new UpdateBalance();
+                updateBalance.setId(accountBook.getId());
+                updateBalance.setAmount(paymentAccountAmount);
+                updateBalance.setOldBalance(paymentBalanceOld);
+                updateBalance.setNewBalance(paymentBalanceNew);
+                updateBalance.setMessage(message);
+                handlerManager.getAccountBookService().updatePaymentBalanceByIdAndPaymentBalance(updateBalance);
+                sendMessage = new SendMessage(message.getMessage().chat().id(), String.format("入账提醒\n入账金额: %.2f\n入账前: %.2f\n入账后: %.2f", paymentAccountAmount, paymentBalanceOld, paymentBalanceNew));
+            } catch (Exception e) {
+                log.error("更新商户群组账本失败,群组ID:{},群组名称:{},用户ID:{},用户名称:{},金额:{},错误信息:{}", message.getBotGroup().getDataId(), message.getBotGroup().getDataName(), message.getBotUser().getUserId(), message.getBotUser().getUserName(), paymentAccountAmount, e.getMessage());
+                e.printStackTrace();
+                sendMessage = new SendMessage(message.getMessage().chat().id(), "处理失败,请重试");
+            } finally {
+                if (sendMessage != null) {
+                    message.getTelegramBot().execute(sendMessage);
+                }
+            }
+            return true;
+        }
+
+        //代付处理记账
+        if (agentAccount.matcher(messageText).find()) {
+            double agentAccountAmount = Double.parseDouble(messageText.replace(AGENT_ACCOUNT_FLAG, ""));
+            if (agentAccountAmount == 0) {
+                return true;
+            }
+            double agentBalanceOld = accountBook.getAgentBalance();
+            double agentBalanceNew = agentBalanceOld + agentAccountAmount;
+            SendMessage sendMessage = null;
+            try {
+                UpdateBalance updateBalance = new UpdateBalance();
+                updateBalance.setId(accountBook.getId());
+                updateBalance.setAmount(agentAccountAmount);
+                updateBalance.setOldBalance(agentBalanceOld);
+                updateBalance.setNewBalance(agentBalanceNew);
+                updateBalance.setMessage(message);
+                handlerManager.getAccountBookService().updateAgentBalanceByIdAndAgentBalance(updateBalance);
+                sendMessage = new SendMessage(message.getMessage().chat().id(), String.format("入账提醒\n入账金额: %.2f\n入账前: %.2f\n入账后: %.2f", agentAccountAmount, agentBalanceOld, agentBalanceNew));
+            } catch (Exception e) {
+                log.error("更新商户群组账本失败,群组ID:{},群组名称:{},用户ID:{},用户名称:{},金额:{},错误信息:{}", message.getBotGroup().getDataId(), message.getBotGroup().getDataName(), message.getBotUser().getUserId(), message.getBotUser().getUserName(), agentAccountAmount, e.getMessage());
+                e.printStackTrace();
+                sendMessage = new SendMessage(message.getMessage().chat().id(), "处理失败,请重试");
+            } finally {
+                if (sendMessage != null) {
+                    message.getTelegramBot().execute(sendMessage);
+                }
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+}

+ 82 - 0
src/main/java/org/jebot/handler/impl/merchant/MerchantAccountBookHistoryHandler.java

@@ -0,0 +1,82 @@
+package org.jebot.handler.impl.merchant;
+
+import cn.hutool.core.date.DateUtil;
+import com.pengrad.telegrambot.request.SendPhoto;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.logging.log4j.util.Strings;
+import org.jebot.handler.AbstractHandler;
+import org.jebot.handler.dto.BotMessage;
+import org.jebot.models.jebot.BotAccountBookHistory;
+import org.jebot.util.ImageUtil;
+import org.jebot.util.SnowflakeId;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.util.Date;
+import java.util.List;
+
+import static org.jebot.constant.Constant.*;
+
+@Slf4j
+public class MerchantAccountBookHistoryHandler extends AbstractHandler {
+    @Override
+    public boolean msgHandler(BotMessage message) {
+        //只处理群组认证
+        //只处理商户群组消息
+        if (!message.isGroupMch()) {
+            return false;
+        }
+
+        if (message.messageTextIsEmpty()) {
+            return false;
+        }
+
+        String messageText = message.getMessageText();
+        if (!AGENT_ACCOUNT_HISTORY_FLAG.equals(messageText) && !PAYMENT_ACCOUNT_HISTORY_FLAG.equals(messageText)) {
+            return false;
+        }
+
+        if (AGENT_ACCOUNT_HISTORY_FLAG.equals(messageText)) {
+            List<BotAccountBookHistory> bookHistories = handlerManager.getAccountBookHistoryRepository().findByBelongIdAndBelongTypeAndType(message.getBotGroup().getDataId(), DATA_TYPE_MERCHANT, AGENT, DateUtil.offsetHour(new Date(), -24));
+            String format = String.format("./%d.png", SnowflakeId.getId());
+            File imageFile = null;
+            try {
+                ImageUtil.generateTableImage(handlerManager.loadFontFromResources(), bookHistories, format);
+                imageFile = new File(format);
+                SendPhoto sendPhoto = new SendPhoto(message.getMessage().chat().id(), imageFile);
+                message.getTelegramBot().execute(sendPhoto);
+            } catch (Exception e) {
+                log.error("生成图片失败,错误信息:{}", e.getMessage());
+                e.printStackTrace();
+            } finally {
+                if (imageFile != null && imageFile.exists()) {
+                    imageFile.delete();
+                }
+            }
+            return true;
+        }
+
+        if (PAYMENT_ACCOUNT_HISTORY_FLAG.equals(messageText)) {
+            List<BotAccountBookHistory> bookHistories = handlerManager.getAccountBookHistoryRepository().findByBelongIdAndBelongTypeAndType(message.getBotGroup().getDataId(), DATA_TYPE_MERCHANT, PAYMENT, DateUtil.offsetHour(new Date(), -24));
+            String format = String.format("./%d.png", SnowflakeId.getId());
+            File imageFile = null;
+            try {
+                ImageUtil.generateTableImage(handlerManager.loadFontFromResources(), bookHistories, format);
+                imageFile = new File(format);
+                SendPhoto sendPhoto = new SendPhoto(message.getMessage().chat().id(), imageFile);
+                message.getTelegramBot().execute(sendPhoto);
+            } catch (Exception e) {
+                log.error("生成图片失败,错误信息:{}", e.getMessage());
+                e.printStackTrace();
+            } finally {
+                if (imageFile != null && imageFile.exists()) {
+                    imageFile.delete();
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+}

+ 154 - 0
src/main/java/org/jebot/handler/impl/merchant/MerchantBindGroupOrUnbindGroupHandler.java

@@ -0,0 +1,154 @@
+package org.jebot.handler.impl.merchant;
+
+import com.pengrad.telegrambot.request.SendMessage;
+import com.pengrad.telegrambot.response.SendResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.logging.log4j.util.Strings;
+import org.jebot.constant.Constant;
+import org.jebot.handler.AbstractHandler;
+import org.jebot.handler.dto.BotMessage;
+import org.jebot.models.jebot.BotGroup;
+import org.jebot.models.xxpay.MchAccount;
+
+import java.util.regex.Pattern;
+
+import static org.jebot.constant.Constant.BIND_MCH_FLAG;
+import static org.jebot.constant.Constant.UNBIND_MCH_FLAG;
+
+@Slf4j
+public class MerchantBindGroupOrUnbindGroupHandler extends AbstractHandler {
+
+    //解绑商户与群组绑定
+    private static final Pattern UNBIND_MCH = Pattern.compile("^" + UNBIND_MCH_FLAG + "[a-zA-Z0-9]*$|^[a-zA-Z0-9]$");
+
+    //将群组与商户绑定
+    private static final Pattern BIND_MCH = Pattern.compile("^" + BIND_MCH_FLAG + "[a-zA-Z0-9]*$|^[a-zA-Z0-9]$");
+
+    @Override
+    public boolean msgHandler(BotMessage botMessage) {
+
+        //只处理来自管理消息
+        if (!botMessage.isUserAuthenticated()) {
+            return false;
+        }
+
+        String text = botMessage.getMessage().text();
+        if (Strings.isEmpty(text)) {
+            return false;
+        }
+
+        //根据商户号绑定商户群组
+        if (BIND_MCH.matcher(text).find()) {
+            bindMerchantGroupByMchId(botMessage);
+            return true;
+        }
+
+        //解绑商户群组
+        if (UNBIND_MCH_FLAG.equals(text)) {
+            unbindByGroup(botMessage);
+            return true;
+        }
+
+        //根据商户号解绑商户群组
+        if (UNBIND_MCH.matcher(text).find()) {
+            unbindByMerchant(botMessage);
+            return true;
+        }
+
+        return false;
+    }
+
+    //根据商户id绑定商户群组
+    private void bindMerchantGroupByMchId(BotMessage botMessage) {
+        Long mchId = Long.valueOf(botMessage.getMessage().text().replace(BIND_MCH_FLAG, ""));//获取商户号
+
+        //当前群组是否绑定了数据
+        if (botMessage.isGroupAuthenticated()) {
+            //判断当前群组是否绑定商户号
+            if (Constant.DATA_TYPE_MERCHANT.equalsIgnoreCase(botMessage.getBotGroup().getDataType())) {
+                if (botMessage.getBotGroup().getDataId().equals(mchId)) {
+                    botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), "当前群组已与当前商户绑定"));
+                } else {
+                    botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), "当前群组已绑定其他商户,请先解绑!"));
+                }
+                return;
+            }
+
+            //判断当前群组是否绑定通道号
+            if (Constant.DATA_TYPE_CHANNEL.equalsIgnoreCase(botMessage.getBotGroup().getDataType())) {
+                botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), "当前群组已绑定通道,请先解绑!"));
+                return;
+            }
+            return;
+
+        }
+
+        //根据商户号查询商户信息
+        MchAccount mchAccount = handlerManager.getMchAccountRepository().findByMchId(mchId);
+        if (mchAccount == null) {
+            // 如果商户号不存在,返回提示
+            botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), "当前商户号不存在"));
+            return;
+        }
+
+        //获取群组 ID
+        Long chatId = botMessage.getMessage().chat().id();
+        //查询当前群组是否绑定商户号
+        BotGroup merchant = this.handlerManager.getGroupRepository().findBotGroupByDataIdAndDataType(mchId, Constant.DATA_TYPE_MERCHANT);
+        if (merchant != null) {
+            //如果商户号已经存在,返回提示
+            SendResponse execute = botMessage.getTelegramBot().execute(new SendMessage(chatId, String.format("当前商户已在群组 %s 绑定,请先解绑!", merchant.getGroupName())));
+            if (!execute.isOk()) {
+                log.error("绑定商户号结果发送失败: {}", execute);
+            }
+            return;
+        }
+
+
+        // 如果商户信息不存在,保存商户信息
+        BotGroup botMerchant = new BotGroup();//创建商户对象
+        botMerchant.setDataId(mchId);//设置商户号
+        botMerchant.setDataName(mchAccount.getName());//设置商户名称
+        botMerchant.setDataType(Constant.DATA_TYPE_MERCHANT);//设置数据类型
+        botMerchant.setGroupId(chatId);//设置群组 ID
+        botMerchant.setGroupName(botMessage.getMessage().chat().title());//设置群组名称
+        this.handlerManager.getGroupRepository().save(botMerchant);
+        botMessage.getTelegramBot().execute(new SendMessage(chatId, "绑定成功"));
+    }
+
+    private void unbindByMerchant(BotMessage botMessage) {
+        Long mchId = Long.valueOf(botMessage.getMessage().text().replace(UNBIND_MCH_FLAG, ""));//移除解绑标识
+        Long chatId = botMessage.getMessage().chat().id();//获取群组 ID
+        BotGroup merchant = this.handlerManager.getGroupRepository().findBotGroupByDataIdAndDataType(mchId, Constant.DATA_TYPE_MERCHANT);//查询商户号
+        if (merchant == null) {
+            // 如果商户号不存在,返回提示
+            botMessage.getTelegramBot().execute(new SendMessage(chatId, "当前商户号不存在"));
+            return;
+        }
+        // 删除商户号
+        this.handlerManager.getGroupRepository().delete(merchant);
+        // 返回提示
+        botMessage.getTelegramBot().execute(new SendMessage(chatId, "解绑成功"));
+    }
+
+    private void unbindByGroup(BotMessage botMessage) {
+        Long chatId = botMessage.getMessage().chat().id();
+        BotGroup botMerchantGroup = botMessage.getBotGroup();
+        if (botMerchantGroup == null) {
+            // 如果商户号不存在,返回提示
+            botMessage.getTelegramBot().execute(new SendMessage(chatId, "当前群组未绑定"));
+            return;
+        }
+
+        // 判断群组是否绑定商户号
+        if (Constant.DATA_TYPE_MERCHANT.equalsIgnoreCase(botMerchantGroup.getDataType())) {
+            // 删除群组
+            this.handlerManager.getGroupRepository().delete(botMerchantGroup);
+            // 返回提示
+            botMessage.getTelegramBot().execute(new SendMessage(chatId, "解绑成功"));
+        }
+
+    }
+
+
+}

+ 61 - 0
src/main/java/org/jebot/handler/impl/merchant/MerchantDropOrderHandler.java

@@ -0,0 +1,61 @@
+package org.jebot.handler.impl.merchant;
+
+import com.pengrad.telegrambot.model.request.ReplyParameters;
+import com.pengrad.telegrambot.request.CopyMessage;
+import com.pengrad.telegrambot.request.SendMessage;
+import com.pengrad.telegrambot.response.MessageIdResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.jebot.constant.Constant;
+import org.jebot.handler.AbstractHandler;
+import org.jebot.handler.dto.BotMessage;
+import org.jebot.models.jebot.BotGroup;
+import org.jebot.models.xxpay.PayOrder;
+
+@Slf4j
+public class MerchantDropOrderHandler extends AbstractHandler {
+    @Override
+    public boolean msgHandler(BotMessage botMessage) {
+        //判断是否类似掉单待处理
+        if (botMessage.isContainsTextAndPhoto()) {
+            //查询当前群组是否绑定商户号
+            if (!botMessage.isGroupMch()) {
+                return false;
+            }
+
+            //如果商户号存在,查询订单所属上游
+            PayOrder payOrder = handlerManager.getPayOrderRepository().findByPayOrderIdOrMchOrderNo(botMessage.getMessage().caption().trim(), botMessage.getBotGroup().getDataId());
+            if (payOrder == null) {
+                SendMessage sendMessage = new SendMessage(botMessage.getMessage().chat().id(), "订单不存在");
+                sendMessage.replyParameters(new ReplyParameters(botMessage.getMessage().messageId()));
+                botMessage.getTelegramBot().execute(sendMessage);
+                return true;
+            }
+
+            //获取通道id
+            Long channelId = payOrder.getPassageId();
+
+            //根据通道id获取绑定的群组
+            BotGroup botChannel = this.handlerManager.getGroupRepository().findBotGroupByDataIdAndDataType(channelId, Constant.DATA_TYPE_CHANNEL);
+            if (botChannel == null) {
+                SendMessage sendMessage = new SendMessage(botMessage.getMessage().chat().id(), "订单通道未绑定群组");
+                sendMessage.replyParameters(new ReplyParameters(botMessage.getMessage().messageId()));
+                botMessage.getTelegramBot().execute(sendMessage);
+                return false;
+            }
+
+            //复制需要转发的掉单信息到通道群组
+            CopyMessage message = new CopyMessage(botChannel.getGroupId(), botMessage.getMessage().chat().id(), botMessage.getMessage().messageId());
+            message.caption(payOrder.getPayOrderId());
+            MessageIdResponse execute = botMessage.getTelegramBot().execute(message);
+            if (!execute.isOk()) {
+                log.error("转发消息失败: {}", execute);
+            }
+            SendMessage sendMessage = new SendMessage(botMessage.getMessage().chat().id(), "处理成功");
+            sendMessage.replyParameters(new ReplyParameters(botMessage.getMessage().messageId()));
+            botMessage.getTelegramBot().execute(sendMessage);
+            return true;
+        }
+
+        return false;
+    }
+}

+ 138 - 0
src/main/java/org/jebot/handler/impl/merchant/MerchantInfoHandler.java

@@ -0,0 +1,138 @@
+package org.jebot.handler.impl.merchant;
+
+import cn.hutool.core.date.DateUtil;
+import com.pengrad.telegrambot.model.request.ParseMode;
+import com.pengrad.telegrambot.model.request.ReplyParameters;
+import com.pengrad.telegrambot.request.SendMessage;
+import lombok.extern.slf4j.Slf4j;
+import org.jebot.constant.Constant;
+import org.jebot.handler.AbstractHandler;
+import org.jebot.handler.dto.BotMessage;
+import org.jebot.models.jebot.BotAccountBook;
+import org.jebot.models.xxpay.MchAccount;
+import org.jebot.models.xxpay.PayOrder;
+import org.jebot.repository.xxpay.MchAccountRepository;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import static org.jebot.constant.Constant.*;
+
+@Slf4j
+public class MerchantInfoHandler extends AbstractHandler {
+
+    //商户查询订单
+    private static final Pattern QUERY_ORDER = Pattern.compile("^" + QUERY_MCH_ORDER_FLAG + "[a-zA-Z0-9]+$");
+
+
+    @Override
+    public boolean msgHandler(BotMessage botMessage) {
+
+        //只处理来自群组的消息
+        if (!botMessage.isGroupMch()) {
+            return false;
+        }
+
+        //查询当前群组商户信息
+        if (botMessage.isGroupMch() && MCH_INFO.equals(botMessage.getMessage().text())) {
+            SendMessage sendMessage = new SendMessage(botMessage.getMessage().chat().id(), String.format("商户号: `%s`\n商户名: %s", botMessage.getBotGroup().getDataId(), botMessage.getBotGroup().getDataName()));
+            sendMessage.replyParameters(new ReplyParameters(botMessage.getMessage().messageId()));
+            sendMessage.parseMode(ParseMode.MarkdownV2);
+            botMessage.getTelegramBot().execute(sendMessage);
+            return true;
+
+        }
+
+        //查询代收余额
+        if (botMessage.isGroupMch() && !botMessage.messageTextIsEmpty() && QUERY_MCH_BALANCE.equals(botMessage.getMessage().text())) {
+            Date endDate = new Date();
+            Date startDate = org.jebot.util.DateUtil.getTodayMidnight(endDate);
+            MchAccount mchAccount = handlerManager.getMchAccountRepository().findByMchId(botMessage.getBotGroup().getDataId());
+            if (mchAccount == null) {
+                botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), "商户不存在"));
+                return true;
+            }
+            BotAccountBook merchantAccountBook = handlerManager.getAccountBookRepository().findByBelongIdAndType(mchAccount.getMchId(), DATA_TYPE_MERCHANT);
+            if (merchantAccountBook == null) {
+                merchantAccountBook = new BotAccountBook();
+                merchantAccountBook.setBelongId(botMessage.getBotGroup().getDataId());
+                merchantAccountBook.setBelongName(botMessage.getBotGroup().getDataName());
+                merchantAccountBook.setType(Constant.DATA_TYPE_MERCHANT);
+                merchantAccountBook.setAgentBalance(0L);
+                merchantAccountBook.setPaymentBalance(0L);
+                merchantAccountBook = handlerManager.getAccountBookRepository().save(merchantAccountBook);
+            }
+
+            List<PayOrder> payOrderList = handlerManager.getPayOrderRepository().findSuccessPayOrderByMchIdAndCreateTime(Long.valueOf(botMessage.getBotGroup().getDataId()), startDate, endDate);
+            StringBuilder stringBuffer = new StringBuilder();
+            stringBuffer.append("商户名: ").append(mchAccount.getName()).append("\n");
+            stringBuffer.append("商户号: ").append(mchAccount.getMchId()).append("\n");
+            if (payOrderList != null && !payOrderList.isEmpty()) {
+                // 计算未减去手续费的总金额
+                BigDecimal totalAmount = payOrderList.stream()
+                        .map(PayOrder::getAmount)
+                        .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+                // 计算减去手续费后的总金额
+                BigDecimal totalAmountAfterFee = payOrderList.stream()
+                        .map(order -> {
+                            BigDecimal amount = order.getAmount();
+                            BigDecimal mchRate = order.getMchRate();
+                            if (mchRate.doubleValue() < 100.00) {
+                                return amount.subtract(amount.multiply(mchRate.movePointLeft(AMOUNT_MOVE_POINT)));
+                            }
+                            return amount;
+                        })
+                        .reduce(BigDecimal.ZERO, BigDecimal::add);
+                stringBuffer.append("当日跑量: ").append(totalAmount.movePointLeft(Constant.AMOUNT_MOVE_POINT).doubleValue()).append("\n");
+                stringBuffer.append("应得余额: ").append(totalAmountAfterFee.movePointLeft(Constant.AMOUNT_MOVE_POINT).doubleValue()).append("\n");
+
+                double paymentBalance = merchantAccountBook.getPaymentBalance();
+                stringBuffer.append("当前预付: ").append(paymentBalance).append("\n");
+                stringBuffer.append("剩余预付: ").append(paymentBalance - totalAmountAfterFee.movePointLeft(Constant.AMOUNT_MOVE_POINT).doubleValue()).append("\n");
+            } else {
+                double paymentBalance = merchantAccountBook.getPaymentBalance();
+                stringBuffer.append("当日跑量: 0.0\n");
+                stringBuffer.append("应得余额: 0.0\n");
+                stringBuffer.append("当前预付: ").append(paymentBalance).append("\n");
+                stringBuffer.append("剩余预付: ").append(paymentBalance).append("\n");
+            }
+            stringBuffer.append("统计时间: ").append(DateUtil.formatDateTime(endDate));
+            botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), stringBuffer.toString()));
+            return true;
+        }
+
+        //商户查单
+        if (botMessage.isGroupMch() && !botMessage.messageTextIsEmpty() && QUERY_ORDER.matcher(botMessage.getMessage().text()).find()) {
+            String orderId = botMessage.getMessage().text().replace(QUERY_MCH_ORDER_FLAG, "");
+            PayOrder payOrder = this.handlerManager.getPayOrderRepository().findByPayOrderIdOrMchOrderNo(orderId, Long.valueOf(botMessage.getBotGroup().getDataId()));
+            if (payOrder == null) {
+                botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), "订单不存在"));
+                return true;
+            }
+
+            StringBuilder stringBuffer = new StringBuilder();
+            stringBuffer.append("商户号: ").append(payOrder.getMchId()).append("\n");
+            stringBuffer.append("商户单号: ").append("`").append(payOrder.getMchOrderNo()).append("`").append("\n");
+            stringBuffer.append("平台单号: ").append("`").append(payOrder.getPayOrderId()).append("`").append("\n");
+            stringBuffer.append("支付金额: ").append(payOrder.getAmount().movePointLeft(Constant.AMOUNT_MOVE_POINT)).append("\n");
+            stringBuffer.append("支付状态: ").append(payOrder.getStatusMsg()).append("\n");
+            if (payOrder.getPaySuccTime() != null) {
+                stringBuffer.append("支付时间: ").append(DateUtil.formatDateTime(payOrder.getPaySuccTime())).append("\n");
+            } else {
+                stringBuffer.append("支付时间: 无\n");
+            }
+            stringBuffer.append("创建时间: ").append(payOrder.getCreateTime()).append("\n");
+            SendMessage sendMessage = new SendMessage(botMessage.getMessage().chat().id(), this.strEscape(stringBuffer.toString()));
+            sendMessage.parseMode(ParseMode.MarkdownV2);
+            sendMessage.replyParameters(new ReplyParameters(botMessage.getMessage().messageId()));
+            botMessage.getTelegramBot().execute(sendMessage);
+            return true;
+        }
+        return false;
+    }
+
+
+}

+ 250 - 0
src/main/java/org/jebot/handler/impl/merchant/MerchantOneClickSettlementHandler.java

@@ -0,0 +1,250 @@
+package org.jebot.handler.impl.merchant;
+
+import cn.hutool.core.date.DateUtil;
+import com.pengrad.telegrambot.request.SendMessage;
+import com.pengrad.telegrambot.response.SendResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.jebot.constant.Constant;
+import org.jebot.handler.AbstractHandler;
+import org.jebot.handler.dto.BotMessage;
+import org.jebot.models.jebot.BotAccountBook;
+import org.jebot.models.jebot.BotGroup;
+import org.jebot.models.xxpay.MchAccount;
+import org.jebot.models.xxpay.PayOrder;
+import org.jebot.service.dto.UpdateBalance;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static org.jebot.constant.Constant.*;
+import static org.jebot.util.DateUtil.getTodayMidnight;
+
+@Slf4j
+public class MerchantOneClickSettlementHandler extends AbstractHandler {
+
+    private static final Pattern SETTLE_MCH_ID = Pattern.compile("^" + SETTLE_MCH + "[a-zA-Z0-9]*$|^[a-zA-Z0-9]$");
+
+    @Override
+    public boolean msgHandler(BotMessage message) {
+
+        if (!message.isUserAuthenticated()) {
+            return false;
+        }
+
+        if (message.messageTextIsEmpty()) {
+            return false;
+        }
+
+        //一键所有商户结算
+        if (SETTLE_MCH.equals(message.getMessageText())) {
+            Date endDate = new Date();
+            Date todayMidnight = getTodayMidnight(endDate);
+            List<BotGroup> botGroups = handlerManager.getGroupRepository().findAllByAgentWarningThresholdOrPaymentWarningThresholdGreaterThanZero();
+            List<BotGroup> mchGroups = botGroups.stream().filter(botGroup -> Constant.DATA_TYPE_MERCHANT.equals(botGroup.getDataType())).collect(Collectors.toList());
+            if (!mchGroups.isEmpty()) {
+                List<Long> mchIds = mchGroups.stream().map(BotGroup::getDataId).collect(Collectors.toList());
+                List<MchAccount> mchAccounts = handlerManager.getMchAccountRepository().findByMchIds(mchIds);
+                if (mchAccounts.isEmpty()) {
+                    return true;
+                }
+                Map<Long, MchAccount> mchAccountMap = mchAccounts.stream()
+                        .collect(Collectors.toMap(MchAccount::getMchId, mchAccount -> mchAccount));
+                StringBuilder messageText = new StringBuilder();
+                for (BotGroup mchGroup : mchGroups) {
+                    //判断商户是否存在
+                    MchAccount mchAccount = mchAccountMap.get(mchGroup.getDataId());
+                    if (mchAccount == null) {
+                        messageText.append(mchGroup.getDataName()).append("[").append(mchGroup.getDataId()).append("]").append(": 商户不存在\n");
+                        return false;
+                    }
+                    if (mchAccount.getStatus() != 1) {
+                        messageText.append(mchAccount.getName()).append(": 商户状态异常\n");
+                    }
+                    //查询订单出账单
+                    List<PayOrder> payOrderList = handlerManager.getPayOrderRepository().findSuccessPayOrderByMchIdAndCreateTime(mchGroup.getDataId(), todayMidnight, endDate);
+                    if (payOrderList.isEmpty()) {
+                        messageText.append(mchAccount.getName()).append(": 无订单数据\n");
+                        return false;
+                    }
+
+                    if (SettlementMch(message, mchGroup, payOrderList, todayMidnight, endDate)) {
+                        messageText.append(mchAccount.getName()).append(": 结算成功\n");
+                    } else {
+                        messageText.append(mchAccount.getName()).append(": 结算失败\n");
+                    }
+
+                    //防止发送消息过快
+                    try {
+                        Thread.sleep(500);
+                    } catch (InterruptedException e) {
+                        log.error(e.getMessage());
+                        e.printStackTrace();
+                    }
+
+                }
+                SendMessage sendMessage = new SendMessage(message.getMessage().chat().id(), messageText.toString());
+                SendResponse execute = message.getTelegramBot().execute(sendMessage);
+                if (!execute.isOk()) {
+                    log.error("一键结算发送消息失败, 错误信息: {}", execute.description());
+                } else {
+                    //记录结算记录
+                    log.info("一键结算发送消息成功, 群组ID: {}, 结算记录: {}", message.getMessage().chat().id(), messageText.toString());
+                }
+            } else {
+                SendMessage sendMessage = new SendMessage(message.getMessage().chat().id(), "没有商户群组");
+                SendResponse execute = message.getTelegramBot().execute(sendMessage);
+                if (!execute.isOk()) {
+                    log.error("一键结算发送消息失败, 错误信息: {}", execute.description());
+                } else {
+                    //记录结算记录
+                    log.info("一键结算发送消息成功, 群组ID: {}, 结算记录: {}", message.getMessage().chat().id(), "没有商户群组");
+                }
+            }
+            return true;
+        }
+
+        //结算指定商户
+        if (SETTLE_MCH_ID.matcher(message.getMessageText()).find()) {
+            Date endDate = new Date();
+            Date todayMidnight = getTodayMidnight(endDate);
+            Long mchId = Long.valueOf(message.getMessageText().replace(SETTLE_MCH, ""));
+            BotGroup mchGroup = handlerManager.getGroupRepository().findBotGroupByDataIdAndDataType(mchId, DATA_TYPE_MERCHANT);
+            if (mchGroup == null) {
+                SendMessage sendMessage = new SendMessage(message.getMessage().chat().id(), "商户未绑定群组");
+                message.getTelegramBot().execute(sendMessage);
+                return true;
+            }
+            //判断商户是否存在
+            MchAccount mchAccount = handlerManager.getMchAccountRepository().findByMchId(mchId);
+            if (mchAccount == null) {
+                SendMessage sendMessage = new SendMessage(message.getMessage().chat().id(), "商户不存在");
+                message.getTelegramBot().execute(sendMessage);
+                return true;
+            }
+            if (mchAccount.getStatus() != 1) {
+                SendMessage sendMessage = new SendMessage(message.getMessage().chat().id(), "商户状态异常");
+                message.getTelegramBot().execute(sendMessage);
+                return true;
+            }
+            //查询订单出账单
+            List<PayOrder> payOrderList = handlerManager.getPayOrderRepository().findSuccessPayOrderByMchIdAndCreateTime(mchGroup.getDataId(), todayMidnight, endDate);
+            if (payOrderList.isEmpty()) {
+                SendMessage sendMessage = new SendMessage(message.getMessage().chat().id(), "无订单数据");
+                message.getTelegramBot().execute(sendMessage);
+                return false;
+            }
+
+            if (SettlementMch(message, mchGroup, payOrderList, todayMidnight, endDate)) {
+                SendMessage sendMessage = new SendMessage(message.getMessage().chat().id(), "结算成功");
+                message.getTelegramBot().execute(sendMessage);
+            } else {
+                SendMessage sendMessage = new SendMessage(message.getMessage().chat().id(), "结算失败");
+                message.getTelegramBot().execute(sendMessage);
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+    private boolean SettlementMch(BotMessage message, BotGroup mchGroup, List<PayOrder> payOrderList, Date todayMidnight, Date endDate) {
+
+
+        StringBuilder stringBuilder = new StringBuilder();
+
+        // 计算总金额
+        BigDecimal totalAmount = payOrderList.stream()
+                .map(PayOrder::getAmount)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+        // 成功总笔数
+        long totalOrders = payOrderList.size();
+
+        // 扣除手续费总跑量
+        BigDecimal totalAmountByMchRate = payOrderList.stream()
+                .map(order -> BigDecimal.valueOf(1).subtract(order.getMchRate().movePointLeft(Constant.AMOUNT_MOVE_POINT)).multiply(order.getAmount()))
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+        stringBuilder.append(DateUtil.formatDate(endDate)).append("账单,请核对:\n");
+        stringBuilder.append("总跑量: ").append(totalAmount.movePointLeft(Constant.AMOUNT_MOVE_POINT).doubleValue()).append("\n");
+        stringBuilder.append("成功笔数: ").append(totalOrders).append("\n");
+        stringBuilder.append("扣除手续费总跑量: ").append(totalAmountByMchRate.movePointLeft(Constant.AMOUNT_MOVE_POINT).doubleValue()).append("\n");
+        BigDecimal deductionAmount = totalAmount.subtract(totalAmountByMchRate).add(totalAmount).movePointLeft(AMOUNT_MOVE_POINT);
+
+        //根据通道ID分组
+        Map<Long, List<PayOrder>> groupedByPassageId = payOrderList.stream()
+                .collect(Collectors.groupingBy(PayOrder::getPassageId));
+
+        for (Map.Entry<Long, List<PayOrder>> entry : groupedByPassageId.entrySet()) {
+            stringBuilder.append(entry.getKey()).append(":\n");
+            //根据商户费率分组
+            Map<BigDecimal, List<PayOrder>> groupedByMchRate = entry.getValue().stream().collect(Collectors.groupingBy(PayOrder::getMchRate)).entrySet().stream()
+                    .sorted(Map.Entry.comparingByKey()) // 根据 getMchRate 排序
+                    .collect(Collectors.toMap(
+                            Map.Entry::getKey,
+                            Map.Entry::getValue,
+                            (e1, e2) -> e1,
+                            HashMap::new // 保证顺序
+                    ));
+
+            //根据费率计算金额
+            for (Map.Entry<BigDecimal, List<PayOrder>> rateEntry : groupedByMchRate.entrySet()) {
+                //费率
+                BigDecimal mchRate = rateEntry.getKey();
+
+                // 计算金额
+                BigDecimal amount = rateEntry.getValue().stream()
+                        .map(PayOrder::getAmount)
+                        .reduce(BigDecimal.ZERO, BigDecimal::add);
+                stringBuilder.append("费率").append(mchRate.doubleValue()).append(": ").append(amount.movePointLeft(AMOUNT_MOVE_POINT).doubleValue()).append("\n");
+            }
+        }
+        SendMessage sendMessage = new SendMessage(mchGroup.getGroupId(), stringBuilder.toString());
+        SendResponse execute = message.getTelegramBot().execute(sendMessage);
+        if (!execute.isOk()) {
+            log.error("一键结算发送消息失败, 错误信息: {}", execute.description());
+            return false;
+        } else {
+            BotAccountBook accountBook = handlerManager.getAccountBookRepository().findByBelongIdAndType(mchGroup.getDataId(), DATA_TYPE_MERCHANT);
+            if (accountBook == null) {
+                accountBook = new BotAccountBook();
+                accountBook.setBelongId(mchGroup.getDataId());
+                accountBook.setType(DATA_TYPE_MERCHANT);
+                accountBook.setPaymentBalance(0);
+                accountBook.setAgentBalance(0);
+                handlerManager.getAccountBookRepository().save(accountBook);
+            }
+            log.info("一键结算发送消息成功, 群组ID: {}, 结算记录: {}", mchGroup.getGroupId(), stringBuilder.toString());
+            double oldPaymentBalance = accountBook.getPaymentBalance();
+            double newPaymentBalance = BigDecimal.valueOf(oldPaymentBalance).subtract(deductionAmount).doubleValue();
+            BotMessage botMessage = new BotMessage();
+            botMessage.setBotGroup(mchGroup);
+            botMessage.setBotUser(message.getBotUser());
+            try {
+                UpdateBalance updateMerchantPaymentBalance = new UpdateBalance();
+                updateMerchantPaymentBalance.setId(accountBook.getId());
+                updateMerchantPaymentBalance.setOldBalance(oldPaymentBalance);
+                updateMerchantPaymentBalance.setNewBalance(newPaymentBalance);
+                updateMerchantPaymentBalance.setAmount(deductionAmount.doubleValue());
+                updateMerchantPaymentBalance.setMessage(botMessage);
+                handlerManager.getAccountBookService().updatePaymentBalanceByIdAndPaymentBalance(updateMerchantPaymentBalance);
+                sendMessage = new SendMessage(mchGroup.getGroupId(), String.format("下发提醒\n下发金额: %.2f\n下发前: %.2f\n下发后: %.2f", deductionAmount.doubleValue(), oldPaymentBalance, newPaymentBalance));
+            } catch (Exception e) {
+                log.error("更新商户群组账本失败,群组ID:{},群组名称:{},用户ID:{},用户名称:{},金额:{},错误信息:{}", message.getBotGroup().getDataId(), message.getBotGroup().getDataName(), message.getBotUser().getUserId(), message.getBotUser().getUserName(), deductionAmount.doubleValue(), e.getMessage());
+                e.printStackTrace();
+                sendMessage = new SendMessage(message.getMessage().chat().id(), "下发处理失败,请手动处理");
+            } finally {
+                if (sendMessage != null) {
+                    message.getTelegramBot().execute(sendMessage);
+                    log.info("一键结算发送消息成功, 群组ID: {}, 结算记录: {}", message.getMessage().chat().id(), sendMessage);
+                }
+            }
+            //记录结算记录
+            return true;
+        }
+    }
+}

+ 56 - 0
src/main/java/org/jebot/handler/impl/merchant/MerchantStatusHandler.java

@@ -0,0 +1,56 @@
+package org.jebot.handler.impl.merchant;
+
+import cn.hutool.json.JSONObject;
+import com.pengrad.telegrambot.request.SendMessage;
+import lombok.extern.slf4j.Slf4j;
+import org.jebot.constant.Constant;
+import org.jebot.handler.AbstractHandler;
+import org.jebot.handler.dto.BotMessage;
+
+import static org.jebot.constant.Constant.DISABLE_MCH;
+import static org.jebot.constant.Constant.ENABLE_MCH;
+
+@Slf4j
+public class MerchantStatusHandler extends AbstractHandler {
+    @Override
+    public boolean msgHandler(BotMessage botMessage) {
+        if (!botMessage.isUserAuthenticated()) {
+            return false;
+        }
+
+        if (botMessage.messageTextIsEmpty()) {
+            return false;
+        }
+        //禁用商户
+        if (DISABLE_MCH.equals(botMessage.getMessageText())) {
+            if (botMessage.getBotGroup() == null) {
+                botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), "当前群组未绑定"));
+                return true;
+            }
+
+            if (handlerManager.getMchService().updateMchStatus(botMessage.getBotGroup().getDataId(), Constant.CLOSE_MERCHANT) < 1) {
+                botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), "关闭商户失败,请重试"));
+                return true;
+            } else {
+                botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), "关闭商户成功"));
+            }
+            return true;
+        }
+
+        //启用商户
+        if (ENABLE_MCH.equals(botMessage.getMessageText())) {
+            if (botMessage.getBotGroup() == null) {
+                botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), "当前群组未绑定"));
+                return true;
+            }
+            if (handlerManager.getMchService().updateMchStatus(botMessage.getBotGroup().getDataId(), Constant.OPEN_MERCHANT) < 1) {
+                botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), "开启商户失败,请重试"));
+                return true;
+            } else {
+                botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), "开启商户成功"));
+            }
+            return true;
+        }
+        return false;
+    }
+}

+ 104 - 0
src/main/java/org/jebot/handler/impl/merchant/MerchantSuccessRateHandler.java

@@ -0,0 +1,104 @@
+package org.jebot.handler.impl.merchant;
+
+import cn.hutool.core.date.DateUtil;
+import com.pengrad.telegrambot.request.SendMessage;
+import lombok.extern.slf4j.Slf4j;
+import org.jebot.constant.Constant;
+import org.jebot.handler.AbstractHandler;
+import org.jebot.handler.dto.BotMessage;
+import org.jebot.models.xxpay.PayOrder;
+import org.jebot.models.xxpay.PayPassage;
+import org.jebot.repository.xxpay.PayOrderRepository;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.jebot.constant.Constant.QUERY_MCH_TODAY_SUCCESS_RATE;
+
+@Slf4j
+public class MerchantSuccessRateHandler extends AbstractHandler {
+    @Override
+    public boolean msgHandler(BotMessage botMessage) {
+
+        //只处理来自商户群组的消息
+        if (!botMessage.isGroupMch()) {
+            return false;
+        }
+
+        //获取商户成功率
+        if (botMessage.isGroupMch() && QUERY_MCH_TODAY_SUCCESS_RATE.equals(botMessage.getMessage().text())) {
+            PayOrderRepository payOrderRepository = handlerManager.getPayOrderRepository();
+            Date currentTime = new Date();
+            List<PayOrder> payOrderList = payOrderRepository.findPayOrderByMchIdAndCreateTime(botMessage.getBotGroup().getDataId(), org.jebot.util.DateUtil.getTodayMidnight(currentTime), currentTime);
+            if (payOrderList == null || payOrderList.isEmpty()) {
+                botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), "无当日记录"));
+                return true;
+            }
+
+            List<Long> passageIds = new ArrayList<>(payOrderList.stream()
+                    .filter(order -> order.getPassageId() != null)
+                    .collect(Collectors.toMap(
+                            PayOrder::getPassageId,
+                            PayOrder::getPassageId,
+                            (existing, replacement) -> existing))
+                    .values());
+
+            List<PayPassage> passages = handlerManager.getPassageRepository().findByPassageIds(passageIds);
+            StringBuilder result = new StringBuilder();
+
+            // 总单数和总金额
+//            int totalOrders = payOrderList.size();
+//            BigDecimal totalAmount = payOrderList.stream().map(PayOrder::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
+
+            // 有效单数和有效金额
+            List<PayOrder> validOrders = payOrderList.stream().filter(order -> order.getStatus() == 2 || order.getStatus() == 3) // 假设状态 2 和 3 为有效
+                    .collect(Collectors.toList());
+//            int validOrderCount = validOrders.size();
+//            BigDecimal validAmount = validOrders.stream().map(PayOrder::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
+
+            result.append("商户id: ").append(botMessage.getBotGroup().getDataId()).append("\n");
+            result.append("商户名: ").append(botMessage.getBotGroup().getDataName()).append("\n");
+//            result.append("总单数: ").append(totalOrders).append("\n");
+//            result.append("有效单数: ").append(validOrderCount).append("\n");
+//            result.append("总金额: ").append(totalAmount.movePointLeft(Constant.AMOUNT_MOVE_POINT)).append("\n");
+//            result.append("有效金额: ").append(validAmount.movePointLeft(Constant.AMOUNT_MOVE_POINT)).append("\n");
+
+            // 时间段
+            Date halfHourAgo = DateUtil.offsetMinute(currentTime, -30);
+            Date oneHourAgo = DateUtil.offsetMinute(currentTime, -60);
+            Date threeHoursAgo = DateUtil.offsetMinute(currentTime, -180);
+
+            // 按通道统计
+            for (PayPassage passage : passages) {
+                List<PayOrder> passageOrders = payOrderList.stream().filter(order -> passage.getId().equals(order.getPassageId())).collect(Collectors.toList());
+
+                // 统计不同时间段的成功率
+                double halfHourSuccessRate = calculateSuccessRate(passageOrders, halfHourAgo, currentTime);
+                double oneHourSuccessRate = calculateSuccessRate(passageOrders, oneHourAgo, currentTime);
+                double threeHourSuccessRate = calculateSuccessRate(passageOrders, threeHoursAgo, currentTime);
+                double dailySuccessRate = calculateSuccessRate(passageOrders, DateUtil.beginOfDay(currentTime), currentTime);
+
+                result.append(passage.getPassageName()).append(":\n");
+                result.append("   30分钟成功率: ").append(String.format("%.2f", halfHourSuccessRate)).append("%\n");
+                result.append("   1小时成功率: ").append(String.format("%.2f", oneHourSuccessRate)).append("%\n");
+                result.append("   3小时成功率: ").append(String.format("%.2f", threeHourSuccessRate)).append("%\n");
+                result.append("   当日成功率: ").append(String.format("%.2f", dailySuccessRate)).append("%\n");
+            }
+            botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), result.toString()));
+            return true;
+        }
+
+        return false;
+    }
+
+    // 计算成功率的方法
+    private static double calculateSuccessRate(List<PayOrder> orders, Date startTime, Date endTime) {
+        List<PayOrder> filteredOrders = orders.stream().filter(order -> startTime.before(order.getCreateTime()) && endTime.after(order.getCreateTime())).collect(Collectors.toList());
+        long successCount = filteredOrders.stream().filter(order -> order.getStatus() == 2 || order.getStatus() == 3) // 假设状态 2 和 3 为成功
+                .count();
+        return filteredOrders.isEmpty() ? 0 : (successCount * 100.0 / filteredOrders.size());
+    }
+}

+ 73 - 0
src/main/java/org/jebot/handler/impl/merchant/MerchantWarnHandler.java

@@ -0,0 +1,73 @@
+package org.jebot.handler.impl.merchant;
+
+import com.pengrad.telegrambot.request.SendMessage;
+import org.jebot.handler.AbstractHandler;
+import org.jebot.handler.dto.BotMessage;
+import org.jebot.models.jebot.BotGroup;
+import org.jebot.repository.jebot.BotGroupRepository;
+
+import java.util.regex.Pattern;
+
+import static org.jebot.constant.Constant.*;
+
+public class MerchantWarnHandler extends AbstractHandler {
+    private static final Pattern UPDATE_MCH_AGENT_WARN_THRESHOLD = Pattern.compile("^" + UPDATE_MCH_AGENT_WARN_THRESHOLD_FLAG + "-?\\d+(\\.\\d+)?$");
+
+    private static final Pattern UPDATE_MCH_PAYMENT_WARN_THRESHOLD = Pattern.compile("^" + UPDATE_MCH_PAYMENT_WARN_THRESHOLD_FLAG + "-?\\d+(\\.\\d+)?$");
+
+    @Override
+    public boolean msgHandler(BotMessage botMessage) {
+
+        if (botMessage.isGroupMch()) {
+            return false;
+        }
+
+        if (!botMessage.isUserAuthenticated()) {
+            return false;
+        }
+
+        if (botMessage.messageTextIsEmpty()) {
+            return false;
+        }
+        String text = botMessage.getMessageText();
+
+        //设置商户代收预警值
+        if (UPDATE_MCH_PAYMENT_WARN_THRESHOLD.matcher(text).find()) {
+            if (botMessage.getBotGroup() == null) {
+                botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), "当前群组未绑定"));
+                return true;
+            }
+            String warnThresholdStr = text.replace(UPDATE_MCH_PAYMENT_WARN_THRESHOLD_FLAG, "");
+            double warnThreshold = Double.parseDouble(warnThresholdStr);
+            if (0 > warnThreshold || warnThreshold > 50) {
+                botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), "预警值范围在0-50之间,0表示不预警"));
+                return true;
+            }
+            BotGroup botGroup = botMessage.getBotGroup();
+            BotGroupRepository groupRepository = handlerManager.getGroupRepository();
+            groupRepository.updatePaymentWarningThresholdByIdAndPaymentWarningThreshold(warnThreshold, botGroup.getId());
+            botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), "设置成功"));
+            return true;
+        }
+
+        //设置商户代收预警值
+        if (UPDATE_MCH_AGENT_WARN_THRESHOLD.matcher(text).find()) {
+            if (botMessage.getBotGroup() == null) {
+                botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), "当前群组未绑定"));
+                return true;
+            }
+            String warnThresholdStr = text.replace(UPDATE_MCH_AGENT_WARN_THRESHOLD_FLAG, "");
+            double warnThreshold = Double.parseDouble(warnThresholdStr);
+            if (0 > warnThreshold || warnThreshold > 50) {
+                botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), "预警值范围在0-50之间,0表示不预警"));
+                return true;
+            }
+            BotGroup botGroup = botMessage.getBotGroup();
+            BotGroupRepository groupRepository = handlerManager.getGroupRepository();
+            groupRepository.updateAgentWarningThresholdByIdAndAgentWarningThreshold(warnThreshold, botGroup.getId());
+            botMessage.getTelegramBot().execute(new SendMessage(botMessage.getMessage().chat().id(), "设置成功"));
+            return true;
+        }
+        return false;
+    }
+}

+ 44 - 0
src/main/java/org/jebot/handler/impl/price/PriceQueryHandler.java

@@ -0,0 +1,44 @@
+package org.jebot.handler.impl.price;
+
+import com.pengrad.telegrambot.request.SendMessage;
+import com.pengrad.telegrambot.response.SendResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.jebot.handler.AbstractHandler;
+import org.jebot.handler.dto.BotMessage;
+import org.jebot.util.PriceUtil;
+import org.jebot.util.QueryType;
+
+import static org.jebot.constant.Constant.*;
+
+@Slf4j
+public class PriceQueryHandler extends AbstractHandler {
+    public boolean msgHandler(BotMessage botMessage) {
+        if (botMessage.messageTextIsEmpty()) {
+            return false;
+        }
+
+        String text = botMessage.getMessageText();
+        switch (text) {
+            case QUERY_USDT_TO_ALIPAY_CNY:
+                text = PriceUtil.QueryUsdtToCny(QueryType.ALIPAY);
+                break;
+            case QUERY_USDT_TO_WECAHT_CNY:
+                text = PriceUtil.QueryUsdtToCny(QueryType.WECHAT);
+                break;
+            case QUERY_USDT_TO_BANK_CNY:
+                text = PriceUtil.QueryUsdtToCny(QueryType.BANK);
+                break;
+            default:
+                return false;
+        }
+        // 获取群组 ID
+        Long chatId = botMessage.getMessage().chat().id();
+        // 将群组 ID 发回
+        SendMessage sendMessage = new SendMessage(chatId, text);
+        SendResponse execute = botMessage.getTelegramBot().execute(sendMessage);
+        if (!execute.isOk()) {
+            log.error("价格查询结果发送失败: {}", execute);
+        }
+        return true;
+    }
+}

+ 39 - 0
src/main/java/org/jebot/models/jebot/BotAccountBook.java

@@ -0,0 +1,39 @@
+package org.jebot.models.jebot;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.hibernate.annotations.Comment;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+@Getter
+@Setter
+@Entity(name = "bot_account_book")
+@Table(
+        name = "bot_account_book",uniqueConstraints = @UniqueConstraint(columnNames = {"type", "belong_id"})
+)
+public class BotAccountBook extends BotModel {
+
+    @Column(name = "belong_id")
+    @Comment("所属ID")
+    Long belongId;
+
+    @Column(name = "belong_name")
+    @Comment("所属名称")
+    String belongName;
+
+    @Column(name = "payment_balance")
+    @Comment("代收余额")
+    double paymentBalance;
+
+    @Column(name = "agent_balance")
+    @Comment("代付余额")
+    double agentBalance;
+
+    @Column(name = "type")
+    @Comment("类型: merchant, channel")
+    String type;
+}

+ 50 - 0
src/main/java/org/jebot/models/jebot/BotAccountBookHistory.java

@@ -0,0 +1,50 @@
+package org.jebot.models.jebot;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.hibernate.annotations.Comment;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+
+@Setter
+@Getter
+@Entity(name = "bot_account_book_history")
+public class BotAccountBookHistory extends BotModel {
+
+    @Comment("所属ID")
+    @Column(name = "belong_id")
+    Long belongId;
+
+    @Column(name = "belong_name")
+    @Comment("所属名称")
+    String belongName;
+
+    @Column(name = "belong_type")
+    @Comment("所属类型: merchant, channel")
+    String belongType;
+
+    @Column(name = "amount")
+    @Comment("交易金额")
+    double amount;
+
+    @Column(name = "after_balance")
+    @Comment("交易后余额")
+    double afterBalance;
+
+    @Column(name = "before_balance")
+    @Comment("交易前余额")
+    double beforeBalance;
+
+    @Column(name = "type")
+    @Comment("操作类型: agent:代付, payment:代收")
+    String type;
+
+    @Column(name = "user_id")
+    @Comment("操作用户id")
+    Long userId;
+
+    @Column(name = "user_name")
+    @Comment("操作用户名称")
+    String userName;
+}

+ 33 - 0
src/main/java/org/jebot/models/jebot/BotComplaint.java

@@ -0,0 +1,33 @@
+package org.jebot.models.jebot;
+
+import lombok.*;
+import org.hibernate.annotations.Comment;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@Entity(name = "bot_complaint")
+@EqualsAndHashCode(callSuper = true)
+public class BotComplaint extends BotModel {
+
+    @Column(nullable = false, name = "mch_id")
+    @Comment("商户id")
+    private Long mchId;
+
+    @Column(nullable = false,name = "complaint_id")
+    @Comment("投诉id")
+    private String complaintId;
+
+    @Column(columnDefinition = "TEXT", name = "complaint_content")
+    @Comment("投诉文字")
+    private String complaintContent;
+
+    @Column(columnDefinition = "TEXT", name = "complaint_photo")
+    @Comment("投诉图片")
+    private String complaintPhoto;
+
+}

+ 46 - 0
src/main/java/org/jebot/models/jebot/BotGroup.java

@@ -0,0 +1,46 @@
+package org.jebot.models.jebot;
+
+
+import lombok.*;
+import org.hibernate.annotations.Comment;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+
+
+@Getter
+@Setter
+@Entity(name = "bot_group")
+@AllArgsConstructor
+@NoArgsConstructor
+public class BotGroup extends BotModel {
+
+    @Comment("tg群组id")
+    @Column(nullable = false,name = "group_id")
+    private Long groupId;
+
+    @Comment("tg群组名称")
+    @Column(nullable = false,name = "group_name")
+    private String groupName;
+
+    @Comment("数据id")
+    @Column(nullable = false,name = "data_id")
+    private Long dataId;
+
+    @Comment("数据名称")
+    @Column(nullable = false,name = "data_name")
+    private String dataName;
+
+    @Column(nullable = false,name = "data_type")
+    @Comment("数据类型:channel,merchant")
+    private String dataType;
+
+    @Column(nullable = false,name = "payment_warning_threshold")
+    @Comment("代收警告阈值")
+    private double paymentWarningThreshold;
+
+    @Column(nullable = false,name = "agent_warning_threshold")
+    @Comment("代付警告阈值")
+    private double agentWarningThreshold;
+
+}

+ 37 - 0
src/main/java/org/jebot/models/jebot/BotModel.java

@@ -0,0 +1,37 @@
+package org.jebot.models.jebot;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.jebot.util.SnowflakeId;
+
+import javax.persistence.*;
+import java.util.Date;
+
+
+@Getter
+@Setter
+@MappedSuperclass
+public class BotModel {
+    @Id
+    private Long id;
+
+    @Column(updatable = false,name = "create_time")
+    private Date createTime;
+
+    @Column(name = "update_time")
+    private Date updateTime;
+
+    @PrePersist
+    public void prePersist() {
+        if (this.id == null) {
+            this.id = SnowflakeId.getId();
+        }
+        this.createTime = new Date();
+        this.updateTime = new Date();
+    }
+
+    @PreUpdate
+    public void preUpdate() {
+        this.updateTime = new Date();
+    }
+}

+ 23 - 0
src/main/java/org/jebot/models/jebot/BotUser.java

@@ -0,0 +1,23 @@
+package org.jebot.models.jebot;
+
+import lombok.*;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+
+
+@Getter
+@Setter
+@Entity(name = "bot_user")
+@NoArgsConstructor
+@AllArgsConstructor
+public class BotUser extends BotModel {
+
+    @Column(name = "user_id")
+    private Long userId;
+
+    @Column(nullable = false, unique = true, name = "user_name")
+    private String userName;
+
+
+}

+ 33 - 0
src/main/java/org/jebot/models/jebot/BotWarn.java

@@ -0,0 +1,33 @@
+package org.jebot.models.jebot;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import java.util.Date;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity(name = "bot_warn")
+public class BotWarn extends BotModel {
+
+
+    @Column(name = "data_id")
+    private Long dataId;
+
+    @Column(name = "data_type")
+    private String dataType;
+
+    @Column(name = "warn_type")
+    private String warnType;
+
+    @Column(name = "update_time")
+    private Date updateTime;
+
+
+}

+ 35 - 0
src/main/java/org/jebot/models/xxpay/MchAccount.java

@@ -0,0 +1,35 @@
+package org.jebot.models.xxpay;
+
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import java.math.BigDecimal;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity(name = "t_mch_account")
+public class MchAccount {
+    @Id
+    @Column(name = "MchId")
+    private Long mchId;
+
+    @Column(name = "Name")
+    private String name;
+
+    @Column(name = "Balance")
+    private BigDecimal balance;
+
+    @Column(name = "AgentpayBalance")
+    private BigDecimal agentBalance;
+
+    @Column(name = "Status")
+    private int status;
+}

+ 27 - 0
src/main/java/org/jebot/models/xxpay/MchInfo.java

@@ -0,0 +1,27 @@
+package org.jebot.models.xxpay;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+@Getter
+@Setter
+@Entity(name = "t_mch_info")
+@AllArgsConstructor
+@NoArgsConstructor
+public class MchInfo {
+    @Id
+    @Column(name = "MchId")
+    private Long mchId;
+
+    @Column(name = "Name")
+    private String name;
+
+    @Column(name = "Status")
+    private int status;//商户状态,-1-待审核,0-停止使用,1-使用中
+}

+ 244 - 0
src/main/java/org/jebot/models/xxpay/PayOrder.java

@@ -0,0 +1,244 @@
+package org.jebot.models.xxpay;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.*;
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+@Entity(name = "t_pay_order")
+@NoArgsConstructor
+@AllArgsConstructor
+public class PayOrder {
+
+    @Id
+    @Column(name = "PayOrderId")
+    private String payOrderId;
+
+    @Column(name = "MchId")
+    private Long mchId;
+
+    @Column(name = "MchType")
+    private String mchType;
+
+    @Column(name = "MchRate")
+    private BigDecimal mchRate;
+
+    @Column(name = "MchIncome")
+    private BigDecimal mchIncome;
+
+    @Column(name = "AppId")
+    private String appId;
+
+    @Column(name = "MchOrderNo")
+    private String mchOrderNo;
+
+    @Column(name = "AgentId")
+    private String agentId;
+
+    @Column(name = "ParentAgentId")
+    private String parentAgentId;
+
+    @Column(name = "AgentRate")
+    private BigDecimal agentRate;
+
+    @Column(name = "ParentAgentRate")
+    private BigDecimal parentAgentRate;
+
+    @Column(name = "AgentProfit")
+    private BigDecimal agentProfit;
+
+    @Column(name = "ParentAgentProfit")
+    private BigDecimal parentAgentProfit;
+
+    @Column(name = "ProductId")
+    private String productId;
+
+    @Column(name = "PassageId")
+    private Long passageId;
+
+    @Column(name = "PassageAccountId")
+    private String passageAccountId;
+
+    @Column(name = "ChannelType")
+    private String channelType;
+
+    @Column(name = "ChannelId")
+    private String channelId;
+
+    @Column(name = "Amount")
+    private BigDecimal amount;
+
+    @Column(name = "Currency")
+    private String currency;
+
+    @Column(name = "Status")
+    private Integer status;
+
+    @Column(name = "ClientIp")
+    private String clientIp;
+
+    @Column(name = "Device")
+    private String device;
+
+    @Column(name = "Subject")
+    private String subject;
+
+    @Column(name = "Body")
+    private String body;
+
+    @Column(name = "Extra")
+    private String extra;
+
+    @Column(name = "ChannelUser")
+    private String channelUser;
+
+    @Column(name = "ChannelMchId")
+    private String channelMchId;
+
+    @Column(name = "ChannelOrderNo")
+    private String channelOrderNo;
+
+    @Column(name = "ChannelAttach")
+    private String channelAttach;
+
+    @Column(name = "PlatProfit")
+    private BigDecimal platProfit;
+
+    @Column(name = "ChannelRate")
+    private BigDecimal channelRate;
+
+    @Column(name = "ChannelCost")
+    private BigDecimal channelCost;
+
+    @Column(name = "PlatMchProfit")
+    private BigDecimal platMchProfit;
+
+    @Column(name = "PayPassageRate")
+    private BigDecimal payPassageRate;
+
+    @Column(name = "PayPassageCost")
+    private BigDecimal payPassageCost;
+
+    @Column(name = "IsRefund")
+    private Boolean isRefund;
+
+    @Column(name = "RefundTimes")
+    private Integer refundTimes;
+
+    @Column(name = "SuccessRefundAmount")
+    private BigDecimal successRefundAmount;
+
+    @Column(name = "ErrCode")
+    private String errCode;
+
+    @Column(name = "ErrMsg")
+    private String errMsg;
+
+    @Column(name = "Param1")
+    private String param1;
+
+    @Column(name = "Param2")
+    private String param2;
+
+    @Column(name = "NotifyUrl")
+    private String notifyUrl;
+
+    @Column(name = "ReturnUrl")
+    private String returnUrl;
+
+    @Column(name = "RandomRemark")
+    private String randomRemark;
+
+    @Column(name = "IsReissue")
+    private Boolean isReissue;
+
+    @Column(name = "ExpireTime")
+    private Date expireTime;
+
+    @Column(name = "PaySuccTime")
+    private Date paySuccTime;
+
+    @Column(name = "PayOpenTime")
+    private Date payOpenTime;
+
+    @Column(name = "CreateTime")
+    private Date createTime;
+
+    @Column(name = "UpdateTime")
+    private Date updateTime;
+
+    @Column(name = "ProductType")
+    private String productType;
+
+    @Column(name = "IsReConfirm")
+    private Boolean isReConfirm;
+
+    @Column(name = "IsDivision")
+    private Boolean isDivision;
+
+    @Column(name = "OrderAmount")
+    private BigDecimal orderAmount;
+
+    @Column(name = "PaymentAmount")
+    private BigDecimal paymentAmount;
+
+    @Column(name = "TopayUrl")
+    private String topayUrl;
+
+    @Column(name = "ChannelSMId")
+    private String channelSMId;
+
+    @Column(name = "SkName")
+    private String skName;
+
+    @Column(name = "SkBank")
+    private String skBank;
+
+    @Column(name = "SkCard")
+    private String skCard;
+
+    @Column(name = "BuyerId")
+    private String buyerId;
+
+    @Column(name = "FloatAmount")
+    private BigDecimal floatAmount;
+
+    @Column(name = "buyIdCount")
+    private Integer buyIdCount;
+
+    @Column(name = "buyIdCountAll")
+    private Integer buyIdCountAll;
+
+    //获取订单状态
+    public String getStatusMsg(){
+        switch (status) {
+            case -2:
+                return "订单已关闭";
+            case 0:
+                return "订单生成";
+            case 1:
+                return "支付中";
+            case 2:
+                return "支付成功";
+            case 3:
+                return "业务处理完成";
+            case 4:
+                return "已退款";
+            default:
+                return "未知状态";
+        }
+    }
+
+    public PayOrder(BigDecimal amount,Long mchId,BigDecimal mchRate,  Long passageId,  Integer status, Date createTime) {
+        this.amount = amount;
+        this.passageId = passageId;
+        this.mchId = mchId;
+        this.mchRate = mchRate;
+        this.status = status;
+        this.createTime = createTime;
+    }
+}

+ 27 - 0
src/main/java/org/jebot/models/xxpay/PayPassage.java

@@ -0,0 +1,27 @@
+package org.jebot.models.xxpay;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@Entity(name = "t_pay_passage")
+public class PayPassage {
+
+    @Id
+    private Long id;
+
+    @Column(name = "PassageName")
+    private String passageName;       // 通道名称
+
+    @Column(name = "Status")
+    private int status;      // 通道ID 通道状态,0-关闭,1-开启
+}

+ 38 - 0
src/main/java/org/jebot/models/xxpay/TransOrder.java

@@ -0,0 +1,38 @@
+package org.jebot.models.xxpay;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+@Setter
+@Getter
+@Entity(name = "t_trans_order")
+@NoArgsConstructor
+@AllArgsConstructor
+public class TransOrder {
+
+    @Id
+    @Column(name = "TransOrderId")
+    String TransOrderId;
+
+    @Column(name = "MchId")
+    Long MchId;
+
+    @Column(name = "Status")
+    int Status;
+
+    @Column(name = "PassageId")
+    Long PassageId;
+
+    @Column(name = "Amount")
+    Long Amount;
+
+    @Column(name = "CreateTime")
+    String CreateTime;
+
+}

+ 17 - 0
src/main/java/org/jebot/repository/jebot/BotAccountBookHistoryRepository.java

@@ -0,0 +1,17 @@
+package org.jebot.repository.jebot;
+
+import org.jebot.models.jebot.BotAccountBookHistory;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+import java.util.List;
+
+@Component
+public interface BotAccountBookHistoryRepository extends JpaRepository<BotAccountBookHistory, Long> {
+
+    @Query( "SELECT abh FROM bot_account_book_history abh WHERE abh.belongId = :dataId and abh.belongType =:belongType AND abh.type = :historyType and abh.createTime >= :startTime order by  abh.createTime desc")
+    List<BotAccountBookHistory> findByBelongIdAndBelongTypeAndType(Long dataId, String belongType, String historyType, Date startTime);
+
+}

+ 24 - 0
src/main/java/org/jebot/repository/jebot/BotAccountBookRepository.java

@@ -0,0 +1,24 @@
+package org.jebot.repository.jebot;
+
+import org.jebot.models.jebot.BotAccountBook;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Component;
+
+import javax.transaction.Transactional;
+
+@Component
+public interface BotAccountBookRepository extends JpaRepository<BotAccountBook, Long> {
+    BotAccountBook findByBelongIdAndType(Long belongId, String type);
+
+    @Modifying
+    @Transactional
+    @Query("UPDATE bot_account_book SET paymentBalance =:paymentBalanceNew WHERE id =:id AND paymentBalance =:paymentBalanceOld")
+    void updatePaymentBalanceByIdAndPaymentBalance(double paymentBalanceNew,Long id, double paymentBalanceOld);
+
+    @Modifying
+    @Transactional
+    @Query("UPDATE bot_account_book SET agentBalance =:agentBalanceNew WHERE id =:id AND agentBalance =:agentBalanceOld")
+    void updateAgentBalanceByIdAndAgentBalance(double agentBalanceNew, Long id, double agentBalanceOld);
+}

+ 12 - 0
src/main/java/org/jebot/repository/jebot/BotComplaintRepository.java

@@ -0,0 +1,12 @@
+package org.jebot.repository.jebot;
+
+import org.jebot.models.jebot.BotComplaint;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Component;
+
+@Component
+public interface BotComplaintRepository extends JpaRepository<BotComplaint, Long> {
+
+    //通过投诉单号查询投诉
+    BotComplaint findByComplaintId(String complaintId);
+}

+ 46 - 0
src/main/java/org/jebot/repository/jebot/BotGroupRepository.java

@@ -0,0 +1,46 @@
+package org.jebot.repository.jebot;
+
+import org.jebot.models.jebot.BotGroup;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Component;
+
+import javax.transaction.Transactional;
+import java.util.List;
+
+@Component
+public interface BotGroupRepository extends JpaRepository<BotGroup, Long> {
+
+    //通过群组 ID 查询群组
+    @Query("select g from bot_group g where g.groupId =:groupId")
+    List<BotGroup> findByGroupId(@Param("groupId") Long groupId);
+
+    //通过data ID 和数据类型查询群组
+    BotGroup findBotGroupByDataIdAndDataType(@Param("dataId") Long dataId, @Param("dataType") String dataType);
+
+    //通过群组 ID 和数据类型查询群组列表
+    List<BotGroup> findAllByGroupIdAndDataType(@Param("groupId") Long groupId, @Param("dataType") String dataType);
+
+    //通过dataId和数据类型删除群组
+    @Modifying
+    @Transactional
+    @Query("delete from bot_group g where g.dataId =:dataId and g.dataType =:dataType")
+    void deleteByDataIDAndDataType(@Param("dataId") Long dataId, @Param("dataType") String dataType);
+
+
+    @Modifying
+    @Transactional
+    @Query("UPDATE bot_group g SET g.paymentWarningThreshold = :paymentWarningThresholdNew WHERE g.id = :id")
+    void updatePaymentWarningThresholdByIdAndPaymentWarningThreshold(@Param("paymentWarningThresholdNew") double paymentWarningThresholdNew, @Param("id") Long id);
+
+    @Modifying
+    @Transactional
+    @Query("UPDATE bot_group g SET g.agentWarningThreshold =:agentWarningThresholdNew WHERE g.id =:id ")
+    void updateAgentWarningThresholdByIdAndAgentWarningThreshold(@Param("agentWarningThresholdNew") double agentWarningThresholdNew, @Param("id") Long id);
+
+    @Query("SELECT g FROM bot_group g WHERE g.agentWarningThreshold >0 or g.paymentWarningThreshold >0")
+    List<BotGroup> findAllByAgentWarningThresholdOrPaymentWarningThresholdGreaterThanZero();
+
+}

+ 23 - 0
src/main/java/org/jebot/repository/jebot/BotUserRepository.java

@@ -0,0 +1,23 @@
+package org.jebot.repository.jebot;
+
+import org.jebot.models.jebot.BotUser;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Component;
+
+import javax.transaction.Transactional;
+
+@Component
+public interface BotUserRepository extends JpaRepository<BotUser, Long> {
+
+    //通过用户名查询用户
+    BotUser findByUserName(@Param("userName") String userName);
+
+    @Modifying
+    @Transactional
+    @Query("UPDATE bot_user u SET u.userId = :userId WHERE u.userName = :userName")
+    void updateUserIdByUserName(@Param("userId") Long userId, @Param("userName") String userName);
+
+}

+ 14 - 0
src/main/java/org/jebot/repository/jebot/BotWarnRepository.java

@@ -0,0 +1,14 @@
+package org.jebot.repository.jebot;
+
+import org.jebot.models.jebot.BotWarn;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Component;
+
+@Component
+public interface BotWarnRepository extends JpaRepository<BotWarn, Long> {
+
+    @Query("select w from bot_warn w where w.dataId =:dataId and w.warnType =:warnType and  w.dataType =:dataType")
+    BotWarn findByDataIdAndWarnType(Long dataId, String warnType,String dataType);
+
+}

+ 25 - 0
src/main/java/org/jebot/repository/xxpay/MchAccountRepository.java

@@ -0,0 +1,25 @@
+package org.jebot.repository.xxpay;
+
+import org.jebot.models.xxpay.MchAccount;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Component;
+
+import javax.transaction.Transactional;
+import java.util.List;
+
+@Component
+public interface MchAccountRepository extends JpaRepository<MchAccount, String> {
+
+    @Query("SELECT t FROM t_mch_account t WHERE t.mchId =:mchId")
+    MchAccount findByMchId(Long mchId);
+
+    @Query("SELECT t FROM t_mch_account t WHERE t.mchId IN (:mchIds)")
+    List<MchAccount> findByMchIds(List<Long> mchIds);
+
+    @Modifying
+    @Transactional
+    @Query("UPDATE t_mch_account t SET t.status =:status WHERE t.mchId =:mchId")
+    int updateStatusByMchId(Long mchId, int status);
+}

+ 19 - 0
src/main/java/org/jebot/repository/xxpay/MchInfoRepository.java

@@ -0,0 +1,19 @@
+package org.jebot.repository.xxpay;
+
+import org.jebot.models.xxpay.MchInfo;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Component;
+
+import javax.transaction.Transactional;
+
+@Component
+public interface MchInfoRepository extends JpaRepository<MchInfo, Long> {
+
+    @Modifying
+    @Transactional
+    @Query("UPDATE t_mch_info t SET t.status =:status WHERE t.mchId =:mchId")
+    int updateStatusByMchId(Long mchId, int status);
+
+}

+ 40 - 0
src/main/java/org/jebot/repository/xxpay/PayOrderRepository.java

@@ -0,0 +1,40 @@
+package org.jebot.repository.xxpay;
+
+import org.jebot.models.xxpay.PayOrder;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+@Component
+public interface PayOrderRepository extends JpaRepository<PayOrder, String> {
+
+    //通过支付单号查询支付订单
+    @Query("SELECT t FROM t_pay_order t WHERE t.payOrderId =:orderId  OR t.channelOrderNo =:orderId ")
+    PayOrder findByPayOrderIdOrChannelOrderNo(@Param("orderId") String orderId);
+
+    //通过支付单号查询支付订单
+    @Query("SELECT t FROM t_pay_order t WHERE (t.payOrderId =:orderId OR t.mchOrderNo =:orderId) and t.mchId =:mchId")
+    PayOrder findByPayOrderIdOrMchOrderNo(@Param("orderId") String orderId, @Param("mchId") Long mchId);
+
+    //通过支付单号查询支付订单
+//    @Query("SELECT t FROM t_pay_order t WHERE t.payOrderId =:orderId OR t.mchOrderNo =:orderId OR t.channelOrderNo =:orderId ")
+//    PayOrder findByPayOrderIdOrMchOrderNoOrChannelOrderNo(@Param("orderId") String orderId);
+
+    @Query("SELECT new org.jebot.models.xxpay.PayOrder( tpo.amount,tpo.mchId,tpo.mchRate,tpo.passageId,tpo.status,tpo.createTime) FROM t_pay_order as tpo WHERE tpo.mchId =:mchId and tpo.createTime >= :startTime and tpo.createTime <= :endTime")
+    List<PayOrder> findPayOrderByMchIdAndCreateTime(Long mchId, Date startTime, Date endTime);
+
+    @Query("SELECT new org.jebot.models.xxpay.PayOrder( tpo.amount,tpo.mchId,tpo.mchRate,tpo.passageId,tpo.status,tpo.createTime) FROM t_pay_order as tpo WHERE  tpo.mchId =:mchId and tpo.status in (2,3) and tpo.createTime >= :startTime and tpo.createTime <= :endTime")
+    List<PayOrder> findSuccessPayOrderByMchIdAndCreateTime(Long mchId, Date startTime, Date endTime);
+
+    @Query("SELECT new org.jebot.models.xxpay.PayOrder( tpo.amount,tpo.mchId,tpo.mchRate,tpo.passageId,tpo.status,tpo.createTime) FROM t_pay_order as tpo WHERE tpo.passageId in (:passageIds) and tpo.createTime >= :startTime and tpo.createTime <= :endTime")
+    List<PayOrder> findPayOrderByPassageAndCreateTime(List<Long> passageIds, Date startTime, Date endTime);
+
+    @Query("SELECT SUM(tpo.amount) FROM t_pay_order as tpo WHERE tpo.mchId =:mchId AND tpo.createTime >=:createTime and tpo.status in(2,3)")
+    BigDecimal findPayOrderAmountByMchIdAndCreateTime(@Param("mchId") Long mchId, @Param("createTime") Date createTime);
+
+}

+ 27 - 0
src/main/java/org/jebot/repository/xxpay/PayPassageRepository.java

@@ -0,0 +1,27 @@
+package org.jebot.repository.xxpay;
+
+import org.jebot.models.xxpay.PayPassage;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Component;
+
+import javax.transaction.Transactional;
+import java.util.List;
+
+@Component
+public interface PayPassageRepository extends JpaRepository<PayPassage, Long> {
+
+    @Query("SELECT t FROM t_pay_passage t WHERE t.id in (:passageIds)")
+    List<PayPassage> findByPassageIds(List<Long> passageIds);
+
+    @Query("SELECT t FROM t_pay_passage t WHERE t.id =:passageId")
+    PayPassage findByPassageId(Long passageId);
+
+    @Modifying
+    @Transactional
+    @Query("UPDATE t_pay_passage t SET t.status =:status WHERE t.id =:id")
+    int updateStatusByIdI(@Param("status") Integer Status, Long id);
+
+}

+ 16 - 0
src/main/java/org/jebot/repository/xxpay/TransOrderRepository.java

@@ -0,0 +1,16 @@
+package org.jebot.repository.xxpay;
+
+import org.jebot.models.xxpay.TransOrder;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+
+@Component
+public interface TransOrderRepository extends JpaRepository<TransOrder, Long> {
+
+    @Query("SELECT SUM(t.Amount) FROM t_trans_order t WHERE t.MchId = :mchId AND t.CreateTime>= :startTime")
+    BigDecimal findTransOrderAmountByMchIdAndCreateTime(Long mchId, String startTime);
+
+}

+ 30 - 0
src/main/java/org/jebot/rest/BotRest.java

@@ -0,0 +1,30 @@
+package org.jebot.rest;
+
+import org.jebot.config.BotConfig;
+import org.jebot.handler.HandlerManager;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class BotRest {
+
+    private final BotConfig botConfig;
+    private final HandlerManager handlerManager;
+
+    public BotRest(BotConfig botConfig, HandlerManager handlerManager) {
+        this.botConfig = botConfig;
+        this.handlerManager = handlerManager;
+    }
+
+    @GetMapping("/reload-bot")
+    public String reloadBot(@RequestParam String newToken) {
+        botConfig.reloadBot(newToken, handlerManager);
+        return "TelegramBot 已成功重载";
+    }
+
+}
+
+
+
+

+ 185 - 0
src/main/java/org/jebot/scheduled/MchScheduled.java

@@ -0,0 +1,185 @@
+package org.jebot.scheduled;
+
+import cn.hutool.core.date.DateUtil;
+import com.pengrad.telegrambot.TelegramBot;
+import com.pengrad.telegrambot.request.SendMessage;
+import com.pengrad.telegrambot.response.SendResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.jebot.constant.Constant;
+import org.jebot.models.jebot.BotAccountBook;
+import org.jebot.models.jebot.BotGroup;
+import org.jebot.models.jebot.BotWarn;
+import org.jebot.models.xxpay.MchAccount;
+import org.jebot.models.xxpay.PayOrder;
+import org.jebot.repository.jebot.BotAccountBookRepository;
+import org.jebot.repository.jebot.BotGroupRepository;
+import org.jebot.repository.jebot.BotWarnRepository;
+import org.jebot.repository.xxpay.MchAccountRepository;
+import org.jebot.repository.xxpay.PayOrderRepository;
+import org.jebot.repository.xxpay.TransOrderRepository;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.Scheduled;
+
+import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static org.jebot.util.DateUtil.getTodayMidnight;
+
+@Slf4j
+@Configuration
+public class MchScheduled {
+
+    @Resource
+    TelegramBot telegramBot;
+
+
+    @Resource
+    private BotWarnRepository botWarnRepository;
+
+    @Resource
+    private BotGroupRepository botGroupRepository;
+
+    @Resource
+    private PayOrderRepository payOrderRepository;
+
+    @Resource
+    private TransOrderRepository transOrderRepository;
+
+    @Resource
+    private MchAccountRepository mchAccountRepository;
+
+    @Resource
+    private BotAccountBookRepository botAccountBookRepository;
+
+    @Scheduled(cron = "*/30 * * * * *")
+    public void insufficientBalanceWarning() {
+        log.info("定时任务开始执行");
+        // 定时任务逻辑
+        List<BotGroup> botGroups = botGroupRepository.findAllByAgentWarningThresholdOrPaymentWarningThresholdGreaterThanZero();
+        // 过滤出商户群组
+        List<BotGroup> mchGroups = botGroups.stream().filter(botGroup -> Constant.DATA_TYPE_MERCHANT.equals(botGroup.getDataType())).collect(Collectors.toList());
+        if (!mchGroups.isEmpty()) {
+            List<Long> mchIds = mchGroups.stream().map(BotGroup::getDataId).collect(Collectors.toList());
+            List<MchAccount> mchAccounts = mchAccountRepository.findByMchIds(mchIds);
+            if (mchAccounts.isEmpty()) {
+                return;
+            }
+            Map<Long, MchAccount> mchAccountMap = mchAccounts.stream()
+                    .collect(Collectors.toMap(MchAccount::getMchId, mchAccount -> mchAccount));
+
+            for (BotGroup mchGroup : mchGroups) {
+                MchAccount mchAccount = mchAccountMap.get(mchGroup.getDataId());
+                if (mchAccount == null || mchAccount.getStatus() != 1) {
+                    continue;
+                }
+                if (mchGroup.getPaymentWarningThreshold() < 1 && mchGroup.getAgentWarningThreshold() < 1) {
+                    continue;
+                }
+                BotAccountBook accountBook = botAccountBookRepository.findByBelongIdAndType(mchGroup.getDataId(), Constant.DATA_TYPE_MERCHANT);
+                if (accountBook == null) {
+                    continue;
+                }
+                if (mchGroup.getPaymentWarningThreshold() > 0) {
+                    BigDecimal totalPayOrderAmount = payOrderRepository.findPayOrderAmountByMchIdAndCreateTime(Long.valueOf(mchGroup.getDataId()), getTodayMidnight(new Date()));
+                    if (totalPayOrderAmount == null) {
+                        continue;
+                    }
+
+                    BigDecimal paymentBalance = BigDecimal.valueOf(accountBook.getPaymentBalance()).movePointRight(Constant.AMOUNT_MOVE_POINT);
+
+                    // 计算差额
+                    BigDecimal difference = paymentBalance.subtract(totalPayOrderAmount);
+
+                    // 计算百分比
+                    BigDecimal percentage = totalPayOrderAmount
+                            .divide(paymentBalance, 2, RoundingMode.HALF_UP)
+                            .movePointRight(Constant.AMOUNT_MOVE_POINT);
+                    percentage = BigDecimal.valueOf(100).subtract(percentage);
+                    if (percentage.doubleValue() < mchGroup.getPaymentWarningThreshold()) {
+                        //30分钟内已经发送过预警消息,跳过预警
+                        BotWarn botWarn = botWarnRepository.findByDataIdAndWarnType(mchGroup.getDataId(), Constant.PAYMENT, Constant.DATA_TYPE_MERCHANT);
+                        if (botWarn != null && botWarn.getUpdateTime().after(DateUtil.offsetMinute(new Date(), -30))) {
+                            continue;
+                        }
+                        SendMessage sendMessage = new SendMessage(mchGroup.getGroupId(), "预付余额不足,请及时充值 " +
+                                "\n差额比例: " + percentage +
+                                "%\n当前余额: " + accountBook.getPaymentBalance() +
+                                "\n已用额度: " + totalPayOrderAmount.movePointLeft(Constant.AMOUNT_MOVE_POINT) +
+                                "\n剩余额度: " + difference.movePointLeft(Constant.AMOUNT_MOVE_POINT));
+                        SendResponse execute = telegramBot.execute(sendMessage);
+                        if (execute.isOk()) {
+                            if (botWarn == null) {
+                                botWarn = new BotWarn();
+                                botWarn.setDataId(mchGroup.getDataId());
+                                botWarn.setWarnType(Constant.PAYMENT);
+                                botWarn.setDataType(Constant.DATA_TYPE_MERCHANT);
+                                botWarn.setUpdateTime(new Date());
+                                botWarnRepository.save(botWarn);
+                            } else {
+                                botWarn.setUpdateTime(new Date());
+                                botWarnRepository.save(botWarn);
+                            }
+                            log.info("成功发送余额不足预警消息,商户ID为:" + mchGroup.getDataId() + ",群组ID为:" + mchGroup.getGroupId());
+                        } else {
+                            log.error("发送余额不足预警消息失败,商户ID为:" + mchGroup.getDataId() + ",群组ID为:" + mchGroup.getGroupId());
+                        }
+
+                    }
+                }
+                if (mchGroup.getAgentWarningThreshold() > 0) {
+                    BigDecimal totalTransOrderAmount = transOrderRepository.findTransOrderAmountByMchIdAndCreateTime(Long.valueOf(mchGroup.getDataId()), DateUtil.formatDateTime(getTodayMidnight(new Date())));
+                    if (totalTransOrderAmount == null) {
+                        continue;
+                    }
+                    BigDecimal agentBalance = BigDecimal.valueOf(accountBook.getAgentBalance()).movePointRight(Constant.AMOUNT_MOVE_POINT);
+                    // 计算差额
+                    BigDecimal difference = agentBalance.subtract(totalTransOrderAmount);
+                    // 计算百分比
+                    BigDecimal percentage = totalTransOrderAmount
+                            .divide(agentBalance, 2, RoundingMode.HALF_UP)
+                            .movePointRight(Constant.AMOUNT_MOVE_POINT);
+                    percentage = BigDecimal.valueOf(100).subtract(percentage);
+                    if (percentage.doubleValue() < mchGroup.getAgentWarningThreshold()) {
+                        //15分钟内已经发送过预警消息,跳过预警
+                        BotWarn botWarn = botWarnRepository.findByDataIdAndWarnType(mchGroup.getDataId(), Constant.AGENT, Constant.DATA_TYPE_MERCHANT);
+                        if (botWarn != null && botWarn.getUpdateTime().after(DateUtil.offsetMinute(new Date(), -15))) {
+                            continue;
+                        }
+                        SendMessage sendMessage = new SendMessage(mchGroup.getGroupId(), "代付余额不足,请及时充值 " +
+                                "\n差额比例: " + percentage +
+                                "%\n当前余额: " + accountBook.getAgentBalance() +
+                                "\n已用额度: " + totalTransOrderAmount.movePointLeft(Constant.AMOUNT_MOVE_POINT) +
+                                "\n剩余额度: " + difference.movePointLeft(Constant.AMOUNT_MOVE_POINT));
+                        SendResponse execute = telegramBot.execute(sendMessage);
+                        if (execute.isOk()) {
+                            if (botWarn == null) {
+                                botWarn = new BotWarn();
+                                botWarn.setDataId(mchGroup.getDataId());
+                                botWarn.setWarnType(Constant.AGENT);
+                                botWarn.setDataType(Constant.DATA_TYPE_MERCHANT);
+                                botWarn.setUpdateTime(new Date());
+                                botWarnRepository.save(botWarn);
+                            } else {
+                                botWarn.setUpdateTime(new Date());
+                                botWarnRepository.save(botWarn);
+                            }
+                            log.info("成功发送余额不足预警消息,商户ID为:" + mchGroup.getDataId() + ",群组ID为:" + mchGroup.getGroupId());
+                        } else {
+                            log.error("发送余额不足预警消息失败,商户ID为:" + mchGroup.getDataId() + ",群组ID为:" + mchGroup.getGroupId());
+                        }
+
+                    }
+
+                }
+            }
+
+        }
+    }
+
+}

+ 21 - 0
src/main/java/org/jebot/service/IAccountBookService.java

@@ -0,0 +1,21 @@
+package org.jebot.service;
+
+import org.jebot.handler.dto.BotMessage;
+import org.jebot.service.dto.UpdateBalance;
+
+import java.util.List;
+
+public interface IAccountBookService {
+
+
+    void updatePaymentBalanceByIdAndPaymentBalance(UpdateBalance updateBalance);
+
+
+    void updateAgentBalanceByIdAndAgentBalance(UpdateBalance updateBalance);
+
+
+    void updateMutiPaymentBalanceByIdAndPaymentBalance(UpdateBalance ...updateBalance);
+
+
+
+}

+ 7 - 0
src/main/java/org/jebot/service/IMchService.java

@@ -0,0 +1,7 @@
+package org.jebot.service;
+
+public interface IMchService {
+
+    public int updateMchStatus(Long mchId, int status);
+
+}

+ 19 - 0
src/main/java/org/jebot/service/dto/UpdateBalance.java

@@ -0,0 +1,19 @@
+package org.jebot.service.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.jebot.handler.dto.BotMessage;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+public class UpdateBalance {
+    private Long id;
+    private double amount;
+    private double oldBalance;
+    private double newBalance;
+    private BotMessage message;
+}

+ 77 - 0
src/main/java/org/jebot/service/impl/AccountBookServiceImpl.java

@@ -0,0 +1,77 @@
+package org.jebot.service.impl;
+
+import org.jebot.constant.Constant;
+import org.jebot.models.jebot.BotAccountBookHistory;
+import org.jebot.repository.jebot.BotAccountBookHistoryRepository;
+import org.jebot.repository.jebot.BotAccountBookRepository;
+import org.jebot.service.IAccountBookService;
+import org.jebot.service.dto.UpdateBalance;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import javax.transaction.Transactional;
+
+
+@Service
+public class AccountBookServiceImpl implements IAccountBookService {
+
+    @Resource
+    BotAccountBookRepository accountBookRepository;
+
+    @Resource
+    BotAccountBookHistoryRepository accountBookHistoryRepository;
+
+    @Override
+    @Transactional
+    public void updatePaymentBalanceByIdAndPaymentBalance(UpdateBalance updateBalance) {
+        accountBookRepository.updatePaymentBalanceByIdAndPaymentBalance(updateBalance.getNewBalance(), updateBalance.getId(), updateBalance.getOldBalance());
+        BotAccountBookHistory accountBookHistory = new BotAccountBookHistory();
+        accountBookHistory.setAmount(updateBalance.getAmount());
+        accountBookHistory.setBeforeBalance(updateBalance.getOldBalance());
+        accountBookHistory.setAfterBalance(updateBalance.getNewBalance());
+        accountBookHistory.setType(Constant.PAYMENT);
+        if (updateBalance.getMessage().isGroupMch()) {
+            accountBookHistory.setBelongType(Constant.DATA_TYPE_MERCHANT);
+        }
+        if (updateBalance.getMessage().isGroupChannel()) {
+            accountBookHistory.setBelongType(Constant.DATA_TYPE_CHANNEL);
+        }
+        accountBookHistory.setBelongId(updateBalance.getMessage().getBotGroup().getDataId());
+        accountBookHistory.setBelongName(updateBalance.getMessage().getBotGroup().getDataName());
+        accountBookHistory.setUserId(updateBalance.getMessage().getBotUser().getUserId());
+        accountBookHistory.setUserName(updateBalance.getMessage().getBotUser().getUserName());
+        accountBookHistoryRepository.save(accountBookHistory);
+    }
+
+    @Override
+    @Transactional
+    public void updateAgentBalanceByIdAndAgentBalance(UpdateBalance updateBalance) {
+        accountBookRepository.updateAgentBalanceByIdAndAgentBalance(updateBalance.getNewBalance(), updateBalance.getId(), updateBalance.getOldBalance());
+        BotAccountBookHistory accountBookHistory = new BotAccountBookHistory();
+        accountBookHistory.setAmount(updateBalance.getAmount());
+        accountBookHistory.setBeforeBalance(updateBalance.getOldBalance());
+        accountBookHistory.setAfterBalance(updateBalance.getNewBalance());
+        if (updateBalance.getMessage().isGroupMch()) {
+            accountBookHistory.setBelongType(Constant.DATA_TYPE_MERCHANT);
+        }
+        if (updateBalance.getMessage().isGroupChannel()) {
+            accountBookHistory.setBelongType(Constant.DATA_TYPE_CHANNEL);
+        }
+        accountBookHistory.setType(Constant.AGENT);
+        accountBookHistory.setBelongId(updateBalance.getMessage().getBotGroup().getDataId());
+        accountBookHistory.setBelongName(updateBalance.getMessage().getBotGroup().getDataName());
+        accountBookHistory.setUserId(updateBalance.getMessage().getBotUser().getUserId());
+        accountBookHistory.setUserName(updateBalance.getMessage().getBotUser().getUserName());
+        accountBookHistoryRepository.save(accountBookHistory);
+    }
+
+    @Override
+    @Transactional
+    public void updateMutiPaymentBalanceByIdAndPaymentBalance(UpdateBalance... updateBalance) {
+        for (UpdateBalance balance : updateBalance) {
+            this.updatePaymentBalanceByIdAndPaymentBalance(balance);
+        }
+    }
+
+
+}

+ 31 - 0
src/main/java/org/jebot/service/impl/MchServiceImpl.java

@@ -0,0 +1,31 @@
+package org.jebot.service.impl;
+
+import org.jebot.repository.xxpay.MchAccountRepository;
+import org.jebot.repository.xxpay.MchInfoRepository;
+import org.jebot.service.IMchService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+
+@Service
+public class MchServiceImpl implements IMchService {
+
+    @Resource
+    private MchAccountRepository mchAccountRepository;
+
+    @Resource
+    private MchInfoRepository mchInfoRepository;
+
+    @Override
+    @Transactional
+    public int updateMchStatus(Long mchId, int status) {
+        if (mchInfoRepository.updateStatusByMchId(mchId, status) < 1) {
+            return -1;
+        }
+        if (mchAccountRepository.updateStatusByMchId(mchId, status) < 1) {
+            throw new RuntimeException("更新商户账户状态失败");
+        }
+        return 1;
+    }
+}

+ 26 - 0
src/main/java/org/jebot/util/DateUtil.java

@@ -0,0 +1,26 @@
+package org.jebot.util;
+
+import java.util.Calendar;
+import java.util.Date;
+
+public class DateUtil {
+
+    // 添加或减少分钟
+    public static Date addMinutes(Date date, int minutes) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(date);
+        calendar.add(Calendar.MINUTE, minutes);
+        return calendar.getTime();
+    }
+
+    // 获取当天 00:00
+    public static Date getTodayMidnight(Date date) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(date);
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 0);
+        return calendar.getTime();
+    }
+}

+ 70 - 0
src/main/java/org/jebot/util/ImageUtil.java

@@ -0,0 +1,70 @@
+package org.jebot.util;
+
+import cn.hutool.core.date.DateUtil;
+import org.jebot.models.jebot.BotAccountBookHistory;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.util.List;
+
+public class ImageUtil {
+
+    public static void generateTableImage(Font chineseFont , List<BotAccountBookHistory> bookHistories, String outputPath) throws Exception {
+        int rowHeight = 30;
+        int tableWidth = 800;
+        int tableHeight = (bookHistories.size() + 1) * rowHeight + 20;
+
+        BufferedImage image = new BufferedImage(tableWidth, tableHeight, BufferedImage.TYPE_INT_RGB);
+        Graphics2D g2d = image.createGraphics();
+
+        // 设置背景颜色
+        g2d.setColor(Color.WHITE);
+        g2d.fillRect(0, 0, tableWidth, tableHeight);
+
+        if (chineseFont==null){
+            // 设置字体
+            g2d.setFont(new Font("SansSerif", Font.PLAIN, 16));
+            g2d.setColor(Color.BLACK);
+
+        }else{
+            // 设置字体
+            g2d.setFont(chineseFont);
+            g2d.setColor(Color.BLACK);
+        }
+
+
+        // 绘制表头
+        String[] headers = {"金额", "更新前余额", "更新后余额", "更新时间", "更新人"};
+        int[] columnWidths = {100, 150, 150, 250, 150};
+        int x = 10;
+        int y = 30;
+
+        for (int i = 0; i < headers.length; i++) {
+            g2d.drawString(headers[i], x, y);
+            x += columnWidths[i];
+        }
+
+        // 绘制表格内容
+        y += rowHeight;
+        for (BotAccountBookHistory bookHistory : bookHistories) {
+            x = 10;
+            g2d.drawString(String.format("%.2f", bookHistory.getAmount()), x, y);
+            x += columnWidths[0];
+            g2d.drawString(String.format("%.2f", bookHistory.getBeforeBalance()), x, y);
+            x += columnWidths[1];
+            g2d.drawString(String.format("%.2f", bookHistory.getAfterBalance()), x, y);
+            x += columnWidths[2];
+            g2d.drawString(DateUtil.formatDateTime(bookHistory.getCreateTime()), x, y);
+            x += columnWidths[3];
+            g2d.drawString(bookHistory.getUserName(), x, y);
+            y += rowHeight;
+        }
+
+        g2d.dispose();
+
+        // 保存图片
+        ImageIO.write(image, "png", new File(outputPath));
+    }
+}

+ 73 - 0
src/main/java/org/jebot/util/PriceUtil.java

@@ -0,0 +1,73 @@
+package org.jebot.util;
+
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.http.HttpUtil;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import org.apache.logging.log4j.util.Strings;
+import org.springframework.http.HttpHeaders;
+
+
+public class PriceUtil {
+
+    private static final String url = "https://www.okx.com/v3/c2c/tradingOrders/books?quoteCurrency=CNY&baseCurrency=USDT&side=sell&paymentMethod=%s&userType=all&receivingAds=false&t=%d";
+
+    public static String QueryUsdtToCny(QueryType queryType) {
+        // 将价格格式化为两位小数
+        HttpRequest request = HttpUtil.createGet(String.format(url, queryType.getValue(), System.currentTimeMillis() / 1000));
+        // 设置请求头
+        request.header(HttpHeaders.HOST, "www.okx.com");
+        request.header("sec-ch-ua", "\"Chromium\";v=\"136\", \"Microsoft Edge\";v=\"136\", \"Not.A/Brand\";v=\"99\"");
+        request.header("sec-ch-ua-mobile", "?0");
+        request.header("sec-ch-ua-platform", "\"Windows\"");
+        request.header(HttpHeaders.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0");
+        request.header(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7");
+        request.header("Service-Worker-Navigation-Preload", "true");
+        request.header("Sec-Fetch-Site", "none");
+        request.header("Sec-Fetch-Mode", "navigate");
+        request.header("Sec-Fetch-User", "?1");
+        request.header("Sec-Fetch-Dest", "document");
+        request.header(HttpHeaders.ACCEPT_ENCODING, "gzip, deflate, br, zstd");
+        request.header(HttpHeaders.ACCEPT_LANGUAGE, "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6");
+        HttpResponse response = request.execute();
+        if (response.getStatus() != 200) {
+            return "请求失败";
+        }
+        String body = response.body();
+        if (Strings.isEmpty(body)) {
+            return "请求失败";
+        }
+
+        JSONObject result = JSONUtil.parseObj(body);
+        String code = result.getStr("code");
+        if (!"0".equals(code)) {
+            return "请求失败";
+        }
+        JSONObject data = JSONUtil.parseObj(result.getStr("data"));
+        JSONArray sell = JSONUtil.parseArray(data.getStr("sell"));
+        StringBuffer buffer = new StringBuffer();
+        buffer.append("欧易筛选: ");
+        if (queryType == QueryType.ALIPAY) {
+            buffer.append("支付宝\r\n");
+        } else if (queryType == QueryType.WECHAT) {
+            buffer.append("微信\r\n");
+        } else if (queryType == QueryType.BANK) {
+            buffer.append("银行\r\n");
+        }
+        int count = 0;
+        for (Object o : sell) {
+            if (count >= 10) {
+                return buffer.toString();
+            }
+            JSONObject jsonObject = JSONUtil.parseObj(o);
+            buffer.append(jsonObject.getStr("price"));
+            buffer.append("        ");
+            buffer.append(jsonObject.getStr("nickName"));
+            buffer.append("\r\n");
+            count++;
+        }
+        return buffer.toString();
+    }
+}

+ 17 - 0
src/main/java/org/jebot/util/QueryType.java

@@ -0,0 +1,17 @@
+package org.jebot.util;
+
+public enum QueryType {
+    ALIPAY("aliPay"),
+    WECHAT("wxPay"),
+    BANK("bank");
+
+    private final String value;
+
+    QueryType(String value) {
+        this.value = value;
+    }
+
+    public String getValue() {
+        return value;
+    }
+}

+ 12 - 0
src/main/java/org/jebot/util/SnowflakeId.java

@@ -0,0 +1,12 @@
+package org.jebot.util;
+
+import cn.hutool.core.lang.Snowflake;
+
+public class SnowflakeId {
+
+   private static Snowflake snowflake = new Snowflake();
+
+   public static long getId() {
+       return snowflake.nextId();
+   }
+}

+ 78 - 0
src/main/resources/application.yml

@@ -0,0 +1,78 @@
+server:
+  port: 8087
+# 机器人配置
+telegram:
+  bot:
+
+    #小易
+    token: "8057879283:AAF5sta5RKgI_HvFqw86-mraHg7fD6p0UQ4"
+    
+# 配置数据源信息
+spring:
+  datasource:
+    jebot:
+      # 数据源的相关配置
+      type: com.zaxxer.hikari.HikariDataSource          # 数据源类型:HikariCP
+      driver-class-name: com.mysql.cj.jdbc.Driver         # mysql驱动
+
+      #小易
+      jdbc-url: jdbc:mysql://rm-3nsku90lhvto0h705.mysql.rds.aliyuncs.com/jebot?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
+      username: bayue
+      password: bvuzCeQMDVb^sy6W
+
+      hikari:
+        connection-timeout: 30000        # 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException, 默认:30秒
+        minimum-idle: 5                  # 最小连接数
+        maximum-pool-size: 20            # 最大连接数
+        auto-commit: true                # 事务自动提交
+        idle-timeout: 600000             # 连接超时的最大时长(毫秒),超时则被释放(retired),默认:10分钟
+        pool-name: DateSourceHikariCP     # 连接池名字
+        max-lifetime: 1800000             # 连接的生命时长(毫秒),超时而且没被使用则被释放(retired),默认:30分钟 1800000ms
+        connection-test-query: SELECT 1  # 连接测试语句
+    xxpay:
+      # 数据源的相关配置
+      type: com.zaxxer.hikari.HikariDataSource          # 数据源类型:HikariCP
+      driver-class-name: com.mysql.cj.jdbc.Driver         # mysql驱动
+
+      #小易
+      jdbc-url: jdbc:mysql://rm-3nsku90lhvto0h705.mysql.rds.aliyuncs.com/xiaoyixxpay?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
+      username: bayue
+      password: bvuzCeQMDVb^sy6W
+
+
+      hikari:
+        connection-timeout: 30000        # 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException, 默认:30秒
+        minimum-idle: 5                  # 最小连接数
+        maximum-pool-size: 20            # 最大连接数
+        auto-commit: true                # 事务自动提交
+        idle-timeout: 600000             # 连接超时的最大时长(毫秒),超时则被释放(retired),默认:10分钟
+        pool-name: DateSourceHikariCP     # 连接池名字
+        max-lifetime: 1800000             # 连接的生命时长(毫秒),超时而且没被使用则被释放(retired),默认:30分钟 1800000ms
+        connection-test-query: SELECT 1  # 连接测试语句
+  jpa:
+    show-sql: true
+    properties:
+      hibernate:
+        format_sql: true
+        use_sql_comments: true
+        dialect: org.hibernate.dialect.MySQL8Dialect
+        # 仅对机器人数据库有效
+        hbm2ddl:
+          auto: update
+logging:
+  file:
+    path: logs
+  level:
+    root: info
+    org:
+      springframework:
+        web:
+          servlet:
+            mvc:
+              method: info
+      hibernate:
+        SQL: debug
+        type: trace
+      aop: info
+      jdbc: debug
+      jpa: debug

+ 69 - 0
src/main/resources/logback-spring.xml

@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="false" debug="false">
+
+    <!-- 日志存放路径, 读取application.yml 需要使用springProperty获取 -->
+    <springProperty scope="context" name="currentLoggerFilePath" source="logging.file.path"/>
+
+    <!-- 主日志级别配置  -->
+    <springProperty scope="context" name="currentRootLevel" source="logging.level.root"/>
+
+    <!-- 项目配置, 如修改包名,请搜索并全部替换掉  -->
+    <springProperty scope="context" name="currentProjectLevel" source="logging.level.org.jebot"/>
+
+    <!-- 日志文件名称  logback属性 -->
+    <property name="currentLoggerFileName" value="jebot" />
+    <!-- 日志格式, 参考:https://logback.qos.ch/manual/layouts.html -->
+    <property name="currentLoggerPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] [%logger{15}] - %msg%n" />
+
+    <!-- appender: 控制台日志 -->
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder charset="UTF-8" >
+            <pattern>${currentLoggerPattern}</pattern>
+        </encoder>
+    </appender>
+
+    <!-- appender:主日志文件 -->
+    <appender name="ALL_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 日志文件路径及文件名 -->
+        <file>${currentLoggerFilePath}/${currentLoggerFileName}.all.log</file>
+        <!-- 内容编码及格式 -->
+        <encoder charset="UTF-8" ><pattern>${currentLoggerPattern}</pattern></encoder>
+        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 每天日志归档路径以及格式 -->
+            <fileNamePattern>${currentLoggerFilePath}/${currentLoggerFileName}.all.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <maxHistory>10</maxHistory> <!--日志文件保留天数-->
+        </rollingPolicy>
+    </appender>
+
+    <!-- appender:错误信息日志文件 -->
+    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 日志文件路径及文件名 -->
+        <file>${currentLoggerFilePath}/${currentLoggerFileName}.error.log</file>
+        <!-- 内容编码及格式 -->
+        <encoder charset="UTF-8" ><pattern>${currentLoggerPattern}</pattern></encoder>
+        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 每天日志归档路径以及格式 -->
+            <fileNamePattern>${currentLoggerFilePath}/${currentLoggerFileName}.error.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <maxHistory>20</maxHistory> <!--日志文件保留天数-->
+        </rollingPolicy>
+        <!-- 此日志文件只记录ERROR级别的 -->
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>ERROR</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <!-- 主日志级别配置 -->
+    <root level="${currentRootLevel}">
+        <appender-ref ref="STDOUT" />
+        <appender-ref ref="ALL_FILE" />
+        <appender-ref ref="ERROR_FILE" />
+    </root>
+
+    <!-- 项目日志级别配置 -->
+    <logger name="org.jebot" level="${currentProjectLevel}"/>
+
+</configuration>

BIN
src/main/resources/msyh.ttf