├── .dockerignore
├── .editorconfig
├── .github
├── dependabot.yml
└── workflows
│ ├── docker.yml
│ ├── dockerhub.yml
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── .mvn
└── wrapper
│ └── maven-wrapper.properties
├── Dockerfile
├── LICENSE
├── README.md
├── docker-bake.hcl
├── mvnw
├── pom.xml
├── sdk
└── libWeWorkFinanceSdk_Java.so
└── src
└── main
├── assembly
└── assembly.xml
├── java
└── com
│ ├── chinayin
│ └── wework
│ │ └── chatdata
│ │ ├── WeworkApplication.java
│ │ ├── config
│ │ ├── ExecutorServiceConfig.java
│ │ ├── MnsClientConfig.java
│ │ ├── OssClientConfig.java
│ │ ├── WeWorkConfig.java
│ │ └── WeWorkEncryptConfig.java
│ │ ├── model
│ │ ├── ChatDataDTO.java
│ │ ├── ChatDataDetailDTO.java
│ │ ├── MediaFileDTO.java
│ │ ├── MessageDTO.java
│ │ └── messagetype
│ │ │ ├── AbstractTypeFile.java
│ │ │ ├── TypeAgree.java
│ │ │ ├── TypeCalendar.java
│ │ │ ├── TypeCard.java
│ │ │ ├── TypeChatRecord.java
│ │ │ ├── TypeCollect.java
│ │ │ ├── TypeDisagree.java
│ │ │ ├── TypeDoc.java
│ │ │ ├── TypeEmotion.java
│ │ │ ├── TypeFile.java
│ │ │ ├── TypeImage.java
│ │ │ ├── TypeLink.java
│ │ │ ├── TypeLocation.java
│ │ │ ├── TypeMarkdown.java
│ │ │ ├── TypeMeeting.java
│ │ │ ├── TypeMixed.java
│ │ │ ├── TypeNews.java
│ │ │ ├── TypeQyDiskFile.java
│ │ │ ├── TypeRedPacket.java
│ │ │ ├── TypeRevoke.java
│ │ │ ├── TypeSphFeed.java
│ │ │ ├── TypeText.java
│ │ │ ├── TypeTodo.java
│ │ │ ├── TypeVideo.java
│ │ │ ├── TypeVoice.java
│ │ │ ├── TypeVoiptext.java
│ │ │ ├── TypeVote.java
│ │ │ ├── TypeWeapp.java
│ │ │ └── item
│ │ │ ├── TypeChatRecordItem.java
│ │ │ └── TypeMixedItem.java
│ │ ├── schedule
│ │ └── ChatDataSchedule.java
│ │ └── service
│ │ ├── ChatDataService.java
│ │ ├── ClientService.java
│ │ ├── EncryptService.java
│ │ ├── FileOutputService.java
│ │ ├── MnsClientService.java
│ │ ├── OssClientService.java
│ │ └── impl
│ │ ├── ChatDataServiceImpl.java
│ │ ├── ClientServiceImpl.java
│ │ ├── EncryptServiceImpl.java
│ │ ├── FileOutputServiceImpl.java
│ │ ├── MnsClientServiceImpl.java
│ │ └── OssClientServiceImpl.java
│ └── tencent
│ └── wework
│ └── Finance.java
└── resources
├── application.properties
├── logback-spring.xml
└── supervisord.conf
/.dockerignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .idea
3 | .vscode
4 | .env
5 | .git
6 | .gitattributes
7 | .gitignore
8 | .github
9 | target/
10 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 |
8 | [**.{yml, sh}]
9 | indent_style = space
10 | indent_size = 2
11 |
12 | [Dockerfile]
13 | indent_style = space
14 | indent_size = 2
15 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "github-actions"
4 | directory: "/"
5 | target-branch: "master"
6 | schedule:
7 | interval: "monthly"
8 | - package-ecosystem: "maven"
9 | directory: "/"
10 | target-branch: "master"
11 | schedule:
12 | interval: "weekly"
13 | ignore:
14 | - dependency-name: com.alibaba:fastjson
15 | versions: [">=2.0"]
16 | - dependency-name: org.springframework.boot:spring-boot-starter-parent
17 | versions: [">=3.0"]
18 |
--------------------------------------------------------------------------------
/.github/workflows/docker.yml:
--------------------------------------------------------------------------------
1 | name: Docker CI
2 |
3 | on:
4 | push:
5 | tags:
6 | - "v*.*.*"
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout code
13 | uses: actions/checkout@v4
14 | - name: Set variables
15 | id: vars
16 | run: |
17 | echo "repository=${GITHUB_ACTOR}/$(basename ${GITHUB_REPOSITORY})" >> $GITHUB_OUTPUT
18 | echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT
19 | - name: Set up QEMU
20 | uses: docker/setup-qemu-action@v3
21 | - name: Set up Docker Buildx
22 | uses: docker/setup-buildx-action@v3
23 | - name: Login to DockerHub
24 | uses: docker/login-action@v3
25 | with:
26 | username: ${{ secrets.DOCKER_HUB_USERNAME }}
27 | password: ${{ secrets.DOCKER_HUB_TOKEN }}
28 | - name: Build and push
29 | uses: docker/bake-action@v5
30 | env:
31 | version: ${{ steps.vars.outputs.tag }}
32 | with:
33 | pull: true
34 | push: true
35 | no-cache: true
36 | files: docker-bake.hcl
37 |
--------------------------------------------------------------------------------
/.github/workflows/dockerhub.yml:
--------------------------------------------------------------------------------
1 | name: Docker Hub Description
2 |
3 | on:
4 | push:
5 | tags:
6 | - "v*.*.*"
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout code
13 | uses: actions/checkout@v4
14 | - name: Set variables
15 | id: vars
16 | run: |
17 | echo "repository=${GITHUB_ACTOR}/weworkchat-sdk" >> $GITHUB_OUTPUT
18 | - name: Docker Hub Description
19 | uses: peter-evans/dockerhub-description@v4
20 | with:
21 | username: ${{ secrets.DOCKER_HUB_USERNAME }}
22 | password: ${{ secrets.DOCKER_HUB_PASSWORD }}
23 | repository: ${{ steps.vars.outputs.repository }}
24 | short-description: ${{ github.event.repository.description }}
25 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release Ci
2 |
3 | on:
4 | push:
5 | tags:
6 | - "v*.*.*"
7 |
8 | jobs:
9 | release:
10 | runs-on: ubuntu-latest
11 | strategy:
12 | matrix:
13 | java: [8]
14 | name: Release JDK ${{ matrix.java }}
15 | steps:
16 | - uses: actions/checkout@v4
17 | - name: Setup java
18 | uses: actions/setup-java@v4
19 | with:
20 | distribution: 'adopt'
21 | java-version: ${{ matrix.java }}
22 | cache: 'maven'
23 | - name: Build with Maven
24 | run: mvn clean package -B --file pom.xml
25 | - name: Release
26 | uses: softprops/action-gh-release@v2
27 | if: startsWith(github.ref, 'refs/tags/')
28 | with:
29 | generate_release_notes: "true"
30 | files: |
31 | target/*.jar
32 | target/*.tar.gz
33 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Java CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | matrix:
10 | java: [8, 17]
11 | name: Test JDK ${{ matrix.java }}
12 | steps:
13 | - uses: actions/checkout@v4
14 | - name: Setup java
15 | uses: actions/setup-java@v4
16 | with:
17 | distribution: 'adopt'
18 | java-version: ${{ matrix.java }}
19 | cache: 'maven'
20 | - name: Build with Maven
21 | run: mvn clean package -B --file pom.xml
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 | logs/
7 |
8 | # BlueJ files
9 | *.ctxt
10 |
11 | # Mobile Tools for Java (J2ME)
12 | .mtj.tmp/
13 |
14 | # Package Files #
15 | *.jar
16 | *.war
17 | *.nar
18 | *.ear
19 | *.zip
20 | *.tar.gz
21 | *.rar
22 |
23 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
24 | hs_err_pid*
25 |
26 | # IntelliJ IDEA
27 | .idea
28 | *.iws
29 | *.iml
30 | *.ipr
31 |
32 | # NetBeans
33 | /nbproject/private/
34 | /nbbuild/
35 | /dist/
36 | /nbdist/
37 | /.nb-gradle/
38 | build/
39 |
40 | # VS Code
41 | .vscode/
42 |
43 | # other
44 | !.mvn/wrapper/maven-wrapper.properties
45 | !**/src/main/**
46 | !**/src/test/**
47 | **/.DS_Store
48 | target/
49 | **/application-dev.properties
50 | **/application-prod.properties
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionUrl=https://maven.aliyun.com/repository/public/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
2 | wrapperUrl=https://maven.aliyun.com/repository/public/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
3 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | #
2 | #
3 | #--------------------------------------------------------------------------
4 | # WeworkChatSDK
5 | #--------------------------------------------------------------------------
6 | #
7 |
8 | FROM --platform=linux/amd64 chinayin/maven:3-jdk-8 AS builder
9 |
10 | RUN set -eux \
11 | #&& install_packages tree vim \
12 | && mkdir -p ~/.m2 \
13 | #&& echo 'aliyun*aliyunhttps://maven.aliyun.com/repository/public' >> ~/.m2/settings.xml \
14 | && mkdir /app
15 |
16 | WORKDIR /app
17 | ADD . /app
18 |
19 | RUN set -eux \
20 | && mvn -B package --file pom.xml \
21 | && ls -l target \
22 | && SDK_VERSION=$(grep '' pom.xml |head -n1 |tr -cd "[0-9.]") \
23 | && echo $SDK_VERSION \
24 | && cp -f target/WeworkChatSDK-${SDK_VERSION}-bundle.tar.gz target/bundle.tar.gz
25 |
26 | FROM --platform=linux/amd64 chinayin/openjdk:8-jre
27 | ENV TZ=PRC
28 | ENV PARAMS=""
29 |
30 | COPY --from=builder /app/target/bundle.tar.gz /app/bundle.tar.gz
31 | WORKDIR /app
32 |
33 | RUN set -eux \
34 | && tar -xzf bundle.tar.gz -C /app --strip-components=1 \
35 | && mkdir -p /usr/lib64 \
36 | && mv *.so /usr/lib64 \
37 | && rm -f bundle.tar.gz
38 |
39 | ENTRYPOINT ["sh","-c","java -jar $JAVA_OPTS WeworkChatSDK-*.jar $PARAMS"]
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### WeworkChatSDK
2 |
3 | [](https://github.com/chinayin/WeworkChatSDK/actions)
4 | [](https://github.com/chinayin)
5 | 
6 |
7 | 企业微信会话存档服务(WeworkChatSDK),提供一键接入java sdk,投递数据到阿里云MNS、OSS。
8 |
9 | ### 功能介绍:
10 |
11 | - 实时获取企业微信会话存档聊天数据,处理速度快。
12 | - 多线程处理聊天资源,支持上G聊天附件文件上传`对象存储 OSS`。
13 | - 聊天、资源数据投递`队列服务 MNS`,非java技术栈也可通过队列消费。
14 | - 支持docker部署。
15 | - 生产环境已稳定使用。
16 |
17 | ### 准备工作:
18 |
19 | - 申请并配置阿里云`消息服务 MNS`、`对象存储 OSS`。
20 | - 获取阿里云AccessKey,配置 `application.properties` 中相应配置项。
21 | - 打包,更新配置文件。
22 | - 安装`supervisord`,配置并执行程序。
23 |
24 |
25 | ### 配置文件`application.properties`说明
26 |
27 | #### 阿里云 消息服务 MNS
28 | ```properties
29 | # 服务可用区endpoint (区分内外网地址)
30 | mns.endpoint=http://
31 | mns.accessKeyId=
32 | mns.accessKeySecret=
33 | # 消息队列名称 (可自定义)
34 | mns.queue=WeworkSyncMessageQueue
35 | ```
36 |
37 | #### 阿里云 对象存储 OSS
38 | ```properties
39 | # 服务可用区endpoint (区分内外网地址)
40 | oss.endpoint=oss-cn-beijing.aliyuncs.com
41 | oss.accessKeyId=
42 | oss.accessKeySecret=
43 | # Bucket名称 (可自定义)
44 | oss.bucket=WeworkResources
45 | ```
46 |
47 | #### 企业微信会话存档 - 应用配置
48 | ```properties
49 | # 企业ID(在我的企业-企业信息里找到)
50 | app.corpId=
51 | # 应用Secret(会话存档应用中找到)
52 | app.corpSecret=
53 | ```
54 |
55 | #### 企业微信会话存档 - 私钥配置
56 | 说明:privateKey.后面的值为版本号(int值)
57 | ```properties
58 | encrypt.privateKey.1=
59 | encrypt.privateKey.2=
60 | ```
61 |
62 | ### 部署
63 |
64 | - 复制`sdk`目录下`.so`文件至Java Library
65 |
66 | 官方`.so`下载链接:https://developer.work.weixin.qq.com/document/path/91774
67 |
68 | 查看目录方法:
69 | ```java
70 | public class Demo {
71 | public static void main(String[] args) {
72 | System.out.println(System.getProperty("java.library.path"));
73 | }
74 | }
75 | ```
76 |
77 | - 生成打包文件
78 | ```bash
79 | ./mvnw clean package
80 | ```
81 |
82 | - 命令行方式运行
83 | ```bash
84 | java -jar WeworkChatSDK-[version].jar
85 | # 生产环境 (目录存在 application-prod.properties )
86 | java -jar WeworkChatSDK-[version].jar --spring.profiles.active=prod
87 | ```
88 |
89 | - `supervisord`方式运行,参照配置文件: `src\main\resources\supervisord.conf`
90 |
91 |
92 | ### 附录:
93 |
94 | #### 使用Alibaba开源的Java诊断工具 `Arthas`
95 |
96 | https://arthas.aliyun.com/
97 |
98 | 下载arthas-boot.jar,然后用java -jar的方式启动:
99 | ```bash
100 | curl -O https://arthas.aliyun.com/arthas-boot.jar
101 | java -jar arthas-boot.jar
102 | ```
103 |
104 | 打印帮助信息:
105 | ```bash
106 | java -jar arthas-boot.jar -h
107 | ```
108 |
109 | 选择应用java进程:
110 | ```bash
111 | $ $ java -jar arthas-boot.jar
112 | * [1]: 12345
113 | ```
114 |
115 | 输入dashboard,按回车/enter,会展示当前进程的信息,按ctrl+c可以中断执行。
116 |
117 | #### 企业微信会话存档官方文档
118 |
119 | https://developer.work.weixin.qq.com/document/path/91361
120 |
121 |
--------------------------------------------------------------------------------
/docker-bake.hcl:
--------------------------------------------------------------------------------
1 | variable "version" {
2 | default = ""
3 | }
4 |
5 | variable "repo" {
6 | default = "chinayin/weworkchat-sdk"
7 | }
8 |
9 | variable "registry" {
10 | default = "docker.io"
11 | }
12 |
13 | variable "repository" {
14 | default = "${registry}/${repo}"
15 | }
16 |
17 | function "platforms" {
18 | params = []
19 | result = ["linux/amd64"]
20 | }
21 |
22 | target "_all_platforms" {
23 | platforms = platforms()
24 | }
25 |
26 | group "default" {
27 | targets = ["jre8"]
28 | }
29 |
30 | target "jre8" {
31 | inherits = ["_all_platforms"]
32 | context = "."
33 | tags = [
34 | "${repository}:latest",
35 | "${repository}:${version}",
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/mvnw:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # ----------------------------------------------------------------------------
3 | # Licensed to the Apache Software Foundation (ASF) under one
4 | # or more contributor license agreements. See the NOTICE file
5 | # distributed with this work for additional information
6 | # regarding copyright ownership. The ASF licenses this file
7 | # to you under the Apache License, Version 2.0 (the
8 | # "License"); you may not use this file except in compliance
9 | # with the License. You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing,
14 | # software distributed under the License is distributed on an
15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | # KIND, either express or implied. See the License for the
17 | # specific language governing permissions and limitations
18 | # under the License.
19 | # ----------------------------------------------------------------------------
20 |
21 | # ----------------------------------------------------------------------------
22 | # Maven Start Up Batch script
23 | #
24 | # Required ENV vars:
25 | # ------------------
26 | # JAVA_HOME - location of a JDK home dir
27 | #
28 | # Optional ENV vars
29 | # -----------------
30 | # M2_HOME - location of maven2's installed home dir
31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven
32 | # e.g. to debug Maven itself, use
33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files
35 | # ----------------------------------------------------------------------------
36 |
37 | if [ -z "$MAVEN_SKIP_RC" ] ; then
38 |
39 | if [ -f /etc/mavenrc ] ; then
40 | . /etc/mavenrc
41 | fi
42 |
43 | if [ -f "$HOME/.mavenrc" ] ; then
44 | . "$HOME/.mavenrc"
45 | fi
46 |
47 | fi
48 |
49 | # OS specific support. $var _must_ be set to either true or false.
50 | cygwin=false;
51 | darwin=false;
52 | mingw=false
53 | case "`uname`" in
54 | CYGWIN*) cygwin=true ;;
55 | MINGW*) mingw=true;;
56 | Darwin*) darwin=true
57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
59 | if [ -z "$JAVA_HOME" ]; then
60 | if [ -x "/usr/libexec/java_home" ]; then
61 | export JAVA_HOME="`/usr/libexec/java_home`"
62 | else
63 | export JAVA_HOME="/Library/Java/Home"
64 | fi
65 | fi
66 | ;;
67 | esac
68 |
69 | if [ -z "$JAVA_HOME" ] ; then
70 | if [ -r /etc/gentoo-release ] ; then
71 | JAVA_HOME=`java-config --jre-home`
72 | fi
73 | fi
74 |
75 | if [ -z "$M2_HOME" ] ; then
76 | ## resolve links - $0 may be a link to maven's home
77 | PRG="$0"
78 |
79 | # need this for relative symlinks
80 | while [ -h "$PRG" ] ; do
81 | ls=`ls -ld "$PRG"`
82 | link=`expr "$ls" : '.*-> \(.*\)$'`
83 | if expr "$link" : '/.*' > /dev/null; then
84 | PRG="$link"
85 | else
86 | PRG="`dirname "$PRG"`/$link"
87 | fi
88 | done
89 |
90 | saveddir=`pwd`
91 |
92 | M2_HOME=`dirname "$PRG"`/..
93 |
94 | # make it fully qualified
95 | M2_HOME=`cd "$M2_HOME" && pwd`
96 |
97 | cd "$saveddir"
98 | # echo Using m2 at $M2_HOME
99 | fi
100 |
101 | # For Cygwin, ensure paths are in UNIX format before anything is touched
102 | if $cygwin ; then
103 | [ -n "$M2_HOME" ] &&
104 | M2_HOME=`cygpath --unix "$M2_HOME"`
105 | [ -n "$JAVA_HOME" ] &&
106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
107 | [ -n "$CLASSPATH" ] &&
108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
109 | fi
110 |
111 | # For Mingw, ensure paths are in UNIX format before anything is touched
112 | if $mingw ; then
113 | [ -n "$M2_HOME" ] &&
114 | M2_HOME="`(cd "$M2_HOME"; pwd)`"
115 | [ -n "$JAVA_HOME" ] &&
116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
117 | fi
118 |
119 | if [ -z "$JAVA_HOME" ]; then
120 | javaExecutable="`which javac`"
121 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
122 | # readlink(1) is not available as standard on Solaris 10.
123 | readLink=`which readlink`
124 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
125 | if $darwin ; then
126 | javaHome="`dirname \"$javaExecutable\"`"
127 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
128 | else
129 | javaExecutable="`readlink -f \"$javaExecutable\"`"
130 | fi
131 | javaHome="`dirname \"$javaExecutable\"`"
132 | javaHome=`expr "$javaHome" : '\(.*\)/bin'`
133 | JAVA_HOME="$javaHome"
134 | export JAVA_HOME
135 | fi
136 | fi
137 | fi
138 |
139 | if [ -z "$JAVACMD" ] ; then
140 | if [ -n "$JAVA_HOME" ] ; then
141 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
142 | # IBM's JDK on AIX uses strange locations for the executables
143 | JAVACMD="$JAVA_HOME/jre/sh/java"
144 | else
145 | JAVACMD="$JAVA_HOME/bin/java"
146 | fi
147 | else
148 | JAVACMD="`which java`"
149 | fi
150 | fi
151 |
152 | if [ ! -x "$JAVACMD" ] ; then
153 | echo "Error: JAVA_HOME is not defined correctly." >&2
154 | echo " We cannot execute $JAVACMD" >&2
155 | exit 1
156 | fi
157 |
158 | if [ -z "$JAVA_HOME" ] ; then
159 | echo "Warning: JAVA_HOME environment variable is not set."
160 | fi
161 |
162 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
163 |
164 | # traverses directory structure from process work directory to filesystem root
165 | # first directory with .mvn subdirectory is considered project base directory
166 | find_maven_basedir() {
167 |
168 | if [ -z "$1" ]
169 | then
170 | echo "Path not specified to find_maven_basedir"
171 | return 1
172 | fi
173 |
174 | basedir="$1"
175 | wdir="$1"
176 | while [ "$wdir" != '/' ] ; do
177 | if [ -d "$wdir"/.mvn ] ; then
178 | basedir=$wdir
179 | break
180 | fi
181 | # workaround for JBEAP-8937 (on Solaris 10/Sparc)
182 | if [ -d "${wdir}" ]; then
183 | wdir=`cd "$wdir/.."; pwd`
184 | fi
185 | # end of workaround
186 | done
187 | echo "${basedir}"
188 | }
189 |
190 | # concatenates all lines of a file
191 | concat_lines() {
192 | if [ -f "$1" ]; then
193 | echo "$(tr -s '\n' ' ' < "$1")"
194 | fi
195 | }
196 |
197 | BASE_DIR=`find_maven_basedir "$(pwd)"`
198 | if [ -z "$BASE_DIR" ]; then
199 | exit 1;
200 | fi
201 |
202 | ##########################################################################################
203 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
204 | # This allows using the maven wrapper in projects that prohibit checking in binary data.
205 | ##########################################################################################
206 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
207 | if [ "$MVNW_VERBOSE" = true ]; then
208 | echo "Found .mvn/wrapper/maven-wrapper.jar"
209 | fi
210 | else
211 | if [ "$MVNW_VERBOSE" = true ]; then
212 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
213 | fi
214 | if [ -n "$MVNW_REPOURL" ]; then
215 | jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
216 | else
217 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
218 | fi
219 | while IFS="=" read key value; do
220 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
221 | esac
222 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
223 | if [ "$MVNW_VERBOSE" = true ]; then
224 | echo "Downloading from: $jarUrl"
225 | fi
226 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
227 | if $cygwin; then
228 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
229 | fi
230 |
231 | if command -v wget > /dev/null; then
232 | if [ "$MVNW_VERBOSE" = true ]; then
233 | echo "Found wget ... using wget"
234 | fi
235 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
236 | wget "$jarUrl" -O "$wrapperJarPath"
237 | else
238 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
239 | fi
240 | elif command -v curl > /dev/null; then
241 | if [ "$MVNW_VERBOSE" = true ]; then
242 | echo "Found curl ... using curl"
243 | fi
244 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
245 | curl -o "$wrapperJarPath" "$jarUrl" -f
246 | else
247 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
248 | fi
249 |
250 | else
251 | if [ "$MVNW_VERBOSE" = true ]; then
252 | echo "Falling back to using Java to download"
253 | fi
254 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
255 | # For Cygwin, switch paths to Windows format before running javac
256 | if $cygwin; then
257 | javaClass=`cygpath --path --windows "$javaClass"`
258 | fi
259 | if [ -e "$javaClass" ]; then
260 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
261 | if [ "$MVNW_VERBOSE" = true ]; then
262 | echo " - Compiling MavenWrapperDownloader.java ..."
263 | fi
264 | # Compiling the Java class
265 | ("$JAVA_HOME/bin/javac" "$javaClass")
266 | fi
267 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
268 | # Running the downloader
269 | if [ "$MVNW_VERBOSE" = true ]; then
270 | echo " - Running MavenWrapperDownloader.java ..."
271 | fi
272 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
273 | fi
274 | fi
275 | fi
276 | fi
277 | ##########################################################################################
278 | # End of extension
279 | ##########################################################################################
280 |
281 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
282 | if [ "$MVNW_VERBOSE" = true ]; then
283 | echo $MAVEN_PROJECTBASEDIR
284 | fi
285 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
286 |
287 | # For Cygwin, switch paths to Windows format before running java
288 | if $cygwin; then
289 | [ -n "$M2_HOME" ] &&
290 | M2_HOME=`cygpath --path --windows "$M2_HOME"`
291 | [ -n "$JAVA_HOME" ] &&
292 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
293 | [ -n "$CLASSPATH" ] &&
294 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
295 | [ -n "$MAVEN_PROJECTBASEDIR" ] &&
296 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
297 | fi
298 |
299 | # Provide a "standardized" way to retrieve the CLI args that will
300 | # work with both Windows and non-Windows executions.
301 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
302 | export MAVEN_CMD_LINE_ARGS
303 |
304 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
305 |
306 | exec "$JAVACMD" \
307 | $MAVEN_OPTS \
308 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
309 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
310 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
311 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | com.chinayin
6 | WeworkChatSDK
7 | WeworkChatSDK
8 | 企业微信会话存档服务(WeworkChatSDK),提供一键接入java sdk,导出数据到阿里云msn、oss
9 | 1.4.1
10 |
11 |
12 |
13 | Apache License, Version 2.0
14 | https://www.apache.org/licenses/LICENSE-2.0.txt
15 |
16 |
17 |
18 |
19 | org.springframework.boot
20 | spring-boot-starter-parent
21 | 2.7.18
22 |
23 |
24 |
25 |
26 | 1.8
27 |
28 |
29 |
30 |
31 | org.springframework.boot
32 | spring-boot-starter
33 |
34 |
35 | org.springframework.boot
36 | spring-boot-configuration-processor
37 | true
38 |
39 |
40 | org.projectlombok
41 | lombok
42 | true
43 |
44 |
45 | com.alibaba
46 | fastjson
47 | 1.2.83
48 |
49 |
50 | com.aliyun.mns
51 | aliyun-sdk-mns
52 | 1.3.0
53 |
54 |
55 | com.aliyun.oss
56 | aliyun-sdk-oss
57 | 3.18.1
58 |
59 |
60 | io.sentry
61 | sentry-spring-boot-starter
62 | 6.34.0
63 |
64 |
65 | org.springframework.boot
66 | spring-boot-starter-test
67 | test
68 |
69 |
70 | org.junit.vintage
71 | junit-vintage-engine
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | org.springframework.boot
81 | spring-boot-maven-plugin
82 |
83 |
84 | org.apache.maven.plugins
85 | maven-assembly-plugin
86 | 3.7.1
87 |
88 |
89 | bundle
90 | package
91 |
92 | single
93 |
94 |
95 |
96 | src/main/assembly/assembly.xml
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/sdk/libWeWorkFinanceSdk_Java.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chinayin/WeworkChatSDK/1b039f2403f6ca6c9b0f8d81805962d50340e29e/sdk/libWeWorkFinanceSdk_Java.so
--------------------------------------------------------------------------------
/src/main/assembly/assembly.xml:
--------------------------------------------------------------------------------
1 |
2 | bundle
3 |
4 | tar.gz
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | src/main/resources
15 | /
16 |
17 | application.properties
18 |
19 |
20 |
21 | 0644
22 |
23 |
24 | sdk
25 | /
26 |
27 | *.so
28 |
29 |
30 |
31 | ${project.build.directory}
32 | /
33 |
34 | ${project.build.finalName}.jar
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/WeworkApplication.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata;
2 |
3 | import com.tencent.wework.Finance;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.springframework.boot.WebApplicationType;
6 | import org.springframework.boot.autoconfigure.SpringBootApplication;
7 | import org.springframework.boot.builder.SpringApplicationBuilder;
8 | import org.springframework.scheduling.annotation.EnableScheduling;
9 |
10 | /**
11 | * @author chianyin
12 | */
13 | @SpringBootApplication
14 | @EnableScheduling
15 | @Slf4j
16 | public class WeworkApplication {
17 |
18 | public static void main(String[] args) {
19 | // 检查sdk
20 | try {
21 | long sdk = Finance.NewSdk();
22 | Finance.DestroySdk(sdk);
23 | } catch (Exception ex) {
24 | log.error("WeWorkSdk.NewSdk error, ", ex.getMessage());
25 | System.exit(1);
26 | }
27 | new SpringApplicationBuilder(WeworkApplication.class)
28 | .web(WebApplicationType.NONE)
29 | .run(args);
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/config/ExecutorServiceConfig.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.config;
2 |
3 | import lombok.Data;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.springframework.boot.context.properties.ConfigurationProperties;
6 | import org.springframework.context.annotation.Bean;
7 | import org.springframework.context.annotation.Configuration;
8 |
9 | import java.util.concurrent.*;
10 |
11 | /**
12 | * @author chianyin
13 | */
14 | @Configuration
15 | @Slf4j
16 | @Data
17 | @ConfigurationProperties(prefix = "pool")
18 | public class ExecutorServiceConfig {
19 |
20 | // 核心线程池数
21 | private Integer corePoolSize = 10;
22 | // 最大线程池数
23 | private Integer maxPoolSize = 20;
24 | // 任务队列的容量
25 | private Integer queueCapacity = 10;
26 | // 非核心线程的存活时间
27 | private Integer keepAliveSeconds = 10;
28 |
29 | @Bean("threadPool")
30 | public ExecutorService newFixedThreadPool() {
31 | return new ThreadPoolExecutor(
32 | corePoolSize,
33 | maxPoolSize,
34 | keepAliveSeconds,
35 | TimeUnit.SECONDS,
36 | new ArrayBlockingQueue<>(queueCapacity),
37 | Executors.defaultThreadFactory(),
38 | new ThreadPoolExecutor.CallerRunsPolicy()
39 | );
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/config/MnsClientConfig.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.config;
2 |
3 | import com.aliyun.mns.client.CloudAccount;
4 | import com.aliyun.mns.client.MNSClient;
5 | import lombok.Data;
6 | import lombok.extern.slf4j.Slf4j;
7 | import org.springframework.boot.context.properties.ConfigurationProperties;
8 | import org.springframework.context.annotation.Bean;
9 | import org.springframework.context.annotation.Configuration;
10 |
11 | /**
12 | * @author chianyin
13 | * 阿里云MNS队列配置
14 | */
15 | @Configuration
16 | @Slf4j
17 | @Data
18 | @ConfigurationProperties(prefix = "mns")
19 | public class MnsClientConfig {
20 |
21 | private String accessKeyId;
22 |
23 | private String accessKeySecret;
24 |
25 | private String endpoint;
26 |
27 | private String queue;
28 |
29 | @Bean
30 | public MNSClient mnsClient() {
31 | log.info(endpoint);
32 | return (new CloudAccount(accessKeyId, accessKeySecret, endpoint)).getMNSClient();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/config/OssClientConfig.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.config;
2 |
3 | import com.aliyun.oss.OSS;
4 | import com.aliyun.oss.OSSClientBuilder;
5 | import lombok.Data;
6 | import lombok.extern.slf4j.Slf4j;
7 | import org.springframework.boot.context.properties.ConfigurationProperties;
8 | import org.springframework.context.annotation.Bean;
9 | import org.springframework.context.annotation.Configuration;
10 |
11 | /**
12 | * @author chianyin
13 | * 阿里云OSS上传配置
14 | */
15 | @Configuration
16 | @Slf4j
17 | @Data
18 | @ConfigurationProperties(prefix = "oss")
19 | public class OssClientConfig {
20 |
21 | private String accessKeyId;
22 |
23 | private String accessKeySecret;
24 |
25 | private String endpoint;
26 |
27 | private String bucket;
28 |
29 | @Bean
30 | public OSS ossClient() {
31 | return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/config/WeWorkConfig.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.config;
2 |
3 | import lombok.Data;
4 | import org.springframework.boot.context.properties.ConfigurationProperties;
5 | import org.springframework.context.annotation.Configuration;
6 |
7 |
8 | /**
9 | * @author chianyin
10 | * 企业微信配置文件
11 | */
12 | @Configuration
13 | @Data
14 | @ConfigurationProperties(prefix = "app")
15 | public class WeWorkConfig {
16 |
17 | private Integer limit = 100;
18 |
19 | private Integer timeout = 10;
20 |
21 | private String corpId;
22 |
23 | private String corpSecret;
24 |
25 | private String env;
26 |
27 | private String proxy;
28 |
29 | private String passwd;
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/config/WeWorkEncryptConfig.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.config;
2 |
3 | import lombok.Data;
4 | import org.springframework.boot.context.properties.ConfigurationProperties;
5 | import org.springframework.context.annotation.Configuration;
6 |
7 | import java.util.Map;
8 | import java.util.TreeMap;
9 |
10 |
11 | /**
12 | * @author chianyin
13 | * 企业微信私钥
14 | * key格式 : publicKeyVer,privateKey
15 | * 这个是由 pkcs1 转来的 pkcs8.key
16 | */
17 | @Configuration
18 | @Data
19 | @ConfigurationProperties(prefix = "encrypt")
20 | public class WeWorkEncryptConfig {
21 |
22 | /**
23 | * 加密私钥 <版本号,私钥内容>
24 | */
25 | private Map privateKey = new TreeMap<>();
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/ChatDataDTO.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * @author chianyin
10 | * 企业微信接口获取的传输对象
11 | */
12 | @Data
13 | public class ChatDataDTO {
14 |
15 | /**
16 | * 0表示成功,错误返回非0错误码,需要参看errmsg。Uint32类型
17 | */
18 | @JSONField(name = "errcode")
19 | private Integer errCode;
20 |
21 | /**
22 | * 返回信息,如非空为错误原因。String类型
23 | */
24 | @JSONField(name = "errmsg")
25 | private String errMsg;
26 |
27 | /**
28 | * 聊天记录数据内容。数组类型。包括seq、msgid等内容
29 | */
30 | @JSONField(name = "chatdata")
31 | protected List chatData;
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/ChatDataDetailDTO.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | /**
7 | * @author chianyin
8 | * 企业微信接口获取的传输内层对象
9 | */
10 | @Data
11 | public class ChatDataDetailDTO {
12 |
13 | /**
14 | * 消息的seq值,标识消息的序号。再次拉取需要带上上次回包中最大的seq。Uint64类型,范围0-pow(2,64)-1
15 | */
16 | @JSONField(name = "seq")
17 | private long seq;
18 |
19 | /**
20 | * 消息id,消息的唯一标识,企业可以使用此字段进行消息去重。String类型。msgid以_external结尾的消息,表明该消息是一条外部消息。
21 | */
22 | @JSONField(name = "msgid")
23 | private String msgId;
24 |
25 | /**
26 | * 加密此条消息使用的公钥版本号。Uint32类型
27 | */
28 | @JSONField(name = "publickey_ver")
29 | private Integer publicKeyVer;
30 |
31 | /**
32 | * 使用publickey_ver指定版本的公钥进行非对称加密后base64加密的内容,需要业务方先base64 decode处理后,再使用指定版本的私钥进行解密,得出内容。String类型
33 | */
34 | @JSONField(name = "encrypt_random_key")
35 | private String encryptRandomKey;
36 |
37 | /**
38 | * 消息密文。需要业务方使用将encrypt_random_key解密得到的内容,与encrypt_chat_msg,传入sdk接口DecryptData,得到消息明文。String类型
39 | */
40 | @JSONField(name = "encrypt_chat_msg")
41 | private String encryptChatMsg;
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/MediaFileDTO.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.alibaba.fastjson.annotation.JSONField;
5 | import com.chinayin.wework.chatdata.model.messagetype.TypeEmotion;
6 | import com.chinayin.wework.chatdata.model.messagetype.TypeFile;
7 | import com.chinayin.wework.chatdata.model.messagetype.TypeImage;
8 | import com.chinayin.wework.chatdata.model.messagetype.TypeVideo;
9 | import com.chinayin.wework.chatdata.model.messagetype.item.TypeChatRecordItem;
10 | import com.chinayin.wework.chatdata.model.messagetype.item.TypeMixedItem;
11 | import lombok.Data;
12 | import lombok.extern.slf4j.Slf4j;
13 |
14 | import java.util.ArrayList;
15 | import java.util.List;
16 | import java.util.Objects;
17 |
18 | /**
19 | * @author chianyin
20 | */
21 | @Data
22 | @Slf4j
23 | public class MediaFileDTO {
24 |
25 | /**
26 | * 固定值,队列服务时使用
27 | */
28 | @JSONField(ordinal = 1, name = "job")
29 | private String job = "ChatMessageMedia";
30 |
31 | @JSONField(ordinal = 2, name = "msgtype")
32 | private String msgType;
33 |
34 | @JSONField(ordinal = 3, name = "msgid")
35 | private String msgId;
36 |
37 | @JSONField(ordinal = 4, name = "sdkfileid")
38 | private String sdkFileId;
39 |
40 | @JSONField(ordinal = 5, name = "md5sum")
41 | private String md5sum;
42 |
43 | @JSONField(ordinal = 6, name = "file_ext")
44 | private String fileExt;
45 |
46 | @JSONField(ordinal = 7, name = "file_size")
47 | private long fileSize;
48 |
49 | /**
50 | * oss保存文件名
51 | */
52 | @JSONField(ordinal = 8, name = "oss_url")
53 | private String ossUrl;
54 |
55 | public static final String MSG_TYPE_IMAGE = "image";
56 | public static final String MSG_TYPE_VOICE = "voice";
57 | public static final String MSG_TYPE_VIDEO = "video";
58 | public static final String MSG_TYPE_EMOTION = "emotion";
59 | public static final String MSG_TYPE_FILE = "file";
60 | //
61 | public static final String MSG_TYPE_MIXED = "mixed";
62 | public static final String MSG_TYPE_CHAT_RECORD = "chatrecord";
63 |
64 | public static final List NEED_MEDIA_FILE_TYPE = new ArrayList() {{
65 | add(MSG_TYPE_IMAGE);
66 | add(MSG_TYPE_VOICE);
67 | add(MSG_TYPE_VIDEO);
68 | add(MSG_TYPE_EMOTION);
69 | add(MSG_TYPE_FILE);
70 | }};
71 |
72 | public MediaFileDTO() {
73 |
74 | }
75 |
76 | /**
77 | * 普通消息
78 | *
79 | * @param msgDTO
80 | */
81 | public MediaFileDTO(MessageDTO msgDTO) {
82 | this.msgType = msgDTO.getMsgType();
83 | this.msgId = msgDTO.getMsgId();
84 | switch (msgDTO.getMsgType()) {
85 | case MSG_TYPE_IMAGE:
86 | this.sdkFileId = msgDTO.getImage().getSdkFileId();
87 | this.md5sum = msgDTO.getImage().getMd5sum();
88 | this.fileSize = msgDTO.getImage().getFileSize();
89 | this.fileExt = "jpg";
90 | break;
91 | case MSG_TYPE_EMOTION:
92 | this.sdkFileId = msgDTO.getEmotion().getSdkFileId();
93 | this.md5sum = msgDTO.getEmotion().getMd5sum();
94 | this.fileSize = msgDTO.getEmotion().getImageSize();
95 | if (msgDTO.getEmotion().getType() == 1) {
96 | this.fileExt = "gif";
97 | } else {
98 | this.fileExt = "png";
99 | }
100 | break;
101 | case MSG_TYPE_VOICE:
102 | this.sdkFileId = msgDTO.getVoice().getSdkFileId();
103 | this.md5sum = msgDTO.getVoice().getMd5sum();
104 | this.fileSize = msgDTO.getVoice().getVoiceSize();
105 | this.fileExt = "amr";
106 | break;
107 | case MSG_TYPE_VIDEO:
108 | this.sdkFileId = msgDTO.getVideo().getSdkFileId();
109 | this.md5sum = msgDTO.getVideo().getMd5sum();
110 | this.fileSize = msgDTO.getVideo().getFileSize();
111 | this.fileExt = "mp4";
112 | break;
113 | case MSG_TYPE_FILE:
114 | this.sdkFileId = msgDTO.getFile().getSdkFileId();
115 | this.md5sum = msgDTO.getFile().getMd5sum();
116 | this.fileSize = msgDTO.getFile().getFileSize();
117 | this.fileExt = msgDTO.getFile().getFileExt();
118 | break;
119 | }
120 | // 发现oss上传时有文件md5是空的情况,特殊处理一下
121 | this.fixMsgMd5sum();
122 | }
123 |
124 | /**
125 | * 混合消息
126 | *
127 | * @param item
128 | * @param message
129 | */
130 | public MediaFileDTO(TypeMixedItem item, MessageDTO message) {
131 | this.msgType = item.getType();
132 | this.msgId = message.getMsgId();
133 | switch (this.msgType) {
134 | case MSG_TYPE_IMAGE:
135 | TypeImage image = JSON.parseObject(item.getContent(), TypeImage.class);
136 | this.sdkFileId = image.getSdkFileId();
137 | this.md5sum = image.getMd5sum();
138 | this.fileSize = image.getFileSize();
139 | this.fileExt = "jpg";
140 | break;
141 | case MSG_TYPE_EMOTION:
142 | TypeEmotion emo = JSON.parseObject(item.getContent(), TypeEmotion.class);
143 | this.sdkFileId = emo.getSdkFileId();
144 | this.md5sum = emo.getMd5sum();
145 | this.fileSize = emo.getImageSize();
146 | if (emo.getType() == 1) {
147 | this.fileExt = "gif";
148 | } else {
149 | this.fileExt = "png";
150 | }
151 | break;
152 | case MSG_TYPE_VOICE:
153 | // mixed消息无法同时有语音
154 | break;
155 | case MSG_TYPE_VIDEO:
156 | TypeVideo video = JSON.parseObject(item.getContent(), TypeVideo.class);
157 | this.sdkFileId = video.getSdkFileId();
158 | this.md5sum = video.getMd5sum();
159 | this.fileSize = video.getFileSize();
160 | this.fileExt = "mp4";
161 | break;
162 | case MSG_TYPE_FILE:
163 | TypeFile file = JSON.parseObject(item.getContent(), TypeFile.class);
164 | this.sdkFileId = file.getSdkFileId();
165 | this.md5sum = file.getMd5sum();
166 | this.fileSize = file.getFileSize();
167 | this.fileExt = file.getFileExt();
168 | break;
169 | }
170 | // 发现oss上传时有文件md5是空的情况,特殊处理一下
171 | this.fixMsgMd5sum();
172 | }
173 |
174 | /**
175 | * 转发消息
176 | *
177 | * @param item
178 | * @param message
179 | */
180 | public MediaFileDTO(TypeChatRecordItem item, MessageDTO message) {
181 | this.msgType = this.parseChatRecordType(item.getType());
182 | this.msgId = message.getMsgId();
183 | switch (this.msgType) {
184 | case MSG_TYPE_IMAGE:
185 | TypeImage image = JSON.parseObject(item.getContent(), TypeImage.class);
186 | this.sdkFileId = image.getSdkFileId();
187 | this.md5sum = image.getMd5sum();
188 | this.fileSize = image.getFileSize();
189 | this.fileExt = "jpg";
190 | break;
191 | case MSG_TYPE_EMOTION:
192 | TypeEmotion emo = JSON.parseObject(item.getContent(), TypeEmotion.class);
193 | this.sdkFileId = emo.getSdkFileId();
194 | this.md5sum = emo.getMd5sum();
195 | this.fileSize = emo.getImageSize();
196 | if (emo.getType() == 1) {
197 | this.fileExt = "gif";
198 | } else {
199 | this.fileExt = "png";
200 | }
201 | break;
202 | case MSG_TYPE_VOICE:
203 | // mixed消息无法同时有语音
204 | break;
205 | case MSG_TYPE_VIDEO:
206 | TypeVideo video = JSON.parseObject(item.getContent(), TypeVideo.class);
207 | this.sdkFileId = video.getSdkFileId();
208 | this.md5sum = video.getMd5sum();
209 | this.fileSize = video.getFileSize();
210 | this.fileExt = "mp4";
211 | break;
212 | case MSG_TYPE_FILE:
213 | TypeFile file = JSON.parseObject(item.getContent(), TypeFile.class);
214 | this.sdkFileId = file.getSdkFileId();
215 | this.md5sum = file.getMd5sum();
216 | this.fileSize = file.getFileSize();
217 | this.fileExt = file.getFileExt();
218 | break;
219 | }
220 | // 发现oss上传时有文件md5是空的情况,特殊处理一下
221 | this.fixMsgMd5sum();
222 | }
223 |
224 | /**
225 | * 格式化为普通type值
226 | *
227 | * @param type
228 | * @return
229 | */
230 | private String parseChatRecordType(String type) {
231 | return type.replace("ChatRecord", "").toLowerCase();
232 | }
233 |
234 | /**
235 | * 修复md5sum为空情况
236 | */
237 | private void fixMsgMd5sum() {
238 | if (Objects.nonNull(this.sdkFileId) && (
239 | Objects.isNull(this.md5sum) || this.md5sum.length() == 0
240 | )) {
241 | log.error("md5sum empty, msgId: %s", this.msgId);
242 | // 随机生成一个
243 | String md5sum = "md5sum-" + System.currentTimeMillis();
244 | this.md5sum = md5sum;
245 | }
246 | }
247 |
248 | }
249 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/MessageDTO.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import com.chinayin.wework.chatdata.model.messagetype.*;
5 | import lombok.Data;
6 |
7 | import java.util.List;
8 |
9 | /**
10 | * @author chianyin
11 | * 企业微信接口获取的实际解密后消息
12 | */
13 | @Data
14 | public class MessageDTO {
15 |
16 | /**
17 | * 消息动作,目前有send(发送消息)/recall(撤回消息)/switch(切换企业日志)三种类型。String类型
18 | */
19 | @JSONField(ordinal = 1, name = "action")
20 | private String action;
21 |
22 | /**
23 | * 消息id,消息的唯一标识,企业可以使用此字段进行消息去重。String类型
24 | */
25 | @JSONField(ordinal = 2, name = "msgid")
26 | private String msgId;
27 |
28 | /**
29 | * 消息类型。String类型
30 | */
31 | @JSONField(ordinal = 3, name = "msgtype")
32 | private String msgType;
33 |
34 | /**
35 | * 消息发送时间戳,utc时间,ms单位。
36 | */
37 | @JSONField(ordinal = 4, name = "msgtime")
38 | private Long msgTime;
39 |
40 | /**
41 | * 消息发送方id。同一企业内容为userid,非相同企业为external_userid。消息如果是机器人发出,也为external_userid。String类型
42 | */
43 | @JSONField(ordinal = 20, name = "from")
44 | private String from;
45 |
46 | /**
47 | * 消息接收方列表,可能是多个,同一个企业内容为userid,非相同企业为external_userid。数组,内容为string类型
48 | */
49 | @JSONField(ordinal = 20, name = "tolist")
50 | private List toList;
51 |
52 | /**
53 | * 群聊消息的群id。如果是单聊则为空。String类型
54 | */
55 | @JSONField(ordinal = 20, name = "roomid")
56 | private String roomId;
57 |
58 |
59 | /**
60 | * ============ 消息类型 ============
61 | * @link https://developer.work.weixin.qq.com/document/path/91774
62 | */
63 |
64 | /**
65 | * 文本
66 | */
67 | @JSONField(ordinal = 10, name = "text")
68 | private TypeText text;
69 |
70 | /**
71 | * 图片
72 | */
73 | @JSONField(ordinal = 10, name = "image")
74 | private TypeImage image;
75 |
76 | /**
77 | * 撤回消息
78 | */
79 | @JSONField(ordinal = 10, name = "revoke")
80 | private TypeRevoke revoke;
81 |
82 | /**
83 | * 同意会话聊天内容
84 | */
85 | @JSONField(ordinal = 10, name = "agree")
86 | private TypeAgree agree;
87 |
88 | @JSONField(ordinal = 10, name = "disagree")
89 | private TypeDisagree disagree;
90 |
91 | /**
92 | * 语音
93 | */
94 | @JSONField(ordinal = 10, name = "voice")
95 | private TypeVoice voice;
96 |
97 | /**
98 | * 视频
99 | */
100 | @JSONField(ordinal = 10, name = "video")
101 | private TypeVideo video;
102 |
103 | /**
104 | * 名片
105 | */
106 | @JSONField(ordinal = 10, name = "card")
107 | private TypeCard card;
108 |
109 | /**
110 | * 位置
111 | */
112 | @JSONField(ordinal = 10, name = "location")
113 | private TypeLocation location;
114 |
115 | /**
116 | * 表情
117 | */
118 | @JSONField(ordinal = 10, name = "emotion")
119 | private TypeEmotion emotion;
120 |
121 | /**
122 | * 文件
123 | */
124 | @JSONField(ordinal = 10, name = "file")
125 | private TypeFile file;
126 |
127 | /**
128 | * 链接
129 | */
130 | @JSONField(ordinal = 10, name = "link")
131 | private TypeLink link;
132 |
133 | /**
134 | * 小程序
135 | */
136 | @JSONField(ordinal = 10, name = "weapp")
137 | private TypeWeapp weapp;
138 |
139 | /**
140 | * 会话记录
141 | */
142 | @JSONField(ordinal = 10, name = "chatrecord")
143 | private TypeChatRecord chatRecord;
144 |
145 | /**
146 | * 待办
147 | */
148 | @JSONField(ordinal = 10, name = "todo")
149 | private TypeTodo todo;
150 |
151 | /**
152 | * 投票
153 | */
154 | @JSONField(ordinal = 10, name = "vote")
155 | private TypeVote vote;
156 |
157 | /**
158 | * 填表
159 | */
160 | @JSONField(ordinal = 10, name = "collect")
161 | private TypeCollect collect;
162 |
163 | /**
164 | * 红包
165 | * and
166 | * 互通红包
167 | */
168 | @JSONField(ordinal = 10, name = "redpacket", alternateNames = {"redpacket"})
169 | private TypeRedPacket redpacket;
170 |
171 | /**
172 | * 会议邀请
173 | */
174 | @JSONField(ordinal = 10, name = "meeting")
175 | private TypeMeeting meeting;
176 |
177 | /**
178 | * 切换企业日志
179 | * todo
180 | */
181 |
182 | /**
183 | * 在线文档
184 | */
185 | @JSONField(ordinal = 10, name = "doc")
186 | private TypeDoc doc;
187 |
188 | /**
189 | * markdown
190 | */
191 | @JSONField(ordinal = 10, name = "info")
192 | private TypeMarkdown markdown;
193 |
194 | /**
195 | * 图文
196 | */
197 | @JSONField(ordinal = 10, name = "info")
198 | private TypeNews news;
199 |
200 | /**
201 | * 日程
202 | */
203 | @JSONField(ordinal = 10, name = "calendar")
204 | private TypeCalendar calendar;
205 |
206 | /**
207 | * 混合消息
208 | */
209 | @JSONField(ordinal = 10, name = "mixed")
210 | private TypeMixed mixed;
211 |
212 |
213 | /**
214 | * 音视频通话
215 | */
216 | @JSONField(ordinal = 10, name = "info")
217 | private TypeVoiptext voiptext;
218 |
219 | /**
220 | * 音频存档
221 | * todo
222 | */
223 |
224 | /**
225 | * 音频共享文档
226 | * todo
227 | */
228 |
229 | /**
230 | * 视频号消息
231 | */
232 | @JSONField(ordinal = 10, name = "sphfeed")
233 | private TypeSphFeed sphFeed;
234 |
235 | /**
236 | * 微盘文件
237 | */
238 | @JSONField(ordinal = 10, name = "info")
239 | private TypeQyDiskFile qydiskfile;
240 |
241 | }
242 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/AbstractTypeFile.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | /**
7 | * @author chianyin
8 | * 基础文件
9 | */
10 | @Data
11 | abstract public class AbstractTypeFile {
12 |
13 | /**
14 | * 媒体资源的id信息。String类型
15 | */
16 | @JSONField(name = "sdkfileid")
17 | private String sdkFileId;
18 |
19 | /**
20 | * 资源的md5值,供进行校验。String类型
21 | */
22 | private String md5sum;
23 |
24 | /**
25 | * 资源的文件大小。Uint32类型
26 | */
27 | @JSONField(name = "filesize")
28 | private Integer fileSize;
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeAgree.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | /**
7 | * @author chianyin
8 | * 同意会话聊天内容消息(同意和不同意共用)
9 | */
10 | @Data
11 | public class TypeAgree {
12 |
13 | /**
14 | * 同意/不同意协议者的userid,外部企业默认为external_userid
15 | */
16 | @JSONField(name = "userid")
17 | private String userId;
18 |
19 | /**
20 | * 同意/不同意协议的时间,utc时间,ms单位。
21 | */
22 | @JSONField(name = "agree_time")
23 | private Long agreeTime;
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeCalendar.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | /**
7 | * @author chianyin
8 | * 日程消息
9 | */
10 | @Data
11 | public class TypeCalendar {
12 |
13 | /**
14 | * 日程主题
15 | */
16 | private String title;
17 |
18 | /**
19 | * 日程组织者
20 | */
21 | @JSONField(name = "creatorname")
22 | private String creatorName;
23 |
24 | /**
25 | * 日程参与人
26 | */
27 | @JSONField(name = "attendeename")
28 | private String[] attendeeName;
29 |
30 | /**
31 | * 日程开始时间。Utc时间,单位秒
32 | */
33 | @JSONField(name = "starttime")
34 | private Long startTime;
35 |
36 | /**
37 | * 日程结束时间。Utc时间,单位秒
38 | */
39 | @JSONField(name = "endtime")
40 | private Long endTime;
41 |
42 | /**
43 | * 日程地点
44 | */
45 | private String place;
46 |
47 | /**
48 | * 日程备注
49 | */
50 | private String remarks;
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeCard.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | /**
7 | * @author chianyin
8 | * 名片消息
9 | */
10 | @Data
11 | public class TypeCard {
12 |
13 | /**
14 | * 名片所有者所在的公司名称
15 | */
16 | @JSONField(name = "corpname")
17 | private String corpName;
18 |
19 | /**
20 | * 名片所有者的id,同一公司是userid,不同公司是external_userid
21 | */
22 | @JSONField(name = "userid")
23 | private String userId;
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeChatRecord.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import com.chinayin.wework.chatdata.model.messagetype.item.TypeChatRecordItem;
5 | import lombok.Data;
6 |
7 | import java.util.List;
8 |
9 | /**
10 | * @author chianyin
11 | * 会话记录消息
12 | */
13 | @Data
14 | public class TypeChatRecord {
15 |
16 | /**
17 | * 聊天记录标题。String类型
18 | */
19 | @JSONField(ordinal = 1)
20 | private String title;
21 |
22 | /**
23 | * 消息记录内的消息内容,批量数据
24 | */
25 | @JSONField(ordinal = 2)
26 | private List item;
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeCollect.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * @author chianyin
10 | * 填表消息
11 | */
12 | @Data
13 | public class TypeCollect {
14 |
15 | /**
16 | * 填表消息所在的群名称
17 | */
18 | @JSONField(name = "room_name")
19 | private String roomName;
20 |
21 | /**
22 | * 创建者在群中的名字
23 | */
24 | private String creator;
25 |
26 | /**
27 | * 创建的时间
28 | */
29 | @JSONField(name = "create_time")
30 | private String createTime;
31 |
32 | /**
33 | * 表名
34 | */
35 | private String title;
36 |
37 | /**
38 | * 表内容
39 | */
40 | private List details;
41 |
42 | }
43 |
44 | @Data
45 | class TypeCollectItem {
46 |
47 | /**
48 | * 表项id
49 | */
50 | private long id;
51 | /**
52 | * 表项名称
53 | */
54 | private String ques;
55 |
56 | /**
57 | * 表项类型 有Text(文本),Number(数字),Date(日期),Time(时间)
58 | */
59 | private String type;
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeDisagree.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | /**
7 | * @author chianyin
8 | * 同意会话聊天内容消息(同意和不同意共用)
9 | */
10 | @Data
11 | public class TypeDisagree {
12 |
13 | /**
14 | * 同意/不同意协议者的userid,外部企业默认为external_userid
15 | */
16 | @JSONField(name = "userid")
17 | private String userId;
18 |
19 | /**
20 | * 同意/不同意协议的时间,utc时间,ms单位。
21 | */
22 | @JSONField(name = "disagree_time")
23 | private Long agreeTime;
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeDoc.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | /**
7 | * @author chianyin
8 | * 在线文档消息
9 | */
10 | @Data
11 | public class TypeDoc {
12 |
13 | /**
14 | * 在线文档名称
15 | */
16 | private String title;
17 |
18 | /**
19 | * 在线文档链接
20 | */
21 | @JSONField(name = "link_url")
22 | private String linkUrl;
23 |
24 | /**
25 | * 在线文档创建者。本企业成员创建为userid;外部企业成员创建为external_userid
26 | */
27 | @JSONField(name = "doc_creator")
28 | private String docCreator;
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeEmotion.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | /**
7 | * @author chianyin
8 | * 表情消息
9 | */
10 | @Data
11 | public class TypeEmotion extends AbstractTypeFile {
12 |
13 | /**
14 | * 表情类型,png或者gif.1表示gif 2表示png。Uint32类型
15 | */
16 | private Integer type;
17 |
18 | /**
19 | * 表情图片宽度。Uint32类型
20 | */
21 | private Integer width;
22 |
23 | /**
24 | * 表情图片高度。Uint32类型
25 | */
26 | private Integer height;
27 |
28 | /**
29 | * 资源的文件大小。Uint32类型
30 | */
31 | @JSONField(name = "imagesize")
32 | private Integer imageSize;
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeFile.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | /**
7 | * @author chianyin
8 | * 文件消息
9 | */
10 | @Data
11 | public class TypeFile extends AbstractTypeFile {
12 |
13 | /**
14 | * 文件名称。String类型
15 | */
16 | private String filename;
17 |
18 | /**
19 | * 文件类型后缀。String类型
20 | */
21 | @JSONField(name = "fileext")
22 | private String fileExt;
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeImage.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * @author chianyin
7 | * 图片消息
8 | */
9 | @Data
10 | public class TypeImage extends AbstractTypeFile {
11 |
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeLink.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | /**
7 | * @author chianyin
8 | * 链接消息
9 | */
10 | @Data
11 | public class TypeLink {
12 |
13 | /**
14 | * 消息标题
15 | */
16 | private String title;
17 |
18 | /**
19 | * 消息描述
20 | */
21 | private String description;
22 |
23 | /**
24 | * 链接url地址
25 | */
26 | @JSONField(name = "link_url")
27 | private String linkUrl;
28 |
29 | /**
30 | * 链接图片url
31 | */
32 | @JSONField(name = "image_url")
33 | private String imageUrl;
34 |
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeLocation.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * @author chianyin
7 | * 位置消息
8 | */
9 | @Data
10 | public class TypeLocation {
11 |
12 | /**
13 | * 经度,单位double
14 | */
15 | private Double longitude;
16 |
17 | /**
18 | * 经纬度度,单位double
19 | */
20 | private Double latitude;
21 |
22 | /**
23 | * 地址信息
24 | */
25 | private String address;
26 |
27 | /**
28 | * 位置信息的title
29 | */
30 | private String title;
31 |
32 | /**
33 | * 缩放比例
34 | */
35 | private Integer zoom;
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeMarkdown.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * @author chianyin
7 | * MarkDown格式消息
8 | */
9 | @Data
10 | public class TypeMarkdown {
11 |
12 | /**
13 | * markdown消息内容,目前为机器人发出的消息
14 | */
15 | private String content;
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeMeeting.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | /**
7 | * @author chianyin
8 | * 会议邀请消息
9 | */
10 | @Data
11 | public class TypeMeeting {
12 |
13 | /**
14 | * 会议主题
15 | */
16 | private String topic;
17 |
18 | /**
19 | * 会议开始时间 Utc时间
20 | */
21 | @JSONField(name = "starttime")
22 | private Long startTime;
23 |
24 | /**
25 | * 会议结束时间 Utc时间
26 | */
27 | @JSONField(name = "endtime")
28 | private Long endTime;
29 |
30 | /**
31 | * 会议地址
32 | */
33 | private String address;
34 |
35 | /**
36 | * 会议备注
37 | */
38 | private String remarks;
39 |
40 | /**
41 | * 会议消息类型。101发起会议邀请消息、102处理会议邀请消息
42 | */
43 | @JSONField(name = "meetingtype")
44 | private Integer meetingType;
45 |
46 | /**
47 | * 会议id。方便将发起、处理消息进行对照
48 | */
49 | @JSONField(name = "meetingid")
50 | private Long meetingId;
51 |
52 | /**
53 | * 会议邀请处理状态。1参加会议、2拒绝会议、3待定、4未被邀请、5会议已取消、6会议已过期、7不在房间内。Uint32类型。只有meetingtype为102的时候此字段才有内容
54 | */
55 | @JSONField(name = "status")
56 | private Integer status;
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeMixed.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import com.chinayin.wework.chatdata.model.messagetype.item.TypeMixedItem;
4 | import lombok.Data;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * @author chianyin
10 | * 混合消息
11 | */
12 | @Data
13 | public class TypeMixed {
14 |
15 | /**
16 | * 混合消息列表
17 | */
18 | private List item;
19 | }
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeNews.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * @author chianyin
10 | * 图文消息
11 | */
12 | @Data
13 | public class TypeNews {
14 |
15 | /**
16 | * 图文消息数组,每个item结构包含title、description、url、picurl等结构
17 | */
18 | private List item;
19 |
20 | }
21 |
22 | @Data
23 | class TypeNewsItem {
24 |
25 | /**
26 | * 图文消息标题
27 | */
28 | private String title;
29 |
30 | /**
31 | * 图文消息描述
32 | */
33 | private String description;
34 |
35 | /**
36 | * 图文消息点击跳转地址
37 | */
38 | private String url;
39 |
40 | /**
41 | * 图文消息配图的url
42 | */
43 | @JSONField(name = "picurl")
44 | private String picUrl;
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeQyDiskFile.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | /**
7 | * @author chianyin
8 | * 微盘文件
9 | */
10 | @Data
11 | public class TypeQyDiskFile {
12 |
13 | /**
14 | * 文件名称
15 | */
16 | @JSONField(name = "filename")
17 | private String filename;
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeRedPacket.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | /**
7 | * @author chianyin
8 | * 红包消息
9 | */
10 | @Data
11 | public class TypeRedPacket {
12 |
13 | /**
14 | * 红包消息类型。1 普通红包、2 拼手气群红包、3 激励群红包
15 | */
16 | private Integer type;
17 |
18 | /**
19 | * 红包祝福语
20 | */
21 | private String wish;
22 |
23 | /**
24 | * 红包总个数
25 | */
26 | @JSONField(name = "totalcnt")
27 | private Integer totalCnt;
28 |
29 | /**
30 | * 红包总金额,单位为分
31 | */
32 | @JSONField(name = "totalamount")
33 | private Integer totalAmount;
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeRevoke.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | /**
7 | * @author chianyin
8 | * 撤回消息
9 | */
10 | @Data
11 | public class TypeRevoke {
12 |
13 | /**
14 | * 标识撤回的原消息的msgid
15 | */
16 | @JSONField(name = "pre_msgid")
17 | private String preMsgId;
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeSphFeed.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | /**
7 | * @author chianyin
8 | * 视频号消息
9 | */
10 | @Data
11 | public class TypeSphFeed {
12 |
13 | /**
14 | * 视频号消息类型。2 图片、4 视频、9 直播
15 | */
16 | @JSONField(name = "feed_type")
17 | private Integer feedType;
18 |
19 | /**
20 | * 视频号账号名称
21 | */
22 | @JSONField(name = "sph_name")
23 | private String sphName;
24 |
25 | /**
26 | * 视频号消息描述
27 | */
28 | @JSONField(name = "feed_desc")
29 | private String feedDesc;
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeText.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * @author chianyin
7 | * 文本消息
8 | */
9 | @Data
10 | public class TypeText {
11 |
12 | /**
13 | * type 为 text 的时候文本内容
14 | */
15 | private String content;
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeTodo.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * @author chianyin
7 | * 小程序消息
8 | */
9 | @Data
10 | public class TypeTodo {
11 |
12 | /**
13 | * 待办的来源文本
14 | */
15 | private String title;
16 |
17 | /**
18 | * 待办的具体内容
19 | */
20 | private String content;
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeVideo.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | /**
7 | * @author chianyin
8 | * 视频消息
9 | */
10 | @Data
11 | public class TypeVideo extends AbstractTypeFile {
12 |
13 | /**
14 | * 视频播放长度。Uint32类型
15 | */
16 | @JSONField(name = "play_length")
17 | private Integer playLength;
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeVoice.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | /**
7 | * @author chianyin
8 | * 语音消息
9 | */
10 | @Data
11 | public class TypeVoice extends AbstractTypeFile {
12 |
13 | /**
14 | * 语音消息大小。Uint32类型
15 | */
16 | @JSONField(name = "voice_size")
17 | private Integer voiceSize;
18 |
19 | /**
20 | * 播放长度。Uint32类型
21 | */
22 | @JSONField(name = "play_length")
23 | private Integer playLength;
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeVoiptext.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | /**
7 | * @author chianyin
8 | * 音视频通话
9 | */
10 | @Data
11 | public class TypeVoiptext extends AbstractTypeFile {
12 |
13 | @JSONField(name = "callduration")
14 | private Integer callduration;
15 |
16 | @JSONField(name = "invitetype")
17 | private Integer invitetype;
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeVote.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | /**
7 | * @author chianyin
8 | * 投票消息
9 | */
10 | @Data
11 | public class TypeVote {
12 |
13 | /**
14 | * 投票主题
15 | */
16 | @JSONField(name = "votetitle")
17 | private String title;
18 |
19 | /**
20 | * 投票选项,可能多个内容
21 | */
22 | @JSONField(name = "voteitem")
23 | private String[] item;
24 |
25 | /**
26 | * 投票类型.101发起投票、102参与投票
27 | */
28 | @JSONField(name = "votetype")
29 | private Integer type;
30 |
31 | /**
32 | * 投票id,方便将参与投票消息与发起投票消息进行前后对照
33 | */
34 | @JSONField(name = "voteid")
35 | private String id;
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/TypeWeapp.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | /**
7 | * @author chianyin
8 | * 小程序消息
9 | */
10 | @Data
11 | public class TypeWeapp {
12 |
13 | /**
14 | * 消息标题
15 | */
16 | private String title;
17 |
18 | /**
19 | * 消息描述
20 | */
21 | private String description;
22 |
23 | /**
24 | * 用户名称
25 | */
26 | @JSONField(name = "username")
27 | private String userName;
28 |
29 | /**
30 | * 小程序名称
31 | */
32 | @JSONField(name = "displayname")
33 | private String displayName;
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/item/TypeChatRecordItem.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype.item;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | /**
7 | * @author chianyin
8 | * 会话记录消息 item 内容
9 | */
10 | @Data
11 | public class TypeChatRecordItem {
12 |
13 | /**
14 | * 每条聊天记录的具体消息类型:ChatRecordText/ ChatRecordFile/ ChatRecordImage/ ChatRecordVideo/ ChatRecordLink/ ChatRecordLocation/ ChatRecordMixed ….
15 | */
16 | @JSONField(ordinal = 1)
17 | private String type;
18 |
19 | /**
20 | * 消息时间,utc时间,单位秒。
21 | */
22 | @JSONField(ordinal = 2, name = "msgtime")
23 | private long msgTime;
24 |
25 | /**
26 | * 消息内容。Json串,内容为对应类型的json。String类型
27 | */
28 | @JSONField(ordinal = 3)
29 | private String content;
30 |
31 | /**
32 | * 是否来自群会话。Bool类型
33 | */
34 | @JSONField(ordinal = 4, name = "from_chatroom")
35 | private boolean fromChatRoom;
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/model/messagetype/item/TypeMixedItem.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.model.messagetype.item;
2 |
3 | import com.alibaba.fastjson.annotation.JSONField;
4 | import lombok.Data;
5 |
6 | /**
7 | * @author chianyin
8 | * 混合消息Item
9 | */
10 |
11 | @Data
12 | public class TypeMixedItem {
13 |
14 | /**
15 | * 混合消息单条类型
16 | */
17 | @JSONField(ordinal = 1)
18 | private String type;
19 |
20 | /**
21 | * 混合消息内容
22 | */
23 | @JSONField(ordinal = 2)
24 | private String content;
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/schedule/ChatDataSchedule.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.schedule;
2 |
3 | import com.chinayin.wework.chatdata.service.ChatDataService;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.springframework.scheduling.annotation.Scheduled;
6 | import org.springframework.stereotype.Component;
7 |
8 | import javax.annotation.Resource;
9 | import java.io.*;
10 | import java.util.Objects;
11 |
12 | /**
13 | * @author chianyin
14 | * 获取微信会话消息
15 | */
16 | @Slf4j
17 | @Component
18 | public class ChatDataSchedule {
19 |
20 | /**
21 | * seq 文件保存位置
22 | */
23 | private static final String seqFileName = "/tmp/wework/seq.txt";
24 |
25 | @Resource
26 | private ChatDataService chatDataService;
27 |
28 | @Scheduled(fixedDelay = 1000)
29 | public void syncChatData() {
30 | long seqNum = this.getSeqNum();
31 | log.info("seqNum : {}", seqNum);
32 | try {
33 | long latestSeqNum = chatDataService.getChatData(seqNum);
34 | this.setSeqNum(latestSeqNum);
35 | } catch (Exception e) {
36 | log.error("[异常]主流程失败: {}", e.getMessage());
37 | }
38 | }
39 |
40 | private int getSeqNum() {
41 | try {
42 | BufferedReader in = new BufferedReader(new FileReader(seqFileName));
43 | String seqNum = in.readLine();
44 | log.info("[流程]读取 seqNum: {}", seqNum);
45 | if (Objects.nonNull(seqNum)) {
46 | return Integer.valueOf(seqNum);
47 | }
48 | } catch (Exception e) {
49 | log.error("[异常]读取 seqNum 失败: {}", e.getMessage());
50 | }
51 | return 0;
52 | }
53 |
54 | private void setSeqNum(long seqNum) {
55 | try {
56 | BufferedWriter out = new BufferedWriter(new FileWriter(seqFileName));
57 | out.write(String.valueOf(seqNum));
58 | out.close();
59 | log.info("[流程]记录 seqNum: {}", seqNum);
60 | } catch (IOException e) {
61 | log.error("[异常]记录 seqNum 失败: {}", e.getMessage());
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/service/ChatDataService.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.service;
2 |
3 | /**
4 | * @author chianyin
5 | */
6 | public interface ChatDataService {
7 |
8 | /**
9 | * 拉微信消息
10 | *
11 | * @param seq 起始位置
12 | * @return 拉取位置
13 | */
14 | long getChatData(long seq);
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/service/ClientService.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.service;
2 |
3 | /**
4 | * @author chianyin
5 | */
6 | public interface ClientService {
7 | long getSdk();
8 |
9 | void downloadMediaFile(String sdkFileId, String file, long fileSize);
10 |
11 | String decryptData(Integer publicKeyVer, String encryptKey, String encryptChatMsg);
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/service/EncryptService.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.service;
2 |
3 | /**
4 | * @author chianyin
5 | */
6 | public interface EncryptService {
7 |
8 | /**
9 | * 解密内容
10 | *
11 | * @param encryptRandomKey
12 | */
13 | String decodeEncryptRandomKey(Integer publicKeyVer, String encryptRandomKey);
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/service/FileOutputService.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.service;
2 |
3 | /**
4 | * @author chianyin
5 | */
6 | public interface FileOutputService {
7 |
8 | /**
9 | * 把消息保存到文件
10 | *
11 | * @param msg 消息
12 | */
13 | void saveMessage(String msg);
14 |
15 | /**
16 | * 记录未处理的消息
17 | *
18 | * @param msg 消息
19 | */
20 | void saveOriginMessage(String msg);
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/service/MnsClientService.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.service;
2 |
3 | import com.aliyun.mns.model.Message;
4 |
5 | /**
6 | * @author chianyin
7 | */
8 | public interface MnsClientService {
9 | /**
10 | * 发送消息到 mns
11 | *
12 | * @param msg
13 | * @return
14 | */
15 | String sendMessageToQueue(String msg);
16 |
17 | String sendMessageToQueue(Message msg);
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/service/OssClientService.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.service;
2 |
3 | /**
4 | * @author chianyin
5 | */
6 | public interface OssClientService {
7 |
8 | /**
9 | * 获取媒体地址
10 | *
11 | * @param objectName
12 | * @return
13 | */
14 | String getMediaOssUrl(String objectName);
15 |
16 | /**
17 | * 判断文件是否存在
18 | *
19 | * @param objectName
20 | * @return
21 | */
22 | Boolean doesObjectExist(String objectName);
23 |
24 | /**
25 | * 上传资源到 oss
26 | *
27 | * @param objectName
28 | * @param file
29 | */
30 | void upload(String objectName, String file);
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/service/impl/ChatDataServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.service.impl;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.chinayin.wework.chatdata.config.WeWorkConfig;
5 | import com.chinayin.wework.chatdata.model.ChatDataDTO;
6 | import com.chinayin.wework.chatdata.model.ChatDataDetailDTO;
7 | import com.chinayin.wework.chatdata.model.MediaFileDTO;
8 | import com.chinayin.wework.chatdata.model.MessageDTO;
9 | import com.chinayin.wework.chatdata.model.messagetype.item.TypeChatRecordItem;
10 | import com.chinayin.wework.chatdata.model.messagetype.item.TypeMixedItem;
11 | import com.chinayin.wework.chatdata.service.*;
12 | import com.tencent.wework.Finance;
13 | import lombok.extern.slf4j.Slf4j;
14 | import org.springframework.beans.factory.annotation.Qualifier;
15 | import org.springframework.stereotype.Service;
16 |
17 | import javax.annotation.Resource;
18 | import java.text.SimpleDateFormat;
19 | import java.util.ArrayList;
20 | import java.util.Date;
21 | import java.util.List;
22 | import java.util.Objects;
23 | import java.util.concurrent.ExecutorService;
24 |
25 | /**
26 | * @author chianyin
27 | */
28 | @Service("FinanceService")
29 | @Slf4j
30 | public class ChatDataServiceImpl implements ChatDataService {
31 |
32 | @Resource
33 | private WeWorkConfig weworkConfig;
34 |
35 | @Resource
36 | private MnsClientService queueService;
37 |
38 | @Resource
39 | private OssClientService ossService;
40 |
41 | @Resource
42 | private FileOutputService fileService;
43 |
44 | @Qualifier("threadPool")
45 | @Resource
46 | private ExecutorService executorService;
47 |
48 | @Resource
49 | private ClientService clientService;
50 |
51 | @Override
52 | public long getChatData(long seq) {
53 | // 拉取消息
54 | long slice = Finance.NewSlice();
55 | int ret = Finance.GetChatData(clientService.getSdk(), seq, weworkConfig.getLimit(), weworkConfig.getProxy(), weworkConfig.getPasswd(), weworkConfig.getTimeout(), slice);
56 | if (ret != 0) {
57 | log.error("[异常]微信会话获取数据失败, {}", ret);
58 | Finance.FreeSlice(slice);
59 | return seq;
60 | }
61 | // 获取slice内容
62 | ChatDataDTO chatDataDTO = JSON.parseObject(Finance.GetContentFromSlice(slice), ChatDataDTO.class);
63 | if (chatDataDTO.getErrCode() != 0) {
64 | log.error("[异常]拉消息失败, {}", chatDataDTO);
65 | Finance.FreeSlice(slice);
66 | return seq;
67 | }
68 | if (Objects.isNull(chatDataDTO.getChatData()) || chatDataDTO.getChatData().size() == 0) {
69 | log.info("[流程]暂无消息 seqNum {}, {}", seq, chatDataDTO);
70 | Finance.FreeSlice(slice);
71 | return seq;
72 | }
73 | // 逐条处理
74 | for (ChatDataDetailDTO chatDataDetailDTO : chatDataDTO.getChatData()) {
75 | this.handleMessage(chatDataDetailDTO);
76 | seq = chatDataDetailDTO.getSeq();
77 | }
78 | //
79 | Finance.FreeSlice(slice);
80 | return seq;
81 | }
82 |
83 |
84 | private void handleMessage(ChatDataDetailDTO chatDataDetailDTO) {
85 | String originMsg = clientService.decryptData(chatDataDetailDTO.getPublicKeyVer(), chatDataDetailDTO.getEncryptRandomKey(), chatDataDetailDTO.getEncryptChatMsg());
86 | if (Objects.isNull(originMsg)) {
87 | return;
88 | }
89 |
90 | // 保存原始数据
91 | fileService.saveOriginMessage(originMsg);
92 |
93 | // 解析数据
94 | MessageDTO messageDTO = null;
95 | try {
96 | messageDTO = JSON.parseObject(originMsg, MessageDTO.class);
97 | } catch (Exception e) {
98 | log.error("[异常]数据解析失败, {}", originMsg);
99 | }
100 | if (Objects.isNull(messageDTO)) {
101 | return;
102 | }
103 | log.info("msgId: {}, seq: {}", chatDataDetailDTO.getMsgId(), chatDataDetailDTO.getSeq());
104 |
105 | // 发送去消息队列处理,队列中高优先级
106 | String msg = JSON.toJSONString(messageDTO);
107 | log.debug("msg: {}", msg);
108 | com.aliyun.mns.model.Message mns = new com.aliyun.mns.model.Message(msg);
109 | mns.setPriority(3);
110 | queueService.sendMessageToQueue(mns);
111 |
112 | // 非发送类是没有type的,跳过处理
113 | if (Objects.isNull(messageDTO.getMsgType())) {
114 | log.debug("msg_type is null, {}, {}", chatDataDetailDTO.getMsgId(), msg);
115 | return;
116 | }
117 |
118 | // 上传媒体文件
119 | try {
120 | List mediaList = new ArrayList<>();
121 | // mixed消息,需要判断后拆开处理
122 | if (Objects.nonNull(messageDTO.getMixed()) && Objects.nonNull(messageDTO.getMixed().getItem())) {
123 | for (TypeMixedItem item : messageDTO.getMixed().getItem()) {
124 | if (Objects.isNull(item))
125 | continue;
126 | MediaFileDTO media = new MediaFileDTO(item, messageDTO);
127 | log.debug("mixed.media, {}", JSON.toJSONString(media));
128 | mediaList.add(media);
129 | }
130 | } else if (Objects.nonNull(messageDTO.getChatRecord()) && Objects.nonNull(messageDTO.getChatRecord().getItem())) {
131 | for (TypeChatRecordItem item : messageDTO.getChatRecord().getItem()) {
132 | if (Objects.isNull(item))
133 | continue;
134 | MediaFileDTO media = new MediaFileDTO(item, messageDTO);
135 | log.debug("chatrecord.media, {}", JSON.toJSONString(media));
136 | mediaList.add(media);
137 | }
138 | } else {
139 | mediaList.add(new MediaFileDTO(messageDTO));
140 | }
141 | for (MediaFileDTO media : mediaList) {
142 | if (Objects.nonNull(media.getSdkFileId())) {
143 | executorService.submit(() -> handleMedia(media));
144 | }
145 | }
146 | } catch (Exception ex) {
147 | log.error("[异常]媒体文件解析或处理失败, {}, {}", chatDataDetailDTO.getMsgId(), ex.getMessage());
148 | }
149 |
150 | }
151 |
152 | private void handleMedia(MediaFileDTO media) {
153 | String sdkFileId = media.getSdkFileId();
154 | String md5 = media.getMd5sum();
155 | String day = (new SimpleDateFormat("yyyy/MM")).format(new Date());
156 | String file = "wework/" + day + "/" + md5 + "." + media.getFileExt();
157 | String objectName = "wework/" + day + "/" + md5 + "." + media.getFileExt();
158 | try {
159 | // 当有超级多文件待下载时,且文件较大,带宽会拉满,先判断oss是否存在同名文件
160 | Boolean found = ossService.doesObjectExist(objectName);
161 | if (found) {
162 | log.info("媒体文件跳过: {}, {}, {}", media.getFileSize(), objectName, sdkFileId);
163 | return;
164 | }
165 | // 下载
166 | clientService.downloadMediaFile(sdkFileId, file, media.getFileSize());
167 | // 上传
168 | ossService.upload(objectName, file);
169 | //
170 | String ossUrl = ossService.getMediaOssUrl(objectName);
171 | media.setOssUrl(ossUrl);
172 | // 发送去消息队列处理,低优先级+延迟
173 | String msg = JSON.toJSONString(media);
174 | log.debug("media: {}", msg);
175 | com.aliyun.mns.model.Message mns = new com.aliyun.mns.model.Message(msg);
176 | mns.setPriority(10);
177 | mns.setDelaySeconds(10);
178 | queueService.sendMessageToQueue(mns);
179 | } catch (Exception e) {
180 | e.printStackTrace();
181 | log.error("[异常]处理媒体文件, {}, {}", e.getMessage(), sdkFileId);
182 | }
183 | }
184 |
185 |
186 | }
187 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/service/impl/ClientServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.service.impl;
2 |
3 | import com.chinayin.wework.chatdata.config.WeWorkConfig;
4 | import com.chinayin.wework.chatdata.service.ClientService;
5 | import com.chinayin.wework.chatdata.service.EncryptService;
6 | import com.tencent.wework.Finance;
7 | import lombok.extern.slf4j.Slf4j;
8 | import org.springframework.stereotype.Service;
9 |
10 | import javax.annotation.Resource;
11 | import java.io.File;
12 | import java.io.FileOutputStream;
13 | import java.util.Objects;
14 |
15 | /**
16 | * @author chianyin
17 | */
18 | @Service("ClientService")
19 | @Slf4j
20 | public class ClientServiceImpl implements ClientService {
21 |
22 | @Resource
23 | private WeWorkConfig weworkConfig;
24 |
25 | @Resource
26 | private EncryptService encryptService;
27 |
28 | private long sdk = 0;
29 |
30 | public long getSdk() {
31 | if (sdk == 0)
32 | init();
33 | return sdk;
34 | }
35 |
36 | private void init() {
37 | // 初始化sdk
38 | sdk = Finance.NewSdk();
39 | int status = Finance.Init(sdk, weworkConfig.getCorpId(), weworkConfig.getCorpSecret());
40 | if (status != 0) {
41 | log.error("[异常]MediaServiceImpl.sdk.init失败, {}", status);
42 | Finance.DestroySdk(sdk);
43 | throw new RuntimeException(String.format("[异常]MediaServiceImpl.sdk.init失败, %s", status));
44 | }
45 | }
46 |
47 | public void downloadMediaFile(String sdkFileId, String file, long fileSize) {
48 | log.info("下载媒体文件 size: {}, {}", fileSize, sdkFileId);
49 | // 判断文件夹是否存在
50 | File fs = new File(file);
51 | if (!fs.getParentFile().exists()) {
52 | fs.getParentFile().mkdirs();
53 | }
54 | // 判断本地是否存在文件并且文件大小与远程一致,防止多次下载
55 | if (fs.exists() && fs.length() == fileSize) {
56 | log.debug("媒体文件本地已存在 {},{}", file, sdkFileId);
57 | return;
58 | }
59 | // 下载媒体文件
60 | String buf = "";
61 | long index = 0;
62 | int retry = 0;
63 | while (true) {
64 | if (retry >= 3) {
65 | log.debug("媒体文件下载重试次数超过3次, {}", sdkFileId);
66 | break;
67 | }
68 | long mediaData = Finance.NewMediaData();
69 | int ret = Finance.GetMediaData(sdk, buf, sdkFileId, weworkConfig.getProxy(), weworkConfig.getPasswd(), weworkConfig.getTimeout(), mediaData);
70 | if (ret != 0) {
71 | log.error("获取媒体文件异常, {}", ret);
72 | Finance.FreeMediaData(mediaData);
73 | retry++;
74 | continue;
75 | }
76 | log.debug("media_data_long: {}, len: {}, data_len: {}, is_finish: {}", index, Finance.GetIndexLen(mediaData), Finance.GetDataLen(mediaData), Finance.IsMediaDataFinish(mediaData));
77 | try {
78 | //大于512k的文件会分片拉取,此处需要使用追加写,避免后面的分片覆盖之前的数据。
79 | FileOutputStream outputStream = new FileOutputStream(fs, true);
80 | outputStream.write(Finance.GetData(mediaData));
81 | outputStream.close();
82 | } catch (Exception e) {
83 | log.error("[异常]写本地文件失败, {}", e.getMessage());
84 | Finance.FreeMediaData(mediaData);
85 | retry++;
86 | continue;
87 | }
88 | if (Finance.IsMediaDataFinish(mediaData) == 1) {
89 | Finance.FreeMediaData(mediaData);
90 | break;
91 | }
92 | index++;
93 | buf = Finance.GetOutIndexBuf(mediaData);
94 | Finance.FreeMediaData(mediaData);
95 | }
96 | log.info("[流程]媒体文件 size: {}, {}, {}", fs.length(), file, sdkFileId);
97 | }
98 |
99 | public String decryptData(Integer publicKeyVer, String encryptKey, String encryptChatMsg) {
100 | // 解密key
101 | String decodeKey = encryptService.decodeEncryptRandomKey(publicKeyVer, encryptKey);
102 | if (Objects.isNull(decodeKey)) {
103 | return null;
104 | }
105 | long slice = Finance.NewSlice();
106 | int decryptStatus = Finance.DecryptData(sdk, decodeKey, encryptChatMsg, slice);
107 | if (decryptStatus != 0) {
108 | log.error("DecryptData, {}", decryptStatus);
109 | Finance.FreeSlice(slice);
110 | return null;
111 | }
112 | String msg = Finance.GetContentFromSlice(slice);
113 | Finance.FreeSlice(slice);
114 | return msg;
115 | }
116 |
117 | }
118 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/service/impl/EncryptServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.service.impl;
2 |
3 | import com.chinayin.wework.chatdata.config.WeWorkEncryptConfig;
4 | import com.chinayin.wework.chatdata.service.EncryptService;
5 | import lombok.extern.slf4j.Slf4j;
6 | import org.springframework.stereotype.Service;
7 |
8 | import javax.annotation.Resource;
9 | import javax.crypto.Cipher;
10 | import java.security.KeyFactory;
11 | import java.security.PrivateKey;
12 | import java.security.spec.PKCS8EncodedKeySpec;
13 | import java.util.Base64;
14 | import java.util.Objects;
15 |
16 | /**
17 | * @author chianyin
18 | * 加解密处理
19 | */
20 | @Slf4j
21 | @Service("EncryptService")
22 | public class EncryptServiceImpl implements EncryptService {
23 |
24 | @Resource
25 | private WeWorkEncryptConfig encryptConfig;
26 |
27 | private String getPriKey(Integer publicKeyVer) {
28 | return encryptConfig.getPrivateKey().get(String.valueOf(publicKeyVer));
29 | }
30 |
31 | public String decodeEncryptRandomKey(Integer publicKeyVer, String encryptRandomKey) {
32 | String key = getPriKey(publicKeyVer);
33 | if (Objects.isNull(key)) {
34 | log.error("decodeEncryptRandomKey failed, privateKey(keyVer={}) is null", publicKeyVer);
35 | return null;
36 | }
37 | Base64.Decoder decoder = Base64.getMimeDecoder();
38 | byte[] keyBytes;
39 | try {
40 | keyBytes = decoder.decode(key);
41 | PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
42 | KeyFactory keyFactory = KeyFactory.getInstance("RSA");
43 | PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
44 | Cipher c1 = Cipher.getInstance("RSA");
45 | c1.init(Cipher.DECRYPT_MODE, privateKey);
46 | byte[] temp = c1.doFinal(Base64.getMimeDecoder().decode(encryptRandomKey));
47 | return new String(temp);
48 | } catch (Exception e) {
49 | log.error("decodeEncryptRandomKey failed, {}", e.getMessage());
50 | }
51 | return null;
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/service/impl/FileOutputServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.service.impl;
2 |
3 | import com.chinayin.wework.chatdata.service.FileOutputService;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.springframework.stereotype.Service;
6 |
7 | import java.io.File;
8 | import java.io.FileOutputStream;
9 | import java.text.SimpleDateFormat;
10 | import java.util.Date;
11 |
12 | /**
13 | * @author chianyin
14 | * 保存消息到文件
15 | */
16 | @Slf4j
17 | @Service("FileOutputService")
18 | public class FileOutputServiceImpl implements FileOutputService {
19 |
20 | private static String path = "./logs";
21 |
22 | @Override
23 | public void saveMessage(String msg) {
24 | String file = path + "/msg-" + this.getDateStr();
25 | this.record(file, msg);
26 | }
27 |
28 | @Override
29 | public void saveOriginMessage(String msg) {
30 | String file = path + "/origin-msg-" + this.getDateStr();
31 | this.record(file, msg);
32 | }
33 |
34 | private boolean record(String file, String msg) {
35 | try {
36 | // 判断文件夹是否存在
37 | // if (!fs.getParentFile().exists()) {
38 | // fs.getParentFile().mkdirs();
39 | // }
40 | FileOutputStream outputStream = new FileOutputStream(new File(file), true);
41 | outputStream.write(msg.getBytes());
42 | outputStream.write("\n".getBytes());
43 | outputStream.close();
44 | return true;
45 | } catch (Exception e) {
46 | log.error(e.getMessage());
47 | }
48 | return false;
49 | }
50 |
51 | private String getDateStr() {
52 | return (new SimpleDateFormat("yyyy-MM-dd")).format((new Date())) + ".log";
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/service/impl/MnsClientServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.service.impl;
2 |
3 | import com.aliyun.mns.client.CloudQueue;
4 | import com.aliyun.mns.client.MNSClient;
5 | import com.aliyun.mns.common.ClientException;
6 | import com.aliyun.mns.common.ServiceException;
7 | import com.aliyun.mns.model.Message;
8 | import com.chinayin.wework.chatdata.config.MnsClientConfig;
9 | import com.chinayin.wework.chatdata.service.MnsClientService;
10 | import lombok.extern.slf4j.Slf4j;
11 | import org.springframework.stereotype.Service;
12 |
13 | import javax.annotation.Resource;
14 |
15 | /**
16 | * @author chianyin
17 | * 阿里云mns
18 | */
19 | @Service("MnsClientService")
20 | @Slf4j
21 | public class MnsClientServiceImpl implements MnsClientService {
22 |
23 | @Resource
24 | private MNSClient client;
25 |
26 | @Resource
27 | private MnsClientConfig config;
28 |
29 | @Override
30 | public String sendMessageToQueue(String msg) {
31 | return this.sendMessageToQueue(new Message(msg));
32 | }
33 |
34 | /**
35 | * priority 发送优先级(取值范围1~16,其中1为最高优先级,队列默认优先级为8)
36 | *
37 | * @param msg
38 | * @return
39 | */
40 | @Override
41 | public String sendMessageToQueue(Message msg) {
42 | try {
43 | CloudQueue queue = client.getQueueRef(config.getQueue());
44 | Message message = queue.putMessage(msg);
45 | String msgId = message.getMessageId();
46 | log.info("[流程]投递 mns 成功, {}, {}", msgId, message.getRequestId());
47 | return msgId;
48 | } catch (ClientException e) {
49 | log.error("[异常]投递 mns 失败, Caught an ClientException: {}", e.toString());
50 | } catch (ServiceException e) {
51 | log.error("[异常]投递 mns 失败, Caught an ServiceException: {}", e.toString());
52 | } catch (Exception e) {
53 | log.error("[异常]投递 mns 失败, {}", e.getMessage());
54 | }
55 | return null;
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/com/chinayin/wework/chatdata/service/impl/OssClientServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.chinayin.wework.chatdata.service.impl;
2 |
3 | import com.aliyun.oss.ClientException;
4 | import com.aliyun.oss.OSS;
5 | import com.aliyun.oss.OSSClientBuilder;
6 | import com.aliyun.oss.OSSException;
7 | import com.aliyun.oss.model.ObjectMetadata;
8 | import com.aliyun.oss.model.PutObjectRequest;
9 | import com.aliyun.oss.model.PutObjectResult;
10 | import com.chinayin.wework.chatdata.config.OssClientConfig;
11 | import com.chinayin.wework.chatdata.service.OssClientService;
12 | import lombok.extern.slf4j.Slf4j;
13 | import org.springframework.stereotype.Service;
14 |
15 | import javax.annotation.Resource;
16 | import java.io.File;
17 |
18 | /**
19 | * @author chianyin
20 | * 阿里云oss
21 | */
22 | @Service("OssClientService")
23 | @Slf4j
24 | public class OssClientServiceImpl implements OssClientService {
25 |
26 | @Resource
27 | private OssClientConfig config;
28 |
29 | /**
30 | * 获取媒体地址
31 | *
32 | * @param objectName
33 | * @return
34 | */
35 | public String getMediaOssUrl(String objectName) {
36 | return "https://" + config.getBucket() + "." + config.getEndpoint() + "/" + objectName;
37 | }
38 |
39 | public Boolean doesObjectExist(String objectName) {
40 | OSS ossClient = new OSSClientBuilder().build(
41 | config.getEndpoint(), config.getAccessKeyId(), config.getAccessKeySecret());
42 | try {
43 | return ossClient.doesObjectExist(config.getBucket(), objectName);
44 | } catch (OSSException ex) {
45 | if (ex.getErrorCode().equals("FileAlreadyExists")) {
46 | return true;
47 | } else {
48 | log.error("[异常]Caught an OSSException, which means your request made it to OSS, but was rejected with an error response for some reason.\n" +
49 | "Error Message: {}\n" +
50 | "Error Code: {}\n" +
51 | "Request ID: {}\n" +
52 | "Host ID: {}",
53 | ex.getErrorMessage(), ex.getErrorCode(), ex.getRequestId(), ex.getHostId());
54 | }
55 | } catch (ClientException ex) {
56 | log.error("[异常]Caught an ClientException, which means the client encountered "
57 | + "a serious internal problem while trying to communicate with OSS, "
58 | + "such as not being able to access the network.\nError Message: {}", ex.getMessage());
59 | } catch (Exception e) {
60 | e.printStackTrace();
61 | } finally {
62 | ossClient.shutdown();
63 | }
64 | return false;
65 | }
66 |
67 | /**
68 | * 上传资源到 oss
69 | *
70 | * @param objectName
71 | * @param file
72 | */
73 | public void upload(String objectName, String file) {
74 | File fs = new File(file);
75 | log.info("{} .size = {}", objectName, fs.length());
76 | if (!fs.exists() || fs.length() < 1) {
77 | log.error("本地 oss 文件不存在, {} ", objectName);
78 | return;
79 | }
80 | OSS ossClient = new OSSClientBuilder().build(
81 | config.getEndpoint(), config.getAccessKeyId(), config.getAccessKeySecret());
82 | try {
83 | // 优先判断文件是否存在
84 | boolean found = ossClient.doesObjectExist(config.getBucket(), objectName);
85 | if (found) {
86 | log.info("[流程]上传 oss 同名跳过, {}", objectName);
87 | } else {
88 | // 禁止覆盖同名文件 https://help.aliyun.com/document_detail/146172.html
89 | ObjectMetadata metadata = new ObjectMetadata();
90 | metadata.setHeader("x-oss-forbid-overwrite", "true");
91 | // 上传
92 | PutObjectRequest putObjectRequest = new PutObjectRequest(config.getBucket(), objectName, fs, metadata);
93 | PutObjectResult putObjectResult = ossClient.putObject(putObjectRequest);
94 | //log.info("[流程]上传 oss 成功, {}", putObjectResult.getResponse().getRequestId());
95 | }
96 | // 上传完删除文件
97 | fs.delete();
98 | } catch (OSSException ex) {
99 | // 同名文件不需要上传
100 | if (ex.getErrorCode().equals("FileAlreadyExists")) {
101 | log.info("[流程]上传 oss 同名跳过, {}", objectName);
102 | } else {
103 | log.error("[异常]Caught an OSSException, which means your request made it to OSS, but was rejected with an error response for some reason.\n" +
104 | "Error Message: {}\n" +
105 | "Error Code: {}\n" +
106 | "Request ID: {}\n" +
107 | "Host ID: {}",
108 | ex.getErrorMessage(), ex.getErrorCode(), ex.getRequestId(), ex.getHostId());
109 | }
110 | // 存在同名文件避免后续产生垃圾文件,删除文件
111 | fs.delete();
112 | } catch (ClientException ex) {
113 | log.error("[异常]Caught an ClientException, which means the client encountered "
114 | + "a serious internal problem while trying to communicate with OSS, "
115 | + "such as not being able to access the network.\nError Message: {}", ex.getMessage());
116 | } catch (Exception e) {
117 | e.printStackTrace();
118 | log.error("[异常]上传 oss 失败, {}", e.getMessage());
119 | } finally {
120 | ossClient.shutdown();
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/main/java/com/tencent/wework/Finance.java:
--------------------------------------------------------------------------------
1 | package com.tencent.wework;
2 |
3 | /* sdk返回数据
4 | typedef struct Slice_t {
5 | char* buf;
6 | int len;
7 | } Slice_t;
8 |
9 | typedef struct MediaData {
10 | char* outindexbuf;
11 | int out_len;
12 | char* data;
13 | int data_len;
14 | int is_finish;
15 | } MediaData_t;
16 | */
17 |
18 | import lombok.extern.slf4j.Slf4j;
19 |
20 | @Slf4j
21 | public class Finance {
22 | public native static long NewSdk();
23 |
24 | /**
25 | * 初始化函数
26 | * Return值=0表示该API调用成功
27 | *
28 | * @param [in] sdk NewSdk返回的sdk指针
29 | * @param [in] corpid 调用企业的企业id,例如:wwd08c8exxxx5ab44d,可以在企业微信管理端--我的企业--企业信息查看
30 | * @param [in] secret 聊天内容存档的Secret,可以在企业微信管理端--管理工具--聊天内容存档查看
31 | * @return 返回是否初始化成功
32 | * 0 - 成功
33 | * !=0 - 失败
34 | */
35 | public native static int Init(long sdk, String corpid, String secret);
36 |
37 | /**
38 | * 拉取聊天记录函数
39 | * Return值=0表示该API调用成功
40 | *
41 | * @param [in] sdk NewSdk返回的sdk指针
42 | * @param [in] seq 从指定的seq开始拉取消息,注意的是返回的消息从seq+1开始返回,seq为之前接口返回的最大seq值。首次使用请使用seq:0
43 | * @param [in] limit 一次拉取的消息条数,最大值1000条,超过1000条会返回错误
44 | * @param [in] proxy 使用代理的请求,需要传入代理的链接。如:socks5://10.0.0.1:8081 或者 http://10.0.0.1:8081
45 | * @param [in] passwd 代理账号密码,需要传入代理的账号密码。如 user_name:passwd_123
46 | * @param [out] chatDatas 返回本次拉取消息的数据,slice结构体.内容包括errcode/errmsg,以及每条消息内容。
47 | * @return 返回是否调用成功
48 | * 0 - 成功
49 | * !=0 - 失败
50 | */
51 | public native static int GetChatData(long sdk, long seq, long limit, String proxy, String passwd, long timeout, long chatData);
52 |
53 | /**
54 | * 拉取媒体消息函数
55 | * Return值=0表示该API调用成功
56 | *
57 | * @param [in] sdk NewSdk返回的sdk指针
58 | * @param [in] sdkFileid 从GetChatData返回的聊天消息中,媒体消息包括的sdkfileid
59 | * @param [in] proxy 使用代理的请求,需要传入代理的链接。如:socks5://10.0.0.1:8081 或者 http://10.0.0.1:8081
60 | * @param [in] passwd 代理账号密码,需要传入代理的账号密码。如 user_name:passwd_123
61 | * @param [in] indexbuf 媒体消息分片拉取,需要填入每次拉取的索引信息。首次不需要填写,默认拉取512k,后续每次调用只需要将上次调用返回的outindexbuf填入即可。
62 | * @param [out] media_data 返回本次拉取的媒体数据.MediaData结构体.内容包括data(数据内容)/outindexbuf(下次索引)/is_finish(拉取完成标记)
63 | * @return 返回是否调用成功
64 | * 0 - 成功
65 | * !=0 - 失败
66 | */
67 | public native static int GetMediaData(long sdk, String indexbuf, String sdkField, String proxy, String passwd, long timeout, long mediaData);
68 |
69 | /**
70 | * @param [in] encrypt_key, getchatdata返回的encrypt_key
71 | * @param [in] encrypt_msg, getchatdata返回的content
72 | * @param [out] msg, 解密的消息明文
73 | * @return 返回是否调用成功
74 | * 0 - 成功
75 | * !=0 - 失败
76 | * @brief 解析密文
77 | */
78 | public native static int DecryptData(long sdk, String encrypt_key, String encrypt_msg, long msg);
79 |
80 | public native static void DestroySdk(long sdk);
81 |
82 | public native static long NewSlice();
83 |
84 | /**
85 | * @return
86 | * @brief 释放slice,和NewSlice成对使用
87 | */
88 | public native static void FreeSlice(long slice);
89 |
90 | /**
91 | * @return 内容
92 | * @brief 获取slice内容
93 | */
94 | public native static String GetContentFromSlice(long slice);
95 |
96 | /**
97 | * @return 内容
98 | * @brief 获取slice内容长度
99 | */
100 | public native static int GetSliceLen(long slice);
101 |
102 | public native static long NewMediaData();
103 |
104 | public native static void FreeMediaData(long mediaData);
105 |
106 | /**
107 | * @return outindex
108 | * @brief 获取mediadata outindex
109 | */
110 | public native static String GetOutIndexBuf(long mediaData);
111 |
112 | /**
113 | * @return data
114 | * @brief 获取mediadata data数据
115 | */
116 | public native static byte[] GetData(long mediaData);
117 |
118 | public native static int GetIndexLen(long mediaData);
119 |
120 | public native static int GetDataLen(long mediaData);
121 |
122 | /**
123 | * @return 1完成、0未完成
124 | * @brief 判断mediadata是否结束
125 | */
126 | public native static int IsMediaDataFinish(long mediaData);
127 |
128 | static {
129 | log.info("java.library.path = {}", System.getProperty("java.library.path"));
130 | System.loadLibrary("WeWorkFinanceSdk_Java");
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | # app
2 | app.corpId=
3 | app.corpSecret=
4 | app.env=prod
5 | # log
6 | log.path=./logs
7 | # pool
8 | pool.corePoolSize=10
9 | pool.maximumPoolSize=20
10 | pool.queueCapacity=20
11 | pool.keepAliveSeconds=10
12 | # mns
13 | mns.endpoint=http://
14 | mns.accessKeyId=
15 | mns.accessKeySecret=
16 | mns.queue=
17 | # oss
18 | oss.endpoint=oss-cn-beijing.aliyuncs.com
19 | oss.accessKeyId=
20 | oss.accessKeySecret=
21 | oss.bucket=
22 | # sentry
23 | sentry.dsn=
24 | # privateKey
25 | encrypt.privateKey.1=
26 |
--------------------------------------------------------------------------------
/src/main/resources/logback-spring.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | ${CONSOLE_LOG_PATTERN}
11 |
12 |
13 |
14 |
15 | ${log.path}/wework.log
16 |
17 | ${log.path}/%d{yyyy-MM}/wework-%d{yyyyMMdd}-%i.gz
18 | 100MB
19 | 90
20 |
21 |
22 | ${FILE_LOG_PATTERN}
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/main/resources/supervisord.conf:
--------------------------------------------------------------------------------
1 | [program:WeworkChatSDK]
2 | command=java -jar WeworkChatSDK-1.0.0.jar --spring.profiles.active=prod
3 | directory=/app/target/WeworkChatSDK-1.0.0
4 | autorestart=true
5 | startsecs=10
6 | stdout_logfile=NONE
7 | stderr_logfile=NONE
--------------------------------------------------------------------------------