🆕 #2924【企业微信】增加会议相关的接口

This commit is contained in:
wangmeng3486 2023-02-07 16:57:20 +08:00 committed by GitHub
parent bef154020f
commit 3def66bc78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 690 additions and 0 deletions

View File

@ -0,0 +1,97 @@
package me.chanjar.weixin.cp.api;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.bean.oa.meeting.WxCpMeeting;
import me.chanjar.weixin.cp.bean.oa.meeting.WxCpMeetingUpdateResult;
import me.chanjar.weixin.cp.bean.oa.meeting.WxCpUserMeetingIdResult;
import java.util.List;
/**
* 企业微信日程接口.
* 企业和开发者通过会议接口可以便捷地预定及管理会议用于小组周会部门例会等场景
* 调用接口的应用自动成为会议创建者也可指定成员作为会议管理员辅助管理
* 官方文档https://developer.work.weixin.qq.com/document/path/93626
*
* @author <a href="https://github.com/wangmeng3486">wangmeng3486</a> created on 2023-01-31
*/
public interface WxCpMeetingService {
/**
* 创建预约会议
* <p>
* 该接口用于创建一个预约会议
* <p>
* 请求方式 POSTHTTPS
* 请求地址 https://qyapi.weixin.qq.com/cgi-bin/meeting/create?access_token=ACCESS_TOKEN
*
* @param meeting the meeting
* @return 会议ID string
* @throws WxErrorException the wx error exception
*/
String create(WxCpMeeting meeting) throws WxErrorException;
/**
* 修改预约会议
* <p>
* 该接口用于修改一个指定的预约会议
* <p>
* 请求方式 POSTHTTPS
* 请求地址 https://qyapi.weixin.qq.com/cgi-bin/meeting/update?access_token=ACCESS_TOKEN
*
* @param meeting the meeting
* @return wx cp meeting update result
* @throws WxErrorException the wx error exception
*/
WxCpMeetingUpdateResult update(WxCpMeeting meeting) throws WxErrorException;
/**
* 取消预约会议
* 该接口用于取消一个指定的预约会议
* <p>
* 请求方式 POSTHTTPS
* 请求地址 https://qyapi.weixin.qq.com/cgi-bin/meeting/cancel?access_token=ACCESS_TOKEN
*
* @param meetingId 会议ID
* @throws WxErrorException the wx error exception
*/
void cancel(String meetingId) throws WxErrorException;
/**
* 获取会议详情
* <p>
* 该接口用于获取指定会议的详情内容
* <p>
* 请求方式 POSTHTTPS
* 请求地址 https://qyapi.weixin.qq.com/cgi-bin/oa/meeting/get?access_token=ACCESS_TOKEN
*
* @param meetingId the meeting ids
* @return the details
* @throws WxErrorException the wx error exception
*/
WxCpMeeting getDetail(String meetingId) throws WxErrorException;
/**
* 获取成员会议ID列表
* 该接口用于获取指定成员指定时间内的会议ID列表
* <p>
* 权限说明
* 只能拉取该应用创建的会议ID
* 自建应用需要配置在可调用接口的应用列表
* 第三方服务商创建应用的时候需要开启会议接口权限
* 代开发自建应用需要授权会议接口权限
* <p>
* 请求方式 POSTHTTPS
* 请求地址 https://qyapi.weixin.qq.com/cgi-bin/meeting/get_user_meetingid?access_token=ACCESS_TOKEN
*
* @param userId 企业成员的userid
* @param cursor 上一次调用时返回的cursor初次调用可以填"0"
* @param limit 每次拉取的数据量默认值和最大值都为100
* @param beginTime 开始时间
* @param endTime 结束时间时间跨度不超过180天如果begin_time和end_time都没填的话默认end_time为当前时间
* @return result of listUserMeetingIds
* @throws WxErrorException the wx error exception
*/
WxCpUserMeetingIdResult getUserMeetingIds(String userId, String cursor, Integer limit,
Long beginTime, Long endTime) throws WxErrorException;
}

View File

@ -562,4 +562,11 @@ public interface WxCpService extends WxService {
* @param exportService 异步导出服务
*/
void setExportService(WxCpExportService exportService);
/**
* 相关接口的服务类对象
*
* @return the meeting service
*/
WxCpMeetingService getMeetingService();
}

View File

