企业微信接入自定义系统(Java+Vue3)实现共享文档创建与数据统计

本文将详细介绍如何基于Java(后端)+ Vue3(前端) 技术栈,实现企业微信接入自定义系统,完成企业微信共享文档创建数据统计功能。整体流程分为:企业微信应用配置、后端接口开发(文档创建、数据统计)、前端交互页面开发三大核心环节。

一、前期准备:企业微信相关配置

1.1 注册企业微信并创建应用

  1. 登录企业微信管理后台,完成企业注册。
  2. 进入应用管理创建应用 (自建应用),填写应用名称、图标等信息,创建后记录以下关键信息:
    • CorpID(企业 ID):企业的唯一标识。
    • AgentID(应用 ID):自建应用的 ID。
    • Secret(应用密钥):用于获取接口调用凭证(access_token)。
  3. 配置可信域名 / IP:在应用设置中,添加后端服务的域名或 IP(企业微信接口调用的白名单)。

1.2 开通企业微信文档相关权限

企业微信共享文档属于微盘 / 文档模块,需要在管理后台开通权限:

  1. 进入应用管理 → 对应自建应用 → 权限管理 → 勾选以下权限:
    • 文档权限:文档的读权限文档的写权限创建共享文档等。
    • 通讯录权限(可选,用于统计用户相关数据):获取部门成员获取用户基本信息
  2. 参考企业微信文档接口文档,确认接口调用的权限范围。

1.3 技术栈依赖说明

后端(Java)
  • 框架:Spring Boot(推荐 2.7 + 或 3.x)
  • 依赖:
    • okhttp3/RestTemplate:调用企业微信 API
    • fastjson2/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 部署步骤

  1. 后端:将 Spring Boot 项目打包为 jar 包,部署到服务器(需配置 redis、JDK 环境),启动命令:java -jar wechat-doc-system.jar
  2. 前端:执行npm run build打包项目,将 dist 目录部署到 Nginx 或 CDN,配置 Nginx 反向代理后端接口(解决跨域问题)。
  3. 企业微信:在应用管理中更新可信域名为前端部署的域名和后端接口域名。

4.2 注意事项

  1. access_token 缓存:必须缓存,否则会触发企业微信接口的频率限制(每分钟最多调用 200 次)。
  2. 权限问题:确保企业微信应用已开通所有需要的权限,且调用接口的用户拥有对应的文档权限。
  3. 参数格式:企业微信接口对参数格式要求严格(如日期格式、JSON 结构),需严格按照文档传递。
  4. 分页处理:获取文档列表时,若文档数量较多,需分页遍历(示例中仅获取第一页,实际需处理分页)。
  5. 生产环境安全:前端部署时关闭跨域配置,使用 Nginx 反向代理;后端添加接口鉴权(如 JWT),避免接口被非法调用。

五、扩展功能

  1. 文档编辑与同步:调用企业微信文档编辑接口,实现自定义系统与企业微信文档的数据同步。
  2. 权限管理:在自定义系统中管理企业微信文档的共享权限(如添加 / 移除访问用户)。
  3. 数据可视化扩展:使用 ECharts 实现更多维度的统计图表(如访问量趋势、用户操作排行)。
  4. 消息通知:文档创建或数据统计完成后,通过企业微信应用消息推送通知相关用户。

以上方案完整实现了企业微信接入自定义系统的核心功能,可根据实际业务需求进一步扩展和优化。

相关推荐
橙露1 小时前
Nginx Location配置全解析:从基础到实战避坑
java·linux·服务器
无敌最俊朗@7 小时前
STL-vector面试剖析(面试复习4)
java·面试·职场和发展
PPPPickup8 小时前
easychat项目复盘---获取联系人列表,联系人详细,删除拉黑联系人
java·前端·javascript
LiamTuc8 小时前
Java构造函数
java·开发语言
长安er8 小时前
LeetCode 206/92/25 链表翻转问题-“盒子-标签-纸条模型”
java·数据结构·算法·leetcode·链表·链表翻转
菜鸟plus+8 小时前
N+1查询
java·服务器·数据库
我要添砖java8 小时前
《JAVAEE》网络编程-什么是网络?
java·网络·java-ee
CoderYanger8 小时前
动态规划算法-01背包问题:50.分割等和子集
java·算法·leetcode·动态规划·1024程序员节
菜鸟233号10 小时前
力扣513 找树左下角的值 java实现
java·数据结构·算法·leetcode