Просмотр исходного кода

refactor: 剥离 mjava-guangming / mjava-shunfeng 子模块

mjava-ai 收敛为基础 Java 服务程序,mjava 作为基座,
保留 mjava-mcli/pro/com。光明/顺丰已在 cur/mjava-guangming/
独立仓维护,本仓副本不再保留。

- 根 pom.xml 移除两个 module 声明
- 删除 mjava-guangming/ 和 mjava-shunfeng/ 整目录(23 文件)
- README.md 子项目速览与目录树同步移除两行
malk 1 неделя назад
Родитель
Сommit
686d608a18
23 измененных файлов с 0 добавлено и 1515 удалено
  1. 0 51
      mjava-guangming/pom.xml
  2. 0 23
      mjava-guangming/src/main/java/com/malk/guangming/Boot.java
  3. 0 32
      mjava-guangming/src/main/java/com/malk/guangming/config/GuangmingConfig.java
  4. 0 77
      mjava-guangming/src/main/java/com/malk/guangming/controller/MailSsoController.java
  5. 0 62
      mjava-guangming/src/main/java/com/malk/guangming/service/DingTalkAuthService.java
  6. 0 27
      mjava-guangming/src/main/java/com/malk/guangming/util/RSACrypt.java
  7. 0 39
      mjava-guangming/src/main/resources/application-prod.yml.example
  8. 0 138
      mjava-guangming/src/main/resources/static/sso.html
  9. 0 51
      mjava-shunfeng/pom.xml
  10. 0 30
      mjava-shunfeng/src/main/java/com/malk/shunfeng/Boot.java
  11. 0 93
      mjava-shunfeng/src/main/java/com/malk/shunfeng/controller/DdCallbackController.java
  12. 0 95
      mjava-shunfeng/src/main/java/com/malk/shunfeng/controller/MeetingController.java
  13. 0 76
      mjava-shunfeng/src/main/java/com/malk/shunfeng/server/txmeeting/TxMeetingConf.java
  14. 0 38
      mjava-shunfeng/src/main/java/com/malk/shunfeng/server/txmeeting/TxMeetingR.java
  15. 0 28
      mjava-shunfeng/src/main/java/com/malk/shunfeng/server/zoom/ZoomConf.java
  16. 0 45
      mjava-shunfeng/src/main/java/com/malk/shunfeng/server/zoom/ZoomR.java
  17. 0 49
      mjava-shunfeng/src/main/java/com/malk/shunfeng/service/MeetingService.java
  18. 0 44
      mjava-shunfeng/src/main/java/com/malk/shunfeng/service/TxMeetingClient.java
  19. 0 40
      mjava-shunfeng/src/main/java/com/malk/shunfeng/service/ZoomClient.java
  20. 0 177
      mjava-shunfeng/src/main/java/com/malk/shunfeng/service/impl/MeetingServiceImpl.java
  21. 0 109
      mjava-shunfeng/src/main/java/com/malk/shunfeng/service/impl/TxMeetingClientImpl.java
  22. 0 137
      mjava-shunfeng/src/main/java/com/malk/shunfeng/service/impl/ZoomClientImpl.java
  23. 0 54
      mjava-shunfeng/src/main/resources/application-prod.yml.example

+ 0 - 51
mjava-guangming/pom.xml

@@ -1,51 +0,0 @@
-<?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">
-    <parent>
-        <artifactId>java-mcli</artifactId>
-        <groupId>com.malk</groupId>
-        <version>1.0-SNAPSHOT</version>
-    </parent>
-    <modelVersion>4.0.0</modelVersion>
-
-    <artifactId>mjava-guangming</artifactId>
-    <description>光明集成:钉钉SSO免密登录德惠邮箱</description>
-
-    <properties>
-        <maven.compiler.source>8</maven.compiler.source>
-        <maven.compiler.target>8</maven.compiler.target>
-    </properties>
-
-    <dependencies>
-        <!-- 核心模块 -->
-        <dependency>
-            <groupId>com.malk</groupId>
-            <artifactId>mjava</artifactId>
-            <version>${mjava.version}</version>
-        </dependency>
-    </dependencies>
-
-    <build>
-        <plugins>
-            <plugin>
-                <groupId>org.springframework.boot</groupId>
-                <artifactId>spring-boot-maven-plugin</artifactId>
-                <version>2.1.1.RELEASE</version>
-                <configuration>
-                    <includeSystemScope>true</includeSystemScope>
-                    <fork>false</fork>
-                    <jvmArguments>-Dfile.encoding=UTF-8</jvmArguments>
-                </configuration>
-                <executions>
-                    <execution>
-                        <goals>
-                            <goal>repackage</goal>
-                        </goals>
-                    </execution>
-                </executions>
-            </plugin>
-        </plugins>
-        <finalName>${project.artifactId}</finalName>
-    </build>
-</project>

+ 0 - 23
mjava-guangming/src/main/java/com/malk/guangming/Boot.java

@@ -1,23 +0,0 @@
-package com.malk.guangming;
-
-import com.querydsl.jpa.impl.JPAQueryFactory;
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.context.annotation.Bean;
-import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
-
-import javax.persistence.EntityManager;
-
-@EnableJpaAuditing
-@SpringBootApplication(scanBasePackages = {"com.malk"})
-public class Boot {
-
-    public static void main(String... args) {
-        SpringApplication.run(Boot.class, args);
-    }
-
-    @Bean
-    public JPAQueryFactory jpaQueryFactory(EntityManager entityManager) {
-        return new JPAQueryFactory(entityManager);
-    }
-}

+ 0 - 32
mjava-guangming/src/main/java/com/malk/guangming/config/GuangmingConfig.java

@@ -1,32 +0,0 @@
-package com.malk.guangming.config;
-
-import lombok.Data;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.stereotype.Component;
-
-@Data
-@Component
-@ConfigurationProperties(prefix = "guangming")
-public class GuangmingConfig {
-
-    /** 钉钉配置 */
-    private Dingtalk dingtalk = new Dingtalk();
-
-    /** 邮箱SSO配置 */
-    private MailSso mailSso = new MailSso();
-
-    @Data
-    public static class Dingtalk {
-        private String appKey;
-        private String appSecret;
-        private String corpId;
-    }
-
-    @Data
-    public static class MailSso {
-        /** 德惠邮箱SSO地址 */
-        private String ssoUrl;
-        /** RSA公钥 */
-        private String publicKey;
-    }
-}

+ 0 - 77
mjava-guangming/src/main/java/com/malk/guangming/controller/MailSsoController.java

