提交 072106c9 作者: zhaibin

first commit

父级
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
\ No newline at end of file
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>
\ No newline at end of file
<?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" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
<?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>tencent_callback</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 统一指定所有Azure库的版本 -->
<azure.version>1.10.0</azure.version>
<microsoft.graph.version>5.70.0</microsoft.graph.version>
<jackson.version>2.13.0</jackson.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.13.0</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version> 2.7.0</version>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
<dependency>
<groupId>com.tencentcloudapi.wemeet</groupId>
<artifactId>wemeet-openapi-sdk</artifactId>
<version>v1.0.6</version>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.47</version> <!-- 使用最新版本 -->
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.2</version>
</dependency>
<!-- Microsoft Graph SDK -->
<dependency>
<groupId>com.microsoft.graph</groupId>
<artifactId>microsoft-graph-auth</artifactId>
<version>0.3.0</version>
</dependency>
<dependency>
<!-- Include the sdk as a dependency -->
<groupId>com.microsoft.graph</groupId>
<artifactId>microsoft-graph-core</artifactId>
<version>2.0.14</version>
</dependency>
<dependency>
<!-- Include the sdk as a dependency -->
<groupId>com.microsoft.graph</groupId>
<artifactId>microsoft-graph</artifactId>
<version>${microsoft.graph.version}</version>
</dependency>
<!-- MSAL认证库 -->
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j</artifactId>
<version>1.19.1</version>
</dependency>
<dependency>
<groupId>com.microsoft.ews-java-api</groupId>
<artifactId>ews-java-api</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.20</version> <!-- 请根据需要选择最新版本 -->
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version> <!-- 请根据需要选择最新版本 -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.32</version> <!-- 使用 SLF4J 的最新版本 -->
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.6</version> <!-- 使用 Logback 的最新版本 -->
</dependency>
<!-- Logback Core (日志框架核心) -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.3</version> <!-- SDK会自动管理版本 -->
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
<version>4.9.3</version> <!-- 使用最新版本 -->
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.3.3</version>
</dependency>
<!-- 确保所有Azure库版本一致 -->
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-core</artifactId>
<version>1.34.0</version>
</dependency>
<dependency>
<groupId>com.util</groupId>
<artifactId>llm-api</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version> <!-- 请使用最新版本 -->
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version> <!-- 请使用适合您MySQL版本的驱动 -->
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
<!-- 显式引入 jackson-dataformat-xml(版本由 Spring Boot 管理) -->
<!-- <dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.13.3</version>
</dependency>-->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20240303</version> <!-- 使用最新版本 -->
</dependency>
<!-- Jackson 核心模块 -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
<version>2.13.0</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package com.cmeeting;
import javax.mail.*;
import javax.mail.internet.*;
import java.util.Properties;
import java.io.File;
public class EmailSender {
// 发件人邮箱配置(建议从配置文件读取,此处写死示例)
private static final String FROM_EMAIL = "binzhai321@163.com";
private static final String AUTH_CODE = "RXbRu3AdrCxX57ib"; // 授权码
private static final String SMTP_HOST = "smtp.163.com";
private static final int SMTP_PORT = 465;
/**
* 发送带附件的邮件
* @param toEmail 收件人邮箱
* @param filePath 附件本地路径(如 "C:/test.pdf")
* @param subject 邮件主题(可选,默认值)
* @param text 邮件正文(可选,默认值)
* @return true发送成功,false发送失败
*/
public static boolean sendEmailWithAttachment(String toEmail, String filePath,
String subject, String text) {
// 1. 参数校验
if (toEmail == null || toEmail.isEmpty() || filePath == null) {
System.err.println("参数错误:收件人或附件路径为空");
return false;
}
// 2. 设置默认邮件主题和正文
String mailSubject = (subject != null) ? subject : "";
String mailText = (text != null) ? text : "请查收附件。";
// 3. 配置SMTP
Properties props = new Properties();
props.put("mail.smtp.host", SMTP_HOST);
props.put("mail.smtp.port", SMTP_PORT);
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.ssl.enable", "true");
try {
// 4. 创建会话
Session session = Session.getInstance(props, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(FROM_EMAIL, AUTH_CODE);
}
});
// 5. 构建邮件内容
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(FROM_EMAIL));
message.setRecipient(Message.RecipientType.TO, new InternetAddress(toEmail));
message.setSubject(mailSubject);
// 6. 添加正文和附件
Multipart multipart = new MimeMultipart();
// 文本正文
MimeBodyPart textPart = new MimeBodyPart();
textPart.setText(mailText);
multipart.addBodyPart(textPart);
// 附件
MimeBodyPart attachmentPart = new MimeBodyPart();
attachmentPart.attachFile(new File(filePath));
multipart.addBodyPart(attachmentPart);
message.setContent(multipart);
// 7. 发送邮件
Transport.send(message);
System.out.println("邮件发送成功至: " + toEmail);
return true;
} catch (Exception e) {
System.err.println("邮件发送失败: " + e.getMessage());
return false;
}
}
/**
* 简化版调用(仅需收件人和附件路径)
*/
public static boolean sendEmailWithAttachment(String toEmail, String filePath) {
return sendEmailWithAttachment(toEmail, filePath, null, null);
}
}
\ No newline at end of file
package com.cmeeting;
import com.azure.core.credential.AccessToken;
import com.azure.core.credential.TokenRequestContext;
import com.azure.identity.ClientSecretCredential;
import com.azure.identity.ClientSecretCredentialBuilder;
import com.microsoft.graph.authentication.IAuthenticationProvider;
import com.microsoft.graph.requests.AttachmentCollectionPage;
import com.microsoft.graph.requests.AttachmentCollectionResponse;
import com.microsoft.graph.requests.GraphServiceClient;
import com.microsoft.graph.models.*;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Base64;
import java.util.LinkedList;
public class OutlookTest {
// 创建日志记录器
private static final Logger logger = LoggerFactory.getLogger(OutlookTest.class);
public static void main(String[] args) throws Exception {
// Azure AD 应用程序的客户端凭证
String clientId = "d65aa91b-05f4-42b2-902f-4d82ea5a2b93";
String clientSecret = "~N98Q~83v6dViQFIofwr0fRn4J5VEZ2tvwOz.bPX";
String tenantId = "74cc8acf-aacc-4514-9bbb-dc27ce3096bb";
String scope = "https://graph.microsoft.com/.default";
// 使用 ClientSecretCredential 获取 token
ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder()
.clientId(clientId).tenantId(tenantId).clientSecret(clientSecret).build();
if (null == scope || null == clientSecretCredential) {
throw new Exception("Unexpected error");
}
// 创建身份验证提供者
IAuthenticationProvider authProvider = request -> {
// 创建 TokenRequestContext 对象并指定所需的范围
TokenRequestContext requestContext = new TokenRequestContext();
requestContext.addScopes("https://graph.microsoft.com/.default"); // 这里指定需要的范围
// 获取访问令牌
String accessToken = clientSecretCredential.getToken(requestContext).block().getToken();
// SDK 会自动将令牌添加到请求头中,您不需要手动操作 request 对象
return null;
};
// 创建 Graph 客户端
GraphServiceClient<Request> graphClient = GraphServiceClient
.builder()
.authenticationProvider(authProvider)
.buildClient();
// 打印访问令牌
printAccessToken(clientSecretCredential);
/* // 启用调试日志
GraphServiceClient<okhttp3.Request> graphClient = GraphServiceClient
.builder()
.authenticationProvider(tokenCredAuthProvider)
.httpClient(createHttpClient()) // 设置启用调试日志的 HTTP 客户端
.buildClient();*/
// 调用 Graph API 发送邮件
test(graphClient);
}
static void printAccessToken(ClientSecretCredential clientSecretCredential) {
try {
TokenRequestContext requestContext = new TokenRequestContext();
requestContext.addScopes("https://graph.microsoft.com/.default");
// 获取访问令牌
AccessToken accessToken = clientSecretCredential.getToken(requestContext).block();
if (accessToken != null && !accessToken.getToken().isEmpty()) {
System.out.println("Access Token: " + accessToken.getToken());
System.out.println("Expires On: " + accessToken.getExpiresAt().toEpochSecond());
} else {
System.out.println("Failed to retrieve access token.");
}
} catch (Exception e) {
System.err.println("Error while retrieving access token: " + e.getMessage());
}
}
public static void test(GraphServiceClient<okhttp3.Request> graphClient) throws IOException {
try {
// 创建邮件对象
Message message = new Message();
message.subject = "Meet for lunch?";
ItemBody body = new ItemBody();
body.contentType = BodyType.TEXT;
body.content = "The new cafeteria is open.";
message.body = body;
// 设置收件人
LinkedList<Recipient> toRecipientsList = new LinkedList<>();
Recipient toRecipients = new Recipient();
EmailAddress emailAddress = new EmailAddress();
emailAddress.address = "binzhai321@outlook.com";
toRecipients.emailAddress = emailAddress;
toRecipientsList.add(toRecipients);
message.toRecipients = toRecipientsList;
// 创建附件
LinkedList<Attachment> attachmentsList = new LinkedList<>();
FileAttachment attachments = new FileAttachment();
attachments.name = "1111.txt";
attachments.oDataType = "#microsoft.graph.fileAttachment";
attachments.contentType = "text/plain";
attachments.contentBytes = Base64.getDecoder().decode("SGVsbG8gV29ybGQh");
attachmentsList.add(attachments);
AttachmentCollectionResponse attachmentCollectionResponse = new AttachmentCollectionResponse();
attachmentCollectionResponse.value = attachmentsList;
AttachmentCollectionPage attachmentCollectionPage = new AttachmentCollectionPage(attachmentCollectionResponse, null);
message.attachments = attachmentCollectionPage;
// 发送邮件
graphClient.users("binzhai321@outlook.com")
.sendMail(UserSendMailParameterSet.newBuilder()
.withMessage(message)
.withSaveToSentItems(null)
.build())
.buildRequest()
.post();
System.out.println("Email sent successfully!");
} catch (Exception e) {
System.err.println("Error occurred: " + e.getMessage());
if (e instanceof com.microsoft.graph.http.GraphServiceException) {
com.microsoft.graph.http.GraphServiceException graphException = (com.microsoft.graph.http.GraphServiceException) e;
System.err.println("Error code: " + graphException.getServiceError().code);
System.err.println("Error message: " + graphException.getServiceError().message);
}
e.printStackTrace();
}
}
private static OkHttpClient createHttpClient() {
return new OkHttpClient.Builder()
.addInterceptor(chain -> {
okhttp3.Request originalRequest = chain.request();
// 打印原始请求头
System.out.println("Original Request Headers: " + originalRequest.headers());
// 确保 Authorization 头未被覆盖
okhttp3.Request modifiedRequest = originalRequest.newBuilder()
.headers(originalRequest.headers()) // 保留原始请求头
.build();
return chain.proceed(modifiedRequest);
})
.build();
}
}
package com.cmeeting;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.mail.*;
import javax.mail.internet.*;
import java.util.Properties;
public class QQMailSender {
public static void sendEmailWithAttachment(String toEmail, String filePath) {
// 发件人邮箱和授权码(不是密码)
String from = "1960771676@qq.com";
String password = "iaietlvcipyedjia"; // QQ邮箱的授权码
// QQ邮箱SMTP服务器设置
String host = "smtp.qq.com";
// 设置属性
Properties props = new Properties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true"); // 使用STARTTLS安全连接
props.put("mail.smtp.host", host);
props.put("mail.smtp.port", "587"); // QQ邮箱的端口
// 获取Session对象
Session session = Session.getInstance(props, new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(from, password);
}
});
try {
// 创建邮件消息
Message message = new MimeMessage(session);
// 设置发件人
message.setFrom(new InternetAddress(from));
// 设置收件人
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(toEmail));
// 设置邮件主题
message.setSubject("测试发送带附件的邮件");
// 创建消息体部分
BodyPart messageBodyPart = new MimeBodyPart();
// 设置消息体内容
messageBodyPart.setText("这是一封测试邮件,包含附件。");
// 创建多部分消息
Multipart multipart = new MimeMultipart();
// 设置文本消息部分
multipart.addBodyPart(messageBodyPart);
// 添加附件部分
messageBodyPart = new MimeBodyPart();
DataSource source = new FileDataSource(filePath);
messageBodyPart.setDataHandler(new DataHandler(source));
messageBodyPart.setFileName(source.getName());
multipart.addBodyPart(messageBodyPart);
// 设置完整消息
message.setContent(multipart);
// 发送邮件
Transport.send(message);
System.out.println("邮件发送成功!");
} catch (MessagingException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
// 收件人邮箱
String toEmail = "1960771676@qq.com";
// 本地文件路径
String filePath = "D:\\5397460824645048220.docx";
// 发送邮件
sendEmailWithAttachment(toEmail, filePath);
}
}
\ No newline at end of file
package com.cmeeting;
import com.azure.core.credential.TokenRequestContext;
import com.azure.identity.ClientSecretCredential;
import com.azure.identity.ClientSecretCredentialBuilder;
import com.microsoft.graph.authentication.TokenCredentialAuthProvider;
import com.microsoft.graph.http.GraphServiceException;
import com.microsoft.graph.models.Message;
import com.microsoft.graph.requests.GraphServiceClient;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import com.microsoft.graph.models.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
public class SendMailExample {
private static final Logger logger = LoggerFactory.getLogger(OutlookTest.class);
public static void main(String[] args) {
// 1. Azure AD 配置
final String clientId = "d65aa91b-05f4-42b2-902f-4d82ea5a2b93";
final String tenantId = "74cc8acf-aacc-4514-9bbb-dc27ce3096bb";
final String clientSecret = "~N98Q~83v6dViQFIofwr0fRn4J5VEZ2tvwOz.bPX";
// 2. 创建认证凭据
ClientSecretCredential credential = new ClientSecretCredentialBuilder()
.clientId(clientId)
.tenantId(tenantId)
.clientSecret(clientSecret)
.build();
String token = credential.getToken(
new TokenRequestContext()
.addScopes("https://graph.microsoft.com/.default"))
.block().getToken();
System.out.println("获取到的令牌:"+ token);
// 3. 初始化认证提供者
TokenCredentialAuthProvider authProvider = new TokenCredentialAuthProvider(
Collections.singletonList("https://graph.microsoft.com/.default"),
credential);
// 4. 创建 Graph 客户端
GraphServiceClient<Request> graphClient = GraphServiceClient.builder()
.authenticationProvider(authProvider)
.httpClient(createHttpClient())
.buildClient();
// 5. 构建邮件内容
Message message = new Message();
message.subject = "Test from Java 8";
ItemBody body = new ItemBody();
body.contentType = BodyType.TEXT;
body.content = "This works with Java 8!";
message.body = body;
EmailAddress emailAddress = new EmailAddress();
emailAddress.address = "binzhai321@outlook.com";
Recipient recipient = new Recipient();
recipient.emailAddress = emailAddress;
message.toRecipients = Collections.singletonList(recipient);
// 最兼容的发送方式
try {
// 创建参数集
UserSendMailParameterSet parameterSet = UserSendMailParameterSet.newBuilder()
.withMessage(message)
.withSaveToSentItems(true)
.build();
// 指定目标用户的 ID 或邮箱地址
String userId = "binzhai321@outlook.com";
// 发送邮件
graphClient.users(userId)
.sendMail(parameterSet)
.buildRequest()
.post();
System.out.println("Email sent successfully!");
} catch (GraphServiceException e) {
System.err.println("Error code: " + e.getServiceError().code);
System.err.println("Error message: " + e.getServiceError().message);
System.err.println("Headers: " + e.getRequestHeaders());
System.out.println(e.getMessage());
e.printStackTrace();
}
}
private static OkHttpClient createHttpClient() {
return new OkHttpClient.Builder()
.addInterceptor(chain -> {
okhttp3.Request request = chain.request();
// 打印请求信息
System.out.println("--> " + request.method() + " " + request.url());
if (request.body() != null) {
System.out.println("Request Body: " + request.body().toString());
}
if (request.headers() != null) {
System.out.println("Request Headers: " + request.headers().toString());
}
okhttp3.Response response = chain.proceed(request);
// 打印响应信息
if (response.body() != null) {
String responseBody = response.body().string();
System.out.println("<-- " + response.code() + " " + response.message() + " " + response.request().url());
System.out.println("Response Body: " + responseBody);
// 重新包装响应体
response = response.newBuilder()
.body(okhttp3.ResponseBody.create(responseBody, okhttp3.MediaType.parse("application/json")))
.build();
}
return response;
})
.build();
}
}
\ No newline at end of file
package com.cmeeting;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.cmeeting.mapper")
public class TencentMeetingCallbackApplication {
public static void main(String[] args) {
SpringApplication.run(TencentMeetingCallbackApplication.class, args);
}
}
\ No newline at end of file
package com.cmeeting;
import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.tencentcloudapi.wemeet.Client;
import com.tencentcloudapi.wemeet.core.authenticator.AuthenticatorBuilder;
import com.tencentcloudapi.wemeet.core.authenticator.JWTAuthenticator;
import com.tencentcloudapi.wemeet.core.exception.ClientException;
import com.tencentcloudapi.wemeet.core.exception.ServiceException;
import com.tencentcloudapi.wemeet.core.xhttp.ApiResponse;
import com.tencentcloudapi.wemeet.service.meeting_control.api.MeetingControlApi;
import com.tencentcloudapi.wemeet.service.meeting_control.model.V1RealControlMeetingsMeetingIdAsrPutRequest;
import com.tencentcloudapi.wemeet.service.meetings.api.MeetingsApi;
import com.util.meeting.MeetingMinutesGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.*;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
@RestController
@RequestMapping
public class TencentMeetingCallbackController {
private static final Logger logger = LoggerFactory.getLogger(TencentMeetingCallbackController.class);
// 配置参数 - 从配置文件或环境变量获取
private final String token = "RTLqcY5yXzPleC8MubLdoSwrQ";
private final String encodingAESKey = "Fo8Kb9ooj2Jd2E0GwSRT6OoZ7Pim8Ndmk0CSVkC5RX1";
// 1.构造 client 客户端(jwt 鉴权需要配置 appId sdkId secretID 和 secretKey)
Client client = new Client.Builder()
.withAppId("211153201").withSdkId("28370276340")
.withSecret("BKOMDZVbvh0iT7k6UHsSizAWBCOVDtT6", "3Y1j0mzNp7KChKFJGyaEnZHLobFoAQ8eLwfaMx8nLbtXAerO")
.build();
/**
* 处理GET请求 - URL验证
*/
@GetMapping
public ResponseEntity<String> verifyUrl(
@RequestParam("check_str") String checkStr,
@RequestHeader("timestamp") String timestamp,
@RequestHeader("nonce") String nonce,
@RequestHeader("signature") String signature) {
try {
// 1. URL解码
checkStr = URLDecoder.decode(checkStr, StandardCharsets.UTF_8.name());
// 2. 验证签名
if (!verifySignature(token, timestamp, nonce, checkStr, signature)) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Signature verification failed");
}
// 3. 解密check_str
String decryptedStr = decryptData(checkStr);
// 4. 返回明文(不能加引号或换行符)
return ResponseEntity.ok(decryptedStr);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Error: " + e.getMessage());
}
}
/**
* 处理POST请求 - 事件回调
*/
@PostMapping(consumes = "application/json")
public ResponseEntity<String> handleEvent(
@RequestBody Map<String, String> requestBody,
@RequestHeader("timestamp") String timestamp,
@RequestHeader("nonce") String nonce,
@RequestHeader("signature") String signature) {
try {
// 1. 获取data字段
String encryptedData = requestBody.get("data");
if (encryptedData == null) {
return ResponseEntity.badRequest().body("Missing data field");
}
// 2. 验证签名
if (!verifySignature(token, timestamp, nonce, encryptedData, signature)) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Signature verification failed");
}
// 3. 解密data
String decryptedData = decryptData(encryptedData);
// System.out.println("Decrypted event data: " + decryptedData);
logger.info("Decrypted event data: {}", decryptedData);
// 4. 处理业务逻辑(可根据需要扩展)
processEvent(decryptedData);
// 5. 返回成功响应(必须严格匹配)
return ResponseEntity.ok("successfully received callback");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Error: " + e.getMessage());
}
}
/**
* 验证签名(SHA1)
*/
private boolean verifySignature(String token, String timestamp, String nonce,
String data, String receivedSignature) throws Exception {
// 1. 按字典序排序
String[] arr = new String[]{token, timestamp, nonce, data};
Arrays.sort(arr);
// 2. 拼接字符串
String combined = String.join("", arr);
// 3. SHA1加密
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] digest = md.digest(combined.getBytes(StandardCharsets.UTF_8));
// 4. 转换为16进制字符串
StringBuilder hexStr = new StringBuilder();
for (byte b : digest) {
String hex = Integer.toHexString(b & 0xFF);
if (hex.length() == 1) {
hexStr.append('0');
}
hexStr.append(hex);
}
// 5. 比较签名
return hexStr.toString().equals(receivedSignature);
}
/**
* AES解密数据
*/
private String decryptData(String encryptedData) throws Exception {
// 1. Base64解码
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);
// 2. 处理AES密钥
byte[] aesKeyBytes = Base64.getDecoder().decode(encodingAESKey + "=");
byte[] ivBytes = Arrays.copyOfRange(aesKeyBytes, 0, 16);
// 3. 初始化Cipher
SecretKeySpec keySpec = new SecretKeySpec(aesKeyBytes, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
// 4. 解密
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
// 5. 处理PKCS5Padding补位
int pad = decryptedBytes[decryptedBytes.length - 1];
if (pad < 1 || pad > 32) {
pad = 0;
}
return new String(
Arrays.copyOfRange(decryptedBytes, 0, decryptedBytes.length - pad),
StandardCharsets.UTF_8
);
}
// 类成员变量记录已处理的事件
private final Set<String> startProcessedEvents = Collections.synchronizedSet(new HashSet<>());
private final Set<String> sendProcessedEvents = Collections.synchronizedSet(new HashSet<>());
/**
* 处理事件业务逻辑
*/
private void processEvent(String decryptedData) {
// 这里可以解析JSON并处理具体业务
// 示例: 打印事件数据
/* System.out.println("这是获取到的会议信息:");
System.out.println("Processing event: " + decryptedData);*/
logger.info("这是获取到的会议信息:");
logger.info("Processing event: {}", decryptedData);
String json = decryptedData;
try {
ObjectMapper mapper = new ObjectMapper();
// 解析JSON
java.util.Map<String, Object> jsonMap = mapper.readValue(json, java.util.Map.class);
// 1. 提取 event
String event = (String) jsonMap.get("event");
// 获取payload数组
java.util.List<java.util.Map<String, Object>> payload = (java.util.List<java.util.Map<String, Object>>) jsonMap.get("payload");
java.util.Map<String, Object> firstPayload = payload.get(0);
// 获取meeting_info信息
java.util.Map<String, Object> meetingInfo = (java.util.Map<String, Object>) firstPayload.get("meeting_info");
String meetingId = (String) meetingInfo.get("meeting_id");
Map<String, Object> operator = (Map<String, Object>) firstPayload.get("operator");
String operatorUserId = (String) operator.get("userid");
String instanceIdStr = (String) operator.get("instance_id");
Integer instanceId = Integer.parseInt(instanceIdStr);
//判断事件是否为开始会议
if ("meeting.started".equals(event)) {
try {
// 生成唯一事件ID(使用事件类型+会议ID+时间戳)
String eventId = meetingId;
// 检查是否已处理
if (startProcessedEvents.contains(eventId)) {
//System.out.println("忽略已处理事件: " + eventId);
logger.info("忽略已处理事件: {}", eventId);
return;
}
// 记录已处理事件
startProcessedEvents.add(eventId);
// 原有处理逻辑...
} catch (Exception e) {
e.printStackTrace();
}
//开启语音转写功能
//System.out.println("开启会议语音转写");
logger.info("开启会议语音转写");
ObjectMapper jsonMapper = new ObjectMapper();
ObjectNode jsonBody = jsonMapper.createObjectNode()
.put("operator_id", operatorUserId)
.put("operator_id_type", 1)
.put("instance_id", instanceId) // 自动识别为数字类型
.put("is_open", true)
.put("open_asr_view", 0);
String BODY_JSON = mapper.writeValueAsString(jsonBody);
V1RealControlMeetingsMeetingIdAsrPutRequest body = JSON.parseObject(BODY_JSON, V1RealControlMeetingsMeetingIdAsrPutRequest.class);
// 2.构造请求参数
MeetingControlApi.ApiV1RealControlMeetingsMeetingIdAsrPutRequest request =
new MeetingControlApi.ApiV1RealControlMeetingsMeetingIdAsrPutRequest.Builder(meetingId)
.body(body).build();
// 3.构造 JWT 鉴权器
// 随机数
BigInteger nonce = BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt()));
// 当前时间戳
String timestamp = String.valueOf(System.currentTimeMillis() / 1000L);
AuthenticatorBuilder<JWTAuthenticator> authenticatorBuilder =
new JWTAuthenticator.Builder().nonce(nonce).timestamp(timestamp);
try {
MeetingControlApi.ApiV1RealControlMeetingsMeetingIdAsrPutResponse response =
client.meeting_control().v1RealControlMeetingsMeetingIdAsrPut(request, authenticatorBuilder);
// response from `v1RealControlMeetingsMeetingIdAsrPut`: V1RealControlMeetingsMeetingIdAsrPut200Response
/* System.out.printf("Response from `MeetingControlApi.v1RealControlMeetingsMeetingIdAsrPut`: \nheader: %s\n%s\n",
response.getHeader(), response.getData());*/
logger.info("Response from `MeetingControlApi.v1RealControlMeetingsMeetingIdAsrPut`: \nheader: {}\n{}",
response.getHeader(), response.getData());
} catch (ClientException e) {
// System.out.printf("Error when calling `MeetingControlApi.v1RealControlMeetingsMeetingIdAsrPut`: %s\n", e);
logger.error("Error when calling `MeetingControlApi.v1RealControlMeetingsMeetingIdAsrPut`", e);
throw new RuntimeException(e);
} catch (ServiceException e) {
// System.out.printf("Error when calling `MeetingControlApi.v1RealControlMeetingsMeetingIdAsrPut`: %s\n", e);
logger.error("Error when calling `MeetingControlApi.v1RealControlMeetingsMeetingIdAsrPut`", e);
// System.out.printf("Full HTTP response: %s\n", new String(e.getApiResp().getRawBody()));
logger.error("Full HTTP response: {}", new String(e.getApiResp().getRawBody()));
throw new RuntimeException(e);
}
} else if ("meeting.end".equals(event)) {
try {
// 生成唯一事件ID(使用事件类型+会议ID+时间戳)
String eventId = meetingId;
// 检查是否已处理
if (sendProcessedEvents.contains(eventId)) {
// System.out.println("忽略已处理事件: " + eventId);
logger.info("忽略已处理事件: {}", eventId);
return;
}
// 记录已处理事件
sendProcessedEvents.add(eventId);
// 原有处理逻辑...
} catch (Exception e) {
e.printStackTrace();
}
/*System.out.println("operatorId: " + operatorUserId);
System.out.println("meetingId: " + meetingId);*/
logger.info("operatorId: {}", operatorUserId);
logger.info("meetingId: {}", meetingId);
// 2.构造请求参数
MeetingsApi.ApiV1AsrDetailsGetRequest request =
new MeetingsApi.ApiV1AsrDetailsGetRequest.Builder()
.operatorIdType("1")
.operatorId(operatorUserId)
.meetingId(meetingId)
.fileType("1")
.build();
// 3.构造 JWT 鉴权器
// 随机数
BigInteger nonce = BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt()));
// 当前时间戳
String timestamp = String.valueOf(System.currentTimeMillis() / 1000L);
AuthenticatorBuilder<JWTAuthenticator> authenticatorBuilder =
new JWTAuthenticator.Builder().nonce(nonce).timestamp(timestamp);
// 4.发送对应的请求
try {
ApiResponse response =
client.meetings().v1AsrDetailsGet(request, authenticatorBuilder);
/*System.out.printf("Response from `MeetingsApi.v1AsrDetailsGet`: \nheader: %s\n%s\n",
response.getHeader(), response.getRawBody());*/
logger.info("Response from `MeetingsApi.v1AsrDetailsGet`: \nheader: {}\n{}",
response.getHeader(), response.getRawBody());
String rawBodyString = new String(response.getRawBody(), StandardCharsets.UTF_8);
// System.out.println("Raw Body (string): " + rawBodyString);
logger.info("Raw Body (string): {}", rawBodyString);
try {
// 解析JSON获取下载URL
com.fasterxml.jackson.databind.ObjectMapper resultmapper = new com.fasterxml.jackson.databind.ObjectMapper();
java.util.Map<String, Object> result = resultmapper.readValue(rawBodyString, java.util.Map.class);
String downloadUrl = ((java.util.List<String>) result.get("download_url")).get(0);
// 设置保存路径为D盘根目录
String savePath = "D:/" + meetingId + ".docx"; // 或者 "D:\\downloaded_file.docx"
String targetPath = "D:/" + meetingId + "纪要" + ".docx";
// 下载文件
// System.out.println("开始下载文件到: " + savePath);
logger.info("开始下载文件到: {}", savePath);
try (java.io.BufferedInputStream in = new java.io.BufferedInputStream(new java.net.URL(downloadUrl).openStream());
java.io.FileOutputStream fileOutputStream = new java.io.FileOutputStream(savePath)) {
byte[] dataBuffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) {
fileOutputStream.write(dataBuffer, 0, bytesRead);
}
//System.out.println("文件下载完成,已保存到 " + savePath);
logger.info("文件下载完成,已保存到 {}", savePath);
/**
* 将文件传送给大模型处理
*/
//todo
try {
boolean success = MeetingMinutesGenerator.generateMinutesFromWord(
savePath,
targetPath,
"AKIAXFAXF62IWJXGLVEE.LnKInaahcMZG9zLsGMH3nTLOw3S3lK5Vcu0+ifnO",
"https://bedrock.chatbot.cn/llm/sse-invoke"
);
if (success) {
//System.out.println("会议纪要生成成功!");
logger.info("会议纪要生成成功!");
}
} catch (IOException e) {
//System.err.println("错误: " + e.getMessage());
logger.error("错误: {}", e.getMessage());
}
/**
* 使用邮箱发送邮件
*/
//todo 调用腾讯会议API获得主持人的邮箱地址
Thread.sleep(10000);
EmailSender emailSender = new EmailSender();
//通过查询企业微信表获取到用户的企业微信userid
//在人事表中查询该用户对应的邮箱
String emailAddress = "";
//response.getRawBody()
boolean mailFlag = emailSender.sendEmailWithAttachment("1960771676@qq.com",
targetPath,
"重要文件",
"您好:\n" +
"\n" +
" 附件为您本次会议的会议纪要,烦请下载查看,如需对会议纪要结果进行修改或查看历史会议,可点击下方链接。");
if (mailFlag) {
//System.out.println("邮件发送成功");
logger.info("邮件发送成功");
} else {
//System.out.println("邮件发送失败");
logger.error("邮件发送失败");
}
}
} catch (Exception e) {
//System.out.println("下载文件时出错: " + e.getMessage());
logger.error("下载文件时出错: {}",e.getMessage());
e.printStackTrace();
}
} catch (ClientException e) {
// System.out.printf("Error when calling `MeetingsApi.v1AsrDetailsGet`: %s\n", e);
logger.error("Error when calling `MeetingsApi.v1AsrDetailsGet`", e);
throw new RuntimeException(e);
} catch (ServiceException e) {
//System.out.printf("Error when calling `MeetingsApi.v1AsrDetailsGet`: %s\n", e);
logger.error("Error when calling `MeetingsApi.v1AsrDetailsGet`", e);
// System.out.printf("Full HTTP response: %s\n", new String(e.getApiResp().getRawBody()));
logger.error("Full HTTP response: {}", new String(e.getApiResp().getRawBody()));
throw new RuntimeException(e);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.cmeeting;
import com.cmeeting.pojo.TencentMeetingUser;
import com.cmeeting.pojo.WeComUser;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import com.tencentcloudapi.wemeet.Client;
import com.tencentcloudapi.wemeet.core.authenticator.AuthenticatorBuilder;
import com.tencentcloudapi.wemeet.core.authenticator.JWTAuthenticator;
import com.tencentcloudapi.wemeet.core.exception.ClientException;
import com.tencentcloudapi.wemeet.core.exception.ServiceException;
import com.tencentcloudapi.wemeet.service.user_manager.api.UserManagerApi;
import com.tencentcloudapi.wemeet.service.user_manager.model.V1UsersListGet200Response;
import com.tencentcloudapi.wemeet.service.user_manager.model.V1UsersListGet200ResponseUsersInner;
import org.springframework.stereotype.Service;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class WeComAndTencentMeeting {
private static final String BASE_URL = "https://qyapi.weixin.qq.com/cgi-bin/user/simplelist";
private static final String ACCESS_TOKEN = "wyYAmCbeVIPvj_Gpdd8ixp_td_hPv2OtTU8gpTGSpB7TaftbpNps5fc_JdCRzpgy_tUv-aGueJpRV6XJeYAG-rqkzCfDV-rcpJbqB0LypjvJUtxPeTmPqEtQuCMwH6euGGVaJZXoc33ATrS70U0T4_4WIrATqpsN2Ed2XsNGyN5N6aG8mq6AyLMG9TokVWx1qd2qF1zVTwve6aqRMWHwXg";
public static void main(String[] args) {
try {
// 示例:获取部门ID为6的用户列表
int departmentId = 6;
JsonObject result = getUserListByDepartment(departmentId);
// 处理返回结果
if (result.get("errcode").getAsInt() == 0) {
System.out.println("企微获取用户列表成功:");
JsonArray userList = result.getAsJsonArray("userlist");
for (int i = 0; i < userList.size(); i++) {
JsonObject user = userList.get(i).getAsJsonObject();
System.out.println("姓名: " + user.get("name").getAsString() +
", UserID: " + user.get("userid").getAsString());
}
} else {
System.out.println("企微获取用户列表失败: " + result.get("errmsg").getAsString());
}
} catch (Exception e) {
e.printStackTrace();
}
/**
* 腾讯会议通过通讯录获取员工信息
*/
// 1. 构造client客户端
Client client = new Client.Builder()
.withAppId("211153201").withSdkId("28370276340")
.withSecret("BKOMDZVbvh0iT7k6UHsSizAWBCOVDtT6", "3Y1j0mzNp7KChKFJGyaEnZHLobFoAQ8eLwfaMx8nLbtXAerO")
.build();
// 2. 开始循环获取用户列表
fetchUsersInBatches(client, 3);
}
/**
* 将企业微信返回的JSON转换为WeComUser列表
*/
public static List<WeComUser> convertJsonToWeComUsers(JsonObject json) {
List<WeComUser> users = new ArrayList<>();
if (json.has("userlist") && json.get("userlist").isJsonArray()) {
JsonArray userList = json.getAsJsonArray("userlist");
Gson gson = new Gson();
for (JsonElement element : userList) {
JsonObject userJson = element.getAsJsonObject();
WeComUser user = new WeComUser();
// 根据企业微信API实际返回字段调整
user.setUserId(userJson.get("userid").getAsString());
user.setUserName(userJson.get("name").getAsString());
// 其他字段设置...
users.add(user);
}
}
return users;
}
/**
* 标记企业微信重名用户
*/
public static void markDuplicateNames(List<WeComUser> users) {
// 按姓名分组,统计每个名字出现的次数
Map<String, Long> nameCountMap = users.stream()
.collect(Collectors.groupingBy(WeComUser::getUserName, Collectors.counting()));
// 设置是否重名标志
users.forEach(user -> {
if (nameCountMap.get(user.getUserName()) > 1) {
user.setIsRepeatName("1"); // 重名
} else {
user.setIsRepeatName("0"); // 不重名
}
});
}
/**
* 标记企业微信重名用户
*/
public static void markDuplicateNamesTecent(List<TencentMeetingUser> users) {
// 按姓名分组,统计每个名字出现的次数
Map<String, Long> nameCountMap = users.stream()
.collect(Collectors.groupingBy(TencentMeetingUser::getUserName, Collectors.counting()));
// 设置是否重名标志
users.forEach(user -> {
if (nameCountMap.get(user.getUserName()) > 1) {
user.setIsrepeatName("1"); // 重名
} else {
user.setIsrepeatName("0"); // 不重名
}
});
}
/**
* 根据企业微信部门ID获取用户列表
*
* @param departmentId 部门ID
* @return 包含用户列表的JsonObject
* @throws Exception
*/
public static JsonObject getUserListByDepartment(int departmentId) throws Exception {
String urlStr = BASE_URL + "?access_token=" + ACCESS_TOKEN + "&department_id=" + departmentId;
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
// 使用Gson解析JSON响应
Gson gson = new Gson();
return gson.fromJson(response.toString(), JsonObject.class);
} else {
throw new RuntimeException("HTTP GET请求失败,错误码: " + responseCode);
}
}
/**
* 腾讯会议获取员工信息
*/
/**
* 分批次获取用户列表
*
* @param client 客户端实例
* @param pageSize 每页大小
* @return
*/
public static List<TencentMeetingUser> fetchUsersInBatches(Client client, int pageSize) {
int currentPage = 1;
boolean hasMore = true;
int totalUsers = 0;
List<TencentMeetingUser> userList = new ArrayList<>(); // 创建集合存储用户数据
while (hasMore) {
try {
// 1. 构造请求参数
UserManagerApi.ApiV1UsersListGetRequest request =
new UserManagerApi.ApiV1UsersListGetRequest.Builder()
.page(String.valueOf(currentPage))
.pageSize(String.valueOf(pageSize))
.operatorId("woaJARCQAAJU1EsO73Ww5rn8YHMW6iYA")
.operatorIdType("1")
.build();
// 2. 构造JWT鉴权器
BigInteger nonce = BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt()));
String timestamp = String.valueOf(System.currentTimeMillis() / 1000L);
AuthenticatorBuilder<JWTAuthenticator> authenticatorBuilder =
new JWTAuthenticator.Builder().nonce(nonce).timestamp(timestamp);
// 3. 发送请求
UserManagerApi.ApiV1UsersListGetResponse response =
client.user_manager().v1UsersListGet(request, authenticatorBuilder);
// 4. 处理响应并转换为TencentMeetingUser对象
V1UsersListGet200Response responseData = response.getData();
List<V1UsersListGet200ResponseUsersInner> users = responseData.getUsers();
int currentSize = users.size();
totalUsers += currentSize;
System.out.printf("第 %d 页,获取到 %d 个用户:\n", currentPage, currentSize);
for (V1UsersListGet200ResponseUsersInner user : users) {
// 创建TencentMeetingUser对象并设置属性
TencentMeetingUser meetingUser = new TencentMeetingUser();
meetingUser.setUserId(user.getUserid());
meetingUser.setUserName(user.getUsername());
meetingUser.setIsrepeatName("0"); // 默认设为不重名,可根据实际业务调整
userList.add(meetingUser); // 添加到集合
System.out.printf("用户ID: %s, 用户名: %s\n", user.getUserid(), user.getUsername());
}
// 5. 检查是否还有更多数据
if (currentSize == 0 || currentSize < pageSize) {
hasMore = false;
System.out.printf("\n所有用户获取完成,共获取 %d 个用户\n", totalUsers);
} else {
currentPage++;
}
} catch (ClientException e) {
System.out.printf("客户端错误: %s\n", e);
throw new RuntimeException(e);
} catch (ServiceException e) {
System.out.printf("服务端错误: %s\n", e);
System.out.printf("完整响应: %s\n", new String(e.getApiResp().getRawBody()));
throw new RuntimeException(e);
}
}
return userList; // 返回用户集合
}
}
package com.cmeeting.controller;
import com.cmeeting.pojo.TencentMeetingUser;
import com.cmeeting.service.TecentMeetingService;
import com.tencentcloudapi.wemeet.Client;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import static com.cmeeting.WeComAndTencentMeeting.*;
@RestController
@RequestMapping("/tecent")
public class TencentMeetingController {
@Autowired
private TecentMeetingService tecentMeetingService;
@GetMapping("/add")
public void addUsers() throws Exception {
dousers();
}
private void dousers() {
/**
* 腾讯会议通过通讯录获取员工信息
*/
// 1. 构造client客户端
Client client = new Client.Builder()
.withAppId("211153201").withSdkId("28370276340")
.withSecret("BKOMDZVbvh0iT7k6UHsSizAWBCOVDtT6", "3Y1j0mzNp7KChKFJGyaEnZHLobFoAQ8eLwfaMx8nLbtXAerO")
.build();
// 2.获取到全部用户
List<TencentMeetingUser> users = fetchUsersInBatches(client, 3);;
// 3. 检查重名并设置标志
markDuplicateNamesTecent(users);
// 4. 批量插入数据库
tecentMeetingService.batchInsert(users);
}
}
package com.cmeeting.controller;
import com.azure.core.annotation.Get;
import com.cmeeting.mapper.TecentMeetingMapper;
import com.cmeeting.mapper.UserIdMapper;
import com.cmeeting.mapper.WeComUserMapper;
import com.cmeeting.pojo.TencentMeetingUser;
import com.cmeeting.pojo.UserId;
import com.cmeeting.pojo.WeComUser;
import com.tencentcloudapi.wemeet.Client;
import com.tencentcloudapi.wemeet.core.authenticator.AuthenticatorBuilder;
import com.tencentcloudapi.wemeet.core.authenticator.JWTAuthenticator;
import com.tencentcloudapi.wemeet.core.exception.ClientException;
import com.tencentcloudapi.wemeet.core.exception.ServiceException;
import com.tencentcloudapi.wemeet.service.meetings.api.MeetingsApi;
import com.tencentcloudapi.wemeet.service.meetings.model.V1MeetingsGet200Response;
import com.tencentcloudapi.wemeet.service.meetings.model.V1MeetingsGet200ResponseMeetingInfoListInner;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.json.JSONObject;
import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@RestController
@RequestMapping
public class UnifiedController {
@Autowired
private WeComUserMapper weComUserMapper;
@Autowired
private TecentMeetingMapper tecentMeetingMapper;
@Autowired
private UserIdMapper userIdMapper;
List<WeComUser> weComUsers = new ArrayList<>();
List<TencentMeetingUser> tencentMeetingUsers = new ArrayList<>();
// 用于存放 WeComUser 的分类结果
List<WeComUser> weComUsersIsRepeat0 = new ArrayList<>();
List<WeComUser> weComUsersIsRepeat1 = new ArrayList<>();
// 用于存放 TencentMeetingUser 的分类结果
List<TencentMeetingUser> tencentUsersIsRepeat0 = new ArrayList<>();
List<TencentMeetingUser> tencentUsersIsRepeat1 = new ArrayList<>();
@GetMapping("/unifiled")
public void unifiled() {
weComUsers = weComUserMapper.getAlluser();
tencentMeetingUsers = tecentMeetingMapper.getAlluser();
// 分类 WeComUser 数据
for (WeComUser user : weComUsers) {
if ("0".equals(user.getIsRepeatName())) {
weComUsersIsRepeat0.add(user);
} else if ("1".equals(user.getIsRepeatName())) {
weComUsersIsRepeat1.add(user);
}
}
// 分类 TencentMeetingUser 数据
for (TencentMeetingUser user : tencentMeetingUsers) {
if ("0".equals(user.getIsrepeatName())) {
tencentUsersIsRepeat0.add(user);
} else if ("1".equals(user.getIsrepeatName())) {
tencentUsersIsRepeat1.add(user);
}
}
List<UserId> userIds = mergeUserLists(weComUsersIsRepeat0, tencentUsersIsRepeat0);
userIdMapper.insertUsers(userIds);
}
@GetMapping("/insertTid")
public void insertTid() {
List<UserId> users = userIdMapper.getUsers();
List<UserId> usersWithNullTid = new ArrayList<>();
for (UserId user : users) {
if (user.getTid() == null) { // 同样的,根据 Tid 的实际类型调整判断条件
usersWithNullTid.add(user);
}
}
for (UserId user : usersWithNullTid) {
//获取企业微信token
String weComToken = getWeComToken();
try {
// 3.1 创建会议(传入用户的 wid)
Map<String, String> meetingCodeAndMeetingid = createMeeting(user.getWid(), weComToken);
String meetingId = meetingCodeAndMeetingid.get("meetingid");
String meetingCode = meetingCodeAndMeetingid.get("meeting_code");
if (meetingCode == null || meetingCode.isEmpty()) {
System.err.println("会议创建失败,跳过用户: " + user.getWid());
continue;
}
// 3.2 查询会议详情,获取主持人腾讯会议 userid(假设是 host_userid)
String hostUserId = getMeetingHost(meetingCode);
if (hostUserId == null || hostUserId.isEmpty()) {
System.err.println("获取主持人失败,跳过会议: " + meetingCode);
cancelMeeting(meetingId, weComToken); // 尝试取消无效会议
continue;
}
// 3.3 更新用户的 Tid 字段
user.setTid(hostUserId);
userIdMapper.updateUser(user);
// 3.4 取消预约会议(根据需求决定是否取消)
cancelMeeting(meetingId, weComToken);
System.out.println("处理完成: user=" + user.getWid() + ", tid=" + hostUserId);
} catch (Exception e) {
e.printStackTrace();
System.err.println("处理用户失败: " + user.getWid());
}
}
//发送请求创建预约会议,获得会议号
// createMeeting(userid);
//通过会议号调用腾讯会议查询会议接口获取到主持人腾讯会议userid
//将获取到的腾讯会议userid插入到该用户的Tid字段
//取消预约会议
}
//todo 待测试
@GetMapping("/sameNameInsertTid")
public void sameNameInsertTid() throws IOException {
String weComToken = getWeComToken();
List<WeComUser> sameNameUsers = weComUserMapper.getSameName();
List<UserId> userIds = new ArrayList<>();
for (WeComUser user : sameNameUsers) {
Map<String, String> meetingCodeAndMeetingid = createMeeting(user.getUserId(), weComToken);
String meetingId = meetingCodeAndMeetingid.get("meetingid");
String meetingCode = meetingCodeAndMeetingid.get("meeting_code");
if (meetingCode == null || meetingCode.isEmpty()) {
System.err.println("会议创建失败,跳过用户: " + user.getUserId());
continue;
}
// 3.2 查询会议详情,获取主持人腾讯会议 userid(假设是 host_userid)
String hostUserId = getMeetingHost(meetingCode);
if (hostUserId == null || hostUserId.isEmpty()) {
System.err.println("获取主持人失败,跳过会议: " + meetingCode);
cancelMeeting(meetingId, weComToken); // 尝试取消无效会议
continue;
}
UserId userId = new UserId(user.getUserName(), user.getUserId(), hostUserId);
userIds.add(userId);
}
userIdMapper.insertUsers(userIds);
}
/**
* 通过企业微信接口取消预约会议
*
* @param meetingId
*/
private void cancelMeeting(String meetingId, String weComToken) {
// 1. 构造请求URL
String url = "https://qyapi.weixin.qq.com/cgi-bin/meeting/cancel?access_token=" + weComToken;
// 2. 构造请求体(JSON格式)
JSONObject requestBody = new JSONObject();
requestBody.put("meetingid", meetingId);
// 3. 发送POST请求
OkHttpClient client = new OkHttpClient();
RequestBody body = RequestBody.create(
MediaType.parse("application/json"),
requestBody.toString()
);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
// 4. 解析响应
String responseData = response.body().string();
JSONObject jsonResponse = new JSONObject(responseData);
if (jsonResponse.getInt("errcode") == 0) {
System.out.println("会议取消成功: " + meetingId);
} else {
System.err.println("会议取消失败: " + responseData);
}
} catch (IOException e) {
System.err.println("取消会议请求异常: " + e.getMessage());
}
}
/**
* 通过meetingId获取到会议主持人对应的腾讯会议userid
*
* @param meetingCode
* @return
*/
private String getMeetingHost(String meetingCode) {
Client client = new Client.Builder()
.withAppId("211153201").withSdkId("28370276340")
.withSecret("BKOMDZVbvh0iT7k6UHsSizAWBCOVDtT6", "3Y1j0mzNp7KChKFJGyaEnZHLobFoAQ8eLwfaMx8nLbtXAerO")
.build();
// 2.构造请求参数
MeetingsApi.ApiV1MeetingsGetRequest request =
new MeetingsApi.ApiV1MeetingsGetRequest.Builder()
//.operatorId("")
// .operatorIdType("")
.userid("woaJARCQAAJU1EsO73Ww5rn8YHMW6iYA")//管理员id
.instanceid("1")
.meetingCode(meetingCode)
// .pos("")
// .cursory("")
// .isShowAllSubMeetings("")
.build();
// 3.构造 JWT 鉴权器
// 随机数
BigInteger nonce = BigInteger.valueOf(Math.abs((new SecureRandom()).nextInt()));
// 当前时间戳
String timestamp = String.valueOf(System.currentTimeMillis() / 1000L);
AuthenticatorBuilder<JWTAuthenticator> authenticatorBuilder =
new JWTAuthenticator.Builder().nonce(nonce).timestamp(timestamp);
try {
MeetingsApi.ApiV1MeetingsGetResponse response =
client.meetings().v1MeetingsGet(request, authenticatorBuilder);
// response from `v1MeetingsGet`: V1MeetingsGet200Response
System.out.printf("Response from `MeetingsApi.v1MeetingsGet`: \nheader: %s\n%s\n",
response.getHeader(), response.getData());
// 提取主持人 userid
// 5. 处理响应数据
if (response == null || response.getData() == null) {
throw new RuntimeException("API响应数据为空");
}
// 6. 使用更安全的方式解析响应
V1MeetingsGet200Response responseData = response.getData();
if (responseData.getMeetingInfoList() == null || responseData.getMeetingInfoList().isEmpty()) {
throw new RuntimeException("未找到会议信息");
}
V1MeetingsGet200ResponseMeetingInfoListInner meetingInfo = responseData.getMeetingInfoList().get(0);
if (meetingInfo.getCurrentHosts() == null || meetingInfo.getCurrentHosts().isEmpty()) {
throw new RuntimeException("会议没有主持人");
}
String hostUserId = meetingInfo.getCurrentHosts().get(0).getUserid();
if (hostUserId == null || hostUserId.isEmpty()) {
throw new RuntimeException("主持人userid为空");
}
System.out.println("成功获取主持人userid: {}" + hostUserId);
return hostUserId;
} catch (ClientException e) {
System.out.printf("Error when calling `MeetingsApi.v1MeetingsGet`: %s\n", e);
throw new RuntimeException(e);
} catch (ServiceException e) {
System.out.printf("Error when calling `MeetingsApi.v1MeetingsGet`: %s\n", e);
System.out.printf("Full HTTP response: %s\n", new String(e.getApiResp().getRawBody()));
throw new RuntimeException(e);
}
}
/**
* 获取企业微信token
*
* @return
*/
public String getWeComToken() {
//获取token
String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ww1fd8778458e9f1e8&corpsecret=uFRq9Xi8-dVY90LydXYBhjc91JnnfkPUR6lHDdeJ_fo";
String accessToken = "";
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.get()
.build();
try (Response response = client.newCall(request).execute()) {
if (response.isSuccessful()) {
String responseBody = response.body().string();
JSONObject jsonResponse = new JSONObject(responseBody);
accessToken = jsonResponse.getString("access_token");
} else {
throw new RuntimeException("Failed to fetch token. HTTP Code: " + response.code());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return accessToken;
}
// 创建预约会议(传入 wid 作为主持人)
private static Map<String, String> createMeeting(String wid, String weComToken) throws IOException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/meeting/create?access_token=" + weComToken;
JSONObject body = new JSONObject()
.put("admin_userid", wid) // 主持人 userid
.put("title", "自动创建会议")
.put("meeting_start", System.currentTimeMillis() / 1000 + 3600) // 1小时后开始
.put("meeting_duration", 3600)
.put("invitees", new JSONObject() // 添加 invitees 字段
.put("userid", new String[]{wid}) // 将 wid 作为参会人
);
Request request = new Request.Builder()
.url(url)
.post(RequestBody.create(MediaType.parse("application/json"), body.toString()))
.build();
try (Response response = new OkHttpClient().newCall(request).execute()) {
JSONObject json = new JSONObject(response.body().string());
if (json.getInt("errcode") != 0) {
return null; // 或者抛出异常
}
Map<String, String> result = new HashMap<>();
result.put("meetingid", json.getString("meetingid"));
result.put("meeting_code", json.getString("meeting_code"));
return result;
}
}
public List<UserId> mergeUserLists(List<WeComUser> weComUsersIsRepeat0, List<TencentMeetingUser> tencentUsersIsRepeat0) {
List<UserId> userIds = new ArrayList<>();
// 1. 首先构建姓名到TencentMeetingUser的映射(提高查找效率)
Map<String, TencentMeetingUser> tencentUserMap = tencentUsersIsRepeat0.stream()
.collect(Collectors.toMap(
TencentMeetingUser::getUserName,
user -> user,
(existing, replacement) -> existing)); // 如果有重名,保留第一个
// 2. 遍历WeCom用户列表
for (WeComUser weComUser : weComUsersIsRepeat0) {
UserId userId = new UserId();
userId.setUserName(weComUser.getUserName());
userId.setWid(weComUser.getUserId()); // 始终设置WeCom userid
// 检查是否存在同名Tencent用户
TencentMeetingUser tencentUser = tencentUserMap.get(weComUser.getUserName());
if (tencentUser != null) {
userId.setTid(tencentUser.getUserId()); // 设置Tencent userid
} else {
userId.setTid(null); // 可以省略,默认就是null
}
userIds.add(userId);
}
// 3. 添加只存在于Tencent的用户(如果需要)
// 这里根据需求决定是否需要这部分
return userIds;
}
}
// controller/UserController.java
package com.cmeeting.controller;
import com.cmeeting.dto.LoginDTO;
import com.cmeeting.service.UserService;
import com.cmeeting.vo.LoginVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/login")
public LoginVO login(@RequestBody LoginDTO loginDTO) {
return userService.login(loginDTO);
}
}
\ No newline at end of file
package com.cmeeting.controller;
import com.cmeeting.pojo.WeComUser;
import com.cmeeting.service.WeComService;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import static com.cmeeting.WeComAndTencentMeeting.*;
@RestController
@RequestMapping("/wecom")
public class WeComcontroller {
private static final String BASE_URL = "https://qyapi.weixin.qq.com/cgi-bin/user/simplelist";
private static final String ACCESS_TOKEN = "wyYAmCbeVIPvj_Gpdd8ixp_td_hPv2OtTU8gpTGSpB7TaftbpNps5fc_JdCRzpgy_tUv-aGueJpRV6XJeYAG-rqkzCfDV-rcpJbqB0LypjvJUtxPeTmPqEtQuCMwH6euGGVaJZXoc33ATrS70U0T4_4WIrATqpsN2Ed2XsNGyN5N6aG8mq6AyLMG9TokVWx1qd2qF1zVTwve6aqRMWHwXg";
@Autowired
private WeComService weComService;
@GetMapping("/add")
public void addUsers() throws Exception {
dousers();
}
public void dousers() throws Exception {
// 示例:获取部门ID为6的用户列表
int departmentId = 6;
JsonObject result = getUserListByDepartment(departmentId);
// 处理返回结果
if (result.get("errcode").getAsInt() == 0) {
System.out.println("企微获取用户列表成功:");
JsonArray userList = result.getAsJsonArray("userlist");
for (int i = 0; i < userList.size(); i++) {
JsonObject user = userList.get(i).getAsJsonObject();
System.out.println("姓名: " + user.get("name").getAsString() +
", UserID: " + user.get("userid").getAsString());
}
// 2. 转换为WeComUser集合
List<WeComUser> users = convertJsonToWeComUsers(result);
// 3. 检查重名并设置标志
markDuplicateNames(users);
// 4. 批量插入数据库
weComService.batchInsert(users);
}
}
/**
* 根据企业微信部门ID获取用户列表
*
* @param departmentId 部门ID
* @return 包含用户列表的JsonObject
* @throws Exception
*/
public static JsonObject getUserListByDepartment(int departmentId) throws Exception {
String urlStr = BASE_URL + "?access_token=" + ACCESS_TOKEN + "&department_id=" + departmentId;
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
// 使用Gson解析JSON响应
Gson gson = new Gson();
return gson.fromJson(response.toString(), JsonObject.class);
} else {
throw new RuntimeException("HTTP GET请求失败,错误码: " + responseCode);
}
}
}
\ No newline at end of file
// dto/LoginDTO.java
package com.cmeeting.dto;
import lombok.Data;
@Data
public class LoginDTO {
private String username;
private String password;
}
\ No newline at end of file
package com.cmeeting.mapper;
import com.cmeeting.pojo.TencentMeetingUser;
import com.cmeeting.pojo.WeComUser;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface TecentMeetingMapper {
void batchInsertUsers(@Param("userList") List<TencentMeetingUser> userList);
List<TencentMeetingUser> getAlluser();
}
package com.cmeeting.mapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UnifiedMapper {
}
package com.cmeeting.mapper;
import com.cmeeting.pojo.UserId;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserIdMapper {
void insertUsers (List<UserId> userIds);
List<UserId> getUsers();
void updateUser(UserId user);
}
// mapper/UserMapper.java
package com.cmeeting.mapper;
import com.cmeeting.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface UserMapper {
User findByUsername(@Param("username") String username);
}
\ No newline at end of file
package com.cmeeting.mapper;
import com.cmeeting.pojo.WeComUser;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface WeComUserMapper {
List<WeComUser> getSameName() ;
WeComUser selectById(Integer id);
void insert(WeComUser user);
List<WeComUser> getAlluser();
/**
* 批量插入用户
*/
void batchInsert(List<WeComUser> users);
}
\ No newline at end of file
package com.cmeeting.pojo;
import lombok.Data;
@Data
public class TencentMeetingUser {
/**
* 主键ID
*/
private Integer id;
/**
* 用户姓名
*/
private String userName;
/**
* 用户ID
*/
private String userId;
/**
* 是否是重名用户(1:重名, 0:不重名)
*/
private String isrepeatName;
// 无参构造方法
public TencentMeetingUser() {
}
// 全参构造方法
public TencentMeetingUser(Integer id, String userName, String userId, String isrepeatName) {
this.id = id;
this.userName = userName;
this.userId = userId;
this.isrepeatName = isrepeatName;
}
@Override
public String toString() {
return "TencentMeetingUser{" +
"id=" + id +
", userName='" + userName + '\'' +
", userId='" + userId + '\'' +
", isrepeatName='" + isrepeatName + '\'' +
'}';
}
}
// pojo/User.java
package com.cmeeting.pojo;
import lombok.Data;
@Data
public class User {
private Long id;
private String username;
private String password;
private String salt;
// 其他字段...
public User() {
}
public User(Long id, String username, String password, String salt) {
this.id = id;
this.username = username;
this.password = password;
this.salt = salt;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", salt='" + salt + '\'' +
'}';
}
}
\ No newline at end of file
package com.cmeeting.pojo;
import lombok.Data;
@Data
public class UserId {
/**
* 主键ID
*/
private Integer id;
/**
* 员工姓名
*/
private String userName;
/**
* 企业微信userid
*/
private String wid;
/**
* 腾讯会议userid
*/
private String tid;
// 无参构造方法
public UserId() {
}
// 全参构造方法
public UserId(Integer id, String userName, String wid, String tid) {
this.id = id;
this.userName = userName;
this.wid = wid;
this.tid = tid;
}
// 全参构造方法
public UserId( String userName, String wid, String tid) {
this.userName = userName;
this.wid = wid;
this.tid = tid;
}
}
package com.cmeeting.pojo;
import lombok.Data;
@Data
public class WeComUser {
private Integer id;
private String userName;
private String userId;
private String isRepeatName;
public WeComUser() {
}
public WeComUser(Integer id, String userName, String userId, String isRepeatName) {
this.id = id;
this.userName = userName;
this.userId = userId;
this.isRepeatName = isRepeatName;
}
@Override
public String toString() {
return "WeComUser{" +
"id=" + id +
", userName='" + userName + '\'' +
", userId='" + userId + '\'' +
", isRepeatName='" + isRepeatName + '\'' +
'}';
}
}
package com.cmeeting.service;
import com.cmeeting.pojo.TencentMeetingUser;
import java.util.List;
public interface TecentMeetingService {
void batchInsert(List<TencentMeetingUser> users);
}
// service/UserService.java
package com.cmeeting.service;
import com.cmeeting.dto.LoginDTO;
import com.cmeeting.vo.LoginVO;
public interface UserService {
LoginVO login(LoginDTO loginDTO);
}
\ No newline at end of file
package com.cmeeting.service;
import com.cmeeting.pojo.WeComUser;
import java.util.List;
public interface WeComService {
void batchInsert(List<WeComUser> users);
}
package com.cmeeting.service.impl;
import com.cmeeting.mapper.TecentMeetingMapper;
import com.cmeeting.pojo.TencentMeetingUser;
import com.cmeeting.service.TecentMeetingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class TecentMeetingServiceImpl implements TecentMeetingService {
@Autowired
private TecentMeetingMapper tecentMeetingMapper;
@Override
public void batchInsert(List<TencentMeetingUser> users) {
tecentMeetingMapper.batchInsertUsers(users);
}
}
// service/impl/UserServiceImpl.java
package com.cmeeting.service.impl;
import com.cmeeting.dto.LoginDTO;
import com.cmeeting.mapper.UserMapper;
import com.cmeeting.pojo.User;
import com.cmeeting.service.UserService;
import com.cmeeting.vo.LoginVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public LoginVO login(LoginDTO loginDTO) {
// 1. 验证用户名是否存在
User user = userMapper.findByUsername(loginDTO.getUsername());
if (user == null) {
throw new RuntimeException("用户名或密码错误");
}
// 2. 验证密码
String inputPassword = encryptPassword(loginDTO.getPassword(), user.getSalt());
if (!inputPassword.equals(user.getPassword())) {
throw new RuntimeException("用户名或密码错误");
}
// 3. 生成token (这里简化处理,实际应该用JWT等)
String token = generateToken(user);
// 4. 返回登录结果
LoginVO loginVO = new LoginVO();
loginVO.setUserId(user.getId());
loginVO.setUsername(user.getUsername());
loginVO.setToken(token);
return loginVO;
}
private String encryptPassword(String password, String salt) {
// 实际项目中应该使用更安全的加密方式
return DigestUtils.md5DigestAsHex((password + salt).getBytes());
}
private String generateToken(User user) {
// 简化处理,实际应该用JWT等安全方式生成token
return DigestUtils.md5DigestAsHex((user.getId() + user.getUsername() + System.currentTimeMillis()).getBytes());
}
}
\ No newline at end of file
package com.cmeeting.service.impl;
import com.cmeeting.mapper.WeComUserMapper;
import com.cmeeting.pojo.WeComUser;
import com.cmeeting.service.WeComService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class WeComServiceImpl implements WeComService {
@Autowired
private WeComUserMapper weComUserMapper;
@Override
public void batchInsert(List<WeComUser> users) {
weComUserMapper.batchInsert(users);
}
}
package com.cmeeting.util;
import com.cmeeting.mapper.WeComUserMapper;
import com.cmeeting.pojo.WeComUser;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonElement;
import java.util.*;
import java.util.stream.Collectors;
import static com.cmeeting.WeComAndTencentMeeting.getUserListByDepartment;
public class WeComUserService {
// 假设这是你的MyBatis Mapper
private final WeComUserMapper weComUserMapper;
public WeComUserService(WeComUserMapper weComUserMapper) {
this.weComUserMapper = weComUserMapper;
}
/**
* 处理部门用户数据并存入数据库
*/
public void processAndSaveDepartmentUsers(int departmentId) throws Exception {
// 1. 获取企业微信API数据
JsonObject result = getUserListByDepartment(departmentId);
// 2. 转换为WeComUser集合
List<WeComUser> users = convertJsonToWeComUsers(result);
// 3. 检查重名并设置标志
markDuplicateNames(users);
// 4. 批量插入数据库
weComUserMapper.batchInsert(users);
}
/**
* 将企业微信返回的JSON转换为WeComUser列表
*/
private List<WeComUser> convertJsonToWeComUsers(JsonObject json) {
List<WeComUser> users = new ArrayList<>();
if (json.has("userlist") && json.get("userlist").isJsonArray()) {
JsonArray userList = json.getAsJsonArray("userlist");
Gson gson = new Gson();
for (JsonElement element : userList) {
JsonObject userJson = element.getAsJsonObject();
WeComUser user = new WeComUser();
// 根据企业微信API实际返回字段调整
user.setUserId(userJson.get("userid").getAsString());
user.setUserName(userJson.get("name").getAsString());
// 其他字段设置...
users.add(user);
}
}
return users;
}
/**
* 标记重名用户
*/
private void markDuplicateNames(List<WeComUser> users) {
// 按姓名分组,统计每个名字出现的次数
Map<String, Long> nameCountMap = users.stream()
.collect(Collectors.groupingBy(WeComUser::getUserName, Collectors.counting()));
// 设置是否重名标志
users.forEach(user -> {
if (nameCountMap.get(user.getUserName()) > 1) {
user.setIsRepeatName("1"); // 重名
} else {
user.setIsRepeatName("0"); // 不重名
}
});
}
}
\ No newline at end of file
// vo/LoginVO.java
package com.cmeeting.vo;
import lombok.Data;
@Data
public class LoginVO {
private Long userId;
private String username;
private String token;
}
\ No newline at end of file
server.port=8080
# ????????
tencent.meeting.token=QQZNb7xWQB47MpZF4C2DFAkv8
tencent.meeting.aesKey=agy6ALUePp34lljWz1uIQWa7yQq3dgxxQNmfaN9GROm
# application.yml
spring.datasource.url=jdbc:mysql://192.168.10.155:3306/cmeeting?useSSL=false&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=qizhi123
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# MyBatis ??
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.cmeeting.pojo\
#??????
mybatis.configuration.map-underscore-to-camel-case: true
logging.level.com.zaxxer.hikari=INFO
\ No newline at end of file
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cmeeting.mapper.TecentMeetingMapper">
<insert id="batchInsertUsers" parameterType="list">
INSERT INTO user_tencentmeeting (
user_name,
user_id,
isrepeat_name
) VALUES
<foreach collection="userList" item="user" separator=",">
(
#{user.userName},
#{user.userId},
#{user.isrepeatName}
)
</foreach>
</insert>
<select id="getAlluser" resultType="com.cmeeting.pojo.TencentMeetingUser">
SELECT * FROM user_tencentmeeting
</select>
</mapper>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cmeeting.mapper.UserIdMapper">
<!-- 批量插入用户ID信息 -->
<insert id="insertUsers" parameterType="List">
INSERT INTO userid (
userName,
Wid,
Tid
) VALUES
<foreach collection="list" item="item" separator=",">
(
#{item.userName},
#{item.wid},
#{item.tid}
)
</foreach>
</insert>
<!-- 更新 Tid 字段 -->
<update id="updateUser" parameterType="com.cmeeting.pojo.UserId">
UPDATE userid
SET Tid = #{tid}
WHERE wid = #{wid}
</update>
<select id="getUsers" resultType="com.cmeeting.pojo.UserId">
select * from userid
</select>
</mapper>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cmeeting.mapper.UserMapper">
<select id="findByUsername" resultType="com.cmeeting.pojo.User">
SELECT * FROM user WHERE username = #{username}
</select>
</mapper>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cmeeting.mapper.WeComUserMapper">
<select id="selectById" resultType="com.cmeeting.pojo.WeComUser">
SELECT id, user_name as userName, user_id as userId, isepeat_name as isRepeatName
FROM user_wecom
WHERE id = #{id}
</select>
<insert id="insert" parameterType="com.cmeeting.pojo.WeComUser">
INSERT INTO user_wecom (id, user_name, user_id, isepeat_name)
VALUES (#{id}, #{userName}, #{userId}, #{isRepeatName})
</insert>
<insert id="batchInsert" parameterType="list">
INSERT INTO user_wecom ( user_name, user_id, isepeat_name)
VALUES
<foreach collection="list" item="user" separator=",">
(
#{user.userName},
#{user.userId},
#{user.isRepeatName}
)
</foreach>
</insert>
<select id="getAlluser" resultType="com.cmeeting.pojo.WeComUser">
SELECT * FROM user_wecom
</select>
<select id="getSameName" resultType="com.cmeeting.pojo.WeComUser">
SELECT * FROM user_wecom
WHERE isrepeat_name = "1"
</select>
</mapper>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论