@ -70,6 +70,8 @@ public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestH
private WxCpExportService exportService = new WxCpExportServiceImpl(this);
private final WxCpMeetingService meetingService = new WxCpMeetingServiceImpl(this);
/**
* 全局的是否正在刷新access token的锁.
*/
@ -665,4 +667,9 @@ public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestH
public void setExportService(WxCpExportService exportService) {
this.exportService = exportService;
}
@Override
public WxCpMeetingService getMeetingService() {
return meetingService;
}
}

View File

@ -0,0 +1,78 @@
package me.chanjar.weixin.cp.api.impl;
import com.google.common.collect.ImmutableMap;
import lombok.RequiredArgsConstructor;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.api.WxCpMeetingService;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.oa.meeting.WxCpMeeting;
import me.chanjar.weixin.cp.bean.oa.meeting.WxCpMeetingUpdateResult;
import me.chanjar.weixin.cp.bean.oa.meeting.WxCpUserMeetingIdResult;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Oa.*;
/**
* 企业微信日程接口.
* 企业和开发者通过会议接口可以便捷地预定及管理会议用于小组周会部门例会等场景
* 调用接口的应用自动成为会议创建者也可指定成员作为会议管理员辅助管理
* 官方文档https://developer.work.weixin.qq.com/document/path/93626
*
* @author <a href="https://github.com/wangmeng3486">wangmeng3486</a> created on 2023-01-31
*/
@RequiredArgsConstructor
public class WxCpMeetingServiceImpl implements WxCpMeetingService {
private final WxCpService cpService;
@Override
public String create(WxCpMeeting meeting) throws WxErrorException {
return this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(MEETING_ADD),
WxCpGsonBuilder.create().toJson(meeting));
}
@Override
public WxCpMeetingUpdateResult update(WxCpMeeting meeting) throws WxErrorException {
final String response = this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(MEETING_UPDATE),
WxCpGsonBuilder.create().toJson(meeting));
return WxCpGsonBuilder.create().fromJson(response, WxCpMeetingUpdateResult.class);
}
@Override
public void cancel(String meetingId) throws WxErrorException {
this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(MEETING_CANCEL),
WxCpGsonBuilder.create().toJson(ImmutableMap.of("meetingid", meetingId)));
}
@Override
public WxCpMeeting getDetail(String meetingId) throws WxErrorException {
final String response = this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(MEETING_DETAIL),
WxCpGsonBuilder.create().toJson(ImmutableMap.of("meetingid", meetingId)));
return WxCpGsonBuilder.create().fromJson(response, WxCpMeeting.class);
}
@Override
public WxCpUserMeetingIdResult getUserMeetingIds(String userId, String cursor, Integer limit,
Long beginTime, Long endTime) throws WxErrorException {
final Map<String, Object> param = new HashMap<>(3);
param.put("userid", userId);
if (cursor != null) {
param.put("cursor", cursor);
}
if (limit != null) {
param.put("limit", limit);
}
if (limit != null) {
param.put("begin_time", beginTime);
}
if (limit != null) {
param.put("end_time", endTime);
}
final String response = this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(GET_USER_MEETING_ID),
WxCpGsonBuilder.create().toJson(param));
return WxCpGsonBuilder.create().fromJson(response, WxCpUserMeetingIdResult.class);
}
}

View File

