集成企业微信实现会议预约系统【java】

背景

公司最近在各个会议室都安防了一块屏显示会议预约系统,购买的别家公司的会议预约app软件安装,现在不维护了,会议预约出了问题,于是就研发了一套简易的,集成企业微信会议预约功能的产品。

满足:

  1. 企业微信预约后在会议室门口屏上显示当前是否预订信息,以及下个预订信息
  2. 方便大家快速确认该会议室是否被预订

技术要求:
3. 熟悉企业微信会议预订通讯协议(http)
4. 掌握前端技术,html,css,js即可
5. 熟悉spingboot, websocket

整体架构流程

前端:屏端显示一个本地网页,间隔1分钟更新一次会议信息
后端:websocket监听,有请求会议信息,调用企业微信接口获取数据并返回前端

技术名词解释

都是常用技术,大家直接百度

技术细节

  1. 前端展示,上核心部分代码
(function flexible(window, document) {
  function resetFontSize() {
    const size = (document.documentElement.clientWidth / 1280) * 37.5;
    document.documentElement.style.fontSize = size + 'px';
  }

  // reset root font size on page show or resize
  window.addEventListener('pageshow', resetFontSize);
  window.addEventListener('resize', resetFontSize);
})(window, document);

let roomName = "会议室2";
let wsurl = "ws://192.xxx/webSocket?room=";
let ws;

let heartCheck = {
  timeout: 1000,//1s
  timeoutObj: null,
  serverTimeoutObj: null,
  reset: function(){
    clearTimeout(this.timeoutObj);
    clearTimeout(this.serverTimeoutObj);
    this.start();
  },
  start: function(){
    let self = this;
    this.timeoutObj = setTimeout(function(){
      ws.send(roomName);
      self.serverTimeoutObj = setTimeout(function(){
        ws.close();//如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次
      }, self.timeout)
    }, this.timeout)
  },
}

function getRoomInfo() {
  if("WebSocket" in window){

    if(ws == null || ws.readyState != ws.OPEN){
      ws = new WebSocket(wsurl+roomName);

      ws.onopen = function(){
        //当WebSocket创建成功时,触发onopen事件
        heartCheck.reset();
      }
      ws.onmessage = function(e){
        //当客户端收到服务端发来的消息时,触发onmessage事件,参数e.data包含server传递过来的数据
        //parseAndExeData(e.data);
        //heartCheck.reset();
        console.log("onmessage",new Date(),e.data)
        let {current,next,status} = JSON.parse(e.data);
        if(roomName){
          document.getElementById("roomName").innerHTML=roomName
        }
        if(status){
          document.getElementById("status").innerHTML=status
          document.getElementById("statusImg").style.display = "inline";
          if(status == '空闲'){        
            document.getElementById("roomName").style.fontSize="145px";
            document.getElementById("roomName").style.fontWeight="bold";
            document.getElementById("roomName").style.top="70px";
            document.getElementById("roomName").style.left="355px";
            
            document.getElementById("meetingBg").style.background = "url(./img/free.png) -490px -6px no-repeat";
            document.getElementById("statusImg").src = "./img/metting3.png";
            document.getElementById("status").style.color="rgba(60, 248, 174, 1)";

            document.getElementById("summary").innerHTML=""
            document.getElementById("meetingTime").innerHTML=""
            document.getElementById("meetingTimeImg").style.display = "none";
            document.getElementById("organizer").innerHTML=""
            document.getElementById("organizerImg").style.display = "none";
            document.getElementById("attendNames").innerHTML=""
            document.getElementById("attendNamesImg").style.display = "none";   

          }else if(status == '会议中'){
            document.getElementById("roomName").style.fontSize="48px";
            document.getElementById("roomName").style.fontWeight="Normal";
            document.getElementById("roomName").style.top="21px";
            document.getElementById("roomName").style.left="546px";

            document.getElementById("meetingBg").style.background = "url(./img/meeting.png) -490px -6px no-repeat";
            document.getElementById("statusImg").src = "./img/metting2.png";
            document.getElementById("status").style.color="rgba(255, 88, 82, 1)";
          }

          if(current){
            if(current.summary){
              document.getElementById("summary").innerHTML=current.summary
            }
            if(current.time){
              document.getElementById("meetingTime").innerHTML=current.time
              document.getElementById("meetingTimeImg").style.display = "inline";
            }
            if(current.organizer){
              document.getElementById("organizer").innerHTML=current.organizer
              document.getElementById("organizerImg").style.display = "inline";
            }
            if(current.attendNames){
              document.getElementById("attendNames").innerHTML=current.attendNames
              document.getElementById("attendNamesImg").style.display = "inline";            
            }
          }
        }
        

        if(next) {          
          if(next.summary){            
            document.getElementById("nextSummary").innerHTML=next.summary.slice(0, 9)
            document.getElementById("nextShow").style.display = "flex";
          }else{
            document.getElementById("nextSummary").innerHTML=""
            document.getElementById("nextShow").style.display = "none";
          }
          if(next.time){
            document.getElementById("nextTime").innerHTML=next.time
          }else{
            document.getElementById("nextTime").innerHTML=""
          }
          if(next.organizer){
            document.getElementById("nextOrganizer").innerHTML=next.organizer
          }else{
            document.getElementById("nextOrganizer").innerHTML=""
          }
        }
      }
      ws.onclose = function(e){
        console.log("onclose")
        //当客户端收到服务端发送的关闭连接请求时,触发onclose事件
        ws = null;
        setTimeout(function () {getRoomInfo();}, 60000);
      }
      ws.onerror = function(e){
        console.log("onerror")
        //如果出现连接、处理、接收、发送数据失败的时候触发onerror事件
        ws.close();
      }
    }
  }
}

