瀏覽代碼

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

wangmeng3486 2 年之前
父節點
當前提交
3def66bc78

+ 97 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMeetingService.java

@@ -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>
+   * 请求方式: POST(HTTPS)
+   * 请求地址: 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>
+   * 请求方式: POST(HTTPS)
+   * 请求地址: 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>
+   * 请求方式: POST(HTTPS)
+   * 请求地址: 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>
+   * 请求方式: POST(HTTPS)
+   * 请求地址: 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>
+   * 请求方式: POST(HTTPS)
+   * 请求地址: 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;
+}

+ 7 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java

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

+ 7 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java

@@ -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;
+  }
 }

+ 78 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMeetingServiceImpl.java

@@ -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);
+  }
+}

+ 305 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/meeting/WxCpMeeting.java

@@ -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;
+  }
+}

+ 41 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/meeting/WxCpMeetingUpdateResult.java

@@ -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;
+
+}

+ 40 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/meeting/WxCpUserMeetingIdResult.java

@@ -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;
+}

+ 22 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java

@@ -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";

+ 93 - 0
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpMeetingServiceImplTest.java

@@ -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);
+  }
+}