@@ -1,77 +0,0 @@
-package com.malk.guangming.controller;
-
-import com.malk.guangming.config.GuangmingConfig;
-import com.malk.guangming.service.DingTalkAuthService;
-import com.malk.guangming.util.RSACrypt;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-
-@Slf4j
-@RestController
-public class MailSsoController {
-
-    @Autowired
-    private DingTalkAuthService dingTalkAuthService;
-
-    @Autowired
-    private GuangmingConfig config;
-
-    /**
-     * 钉钉SSO跳转德惠邮箱
-     * 前端通过JSAPI获取authCode后调用此接口,后端完成认证和加密后302跳转到邮箱
-     *
-     * @param authCode 钉钉免登授权码
-     */
-    @GetMapping("/sso/mail")
-    public void ssoMail(@RequestParam String authCode, HttpServletResponse response) throws IOException {
-        try {
-            // 1. 通过authCode获取用户userId
-            String userId = dingTalkAuthService.getUserId(authCode);
-            log.info("[SSO邮箱] userId: {}", userId);
-
-            // 2. 获取用户邮箱
-//            String email = dingTalkAuthService.getUserEmail(userId);
-//            String email = "leixiaochuan@brightfood.com";
-            String email = "linke@brightfood.com";
-
-            log.info("[SSO邮箱] email: {}", email);
-
-            // 3. 截取邮箱@前面的部分作为uid
-            String uid = email.contains("@") ? email.substring(0, email.indexOf("@")) : email;
-
-            // 4. RSA加密 + URL安全处理
-            String encryptedUid = RSACrypt.encrypt(uid, config.getMailSso().getPublicKey());
-            String safeUid = RSACrypt.toUrlSafe(encryptedUid);
-
-            // 5. 拼接SSO URL并跳转
-            String ssoUrl = config.getMailSso().getSsoUrl() + "&uid=" + safeUid;
-            log.info("[SSO邮箱] 跳转: {}", ssoUrl);
-            response.sendRedirect(ssoUrl);
-
-        } catch (Exception e) {
-            log.error("[SSO邮箱] 登录失败", e);
-            response.setContentType("text/html;charset=utf-8");
-            String errMsg = e.getMessage() != null ? e.getMessage().replace("'", "\\'") : "未知错误";
-            response.getWriter().write(
-                "<!DOCTYPE html><html><head><meta charset='UTF-8'><meta name='viewport' content='width=device-width,initial-scale=1.0'>"
-                + "<script src='https://g.alicdn.com/dingding/dingtalk-jsapi/3.0.12/dingtalk.open.js'></script></head>"
-                + "<body><script>"
-                + "if(/DingTalk/i.test(navigator.userAgent)){"
-                +   "dd.device.notification.alert({title:'登录失败',message:'" + errMsg + "',buttonName:'返回',"
-                +     "onSuccess:function(){dd.biz.navigation.close();}});"
-                + "}else{"
-                +   "document.body.innerHTML='<div style=\"display:flex;justify-content:center;align-items:center;height:100vh;font-family:sans-serif\">"
-                +     "<div style=\"text-align:center\"><div style=\"width:48px;height:48px;margin:0 auto 16px;border-radius:50%;background:#fee;display:flex;justify-content:center;align-items:center;font-size:24px\">&#10060;</div>"
-                +     "<p style=\"font-size:15px;color:#666\">" + errMsg + "</p></div></div>';"
-                + "}"
-                + "</script></body></html>"
-            );
-        }
-    }
-}

+ 0 - 62
mjava-guangming/src/main/java/com/malk/guangming/service/DingTalkAuthService.java

@@ -1,62 +0,0 @@
-package com.malk.guangming.service;
-
-import com.malk.guangming.config.GuangmingConfig;
-import com.malk.service.dingtalk.DDClient;
-import com.malk.service.dingtalk.DDClient_Contacts;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-import java.util.Map;
-
-@Slf4j
-@Service
-public class DingTalkAuthService {
-
-    @Autowired
-    private GuangmingConfig config;
-
-    @Autowired
-    private DDClient ddClient;
-
-    @Autowired
-    private DDClient_Contacts ddClientContacts;
-
-    /**
-     * 通过免登授权码获取用户userId
-     *
-     * @param authCode 前端JSAPI获取的免登授权码
-     * @return 用户userId
-     */
-    public String getUserId(String authCode) {
-        String accessToken = ddClient.getAccessToken(
-                config.getDingtalk().getAppKey(),
-                config.getDingtalk().getAppSecret());
-        Map userInfo = ddClient.getUserInfoByCode(accessToken, authCode);
-        return userInfo.get("userid").toString();
-    }
-
-    /**
-     * 根据userId获取用户邮箱
-     *
-     * @param userId 钉钉用户ID
-     * @return 用户企业邮箱
-     */
-    public String getUserEmail(String userId) {
-        String accessToken = ddClient.getAccessToken(
-                config.getDingtalk().getAppKey(),
-                config.getDingtalk().getAppSecret());
-        Map userInfo = ddClientContacts.getUserInfoById(accessToken, userId);
-        // 优先取企业邮箱(org_email),其次取个人邮箱(email)
-        Object orgEmail = userInfo.get("org_email");
-        if (orgEmail != null && !orgEmail.toString().isEmpty()) {
-            return orgEmail.toString();
-        }
-        Object email = userInfo.get("email");
-        if (email != null && !email.toString().isEmpty()) {
-            return email.toString();
-        }
-        log.error("[钉钉] 用户 {} 未配置邮箱, 返回字段: {}", userId, userInfo.keySet());
-        throw new RuntimeException("该用户未配置企业邮箱");
-    }
-}

+ 0 - 27
mjava-guangming/src/main/java/com/malk/guangming/util/RSACrypt.java

@@ -1,27 +0,0 @@
-package com.malk.guangming.util;
-
-/**
- * RSA加密工具类 - 与德惠邮箱SSO文档保持一致
- *
- * @deprecated 已下沉到基座 {@link com.malk.util.crypto.RSACrypt},请改 import 到新位置。
- * 本类保留仅为兼容既有调用方,内部方法直接委托新类。
- */
-@Deprecated
-public class RSACrypt {
-
-    /**
-     * @deprecated 使用 {@link com.malk.util.crypto.RSACrypt#encrypt(String, String)}
-     */
-    @Deprecated
-    public static String encrypt(String str, String publicKey) throws Exception {
-        return com.malk.util.crypto.RSACrypt.encrypt(str, publicKey);
-    }
-
-    /**
-     * @deprecated 使用 {@link com.malk.util.crypto.RSACrypt#toUrlSafe(String)}
-     */
-    @Deprecated
-    public static String toUrlSafe(String base64String) {
-        return com.malk.util.crypto.RSACrypt.toUrlSafe(base64String);
-    }
-}

+ 0 - 39
mjava-guangming/src/main/resources/application-prod.yml.example

@@ -1,39 +0,0 @@
-# mjava-guangming 生产配置模板
-# 使用方式:复制为 application-prod.yml 并填入真实值;真实文件已被 .gitignore 排除
-
-server:
-  port: 9003
-  servlet:
-    context-path: /api/gm
-
-spel:
-  scheduling: false
-  multiSource: false
-
-spring:
-  datasource:
-    hikari:
-      connection-init-sql: SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci
-    driver-class-name: com.mysql.cj.jdbc.Driver
-    username: ${DB_USERNAME}
-    password: ${DB_PASSWORD}
-    url: ${DB_URL}
-  jpa:
-    database: MYSQL
-    database-platform: org.hibernate.dialect.MySQL57Dialect
-
-# 光明配置
-guangming:
-  dingtalk:
-    appKey: ${DINGTALK_APP_KEY}
-    appSecret: ${DINGTALK_APP_SECRET}
-    corpId: ${DINGTALK_CORP_ID}
-  mailSso:
-    ssoUrl: ${MAIL_SSO_URL}
-    publicKey: ${MAIL_SSO_PUBLIC_KEY}
-
-# integration(IAM 集成平台)
-integration:
-  baseUrl: ${INTP_BASE_URL}        # 不含 /iam 路径前缀
-  clientId: ${INTP_CLIENT_ID}
-  clientSecret: ${INTP_CLIENT_SECRET}

+ 0 - 138
mjava-guangming/src/main/resources/static/sso.html

