本文将详细介绍如何基于Java(后端)+ Vue3(前端) 技术栈,实现企业微信接入自定义系统,完成企业微信共享文档创建 和数据统计功能。整体流程分为:企业微信应用配置、后端接口开发(文档创建、数据统计)、前端交互页面开发三大核心环节。
一、前期准备:企业微信相关配置
1.1 注册企业微信并创建应用
- 登录企业微信管理后台,完成企业注册。
- 进入应用管理 → 创建应用 (自建应用),填写应用名称、图标等信息,创建后记录以下关键信息:
- CorpID(企业 ID):企业的唯一标识。
- AgentID(应用 ID):自建应用的 ID。
- Secret(应用密钥):用于获取接口调用凭证(access_token)。
- 配置可信域名 / IP:在应用设置中,添加后端服务的域名或 IP(企业微信接口调用的白名单)。
1.2 开通企业微信文档相关权限
企业微信共享文档属于微盘 / 文档模块,需要在管理后台开通权限:
- 进入应用管理 → 对应自建应用 → 权限管理 → 勾选以下权限:
- 文档权限:
文档的读权限、文档的写权限、创建共享文档等。 - 通讯录权限(可选,用于统计用户相关数据):
获取部门成员、获取用户基本信息。
- 文档权限:
- 参考企业微信文档接口文档,确认接口调用的权限范围。
1.3 技术栈依赖说明
后端(Java)
- 框架:Spring Boot(推荐 2.7 + 或 3.x)
- 依赖:
okhttp3/RestTemplate:调用企业微信 APIfastjson2/jackson:JSON 解析lombok:简化代码redis(可选):缓存 access_token(避免频繁调用获取接口)
前端(Vue3)
- 框架:Vue3 + Vite(或 Vue CLI)
- 依赖:
axios:调用后端接口element-plus:UI 组件库echarts:数据统计可视化
二、后端开发(Java + Spring Boot)
2.1 核心配置:企业微信参数与 HTTP 工具
2.1.1 配置文件(application.yml)
yaml
java
wechat:
work:
corp-id: 你的企业CorpID
agent-id: 你的应用AgentID
secret: 你的应用Secret
# 企业微信API基础地址
base-url: https://qyapi.weixin.qq.com/cgi-bin
# access_token缓存过期时间(企业微信默认7200秒,建议设置为7000秒)
token-expire: 7000
spring:
# redis配置(可选,若不用redis可使用内存缓存)
redis:
host: localhost
port: 6379
password: 你的redis密码
database: 0
2.1.2 企业微信配置类
java
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "wechat.work")
public class WeChatWorkProperties {
private String corpId;
private String agentId;
private String secret;
private String baseUrl;
private Long tokenExpire;
}
2.1.3 HTTP 工具类(调用企业微信 API)
java
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
@Component
public class OkHttpUtil {
private final OkHttpClient client;
public OkHttpUtil() {
this.client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build();
}
// GET请求
public String get(String url) throws IOException {
Request request = new Request.Builder().url(url).get().build();
try (Response response = client.newCall(request).execute()) {
return response.body() != null ? response.body().string() : null;
}
}
// POST请求(JSON参数)
public String post(String url, String json) throws IOException {
okhttp3.MediaType mediaType = okhttp3.MediaType.parse("application/json; charset=utf-8");
okhttp3.RequestBody body = okhttp3.RequestBody.create(json, mediaType);
Request request = new Request.Builder().url(url).post(body).build();
try (Response response = client.newCall(request).execute()) {
return response.body() != null ? response.body().string() : null;
}
}
}
2.2 核心功能 1:获取企业微信 access_token
access_token 是调用企业微信所有接口的凭证,需要缓存(避免频繁调用,企业微信限制每分钟调用次数)。
java
import com.alibaba.fastjson2.JSONObject;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
@Service
public class WeChatWorkService {
@Resource
private WeChatWorkProperties weChatWorkProperties;
@Resource
private OkHttpUtil okHttpUtil;
@Resource
private StringRedisTemplate stringRedisTemplate;
// 缓存key
private static final String ACCESS_TOKEN_KEY = "wechat_work:access_token";
/**
* 获取access_token(优先从redis获取,过期则重新调用接口)
*/
public String getAccessToken() throws IOException {
// 从redis获取
String accessToken = stringRedisTemplate.opsForValue().get(ACCESS_TOKEN_KEY);
if (accessToken != null && !accessToken.isEmpty()) {
return accessToken;
}
// 调用企业微信接口获取
String url = String.format("%s/gettoken?corpid=%s&corpsecret=%s",
weChatWorkProperties.getBaseUrl(),
weChatWorkProperties.getCorpId(),
weChatWorkProperties.getSecret());
String response = okHttpUtil.get(url);
JSONObject jsonObject = JSONObject.parseObject(response);
if (jsonObject.getInteger("errcode") != 0) {
throw new RuntimeException("获取access_token失败:" + jsonObject.getString("errmsg"));
}
accessToken = jsonObject.getString("access_token");
// 存入redis并设置过期时间
stringRedisTemplate.opsForValue().set(ACCESS_TOKEN_KEY, accessToken,
weChatWorkProperties.getTokenExpire(), TimeUnit.SECONDS);
return accessToken;
}
}
2.3 核心功能 2:创建企业微信共享文档
企业微信共享文档的创建需要调用文档接口 ,注意:企业微信文档接口分为旧版文档 和新版微盘文档,以下以 ** 新版微盘文档(推荐)** 为例。
2.3.1 文档创建接口封装
java
import com.alibaba.fastjson2.JSONObject;
import org.springframework.stereotype.Service;
import java.io.IOException;
@Service
public class WeChatDocService {
@Resource
private WeChatWorkService weChatWorkService;
@Resource
private WeChatWorkProperties weChatWorkProperties;
@Resource
private OkHttpUtil okHttpUtil;
/**
* 创建企业微信共享文档(新版微盘文档:文档类型为docx)
* @param docName 文档名称
* @param creator 创建人(企业微信用户ID)
* @param folderId 父文件夹ID(可选,空则创建在根目录)
* @return 文档ID、文档链接等信息
*/
public JSONObject createSharedDoc(String docName, String creator, String folderId) throws IOException {
String accessToken = weChatWorkService.getAccessToken();
// 新版微盘文档创建接口(参考企业微信文档:https://developer.work.weixin.qq.com/document/path/94076)
String url = String.format("%s/wedrive/file_create?access_token=%s",
weChatWorkProperties.getBaseUrl(), accessToken);
// 构建请求参数
JSONObject params = new JSONObject();
params.put("file_name", docName);
params.put("file_type", "docx"); // 文档类型:docx(文档)、xlsx(表格)、pptx(演示)
params.put("creator", creator);
if (folderId != null && !folderId.isEmpty()) {
params.put("fatherid", folderId);
}
// 共享设置:设置为企业内共享(可根据需求调整权限)
params.put("auth_info", new JSONObject() {{
put("auth_type", 1); // 1:企业内共享;2:指定范围共享
put("auth_scope", new JSONObject() {{
put("type", 1); // 1:企业内所有人;2:指定部门/用户
}});
}});
// 调用接口
String response = okHttpUtil.post(url, params.toJSONString());
JSONObject result = JSONObject.parseObject(response);
if (result.getInteger("errcode") != 0) {
throw new RuntimeException("创建共享文档失败:" + result.getString("errmsg"));
}
return result;
}
}
2.4 核心功能 3:文档数据统计
数据统计可分为文档基础数据统计 (文档数量、创建人数、文档类型分布)和文档操作数据统计 (访问量、编辑次数、分享次数)。以下以基础统计为例,操作数据需调用企业微信的数据统计接口(需开通对应权限)。
2.4.1 数据统计服务
java
import com.alibaba.fastjson2.JSONObject;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Service
public class DocStatisticService {
@Resource
private WeChatWorkService weChatWorkService;
@Resource
private WeChatWorkProperties weChatWorkProperties;
@Resource
private OkHttpUtil okHttpUtil;
/**
* 获取企业微信文档基础统计数据
* @return 统计结果(文档总数、各类型数量、创建人数等)
*/
public Map<String, Object> getDocStatistic() throws IOException {
String accessToken = weChatWorkService.getAccessToken();
// 1. 调用企业微信接口获取文档列表(分页获取)
String listUrl = String.format("%s/wedrive/file_list?access_token=%s",
weChatWorkProperties.getBaseUrl(), accessToken);
JSONObject listParams = new JSONObject();
listParams.put("file_type", "docx"); // 可筛选类型,或不传获取所有
listParams.put("page_size", 100);
listParams.put("page_num", 1);
String listResponse = okHttpUtil.post(listUrl, listParams.toJSONString());
JSONObject listResult = JSONObject.parseObject(listResponse);
if (listResult.getInteger("errcode") != 0) {
throw new RuntimeException("获取文档列表失败:" + listResult.getString("errmsg"));
}
// 2. 统计数据(此处为示例,可根据实际需求扩展)
Map<String, Object> statistic = new HashMap<>();
int totalDoc = listResult.getJSONObject("data").getInteger("total_num"); // 文档总数
int docxNum = 0, xlsxNum = 0, pptxNum = 0;
// 遍历文档列表统计类型(示例:实际需分页遍历所有文档)
for (Object doc : listResult.getJSONObject("data").getJSONArray("file_list")) {
JSONObject docObj = (JSONObject) doc;
String fileType = docObj.getString("file_type");
switch (fileType) {
case "docx":
docxNum++;
break;
case "xlsx":
xlsxNum++;
break;
case "pptx":
pptxNum++;
break;
}
}
// 3. 补充统计维度(如创建人数、最近7天新增文档数)
statistic.put("totalDoc", totalDoc);
statistic.put("docTypeCount", new HashMap<String, Integer>() {{
put("docx", docxNum);
put("xlsx", xlsxNum);
put("pptx", pptxNum);
}});
statistic.put("createUserCount", listResult.getJSONObject("data").getJSONArray("file_list").size()); // 示例:实际需去重
statistic.put("newDocIn7Days", 0); // 示例:需根据文档创建时间统计
return statistic;
}
/**
* 获取文档操作数据统计(访问量、编辑次数等)
* 需调用企业微信数据统计接口:https://developer.work.weixin.qq.com/document/path/92714
*/
public JSONObject getDocOperateStatistic(String startDate, String endDate) throws IOException {
String accessToken = weChatWorkService.getAccessToken();
String url = String.format("%s/datastat/get_doc_stat?access_token=%s",
weChatWorkProperties.getBaseUrl(), accessToken);
JSONObject params = new JSONObject();
params.put("start_date", startDate); // 格式:yyyy-MM-dd
params.put("end_date", endDate);
String response = okHttpUtil.post(url, params.toJSONString());
JSONObject result = JSONObject.parseObject(response);
if (result.getInteger("errcode") != 0) {
throw new RuntimeException("获取文档操作统计失败:" + result.getString("errmsg"));
}
return result;
}
}
2.5 控制器(Controller)
封装接口供前端调用:
java
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.Map;
@RestController
@RequestMapping("/api/wechat/doc")
@CrossOrigin // 开发环境允许跨域,生产环境建议配置Nginx
public class DocController {
@Resource
private WeChatDocService weChatDocService;
@Resource
private DocStatisticService docStatisticService;
/**
* 创建共享文档
*/
@PostMapping("/create")
public Object createDoc(@RequestParam String docName,
@RequestParam String creator,
@RequestParam(required = false) String folderId) throws IOException {
return weChatDocService.createSharedDoc(docName, creator, folderId);
}
/**
* 获取文档基础统计数据
*/
@GetMapping("/statistic/base")
public Map<String, Object> getBaseStatistic() throws IOException {
return docStatisticService.getDocStatistic();
}
/**
* 获取文档操作统计数据
*/
@GetMapping("/statistic/operate")
public Object getOperateStatistic(@RequestParam String startDate,
@RequestParam String endDate) throws IOException {
return docStatisticService.getDocOperateStatistic(startDate, endDate);
}
}
三、前端开发(Vue3 + Element Plus + ECharts)
3.1 页面 1:创建共享文档组件(DocCreate.vue)
javascript
<template>
<div class="doc-create">
<el-card title="创建企业微信共享文档">
<el-form :model="docForm" label-width="100px" @submit.prevent="createDoc">
<el-form-item label="文档名称" prop="docName">
<el-input v-model="docForm.docName" placeholder="请输入文档名称"></el-input>
</el-form-item>
<el-form-item label="创建人ID" prop="creator">
<el-input v-model="docForm.creator" placeholder="请输入企业微信用户ID"></el-input>
</el-form-item>
<el-form-item label="父文件夹ID" prop="folderId">
<el-input v-model="docForm.folderId" placeholder="选填,空则创建在根目录"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" type="submit">创建文档</el-button>
</el-form-item>
</el-form>
<!-- 结果展示 -->
<div v-if="docResult" class="result">
<h3>创建成功</h3>
<p>文档ID:{{ docResult.data.fileid }}</p>
<p>文档链接:<a :href="docResult.data.url" target="_blank">{{ docResult.data.url }}</a></p>
</div>
</el-card>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import axios from 'axios'
const docForm = ref({
docName: '',
creator: '',
folderId: ''
})
const docResult = ref(null)
const createDoc = async () => {
if (!docForm.value.docName || !docForm.value.creator) {
ElMessage.warning('文档名称和创建人ID不能为空')
return
}
try {
const res = await axios.post('/wechat/doc/create', null, {
params: {
docName: docForm.value.docName,
creator: docForm.value.creator,
folderId: docForm.value.folderId
}
})
docResult.value = res.data
ElMessage.success('文档创建成功')
} catch (error) {
ElMessage.error('创建失败:' + (error.response?.data?.message || error.message))
}
}
</script>
<style scoped>
.doc-create {
max-width: 800px;
margin: 20px auto;
padding: 20px;
}
.result {
margin-top: 20px;
padding: 10px;
border: 1px solid #e6f7ff;
border-radius: 4px;
background-color: #f0f9ff;
}
</style>
3.2 页面 2:文档数据统计组件(DocStatistic.vue)
javascript
<template>
<div class="doc-statistic">
<el-card title="文档数据统计">
<!-- 基础统计 -->
<el-row :gutter="20">
<el-col :span="6">
<el-card header="文档总数">
<div class="stat-num">{{ baseStat.totalDoc || 0 }}</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card header="创建人数">
<div class="stat-num">{{ baseStat.createUserCount || 0 }}</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card header="7天新增文档">
<div class="stat-num">{{ baseStat.newDocIn7Days || 0 }}</div>
</el-card>
</el-col>
</el-row>
<!-- 文档类型分布图表 -->
<div class="chart-container" style="height: 400px; margin-top: 20px;">
<h3>文档类型分布</h3>
<div id="typeChart" style="width: 100%; height: 300px;"></div>
</div>
<!-- 操作统计 -->
<div class="operate-stat" style="margin-top: 20px;">
<el-form :model="dateForm" inline @submit.prevent="getOperateStat">
<el-form-item label="开始日期">
<el-date-picker v-model="dateForm.startDate" type="date" placeholder="选择开始日期"></el-date-picker>
</el-form-item>
<el-form-item label="结束日期">
<el-date-picker v-model="dateForm.endDate" type="date" placeholder="选择结束日期"></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" type="submit">查询操作数据</el-button>
</el-form-item>
</el-form>
<div v-if="operateStat" class="operate-result">
<p>总访问量:{{ operateStat.data.visit_count || 0 }}</p>
<p>总编辑次数:{{ operateStat.data.edit_count || 0 }}</p>
<p>总分享次数:{{ operateStat.data.share_count || 0 }}</p>
</div>
</div>
</el-card>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import { ElMessage } from 'element-plus'
import axios from 'axios'
import { init } from 'echarts'
const baseStat = ref({})
const operateStat = ref(null)
const dateForm = ref({
startDate: '',
endDate: ''
})
let typeChart = null
// 获取基础统计数据
const getBaseStat = async () => {
try {
const res = await axios.get('/wechat/doc/statistic/base')
baseStat.value = res.data
// 渲染图表
renderTypeChart()
} catch (error) {
ElMessage.error('获取基础统计失败:' + error.message)
}
}
// 获取操作统计数据
const getOperateStat = async () => {
if (!dateForm.value.startDate || !dateForm.value.endDate) {
ElMessage.warning('请选择开始和结束日期')
return
}
try {
const res = await axios.get('/wechat/doc/statistic/operate', {
params: {
startDate: dateForm.value.startDate,
endDate: dateForm.value.endDate
}
})
operateStat.value = res.data
} catch (error) {
ElMessage.error('获取操作统计失败:' + error.message)
}
}
// 渲染文档类型分布图表
const renderTypeChart = () => {
const chartDom = document.getElementById('typeChart')
if (!chartDom) return
typeChart = init(chartDom)
const option = {
title: { text: '文档类型分布' },
tooltip: { trigger: 'item' },
legend: { orient: 'vertical', left: 'left' },
series: [
{
name: '文档类型',
type: 'pie',
radius: ['40%', '70%'],
data: [
{ value: baseStat.value.docTypeCount?.docx || 0, name: 'docx(文档)' },
{ value: baseStat.value.docTypeCount?.xlsx || 0, name: 'xlsx(表格)' },
{ value: baseStat.value.docTypeCount?.pptx || 0, name: 'pptx(演示)' }
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
}
typeChart.setOption(option)
}
// 监听窗口大小变化,重新渲染图表
const resizeChart = () => {
if (typeChart) {
typeChart.resize()
}
}
onMounted(() => {
getBaseStat()
window.addEventListener('resize', resizeChart)
})
// 组件卸载时移除监听
onUnmounted(() => {
window.removeEventListener('resize', resizeChart)
if (typeChart) {
typeChart.dispose()
}
})
</script>
<style scoped>
.doc-statistic {
max-width: 1200px;
margin: 20px auto;
padding: 20px;
}
.stat-num {
font-size: 24px;
font-weight: bold;
text-align: center;
margin-top: 10px;
}
.operate-result {
margin-top: 20px;
padding: 10px;
border: 1px solid #e6f7ff;
border-radius: 4px;
background-color: #f0f9ff;
}
</style>
3.3主页面(App.vue)
javascript
<template>
<div id="app">
<el-menu default-active="1" class="el-menu-demo" mode="horizontal">
<el-menu-item index="1">
<router-link to="/create">创建共享文档</router-link>
</el-menu-item>
<el-menu-item index="2">
<router-link to="/statistic">文档数据统计</router-link>
</el-menu-item>
</el-menu>
<router-view />
</div>
</template>
<script setup>
// 无需额外代码,仅作为路由容器
</script>
<style scoped>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
margin: 0 auto;
padding-top: 20px;
}
</style>
四、部署与注意事项
4.1 部署步骤
- 后端:将 Spring Boot 项目打包为 jar 包,部署到服务器(需配置 redis、JDK 环境),启动命令:
java -jar wechat-doc-system.jar。 - 前端:执行
npm run build打包项目,将 dist 目录部署到 Nginx 或 CDN,配置 Nginx 反向代理后端接口(解决跨域问题)。 - 企业微信:在应用管理中更新可信域名为前端部署的域名和后端接口域名。
4.2 注意事项
- access_token 缓存:必须缓存,否则会触发企业微信接口的频率限制(每分钟最多调用 200 次)。
- 权限问题:确保企业微信应用已开通所有需要的权限,且调用接口的用户拥有对应的文档权限。
- 参数格式:企业微信接口对参数格式要求严格(如日期格式、JSON 结构),需严格按照文档传递。
- 分页处理:获取文档列表时,若文档数量较多,需分页遍历(示例中仅获取第一页,实际需处理分页)。
- 生产环境安全:前端部署时关闭跨域配置,使用 Nginx 反向代理;后端添加接口鉴权(如 JWT),避免接口被非法调用。
五、扩展功能
- 文档编辑与同步:调用企业微信文档编辑接口,实现自定义系统与企业微信文档的数据同步。
- 权限管理:在自定义系统中管理企业微信文档的共享权限(如添加 / 移除访问用户)。
- 数据可视化扩展:使用 ECharts 实现更多维度的统计图表(如访问量趋势、用户操作排行)。
- 消息通知:文档创建或数据统计完成后,通过企业微信应用消息推送通知相关用户。
以上方案完整实现了企业微信接入自定义系统的核心功能,可根据实际业务需求进一步扩展和优化。