function showTime() {
  let now = new Date();
  let h = now.getHours();
	let m = now.getMinutes();	
	let showHM = h + ":" + (m > 9 ? m : "0" + m);
  document.getElementById("curTime").innerHTML = showHM;

  setTimeout("showTime()", 1000);
}


//此处需要延时1秒执行
setTimeout(function () {getRoomInfo();}, 1000);


//刷新当前时间
setTimeout("showTime()", 1000);

会议室可以通过企业微信获取,我这边用名称,更好处理

  1. 后端代码,首先需要新建一个spring boot项目,这里直接上代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;

@ServerEndpoint(value = "/webSocket")
@Component
@Slf4j
public class WebSocketServer
{
    /**
     * 连接建立成功调用的方法
     *
     * @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    @OnOpen
    public void onOpen(Session session) {
//        System.out.println("enter onOpen**********");
        //心跳等待时间1分钟
        session.setMaxIdleTimeout(60000);

        Map<String, List<String>> requestParameterMap = session.getRequestParameterMap();

        List<String> roomList = requestParameterMap.get("room");

        if (null != roomList)
        {
            String room = roomList.get(0);
//            System.out.println("room="+room);

            MySocket mySocket = new MySocket();
            mySocket.setSocketId(Thread.currentThread().getId());
            mySocket.setRoom(room);
            mySocket.setSession(session);
        }
    }

    private void sendRoomMsg(Session session, String room)
    {
        /**
         * 1. 根据会议室名称从企业微信调用会议室预约信息
         * 2. 根据网页展现需求构建需要的json数据
         */
        WeiXinTemplate weiXinTemplate = (WeiXinTemplate)SpringUtil.getBean("weiXinTemplate");
        String scheduleJson = weiXinTemplate.buildMeetingroomBookingInfo(room);
//        System.out.println("sendRoomMsg="+scheduleJson);
        sendMessage(scheduleJson, session);
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose()
    {
//        System.out.println("onClose*******");
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param room 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String room, Session session)
    {
//        System.out.println("onMessage*******");
        sendRoomMsg(session, room);
    }

    /**
     * 发生错误时调用
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error)
    {
//        System.out.println("onError="+error);
        try
        {
            if (session.isOpen())
            {
                session.close();
            }

        }
        catch (IOException e)
        {
            e.printStackTrace();
            log.error(e.getMessage(), e);
        }
        finally
        {

        }
    }

    /**
     * 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。
     *
     * @param message
     * @throws IOException
     */
    public static void sendMessage(String message, Session session)
    {
        if (null != session && session.isOpen()) {
            session.getAsyncRemote().sendText(message);
        }
    }

    private Object getFieldInstance(Object obj, String fieldPath)
    {
        String[] fields = fieldPath.split("#");
        for (String field : fields)
        {
            obj = getField(obj, obj.getClass(), field);
            if (obj == null)
            {
                return null;
            }
        }

        return obj;
    }

    private Object getField(Object obj, Class<?> clazz, String fieldName)
    {
        for (; clazz != Object.class; clazz = clazz.getSuperclass())
        {
            try
            {
                Field field;
                field = clazz.getDeclaredField(fieldName);
                field.setAccessible(true);
                return field.get(obj);
            }
            catch (Exception e)
            {

            }
        }

        return null;
    }
}

以下贴出关键代码,其他业务根据情况调整

 /**
     * 构造前端需要的会议室预定信息
     * @param room
     * @return
     */
    public String buildMeetingroomBookingInfo(String room)
    {
      String accessToken = getAccessTokenFromRoom(WeiXinApiConstant.CORP_ID,WeiXinApiConstant.MEETINGROOM_CORPSECRET);
		
		// #1.查询会议室,并构造名称和id映射关系		
        Map<String, Integer> roomMap = new HashMap<>();
        String roomlistStr = getMeetingroomList(accessToken);
        
        // #2.根据meetingroom_id查询会议室的预定信息
        JSONArray schedule_id_list = getMeetingroomBookingInfo(meetingroom_id,accessToken);
		
		//#3 此处是业务处理,主要获取当天所有预订,按时间升序排列,取前2个,查看第一个时间是否在当前时间内,若是,就是会议中状态

		// #4 构建会议室属性
        JSONObject roomJson = new JSONObject();
        roomJson.put("status", status);
        roomJson.put("current",currentRoom);
        roomJson.put("next",nextRoom);

        return roomJson.toJSONString();
    }
方法名企业微信接口
getAccessTokenFromRoomhttps://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ID&corpsecret=SECRET
getMeetingroomListhttps://qyapi.weixin.qq.com/cgi-bin/oa/meetingroom/list?access_token=ACCESS_TOKEN
getMeetingroomBookingInfohttps://qyapi.weixin.qq.com/cgi-bin/oa/meetingroom/get_booking_info?access_token=ACCESS_TOKEN

小结

1. 企业微信token获取,避免token过期和频繁调用,默认有效期是7200秒,可以用redis做缓存
2.获取的会议预订信息一定要重新按时间排序,这里我是按时间升序重新排序计算
3. 企业微信接口给的时间戳是秒为单位
4. cropID和会议室应用的secret要注意,不是从一个地方获取,不清楚的参看 企业微信扫码登录业务实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瑞瑞绮绮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值