@ -0,0 +1,305 @@
package me.chanjar.weixin.cp.bean.oa.meeting;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.experimental.Accessors;
import me.chanjar.weixin.common.bean.ToJson;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import java.io.Serializable;
import java.util.List;
/**
* 会议信息bean.
*
* @author <a href="https://github.com/wangmeng3486">wangmeng3486</a> created on 2023-02-02
*/
@Data
@Accessors(chain = true)
public class WxCpMeeting implements Serializable, ToJson {
private static final long serialVersionUID = 957588409099876012L;
/**
* 会议id
*/
@SerializedName("meetingid")
private String meetingId;
/**
* 会议管理员userid
*/
@SerializedName("admin_userid")
private String adminUserId;
/**
* 会议的标题最多支持40个字节或者20个utf8字符
*/
@SerializedName("title")
private String title;
/**
* 会议开始时间的unix时间戳需大于当前时间
*/
@SerializedName("meeting_start")
private Long meetingStart;
/**
* 会议持续时间单位秒最小300秒
*/
@SerializedName("meeting_duration")
private Integer meetingDuration;
/**
* 会议的描述最多支持500个字节或者utf8字符
*/
@SerializedName("description")
private String description;
/**
* 会议地点,最多128个字符
*/
@SerializedName("location")
private String location;
/**
* 授权方安装的应用agentid仅旧的第三方多应用套件需要填此参数
*/
@SerializedName("agentid")
private Integer agentId;
/**
* 参与会议的成员会议人数上限以指定的管理员可预约的人数上限来校验普通企业与会人员最多100
* 付费企业根据企业选购的在线会议室容量任何userid不合法或者不在应用可见范围直接报错
*/
@SerializedName("attendees")
private Attendees attendees;
/**
* 会议所属日历ID该日历必须是access_token所对应应用所创建的日历
* 注意若参与人在日历分享范围内则插入到该日历同时会插入会议参与人的默认日历若不在分享范围内否则仅插入到参与者默认日历
* 如果不填那么插入到参与者的默认日历上
* 第三方应用必须指定cal_id
* 不多于64字节
*/
@SerializedName("cal_id")
private String calId;
/**
* 会议配置
*/
@SerializedName("settings")
private Setting settings;
/**
* 重复会议相关配置
*/
@SerializedName("reminders")
private Reminder reminders;
@SerializedName("meeting_code")
private String meetingCode;
@SerializedName("meeting_link")
private String meetingLink;
@Override
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
/**
* The type Attendee.
*/
@Data
@Accessors(chain = true)
public static class Attendees implements Serializable {
private static final long serialVersionUID = 5419000348428480645L;
/**
* 会议参与者ID
* 不多于64字节
*/
@SerializedName("userid")
private List<String> userId;
@SerializedName("member")
private List<Member> member;
@SerializedName("tmp_external_user")
private List<TmpExternalUser> tmpExternalUser;
/**
* 企业内部成员
*/
@Data
@Accessors(chain = true)
public static class Member implements Serializable {
private static final long serialVersionUID = 1001531041411551854L;
/**
* 企业内部成员的userid
*/
@SerializedName("userid")
private String userid;
/**
* 与会状态1为已参与2为未参与
*/
@SerializedName("status")
private Integer status;
/**
* 参会人首次加入会议时间的unix时间戳
*/
@SerializedName("first_join_time")
private Long firstJoinTime;
/**
* 参会人最后一次离开会议时间的unix时间戳
*/
@SerializedName("last_quit_time")
private Long lastQuitTime;
/**
* 参会人累计参会时长单位为秒
*/
@SerializedName("cumulative_time")
private Long cumulativeTime;
}
/**
* 会中参会的外部联系人
*/
@Data
@Accessors(chain = true)
public static class TmpExternalUser implements Serializable {
private static final long serialVersionUID = -1411758297144496040L;
/**
* 会中入会的外部用户临时id同一个用户在不同会议中返回的该id不一致
*/
@SerializedName("tmp_external_userid")
private String tmpExternalUserid;
/**
* 参会人首次加入会议时间的unix时间戳
*/
@SerializedName("first_join_time")
private Long firstJoinTime;
/**
* 参会人最后一次离开会议时间的unix时间戳
*/
@SerializedName("last_quit_time")
private Long lastQuitTime;
/**
* 参会人入会次数
*/
@SerializedName("total_join_count")
private Integer totalJoinCount;
/**
* 参会人累计参会时长单位为秒
*/
@SerializedName("cumulative_time")
private Integer cumulativeTime;
}
}
/**
* The type Reminder.
*/
@Data
@Accessors(chain = true)
public static class Reminder implements Serializable {
private static final long serialVersionUID = -4097232428444045131L;
/**
* 是否是周期性会议1周期性会议 0非周期性会议默认为0
*/
@SerializedName("is_repeat")
private Integer isRepeat;
/**
* 周期性会议重复类型0.每天1.每周2.每月7.每个工作日默认为0周期性会议该字段才生效
*/
@SerializedName("repeat_type")
private Integer repeatType;
/**
* 重复结束时刻周期性会议该字段才生效若会议结束时间超出最大结束时间或者未设置则默认设置为最大结束时间
* 每天\每个工作日\每周 最多重复200次会议
* 每两周\每月最多重复50次会议
*/
@SerializedName("repeat_until")
private Long repeatUntil;
/**
* 重复间隔
* 仅当指定为自定义重复时有效
* 目前仅当repeat_type为2时即周期为周时支持设置该字段且值不能大于2
*/
@SerializedName("repeat_interval")
private Integer repeatInterval;
/**
* 指定会议开始前多久提醒用户相对于meeting_start前的秒数默认不提醒
* 目前仅支持0会议开始时提醒300:5分钟前提醒900:15分钟前提醒3600:一小时前提醒86400一天前提醒
* 若指定了非支持的值则表现为会议开始时提醒
*/
@SerializedName("remind_before")
private List<Integer> remindBefore;
}
/**
* The Settings.
*/
@Data
@Accessors(chain = true)
public static class Setting implements Serializable {
private static final long serialVersionUID = 5030527150838243356L;
/**
* 入会密码仅可输入4-6位纯数字
*/
@SerializedName("password")
private String password;
/**
* 是否开启等候室true:开启等候室false:不开启等候室默认不开
*/
@SerializedName("enable_waiting_room")
private boolean enableWaitingRoom;
/**
* 是否允许成员在主持人进会前加入true:允许false:不允许默认允许
*/
@SerializedName("allow_enter_before_host")
private boolean allowEnterBeforeHost;
/**
* 会议开始时来电提醒方式1.不提醒 2.仅提醒主持人 3.提醒所有成员入 4.指定部分人响铃默认仅提醒主持人
*/
@SerializedName("remind_scope")
private Integer remindScope;
/**
* 成员入会时静音1:开启0:关闭2:超过6人后自动开启静音默认超过6人自动开启静音
*/
@SerializedName("enable_enter_mute")
private Integer enableEnterMute;
/**
* true:所有成员可入会false:仅企业内部成员可入会 默认所有成员可入会
*/
@SerializedName("allow_external_user")
private boolean allowExternalUser;
/**
* 是否开启屏幕水印true:开启false:不开启默认不开启
*/
@SerializedName("enable_screen_watermark")
private boolean enableScreenWatermark;
/**
* 会议主持人人列表主持人员最多10个若包含ceaater_userid,会自动过滤任何userid不合法直接报错
*/
@SerializedName("hosts")
private Attendees hosts;
/**
* 指定响铃的用户列表如果remid_scope为4但是ring_users为空则全部成员均不响铃
*/
@SerializedName("ring_users")
private Attendees ringUsers;
}
}