@@ -1,138 +0,0 @@
-<!DOCTYPE html>
-<html lang="zh-CN">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>邮箱登录</title>
-    <style>
-        * { margin: 0; padding: 0; box-sizing: border-box; }
-        body {
-            display: flex;
-            justify-content: center;
-            align-items: center;
-            height: 100vh;
-            background: #f5f5f5;
-            font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", sans-serif;
-        }
-        .container {
-            text-align: center;
-            padding: 40px;
-        }
-        .tip-icon {
-            width: 48px;
-            height: 48px;
-            margin: 0 auto 16px;
-            border-radius: 50%;
-            display: flex;
-            justify-content: center;
-            align-items: center;
-            font-size: 24px;
-        }
-        .tip-icon.warn { background: #fff3e0; }
-        .spinner {
-            width: 36px;
-            height: 36px;
-            margin: 0 auto 16px;
-            border: 3px solid #e0e0e0;
-            border-top-color: #1890ff;
-            border-radius: 50%;
-            animation: spin 0.8s linear infinite;
-        }
-        @keyframes spin {
-            to { transform: rotate(360deg); }
-        }
-        .tip-text {
-            font-size: 15px;
-            color: #666;
-            line-height: 1.6;
-        }
-    </style>
-</head>
-<body>
-    <!-- 页面内loading(兜底) -->
-    <div class="container" id="loading" style="display:none;">
-        <div class="spinner"></div>
-        <p class="tip-text" id="loadingText">正在登录邮箱...</p>
-    </div>
-
-    <!-- 非钉钉环境提示 -->
-    <div class="container" id="tipContainer" style="display:none;">
-        <div class="tip-icon warn">&#9888;</div>
-        <p class="tip-text">请在钉钉内打开此页面<br>浏览器不支持自动登录</p>
-    </div>
-
-    <script src="https://g.alicdn.com/dingding/dingtalk-jsapi/3.0.12/dingtalk.open.js"></script>
-    <script>
-        var CORP_ID = 'dingc54682076c69f0f2bc961a6cb783455b';
-
-        function isDingTalk() {
-            return /DingTalk/i.test(navigator.userAgent);
-        }
-
-        // 钉钉原生loading
-        function showLoading(text) {
-            dd.device.notification.showPreloader({ text: text || '正在跳转邮箱...' });
-        }
-
-        function hideLoading() {
-            dd.device.notification.hidePreloader();
-        }
-
-        // 钉钉原生toast提示
-        function showToast(text, icon) {
-            dd.device.notification.toast({ icon: icon || '', text: text, duration: 3 });
-        }
-
-        // 钉钉原生alert弹窗(用于错误提示)
-        function showAlert(title, message) {
-            hideLoading();
-            dd.device.notification.alert({
-                title: title || '提示',
-                message: message,
-                buttonName: '我知道了'
-            });
-        }
-
-        function updateLoading(text) {
-            document.getElementById('loadingText').textContent = text;
-        }
-
-        if (!isDingTalk()) {
-            document.getElementById('tipContainer').style.display = 'block';
-        } else {
-            // 页面内loading + 钉钉原生loading
-            document.getElementById('loading').style.display = 'block';
-            showLoading('正在登录邮箱...');
-
-            try {
-                var result = dd.runtime.permission.requestAuthCode({
-                    corpId: CORP_ID,
-                    onSuccess: function(res) {
-                        updateLoading('正在跳转邮箱系统...');
-                        showLoading('正在跳转邮箱系统...');
-                        window.location.href = '/api/gm/sso/mail?authCode=' + res.code;
-                    },
-                    onFail: function(err) {
-                        showAlert('授权失败', err.errorMessage || JSON.stringify(err));
-                    }
-                });
-                // 新版JSAPI返回Promise
-                if (result && typeof result.then === 'function') {
-                    result.then(function(res) {
-                        updateLoading('正在跳转邮箱系统...');
-                        showLoading('正在跳转邮箱系统...');
-                        var code = res.code || (res.result && res.result.code);
-                        if (code) {
-                            window.location.href = '/api/gm/sso/mail?authCode=' + code;
-                        }
-                    }).catch(function(err) {
-                        showAlert('授权失败', err.errorMessage || JSON.stringify(err));
-                    });
-                }
-            } catch(e) {
-                showAlert('系统异常', e.message);
-            }
-        }
-    </script>
-</body>
-</html>

+ 0 - 51
mjava-shunfeng/pom.xml

@@ -1,51 +0,0 @@
-<?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">
-    <parent>
-        <artifactId>java-mcli</artifactId>
-        <groupId>com.malk</groupId>
-        <version>1.0-SNAPSHOT</version>
-    </parent>
-    <modelVersion>4.0.0</modelVersion>
-
-    <artifactId>mjava-shunfeng</artifactId>
-    <description>顺丰集成:腾讯会议 / Zoom / 钉钉日程联动</description>
-
-    <properties>
-        <maven.compiler.source>8</maven.compiler.source>
-        <maven.compiler.target>8</maven.compiler.target>
-    </properties>
-
-    <dependencies>
-        <!-- 核心模块 -->
-        <dependency>
-            <groupId>com.malk</groupId>
-            <artifactId>mjava</artifactId>
-            <version>${mjava.version}</version>
-        </dependency>
-    </dependencies>
-
-    <build>
-        <plugins>
-            <plugin>
-                <groupId>org.springframework.boot</groupId>
-                <artifactId>spring-boot-maven-plugin</artifactId>
-                <version>2.1.1.RELEASE</version>
-                <configuration>
-                    <includeSystemScope>true</includeSystemScope>
-                    <fork>false</fork>
-                    <jvmArguments>-Dfile.encoding=UTF-8</jvmArguments>
-                </configuration>
-                <executions>
-                    <execution>
-                        <goals>
-                            <goal>repackage</goal>
-                        </goals>
-                    </execution>
-                </executions>
-            </plugin>
-        </plugins>
-        <finalName>${project.artifactId}</finalName>
-    </build>
-</project>

+ 0 - 30
mjava-shunfeng/src/main/java/com/malk/shunfeng/Boot.java

@@ -1,30 +0,0 @@
-package com.malk.shunfeng;
-
-import com.querydsl.jpa.impl.JPAQueryFactory;
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.context.annotation.Bean;
-import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
-
-import javax.persistence.EntityManager;
-
-/**
- * 顺丰集成子模块启动类
- * - 扫描公共模块 com.malk(含 mjava 中的 DDClient / DDClient_Schedule / DingCallbackCrypto 等)
- */
-@EnableJpaAuditing
-@SpringBootApplication(scanBasePackages = {"com.malk"})
-public class Boot {
-
-    public static void main(String... args) {
-        SpringApplication.run(Boot.class, args);
-    }
-
-    /**
-     * 让Spring管理JPAQueryFactory
-     */
-    @Bean
-    public JPAQueryFactory jpaQueryFactory(EntityManager entityManager) {
-        return new JPAQueryFactory(entityManager);
-    }
-}

+ 0 - 93
mjava-shunfeng/src/main/java/com/malk/shunfeng/controller/DdCallbackController.java

@@ -1,93 +0,0 @@
-package com.malk.shunfeng.controller;
-
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONObject;
-import com.malk.server.dingtalk.DDConf;
-import com.malk.server.dingtalk.crypto.DingCallbackCrypto;
-import lombok.SneakyThrows;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.Map;
-
-/**
- * 钉钉日程回调 Controller
- * -
- * 接收钉钉 calendar 事件回调并解析(复用 DingCallbackCrypto + DDConf)
- * 事件类型: calendar_event_created / calendar_event_updated / calendar_event_deleted
- */
-@Slf4j
-@RestController
-@RequestMapping("/sf/dd/callback")
-public class DdCallbackController {
-
-    /** 钉钉日程事件类型常量 */
-    private static final String CALENDAR_EVENT_CREATED = "calendar_event_created";
-    private static final String CALENDAR_EVENT_UPDATED = "calendar_event_updated";
-    private static final String CALENDAR_EVENT_DELETED = "calendar_event_deleted";
-
-    @Autowired
-    private DDConf ddConf;
-
-    /**
-     * 接收钉钉日程回调
-     * -
-     * 复用 DingCallbackCrypto 解密请求体,与 mjava 中 DDCallbackController 模式一致
-     * 返回加密响应(钉钉要求 3s 内返回,处理耗时任务请异步)
-     */
-    @SneakyThrows
-    @PostMapping
-    public Map<String, String> callback(
-            @RequestParam(value = "signature", required = false) String signature,
-            @RequestParam(value = "timestamp", required = false) String timestamp,
-            @RequestParam(value = "nonce", required = false) String nonce,
-            @RequestBody(required = false) JSONObject json) {
-
-        // ppExt: corpId 使用 appKey(企业自建应用事件订阅场景)
-        DingCallbackCrypto callbackCrypto = new DingCallbackCrypto(
-                ddConf.getToken(), ddConf.getAesKey(), ddConf.getAppKey()
-        );
-
-        // 解密请求体
-        String decryptMsg = callbackCrypto.getDecryptMsg(
-                signature, timestamp, nonce, json.getString("encrypt")
-        );
-        JSONObject eventJson = JSON.parseObject(decryptMsg);
-        log.info("[SF] 钉钉日程回调, eventJson={}", eventJson);
-
-        // 构建加密成功响应(钉钉要求返回加密格式)
-        Map<String, String> success = callbackCrypto.getEncryptedMap(
-                DDConf.CALLBACK_RESPONSE,
-                System.currentTimeMillis(),
-                DingCallbackCrypto.Utils.getRandomStr(8)
-        );
-
-        String eventType = eventJson.getString("EventType");
-
-        // 验证注册地址
-        if (DDConf.CALLBACK_CHECK.equals(eventType)) {
-            log.info("[SF][DD] 验证注册回调");
-            return success;
-        }
-
-        // fixme: 日程事件处理逻辑在此扩展,当前仅记录日志
-        if (CALENDAR_EVENT_CREATED.equals(eventType)) {
-            log.info("[SF][DD] 日程创建事件, {}", eventJson);
-            return success;
-        }
-
-        if (CALENDAR_EVENT_UPDATED.equals(eventType)) {
-            log.info("[SF][DD] 日程更新事件, {}", eventJson);
-            return success;
-        }
-
-        if (CALENDAR_EVENT_DELETED.equals(eventType)) {
-            log.info("[SF][DD] 日程删除事件, {}", eventJson);
-            return success;
-        }
-
-        log.info("[SF][DD] 未处理的回调事件类型: {}, body={}", eventType, eventJson);
-        return success;
-    }
-}

+ 0 - 95
mjava-shunfeng/src/main/java/com/malk/shunfeng/controller/MeetingController.java

@@ -1,95 +0,0 @@
-package com.malk.shunfeng.controller;
-
-import com.malk.server.common.McR;
-import com.malk.shunfeng.service.MeetingService;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.Map;
-
-/**
- * 会议统一 Controller(腾讯会议 / Zoom)
- * -
- * context-path: /api/sf  → 接口前缀: /api/sf/sf/meeting
- */
-@Slf4j
-@RestController
-@RequestMapping("/sf/meeting")
-public class MeetingController {
-
-    @Autowired
-    private MeetingService meetingService;
-
-    /**
-     * 创建会议
-     * POST /sf/meeting/create?platform=TX&userId=xxx&dingtalkUserId=xxx
-     *
-     * @param platform       TX 或 ZOOM
-     * @param userId         腾讯会议用户 ID(ZOOM 可不传)
-     * @param dingtalkUserId 钉钉用户 ID(用于同步日程,可选)
-     * @param body           会议创建参数
-     */
-    @PostMapping("/create")
-    public McR create(
-            @RequestParam String platform,
-            @RequestParam(required = false) String userId,
-            @RequestParam(required = false) String dingtalkUserId,
-            @RequestBody Map body) {
-        log.info("[SF] 创建会议, platform={}, userId={}, dingtalkUserId={}", platform, userId, dingtalkUserId);
-        return meetingService.createMeeting(platform, userId, dingtalkUserId, body);
-    }
-
-    /**
-     * 更新会议
-     * PUT /sf/meeting/{meetingId}?platform=TX&userId=xxx
-     *
-     * @param meetingId 会议 ID
-     * @param platform  TX 或 ZOOM
-     * @param userId    腾讯会议用户 ID(ZOOM 可不传)
-     * @param body      修改内容
-     */
-    @PutMapping("/{meetingId}")
-    public McR update(
-            @PathVariable String meetingId,
-            @RequestParam String platform,
-            @RequestParam(required = false) String userId,
-            @RequestBody Map body) {
-        log.info("[SF] 更新会议, platform={}, meetingId={}", platform, meetingId);
-        return meetingService.updateMeeting(platform, meetingId, userId, body);
-    }
-
-    /**
-     * 取消/删除会议
-     * DELETE /sf/meeting/{meetingId}?platform=TX&userId=xxx
-     *
-     * @param meetingId 会议 ID
-     * @param platform  TX 或 ZOOM
-     * @param userId    腾讯会议用户 ID(ZOOM 可不传)
-     */
-    @DeleteMapping("/{meetingId}")
-    public McR cancel(
-            @PathVariable String meetingId,
-            @RequestParam String platform,
-            @RequestParam(required = false) String userId) {
-        log.info("[SF] 取消会议, platform={}, meetingId={}", platform, meetingId);
-        return meetingService.cancelMeeting(platform, meetingId, userId);
-    }
-
-    /**
-     * 查询会议详情
-     * GET /sf/meeting/{meetingId}?platform=TX&userId=xxx
-     *
-     * @param meetingId 会议 ID
-     * @param platform  TX 或 ZOOM
-     * @param userId    腾讯会议用户 ID(ZOOM 可不传)
-     */
-    @GetMapping("/{meetingId}")
-    public McR get(
-            @PathVariable String meetingId,
-            @RequestParam String platform,
-            @RequestParam(required = false) String userId) {
-        log.info("[SF] 查询会议, platform={}, meetingId={}", platform, meetingId);
-        return meetingService.getMeeting(platform, meetingId, userId);
-    }
-}

+ 0 - 76
mjava-shunfeng/src/main/java/com/malk/shunfeng/server/txmeeting/TxMeetingConf.java

@@ -1,76 +0,0 @@
-package com.malk.shunfeng.server.txmeeting;
-
-import com.malk.utils.UtilMap;
-import lombok.Data;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.stereotype.Component;
-
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-import java.nio.charset.StandardCharsets;
-import java.util.Base64;
-import java.util.Map;
-import java.util.UUID;
-
-/**
- * 腾讯会议配置 & 签名工具
- * -
- * 签名规范: signContent = "{METHOD}\n{path}\n{timestamp}\n{nonce}\n{body}"
- * signature = Base64( HMAC-SHA256(secretKey, signContent) )
- */
-@Data
-@Component
-@ConfigurationProperties(prefix = "txmeeting")
-public class TxMeetingConf {
-
-    private String secretId;
-
-    private String secretKey;
-
-    /** SDK ID(企业 ID) */
-    private String sdkId;
-
-    private String apiHost;
-
-    /**
-     * 生成 HMAC-SHA256 签名
-     *
-     * @param method    HTTP 方法,如 POST / GET / PUT / DELETE
-     * @param path      URL 路径,如 /v1/meetings(不含 host)
-     * @param timestamp 秒级时间戳
-     * @param nonce     随机字符串
-     * @param body      请求体 JSON 字符串,无请求体传空字符串
-     */
-    public String buildSignature(String method, String path, long timestamp, String nonce, String body) {
-        try {
-            // fixme: body 为 null 或 GET 请求时传空字符串
-            String content = body == null ? "" : body;
-            String signStr = method + "\n" + path + "\n" + timestamp + "\n" + nonce + "\n" + content;
-            Mac mac = Mac.getInstance("HmacSHA256");
-            SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
-            mac.init(keySpec);
-            byte[] rawHmac = mac.doFinal(signStr.getBytes(StandardCharsets.UTF_8));
-            return Base64.getEncoder().encodeToString(rawHmac);
-        } catch (Exception e) {
-            throw new RuntimeException("腾讯会议签名失败", e);
-        }
-    }
-
-    /**
-     * 构建腾讯会议请求 Header(每次调用动态生成签名)
-     *
-     * @param method HTTP 方法
-     * @param path   URL 路径(不含 host)
-     * @param body   请求体 JSON 字符串
-     */
-    public Map initHeaders(String method, String path, String body) {
-        long timestamp = System.currentTimeMillis() / 1000;
-        // fixme: nonce 要求随机字符串,使用 UUID 去横线保证唯一性
-        String nonce = UUID.randomUUID().toString().replace("-", "");
-        String signature = buildSignature(method, path, timestamp, nonce, body);
-        return UtilMap.map(
-                "X-TC-Key, X-TC-Timestamp, X-TC-Nonce, X-TC-Signature, X-TC-Registered",
-                secretId, String.valueOf(timestamp), nonce, signature, "1"
-        );
-    }
-}

+ 0 - 38
mjava-shunfeng/src/main/java/com/malk/shunfeng/server/txmeeting/TxMeetingR.java

@@ -1,38 +0,0 @@
-package com.malk.shunfeng.server.txmeeting;
-
-import com.alibaba.fastjson.annotation.JSONField;
-import com.malk.server.common.McException;
-import com.malk.server.common.VenR;
-import lombok.Data;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * 腾讯会议 API 响应对象
- * -
- * 成功:返回 meeting_info_list
- * 失败:返回 error_info.error_code != 0
- */
-@Data
-public class TxMeetingR extends VenR {
-
-    @JSONField(name = "meeting_info_list")
-    private List<Map<String, Object>> meetingInfoList;
-
-    @JSONField(name = "error_info")
-    private Map<String, Object> errorInfo;
-
-    /**
-     * 断言腾讯会议请求是否成功
-     * fixme: 腾讯会议 error_code=0 表示成功,非 0 或存在 error_info 表示失败
-     */
-    @Override
-    public void assertSuccess() {
-        if (errorInfo != null && !errorInfo.isEmpty()) {
-            Object code = errorInfo.get("error_code");
-            Object msg = errorInfo.get("message");
-            McException.assertException(true, String.valueOf(code), String.valueOf(msg), "txmeeting");
-        }
-    }
-}

+ 0 - 28
mjava-shunfeng/src/main/java/com/malk/shunfeng/server/zoom/ZoomConf.java

@@ -1,28 +0,0 @@
-package com.malk.shunfeng.server.zoom;
-
-import lombok.Data;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.stereotype.Component;
-
-/**
- * Zoom OAuth 2.0 Account Credentials 配置
- * -
- * Token 获取: POST {oauthUrl}?grant_type=account_credentials&account_id={accountId}
- * Basic Auth: clientId:clientSecret
- */
-@Data
-@Component
-@ConfigurationProperties(prefix = "zoom")
-public class ZoomConf {
-
-    private String accountId;
-
-    private String clientId;
-
-    private String clientSecret;
-
-    private String apiHost;
-
-    /** Zoom OAuth Token 获取地址 */
-    private String oauthUrl;
-}

+ 0 - 45
mjava-shunfeng/src/main/java/com/malk/shunfeng/server/zoom/ZoomR.java

@@ -1,45 +0,0 @@
-package com.malk.shunfeng.server.zoom;
-
-import com.malk.server.common.McException;
-import com.malk.server.common.VenR;
-import lombok.Data;
-
-/**
- * Zoom 会议 API 响应对象
- * -
- * 成功:返回 id/topic/join_url 等字段
- * 失败:返回 code/message
- */
-@Data
-public class ZoomR extends VenR {
-
-    /** 会议 ID */
-    private String id;
-
-    private String topic;
-
-    private String join_url;
-
-    private String start_url;
-
-    private String password;
-
-    private String start_time;
-
-    private Integer duration;
-
-    /** 错误码(Zoom 错误时返回) */
-    private Integer code;
-
-    /** 错误描述 */
-    private String message;
-
-    /**
-     * 断言 Zoom 请求是否成功
-     * fixme: Zoom API 错误时返回 code 字段(非 null),成功无 code 字段
-     */
-    @Override
-    public void assertSuccess() {
-        McException.assertException(code != null && code != 0, String.valueOf(code), message, "zoom");
-    }
-}

+ 0 - 49
mjava-shunfeng/src/main/java/com/malk/shunfeng/service/MeetingService.java

@@ -1,49 +0,0 @@
-package com.malk.shunfeng.service;
-
-import com.malk.server.common.McR;
-
-import java.util.Map;
-
-/**
- * 会议统一服务接口(路由 TX / ZOOM)
- */
-public interface MeetingService {
-
-    /**
-     * 创建会议
-     *
-     * @param platform       平台:TX(腾讯会议)或 ZOOM
-     * @param userId         平台用户 ID(腾讯会议操作人)
-     * @param dingtalkUserId 钉钉用户 ID(用于同步钉钉日程)
-     * @param body           创建参数
-     */
-    McR createMeeting(String platform, String userId, String dingtalkUserId, Map body);
-
-    /**
-     * 更新会议
-     *
-     * @param platform  平台
-     * @param meetingId 会议 ID
-     * @param userId    平台用户 ID
-     * @param body      修改内容
-     */
-    McR updateMeeting(String platform, String meetingId, String userId, Map body);
-
-    /**
-     * 取消/删除会议
-     *
-     * @param platform  平台
-     * @param meetingId 会议 ID
-     * @param userId    平台用户 ID(腾讯会议需要)
-     */
-    McR cancelMeeting(String platform, String meetingId, String userId);
-
-    /**
-     * 查询会议详情
-     *
-     * @param platform  平台
-     * @param meetingId 会议 ID
-     * @param userId    平台用户 ID(腾讯会议需要)
-     */
-    McR getMeeting(String platform, String meetingId, String userId);
-}

+ 0 - 44
mjava-shunfeng/src/main/java/com/malk/shunfeng/service/TxMeetingClient.java

@@ -1,44 +0,0 @@
-package com.malk.shunfeng.service;
-
-import com.malk.shunfeng.server.txmeeting.TxMeetingR;
-
-import java.util.Map;
-
-/**
- * 腾讯会议 API 客户端接口
- */
-public interface TxMeetingClient {
-
-    /**
-     * 创建会议
-     *
-     * @param userId 操作人 userId
-     * @param body   请求体(subject/start_time/end_time/type 等)
-     */
-    TxMeetingR createMeeting(String userId, Map body);
-
-    /**
-     * 修改会议
-     *
-     * @param meetingId 会议 ID
-     * @param userId    操作人 userId
-     * @param body      修改内容
-     */
-    TxMeetingR updateMeeting(String meetingId, String userId, Map body);
-
-    /**
-     * 取消会议
-     *
-     * @param meetingId 会议 ID
-     * @param userId    操作人 userId
-     */
-    TxMeetingR cancelMeeting(String meetingId, String userId);
-
-    /**
-     * 查询会议详情
-     *
-     * @param meetingId 会议 ID
-     * @param userId    操作人 userId
-     */
-    TxMeetingR getMeeting(String meetingId, String userId);
-}

+ 0 - 40
mjava-shunfeng/src/main/java/com/malk/shunfeng/service/ZoomClient.java

@@ -1,40 +0,0 @@
-package com.malk.shunfeng.service;
-
-import com.malk.shunfeng.server.zoom.ZoomR;
-
-import java.util.Map;
-
-/**
- * Zoom 会议 API 客户端接口
- */
-public interface ZoomClient {
-
-    /**
-     * 创建会议
-     *
-     * @param body 请求体(topic/start_time/duration/agenda 等)
-     */
-    ZoomR createMeeting(Map body);
-
-    /**
-     * 更新会议(PATCH,响应 204 无内容)
-     *
-     * @param meetingId Zoom 会议 ID
-     * @param body      修改内容
-     */
-    void updateMeeting(String meetingId, Map body);
-
-    /**
-     * 删除会议
-     *
-     * @param meetingId Zoom 会议 ID
-     */
-    void deleteMeeting(String meetingId);
-
-    /**
-     * 查询会议详情
-     *
-     * @param meetingId Zoom 会议 ID
-     */
-    ZoomR getMeeting(String meetingId);
-}

+ 0 - 177
mjava-shunfeng/src/main/java/com/malk/shunfeng/service/impl/MeetingServiceImpl.java

@@ -1,177 +0,0 @@
-package com.malk.shunfeng.service.impl;
-
-import com.malk.server.common.McException;
-import com.malk.server.common.McR;
-import com.malk.shunfeng.server.txmeeting.TxMeetingR;
-import com.malk.shunfeng.server.zoom.ZoomR;
-import com.malk.shunfeng.service.MeetingService;
-import com.malk.shunfeng.service.TxMeetingClient;
-import com.malk.shunfeng.service.ZoomClient;
-import com.malk.service.dingtalk.DDClient;
-import com.malk.service.dingtalk.DDClient_Schedule;
-import com.malk.utils.UtilMap;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * 会议统一服务实现
- * -
- * 路由 TX(腾讯会议) / ZOOM
- * 成功后调 DDClient_Schedule.eventsSchedule() 同步钉钉日程(失败不阻断主流程)
- */
-@Slf4j
-@Service
-public class MeetingServiceImpl implements MeetingService {
-
-    /** 平台常量 */
-    private static final String PLATFORM_TX = "TX";
-    private static final String PLATFORM_ZOOM = "ZOOM";
-
-    @Autowired
-    private TxMeetingClient txMeetingClient;
-
-    @Autowired
-    private ZoomClient zoomClient;
-
-    @Autowired
-    private DDClient ddClient;
-
-    @Autowired
-    private DDClient_Schedule ddClient_Schedule;
-
-    /**
-     * 创建会议
-     * 成功后同步钉钉日程(失败不影响主流程)
-     */
-    @Override
-    public McR createMeeting(String platform, String userId, String dingtalkUserId, Map body) {
-        assertPlatform(platform);
-        if (PLATFORM_TX.equalsIgnoreCase(platform)) {
-            TxMeetingR r = txMeetingClient.createMeeting(userId, body);
-            // ppExt: 创建成功后同步钉钉日程,失败仅记录日志
-            syncDingtalkSchedule(r, dingtalkUserId, body);
-            return McR.success(r);
-        }
-        // ZOOM
-        ZoomR r = zoomClient.createMeeting(body);
-        syncDingtalkScheduleZoom(r, dingtalkUserId, body);
-        return McR.success(r);
-    }
-
-    /**
-     * 更新会议
-     */
-    @Override
-    public McR updateMeeting(String platform, String meetingId, String userId, Map body) {
-        assertPlatform(platform);
-        if (PLATFORM_TX.equalsIgnoreCase(platform)) {
-            TxMeetingR r = txMeetingClient.updateMeeting(meetingId, userId, body);
-            return McR.success(r);
-        }
-        zoomClient.updateMeeting(meetingId, body);
-        return McR.success();
-    }
-
-    /**
-     * 取消/删除会议
-     */
-    @Override
-    public McR cancelMeeting(String platform, String meetingId, String userId) {
-        assertPlatform(platform);
-        if (PLATFORM_TX.equalsIgnoreCase(platform)) {
-            TxMeetingR r = txMeetingClient.cancelMeeting(meetingId, userId);
-            return McR.success(r);
-        }
-        zoomClient.deleteMeeting(meetingId);
-        return McR.success();
-    }
-
-    /**
-     * 查询会议详情
-     */
-    @Override
-    public McR getMeeting(String platform, String meetingId, String userId) {
-        assertPlatform(platform);
-        if (PLATFORM_TX.equalsIgnoreCase(platform)) {
-            TxMeetingR r = txMeetingClient.getMeeting(meetingId, userId);
-            return McR.success(r);
-        }
-        ZoomR r = zoomClient.getMeeting(meetingId);
-        return McR.success(r);
-    }
-
-    /**
-     * 创建腾讯会议后同步钉钉日程
-     * fixme: meetingInfoList 第一条即为新创建的会议,取 start_time/end_time 同步钉钉日程
-     */
-    private void syncDingtalkSchedule(TxMeetingR txR, String dingtalkUserId, Map body) {
-        try {
-            if (StringUtils.isBlank(dingtalkUserId)) return;
-            List<Map<String, Object>> infoList = txR.getMeetingInfoList();
-            if (infoList == null || infoList.isEmpty()) return;
-            Map<String, Object> meetingInfo = infoList.get(0);
-            Map scheduleBody = buildScheduleBody(meetingInfo, body);
-            String accessToken = ddClient.getAccessToken();
-            ddClient_Schedule.eventsSchedule(accessToken, dingtalkUserId, scheduleBody);
-            log.info("[SF] 腾讯会议同步钉钉日程成功, dingtalkUserId={}", dingtalkUserId);
-        } catch (Exception e) {
-            // ppExt: 同步钉钉日程失败不阻断会议创建结果
-            log.error("[SF] 同步钉钉日程失败(腾讯会议), error={}", e.getMessage(), e);
-        }
-    }
-
-    /**
-     * 创建 Zoom 会议后同步钉钉日程
-     */
-    private void syncDingtalkScheduleZoom(ZoomR zoomR, String dingtalkUserId, Map body) {
-        try {
-            if (StringUtils.isBlank(dingtalkUserId)) return;
-            Map scheduleBody = buildScheduleBodyZoom(zoomR, body);
-            String accessToken = ddClient.getAccessToken();
-            ddClient_Schedule.eventsSchedule(accessToken, dingtalkUserId, scheduleBody);
-            log.info("[SF] Zoom 会议同步钉钉日程成功, dingtalkUserId={}", dingtalkUserId);
-        } catch (Exception e) {
-            log.error("[SF] 同步钉钉日程失败(Zoom), error={}", e.getMessage(), e);
-        }
-    }
-
-    /**
-     * 构建腾讯会议 → 钉钉日程参数
-     * fixme: startTime/endTime 需为 ISO-8601 格式,钉钉日程要求 timeZone 字段
-     */
-    private Map buildScheduleBody(Map<String, Object> meetingInfo, Map body) {
-        String subject = String.valueOf(meetingInfo.getOrDefault("subject", body.getOrDefault("subject", "会议")));
-        // ppExt: 腾讯会议时间戳为秒,需转为 ISO-8601 格式(如 2024-01-01T10:00:00+08:00)
-        Object startTimeRaw = meetingInfo.getOrDefault("start_time", body.get("start_time"));
-        Object endTimeRaw = meetingInfo.getOrDefault("end_time", body.get("end_time"));
-        Map start = UtilMap.map("dateTime, timeZone", String.valueOf(startTimeRaw), "Asia/Shanghai");
-        Map end = UtilMap.map("dateTime, timeZone", String.valueOf(endTimeRaw), "Asia/Shanghai");
-        // cast 首参为 Object 以选 varargs 版 UtilMap.map(String, Object...) 避免命中 void 重载
-        return UtilMap.map("summary, start, end", (Object) subject, start, end);
-    }
-
-    /**
-     * 构建 Zoom → 钉钉日程参数
-     * fixme: Zoom start_time 已为 ISO-8601 格式
-     */
-    private Map buildScheduleBodyZoom(ZoomR zoomR, Map body) {
-        String subject = zoomR.getTopic() != null ? zoomR.getTopic() : String.valueOf(body.getOrDefault("topic", "Zoom 会议"));
-        String startTime = StringUtils.isNotBlank(zoomR.getStart_time()) ? zoomR.getStart_time() : String.valueOf(body.get("start_time"));
-        Map start = UtilMap.map("dateTime, timeZone", startTime, "Asia/Shanghai");
-        // fixme: Zoom 无 end_time,通过 start_time + duration(分钟)推算
-        Map end = UtilMap.map("dateTime, timeZone", startTime, "Asia/Shanghai");
-        return UtilMap.map("summary, start, end", (Object) subject, start, end);
-    }
-
-    private void assertPlatform(String platform) {
-        McException.assertParamException(
-                !PLATFORM_TX.equalsIgnoreCase(platform) && !PLATFORM_ZOOM.equalsIgnoreCase(platform),
-                "platform 参数非法,支持 TX / ZOOM"
-        );
-    }
-}

+ 0 - 109
mjava-shunfeng/src/main/java/com/malk/shunfeng/service/impl/TxMeetingClientImpl.java

@@ -1,109 +0,0 @@
-package com.malk.shunfeng.service.impl;
-
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.malk.server.common.McException;
-import com.malk.shunfeng.server.txmeeting.TxMeetingConf;
-import com.malk.shunfeng.server.txmeeting.TxMeetingR;
-import com.malk.shunfeng.service.TxMeetingClient;
-import com.malk.utils.UtilHttp;
-import com.malk.utils.UtilMap;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-import java.util.Map;
-
-/**
- * 腾讯会议 API 客户端实现
- * -
- * 每次请求动态生成签名 Header
- * API 文档: https://cloud.tencent.com/document/product/1095
- */
-@Slf4j
-@Service
-public class TxMeetingClientImpl implements TxMeetingClient {
-
-    @Autowired
-    private TxMeetingConf txMeetingConf;
-
-    /** 腾讯会议创建会议路径 */
-    private static final String PATH_MEETINGS = "/v1/meetings";
-
-    /**
-     * 创建会议
-     * POST /v1/meetings
-     */
-    @Override
-    public TxMeetingR createMeeting(String userId, Map body) {
-        // fixme: 腾讯会议需要在 body 中传 userid 和 instanceid
-        body.put("userid", userId);
-        body.put("instanceid", 1);
-        String bodyJson = toJson(body);
-        Map header = txMeetingConf.initHeaders("POST", PATH_MEETINGS, bodyJson);
-        String url = txMeetingConf.getApiHost() + PATH_MEETINGS;
-        String rsp = UtilHttp.doPost(url, header, null, body);
-        return parseAndAssert(rsp, "创建腾讯会议失败");
-    }
-
-    /**
-     * 修改会议
-     * PUT /v1/meetings/{meetingId}
-     */
-    @Override
-    public TxMeetingR updateMeeting(String meetingId, String userId, Map body) {
-        body.put("userid", userId);
-        body.put("instanceid", 1);
-        String path = PATH_MEETINGS + "/" + meetingId;
-        String bodyJson = toJson(body);
-        Map header = txMeetingConf.initHeaders("PUT", path, bodyJson);
-        String url = txMeetingConf.getApiHost() + path;
-        String rsp = UtilHttp.doPut(url, header, null, body);
-        return parseAndAssert(rsp, "修改腾讯会议失败");
-    }
-
-    /**
-     * 取消会议
-     * POST /v1/meetings/{meetingId}/cancel
-     */
-    @Override
-    public TxMeetingR cancelMeeting(String meetingId, String userId) {
-        String path = PATH_MEETINGS + "/" + meetingId + "/cancel";
-        Map body = UtilMap.map("userid, instanceid", userId, 1);
-        String bodyJson = toJson(body);
-        Map header = txMeetingConf.initHeaders("POST", path, bodyJson);
-        String url = txMeetingConf.getApiHost() + path;
-        String rsp = UtilHttp.doPost(url, header, null, body);
-        return parseAndAssert(rsp, "取消腾讯会议失败");
-    }
-
-    /**
-     * 查询会议详情
-     * GET /v1/meetings/{meetingId}?userid={userId}&instanceid=1
-     */
-    @Override
-    public TxMeetingR getMeeting(String meetingId, String userId) {
-        String path = PATH_MEETINGS + "/" + meetingId;
-        Map header = txMeetingConf.initHeaders("GET", path, "");
-        String url = txMeetingConf.getApiHost() + path;
-        Map param = UtilMap.map("userid, instanceid", userId, 1);
-        String rsp = UtilHttp.doGet(url, header, param);
-        return parseAndAssert(rsp, "查询腾讯会议失败");
-    }
-
-    /**
-     * 解析响应并断言成功
-     */
-    private TxMeetingR parseAndAssert(String rsp, String errMsg) {
-        log.debug("[TxMeeting] 响应: {}", rsp);
-        McException.assertException(StringUtils.isBlank(rsp), "TX_RSP_NULL", errMsg);
-        TxMeetingR r = JSON.parseObject(rsp, TxMeetingR.class);
-        r.assertSuccess();
-        return r;
-    }
-
-    private String toJson(Map body) {
-        return JSON.toJSONString(body, SerializerFeature.WriteMapNullValue);
-    }
-}

+ 0 - 137
mjava-shunfeng/src/main/java/com/malk/shunfeng/service/impl/ZoomClientImpl.java

@@ -1,137 +0,0 @@
-package com.malk.shunfeng.service.impl;
-
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONObject;
-import com.malk.server.common.McException;
-import com.malk.shunfeng.server.zoom.ZoomConf;
-import com.malk.shunfeng.server.zoom.ZoomR;
-import com.malk.shunfeng.service.ZoomClient;
-import com.malk.utils.UtilHttp;
-import com.malk.utils.UtilMap;
-import com.malk.utils.UtilToken;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-import java.util.Map;
-
-/**
- * Zoom 会议 API 客户端实现
- * -
- * 使用 OAuth 2.0 Account Credentials,Token 通过 UtilToken 缓存复用(key="sf-zoom-token")
- */
-@Slf4j
-@Service
-public class ZoomClientImpl implements ZoomClient {
-
-    @Autowired
-    private ZoomConf zoomConf;
-
-    /** UtilToken 缓存 key */
-    private static final String TOKEN_KEY = "sf-zoom-token";
-
-    /** Zoom 会议接口路径前缀 */
-    private static final String PATH_USERS_ME_MEETINGS = "/users/me/meetings";
-
-    /**
-     * 创建会议
-     * POST /users/me/meetings
-     */
-    @Override
-    public ZoomR createMeeting(Map body) {
-        String url = zoomConf.getApiHost() + PATH_USERS_ME_MEETINGS;
-        Map header = buildAuthHeader();
-        String rsp = UtilHttp.doPost(url, header, null, body);
-        return parseAndAssert(rsp, "创建 Zoom 会议失败");
-    }
-
-    /**
-     * 更新会议(PATCH,成功响应 204 No Content)
-     * PATCH /meetings/{meetingId}
-     * fixme: 204 响应无 body,parseAndAssert 不做调用
-     */
-    @Override
-    public void updateMeeting(String meetingId, Map body) {
-        String url = zoomConf.getApiHost() + "/meetings/" + meetingId;
-        Map header = buildAuthHeader();
-        String rsp = UtilHttp.doPatch(url, header, null, body);
-        // fixme: PATCH 成功返回 204 空响应,只打印日志,不解析
-        log.debug("[Zoom] updateMeeting 响应: {}", rsp);
-        if (StringUtils.isNotBlank(rsp)) {
-            ZoomR r = JSON.parseObject(rsp, ZoomR.class);
-            r.assertSuccess();
-        }
-    }
-
-    /**
-     * 删除会议
-     * DELETE /meetings/{meetingId}
-     */
-    @Override
-    public void deleteMeeting(String meetingId) {
-        String url = zoomConf.getApiHost() + "/meetings/" + meetingId;
-        Map header = buildAuthHeader();
-        String rsp = UtilHttp.doDelete(url, header, null, (Map) null);
-        log.debug("[Zoom] deleteMeeting 响应: {}", rsp);
-        if (StringUtils.isNotBlank(rsp)) {
-            ZoomR r = JSON.parseObject(rsp, ZoomR.class);
-            r.assertSuccess();
-        }
-    }
-
-    /**
-     * 查询会议详情
-     * GET /meetings/{meetingId}
-     */
-    @Override
-    public ZoomR getMeeting(String meetingId) {
-        String url = zoomConf.getApiHost() + "/meetings/" + meetingId;
-        Map header = buildAuthHeader();
-        String rsp = UtilHttp.doGet(url, header, (Map) null);
-        return parseAndAssert(rsp, "查询 Zoom 会议失败");
-    }
-
-    /**
-     * 获取 Access Token(缓存复用,过期自动刷新)
-     * POST {oauthUrl}?grant_type=account_credentials&account_id={accountId}
-     * Basic Auth: clientId:clientSecret
-     */
-    private String getAccessToken() {
-        // ppExt: 先从缓存取,命中则直接返回,避免重复申请
-        String cached = UtilToken.get(TOKEN_KEY);
-        if (StringUtils.isNotBlank(cached)) {
-            return cached;
-        }
-        Map param = UtilMap.map("grant_type, account_id", "account_credentials", zoomConf.getAccountId());
-        String rsp = UtilHttp.doRequest(
-                UtilHttp.METHOD.POST,
-                zoomConf.getOauthUrl(),
-                null, param, null, null,
-                zoomConf.getClientId(), zoomConf.getClientSecret()
-        );
-        McException.assertException(StringUtils.isBlank(rsp), "ZOOM_TOKEN_NULL", "Zoom Token 获取失败");
-        JSONObject tokenJson = JSON.parseObject(rsp);
-        String accessToken = tokenJson.getString("access_token");
-        Long expiresIn = tokenJson.getLong("expires_in");
-        McException.assertException(StringUtils.isBlank(accessToken), "ZOOM_TOKEN_EMPTY", "Zoom Token 为空");
-        // fixme: expiresIn 单位为秒,UtilToken.put 单位为毫秒,内部冗余 5s 容错
-        UtilToken.put(TOKEN_KEY, accessToken, expiresIn * 1000);
-        return accessToken;
-    }
-
-    /**
-     * 构建 Zoom Bearer Token 请求 Header
-     */
-    private Map buildAuthHeader() {
-        return UtilMap.map("Authorization", "Bearer " + getAccessToken());
-    }
-
-    private ZoomR parseAndAssert(String rsp, String errMsg) {
-        log.debug("[Zoom] 响应: {}", rsp);
-        McException.assertException(StringUtils.isBlank(rsp), "ZOOM_RSP_NULL", errMsg);
-        ZoomR r = JSON.parseObject(rsp, ZoomR.class);
-        r.assertSuccess();
-        return r;
-    }
-}

+ 0 - 54
mjava-shunfeng/src/main/resources/application-prod.yml.example

@@ -1,54 +0,0 @@
-# mjava-shunfeng 生产配置模板
-# 使用方式:复制为 application-prod.yml 并填入真实值;真实文件已被 .gitignore 排除
-
-server:
-  port: 9002
-  servlet:
-    context-path: /api/sf
-
-spel:
-  scheduling: false
-  multiSource: false
-
-spring:
-  datasource:
-    hikari:
-      connection-init-sql: SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci
-    driver-class-name: com.mysql.cj.jdbc.Driver
-    username: ${DB_USERNAME}
-    password: ${DB_PASSWORD}
-    url: ${DB_URL}
-  jpa:
-    database: MYSQL
-    database-platform: org.hibernate.dialect.MySQL57Dialect
-
-# 腾讯会议配置
-txmeeting:
-  secretId: ${TXMEETING_SECRET_ID}
-  secretKey: ${TXMEETING_SECRET_KEY}
-  sdkId: ${TXMEETING_SDK_APP_ID}
-  apiHost: https://api.meeting.qq.com
-
-# Zoom 配置
-zoom:
-  accountId: ${ZOOM_ACCOUNT_ID}
-  clientId: ${ZOOM_CLIENT_ID}
-  clientSecret: ${ZOOM_CLIENT_SECRET}
-  apiHost: https://api.zoom.us/v2
-  oauthUrl: https://zoom.us/oauth/token
-
-# dingtalk
-dingtalk:
-  agentId: ${DINGTALK_AGENT_ID}
-  appKey: ${DINGTALK_APP_KEY}
-  appSecret: ${DINGTALK_APP_SECRET}
-  corpId: ${DINGTALK_CORP_ID}
-  aesKey: ${DINGTALK_AES_KEY}
-  token: ${DINGTALK_TOKEN}
-  operator: ${DINGTALK_OPERATOR}
-
-# integration(IAM 集成平台)
-integration:
-  baseUrl: ${INTP_BASE_URL}        # 不含 /iam 路径前缀
-  clientId: ${INTP_CLIENT_ID}
-  clientSecret: ${INTP_CLIENT_SECRET}