View File

@ -0,0 +1,41 @@
package me.chanjar.weixin.cp.bean.oa.meeting;
import com.google.common.base.Splitter;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import me.chanjar.weixin.cp.bean.WxCpBaseResp;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import org.apache.commons.lang3.StringUtils;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
/**
* 为标签添加或移除用户结果对象类.
*
* @author <a href="https://github.com/wangmeng3486">wangmeng3486</a> created on 2023-01-31
*/
@Data
public class WxCpMeetingUpdateResult extends WxCpBaseResp implements Serializable {
private static final long serialVersionUID = -4993287594652231097L;
@Override
public String toString() {
return WxCpGsonBuilder.create().toJson(this);
}
/**
* From json wx cp tag add or remove users result.
*
* @param json the json
* @return the wx cp tag add or remove users result
*/
public static WxCpMeetingUpdateResult fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, WxCpMeetingUpdateResult.class);
}
@SerializedName("excess_users")
private String[] excessUsers;
}

View File

@ -0,0 +1,40 @@
package me.chanjar.weixin.cp.bean.oa.meeting;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import me.chanjar.weixin.cp.bean.WxCpBaseResp;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import java.io.Serializable;
/**
* 为标签添加或移除用户结果对象类.
*
* @author <a href="https://github.com/wangmeng3486">wangmeng3486</a> created on 2023-01-31
*/
@Data
public class WxCpUserMeetingIdResult extends WxCpBaseResp implements Serializable {
private static final long serialVersionUID = -4993287594652231097L;
@Override
public String toString() {
return WxCpGsonBuilder.create().toJson(this);
}
/**
* From json wx cp tag add or remove users result.
*
* @param json the json
* @return the wx cp tag add or remove users result
*/
public static WxCpUserMeetingIdResult fromJson(String json) {
return WxCpGsonBuilder.create().fromJson(json, WxCpUserMeetingIdResult.class);
}
@SerializedName("meetingid_list")
private String[] meetingIdList;
@SerializedName("next_cursor")
private String nextCursor;
}

View File

@ -374,6 +374,28 @@ public interface WxCpApiPathConsts {
/**
* 会议
* https://developer.work.weixin.qq.com/document/path/93626
*/
String MEETING_ADD = "/cgi-bin/meeting/create";
/**
* The constant MEETING_UPDATE.
*/
String MEETING_UPDATE = "/cgi-bin/meeting/update";
/**
* The constant MEETING_CANCEL.
*/
String MEETING_CANCEL = "/cgi-bin/meeting/cancel";
/**
* The constant MEETING_DETAIL.
*/
String MEETING_DETAIL = "/cgi-bin/meeting/get_info";
/**
* The constant GET_USER_MEETING_ID.
*/
String GET_USER_MEETING_ID = "/cgi-bin/meeting/get_user_meetingid";
/**
* 会议室
* https://developer.work.weixin.qq.com/document/path/93624
*/
String MEETINGROOM_ADD = "/cgi-bin/oa/meetingroom/add";

View File

@ -0,0 +1,93 @@
package me.chanjar.weixin.cp.api.impl;
import com.google.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.api.ApiTestModule;
import me.chanjar.weixin.cp.api.WxCpMeetingService;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.oa.meeting.WxCpMeeting;
import me.chanjar.weixin.cp.bean.oa.meeting.WxCpMeetingUpdateResult;
import me.chanjar.weixin.cp.bean.oa.meeting.WxCpUserMeetingIdResult;
import org.eclipse.jetty.util.ajax.JSON;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
import org.testng.collections.Lists;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.testng.Assert.assertEquals;
/**
* 单元测试类.
*
* @author <a href="https://github.com/wangmeng3486">wangmeng3486</a> created on 2023-02-03
*/
@Test
@Slf4j
@Guice(modules = ApiTestModule.class)
public class WxCpMeetingServiceImplTest {
/**
* The Wx service.
*/
@Inject
private WxCpService wxCpService;
/**
* The Config storage.
*/
@Inject
protected ApiTestModule.WxXmlCpInMemoryConfigStorage configStorage;
/**
* Test
*
* @throws WxErrorException the wx error exception
*/
@Test
public void testAdd() throws WxErrorException {
WxCpMeetingService wxCpMeetingService = this.wxCpService.getMeetingService();
/*
测试 创建会议
*/
long startTime = System.currentTimeMillis() / 1000 + 30 * 60L;
List<String> userIds = Lists.newArrayList(this.configStorage.getUserId(), "lisi");
WxCpMeeting wxCpMeeting = new WxCpMeeting().setAdminUserId(this.configStorage.getUserId()).setTitle("新建会议")
.setMeetingStart(startTime).setMeetingDuration(3600).setDescription("新建会议描述").setLocation("广州媒体港")
.setAttendees(new WxCpMeeting.Attendees().setUserId(userIds))
.setSettings(new WxCpMeeting.Setting().setRemindScope(1).setPassword("1234").setEnableWaitingRoom(false)
.setAllowEnterBeforeHost(true).setEnableEnterMute(1).setAllowExternalUser(false).setEnableScreenWatermark(false)
.setHosts(new WxCpMeeting.Attendees().setUserId(userIds)).setRingUsers(new WxCpMeeting.Attendees().setUserId(userIds)))
.setReminders(new WxCpMeeting.Reminder().setIsRepeat(1).setRepeatType(0).setRepeatUntil(startTime + 24 * 60 * 60L)
.setRepeatInterval(1).setRemindBefore(Lists.newArrayList(0, 900)));
String meetingId = "hyXG0RCQAAogMgFb9Tx_b-1-lhJRWvvg";// wxCpMeetingService.create(wxCpMeeting);
assertThat(meetingId).isNotNull();
/*
测试 获取用户会议列表
*/
wxCpMeeting.setMeetingId(meetingId);
WxCpUserMeetingIdResult wxCpUserMeetingIdResult = wxCpMeetingService.getUserMeetingIds(this.configStorage.getUserId(), null, null, startTime, null);
assertThat(wxCpUserMeetingIdResult.getMeetingIdList()).isNotNull();
log.info("获取用户会议列表: {}", wxCpUserMeetingIdResult.toJson());
/*
测试 修改会议
*/
wxCpMeeting.setTitle("修改会议");
wxCpMeeting.setDescription("修改会议描述");
WxCpMeetingUpdateResult wxCpMeetingUpdateResult = wxCpMeetingService.update(wxCpMeeting);
assertEquals(wxCpMeetingUpdateResult.getErrcode(), 0L);
/*
测试 获取会议详情
*/
WxCpMeeting wxCpMeetingResult = wxCpMeetingService.getDetail(meetingId);
log.info("获取会议详情: {}", wxCpMeetingResult.toJson());
/*
测试 取消会议
*/
wxCpMeetingService.cancel(meetingId);
}
}