Browse Source

任务接口

wangliang 5 năm trước cách đây
mục cha
commit
3bce8e092b

+ 0 - 41
themis-backend/src/main/java/com/qmth/themis/backend/config/WebSocketConfig.java

@@ -1,41 +0,0 @@
-//package com.qmth.themis.backend.config;
-//
-//import org.springframework.context.annotation.Bean;
-//import org.springframework.context.annotation.Configuration;
-//import org.springframework.web.socket.config.annotation.EnableWebSocket;
-//import org.springframework.web.socket.server.standard.ServerEndpointExporter;
-//
-///**
-// * @Description: 开启WebSocket支持
-// * @Param:
-// * @return:
-// * @Author: wangliang
-// * @Date: 2020/6/3
-// */
-//@Configuration
-//@EnableWebSocket
-//public class WebSocketConfig
-////        implements WebSocketConfigurer
-//{
-//    /**
-//     * ServerEndpointExporter 作用
-//     * 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
-//     *
-//     * @return
-//     */
-//    @Bean
-//    public ServerEndpointExporter serverEndpointExporter() {
-//        return new ServerEndpointExporter();
-//    }
-//
-////    @Override
-////    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
-////        registry.addHandler(myHandler(), "/messageHandler").setAllowedOrigins("*");
-////    }
-////
-////    @Bean
-////    public WebSocketHandler myHandler() {
-////        return new MessageHandler();
-////    }
-//}
-//

+ 0 - 213
themis-backend/src/main/java/com/qmth/themis/backend/websocket/WebSocketServer.java

@@ -1,213 +0,0 @@
-//package com.qmth.themis.backend.websocket;
-//
-//import com.alibaba.fastjson.JSON;
-//import com.alibaba.fastjson.JSONObject;
-//import org.apache.commons.lang3.StringUtils;
-//import org.slf4j.Logger;
-//import org.slf4j.LoggerFactory;
-//import org.springframework.stereotype.Component;
-//
-//import javax.websocket.*;
-//import javax.websocket.server.PathParam;
-//import javax.websocket.server.ServerEndpoint;
-//import java.io.IOException;
-//import java.util.concurrent.ConcurrentHashMap;
-//
-///**
-// * @Description: websocker服务端
-// * @Param:
-// * @return:
-// * @Author: wangliang
-// * @Date: 2020/7/10
-// */
-//@ServerEndpoint("/imserver/{userId}")
-//@Component
-//public class WebSocketServer
-////        implements MessageListenerConcurrently
-//{
-//    private final static Logger log = LoggerFactory.getLogger(WebSocketServer.class);
-//    /**
-//     * 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
-//     */
-//    private static int onlineCount = 0;
-//    /**
-//     * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
-//     */
-//    private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
-//    /**
-//     * 与某个客户端的连接会话,需要通过它来给客户端发送数据
-//     */
-//    private Session session;
-//    /**
-//     * 接收userId
-//     */
-//    private String userId = null;
-//
-//    /**
-//     * 连接建立成功调用的方法
-//     */
-//    @OnOpen
-//    public void onOpen(Session session, @PathParam("userId") String userId) {
-//        this.session = session;
-//        this.userId = userId;
-//        if (webSocketMap.containsKey(userId)) {
-//            webSocketMap.remove(userId);
-//            webSocketMap.put(userId, this);
-//            //加入set中
-//        } else {
-//            webSocketMap.put(userId, this);
-//            //加入set中
-//            addOnlineCount();
-//            //在线数加1
-//        }
-//        log.info("用户连接:" + userId + ",当前在线人数为:" + getOnlineCount());
-//        try {
-//            sendMessage("连接成功");
-//        } catch (IOException e) {
-//            log.error("用户:" + userId + ",网络异常!!!!!!");
-//        }
-//    }
-//
-//    /**
-//     * 连接关闭调用的方法
-//     */
-//    @OnClose
-//    public void onClose() {
-//        if (webSocketMap.containsKey(userId)) {
-//            webSocketMap.remove(userId);
-//            //从set中删除
-//            subOnlineCount();
-//        }
-//        log.info("用户退出:" + userId + ",当前在线人数为:" + getOnlineCount());
-//    }
-//
-//    /**
-//     * 收到客户端消息后调用的方法
-//     *
-//     * @param message 客户端发送过来的消息
-//     */
-//    @OnMessage
-//    public void onMessage(String message, Session session) {
-//        log.info("用户消息:" + userId + ",报文:" + message);
-//        //可以群发消息
-//        //消息保存到数据库、redis
-//        if (StringUtils.isNotBlank(message)) {
-//            try {
-//                //解析发送的报文
-//                JSONObject jsonObject = JSON.parseObject(message);
-//                //追加发送人(防止串改)
-//                jsonObject.put("fromUserId", this.userId);
-//                String toUserId = jsonObject.getString("toUserId");
-//                //传送给对应toUserId用户的websocket
-//                if (StringUtils.isNotBlank(toUserId) && webSocketMap.containsKey(toUserId)) {
-//                    webSocketMap.get(toUserId).sendMessage(jsonObject.toJSONString());
-//                } else {
-//                    log.error("请求的userId:" + toUserId + "不在该服务器上");
-//                    //否则不在这个服务器上,发送到mysql或者redis
-//                }
-//            } catch (Exception e) {
-//                e.printStackTrace();
-//            }
-//        }
-//    }
-//
-//    /**
-//     * @param session
-//     * @param error
-//     */
-//    @OnError
-//    public void onError(Session session, Throwable error) {
-//        log.error("用户错误:" + this.userId + ",原因:" + error.getMessage());
-//        error.printStackTrace();
-//    }
-//
-//    /**
-//     * 实现服务器主动推送
-//     */
-//    public void sendMessage(String message) throws IOException {
-//        this.session.getBasicRemote().sendText(message);
-//    }
-//
-//
-//    /**
-//     * 发送自定义消息
-//     */
-//    public static void sendInfo(String message, @PathParam("userId") String userId) throws IOException {
-//        log.info("发送消息到:" + userId + ",报文:" + message);
-//        if (StringUtils.isNotBlank(userId) && webSocketMap.containsKey(userId)) {
-//            webSocketMap.get(userId).sendMessage(message);
-//        } else {
-//            log.error("用户" + userId + ",不在线!");
-//        }
-//    }
-//
-//    public void wxappPhotoReady(){
-//        log.info("wxappPhotoReady is come in");
-//    }
-//
-//    public static synchronized int getOnlineCount() {
-//        return onlineCount;
-//    }
-//
-//    public static synchronized void addOnlineCount() {
-//        WebSocketServer.onlineCount++;
-//    }
-//
-//    public static synchronized void subOnlineCount() {
-//        WebSocketServer.onlineCount--;
-//    }
-//
-////    @Override
-////    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
-////        try {
-////            long threadId = Thread.currentThread().getId();
-////            String threadName = Thread.currentThread().getName();
-////            for (MessageExt messageExt : msgs) {
-////                log.info(":{}-:{} websocketConsumer 重试次数:{}", threadId, threadName, messageExt.getReconsumeTimes());
-////                MqDto mqDto = JacksonUtil.readJson(new String(messageExt.getBody(), Constants.CHARSET), MqDto.class);
-////                log.info(":{}-:{} websocketConsumer 接收到的消息:{}", threadId, threadName, JacksonUtil.parseJson(mqDto));
-////                log.info(":{}-:{} websocketConsumer mqDto sequence:{},tag:{}", threadId, threadName, mqDto.getSequence(), mqDto.getTag());
-////                Map map = mqDto.getProperties();
-////                String body = JacksonUtil.parseJson(mqDto.getBody());
-////                log.info("map:{},body:{}", JacksonUtil.parseJson(map), body);
-////                String model = String.valueOf(map.get("model"));
-////                MessageModel messageModel = MessageModel.valueOf(model);
-////                if (messageModel.ordinal() == MessageModel.CLUSTERING.ordinal()) {
-////                    webSocketMap.get(map.get("toUserId")).sendMessage(body);
-////                } else {
-////                    webSocketMap.forEach((k, v) -> {
-////                        try {
-////                            v.sendMessage(body);
-////                        } catch (IOException e) {
-////                            e.printStackTrace();
-////                        }
-////                    });
-////                }
-////            }
-////        } catch (Exception e) {
-////            e.printStackTrace();
-////            return ConsumeConcurrentlyStatus.RECONSUME_LATER;//重试
-////        }
-////        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;//成功
-////    }
-////
-////    @Service
-////    @RocketMQMessageListener(consumerGroup = "websocketConsumerImGroup", topic = "websocketImTopic", selectorType = SelectorType.TAG, selectorExpression = "*")
-////    public class sessionConsumerWeb implements RocketMQListener<Message>, RocketMQPushConsumerLifecycleListener {
-////
-////        @Override
-////        public void onMessage(Message message) {
-////            //实现RocketMQPushConsumerLifecycleListener监听器之后,此方法不调用
-////        }
-////
-////        @Override
-////        public void prepareStart(DefaultMQPushConsumer defaultMQPushConsumer) {
-////            defaultMQPushConsumer.setConsumeMessageBatchMaxSize(SystemConstant.CONSUME_MESSAGE_BATCH_MAX_SIZE);//每次拉取10条
-////            defaultMQPushConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
-////            defaultMQPushConsumer.setMaxReconsumeTimes(SystemConstant.MAXRECONSUMETIMES);//最大重试次数
-//////            defaultMQPushConsumer.setMessageModel(MessageModel.BROADCASTING);
-////            defaultMQPushConsumer.registerMessageListener(WebSocketServer.this::consumeMessage);
-////        }
-////    }
-//}
-//

+ 18 - 18
themis-backend/src/main/resources/ehcache.xml

@@ -25,23 +25,23 @@
             timeToLiveSeconds="43200"
             memoryStoreEvictionPolicy="LRU" />
 
-    <cache
-            name="org_cache"
-            eternal="false"
-            maxElementsInMemory="1000"
-            overflowToDisk="false"
-            diskPersistent="false"
-            timeToIdleSeconds="0"
-            timeToLiveSeconds="43200"
-            memoryStoreEvictionPolicy="LRU" />
+<!--    <cache-->
+<!--            name="org_cache"-->
+<!--            eternal="false"-->
+<!--            maxElementsInMemory="1000"-->
+<!--            overflowToDisk="false"-->
+<!--            diskPersistent="false"-->
+<!--            timeToIdleSeconds="0"-->
+<!--            timeToLiveSeconds="43200"-->
+<!--            memoryStoreEvictionPolicy="LRU" />-->
 
-    <cache
-            name="user_cache"
-            eternal="false"
-            maxElementsInMemory="1000"
-            overflowToDisk="false"
-            diskPersistent="false"
-            timeToIdleSeconds="0"
-            timeToLiveSeconds="43200"
-            memoryStoreEvictionPolicy="LRU" />
+<!--    <cache-->
+<!--            name="user_cache"-->
+<!--            eternal="false"-->
+<!--            maxElementsInMemory="1000"-->
+<!--            overflowToDisk="false"-->
+<!--            diskPersistent="false"-->
+<!--            timeToIdleSeconds="0"-->
+<!--            timeToLiveSeconds="43200"-->
+<!--            memoryStoreEvictionPolicy="LRU" />-->
 </ehcache>

+ 4 - 0
themis-business/src/main/java/com/qmth/themis/business/config/EnvConfig.java

@@ -18,8 +18,10 @@ import java.util.Map;
 public class EnvConfig {
     String backendProject = "backend",
             taskProject = "task",
+            examProject = "exam",
             backendPath = "com.qmth.themis.backend.constant.BackendConstant",
             taskPath = "com.qmth.themis.task.constant.TaskConstant",
+            examPath = "com.qmth.themis.exam.constant.ExamConstant",
             methodName = "getAttachmentEnv";
 
     /**
@@ -37,6 +39,8 @@ public class EnvConfig {
                     clazz = Class.forName(backendPath);
                 } else if (path.contains(taskProject)) {
                     clazz = Class.forName(taskPath);
+                } else if (path.contains(examProject)) {
+                    clazz = Class.forName(examPath);
                 }
             } catch (ClassNotFoundException e) {
                 e.printStackTrace();

+ 6 - 2
themis-exam/pom.xml

@@ -15,13 +15,17 @@
 
     <dependencies>
         <dependency>
-            <groupId>com.qmth.themis.business</groupId>
-            <artifactId>themis-business</artifactId>
+            <groupId>com.qmth.themis.mq</groupId>
+            <artifactId>themis-mq</artifactId>
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
     </dependencies>
 
     <build>

+ 10 - 21
themis-exam/src/main/java/com/qmth/themis/exam/config/DictionaryConfig.java

@@ -70,25 +70,14 @@ public class DictionaryConfig {
         return new AliYunOssDomain();
     }
 
-//    /**
-//     * mq配置
-//     *
-//     * @return
-//     */
-//    @Bean
-//    @ConfigurationProperties(prefix = "mq.config", ignoreUnknownFields = false)
-//    public MqConfigDomain mqConfigDomain() {
-//        return new MqConfigDomain();
-//    }
-
-//    /**
-//     * quartz配置
-//     *
-//     * @return
-//     */
-//    @Bean
-//    @ConfigurationProperties(prefix = "quartz.config", ignoreUnknownFields = false)
-//    public QuartzConfigDomain quartzConfigDomain() {
-//        return new QuartzConfigDomain();
-//    }
+    /**
+     * mq配置
+     *
+     * @return
+     */
+    @Bean
+    @ConfigurationProperties(prefix = "mq.config", ignoreUnknownFields = false)
+    public MqConfigDomain mqConfigDomain() {
+        return new MqConfigDomain();
+    }
 }

+ 34 - 0
themis-exam/src/main/java/com/qmth/themis/exam/config/WebMvcConfig.java

@@ -0,0 +1,34 @@
+package com.qmth.themis.exam.config;
+
+import com.qmth.themis.business.constant.SystemConstant;
+import com.qmth.themis.exam.interceptor.AuthInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import javax.annotation.Resource;
+
+/**
+ * @Description: 路径拦截器
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2020/6/27
+ */
+@Configuration
+public class WebMvcConfig implements WebMvcConfigurer {
+
+    @Resource
+    DictionaryConfig dictionaryConfig;
+
+    @Bean
+    public AuthInterceptor AuthInterceptor() {
+        return new AuthInterceptor();
+    }
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(AuthInterceptor()).addPathPatterns(SystemConstant.ALL_PATH).excludePathPatterns(dictionaryConfig.authNoUrlDomain().getUrls());
+    }
+}

+ 41 - 0
themis-exam/src/main/java/com/qmth/themis/exam/config/WebSocketConfig.java

@@ -0,0 +1,41 @@
+package com.qmth.themis.exam.config;//package com.qmth.themis.backend.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.config.annotation.EnableWebSocket;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+/**
+ * @Description: 开启WebSocket支持
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2020/6/3
+ */
+@Configuration
+@EnableWebSocket
+public class WebSocketConfig
+//        implements WebSocketConfigurer
+{
+    /**
+     * ServerEndpointExporter 作用
+     * 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
+     *
+     * @return
+     */
+    @Bean
+    public ServerEndpointExporter serverEndpointExporter() {
+        return new ServerEndpointExporter();
+    }
+
+//    @Override
+//    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
+//        registry.addHandler(myHandler(), "/messageHandler").setAllowedOrigins("*");
+//    }
+//
+//    @Bean
+//    public WebSocketHandler myHandler() {
+//        return new MessageHandler();
+//    }
+}
+

+ 45 - 0
themis-exam/src/main/java/com/qmth/themis/exam/constant/ExamConstant.java

@@ -0,0 +1,45 @@
+package com.qmth.themis.exam.constant;
+
+import com.qmth.themis.business.constant.SpringContextHolder;
+import com.qmth.themis.business.constant.SystemConstant;
+import com.qmth.themis.business.enums.UploadFileEnum;
+import com.qmth.themis.common.enums.ExceptionResultEnum;
+import com.qmth.themis.common.exception.BusinessException;
+import com.qmth.themis.exam.config.DictionaryConfig;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * @Description: 后台系统常量
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2020/7/15
+ */
+public class ExamConstant {
+
+    /**
+     * 获取系统上传环境
+     *
+     * @param type
+     * @return
+     */
+    public static Map<String, Object> getAttachmentEnv(Integer type) {
+        if (Objects.isNull(type) || Objects.equals(type, "")) {
+            throw new BusinessException(ExceptionResultEnum.ATTACHMENT_TYPE_IS_NULL);
+        }
+        DictionaryConfig dictionaryConfig = SpringContextHolder.getBean(DictionaryConfig.class);
+        Map<String, Object> mapParameter = new HashMap<>();
+        mapParameter.put(SystemConstant.END_POINT, dictionaryConfig.aliYunOssDomain().getEndpoint());
+        mapParameter.put(SystemConstant.ACCESS_KEY_ID, dictionaryConfig.aliYunOssDomain().getAccessKeyId());
+        mapParameter.put(SystemConstant.ACCESS_KEY_SECRET, dictionaryConfig.aliYunOssDomain().getAccessKeySecret());
+        mapParameter.put(SystemConstant.BUCKET, dictionaryConfig.aliYunOssDomain().getBucket());
+        mapParameter.put(SystemConstant.OSS, dictionaryConfig.sysDomain().isOss());
+        mapParameter.put(SystemConstant.ATTACHMENT_TYPE, dictionaryConfig.sysDomain().getAttachmentType());
+        String uploadType = UploadFileEnum.convertToName(type);
+        mapParameter.put(SystemConstant.UPLOAD_TYPE, uploadType);
+        return mapParameter;
+    }
+}

+ 158 - 0
themis-exam/src/main/java/com/qmth/themis/exam/interceptor/AuthInterceptor.java

@@ -0,0 +1,158 @@
+package com.qmth.themis.exam.interceptor;
+
+import cn.hutool.http.HttpStatus;
+import com.qmth.themis.business.constant.SystemConstant;
+import com.qmth.themis.business.dto.AuthDto;
+import com.qmth.themis.business.entity.TBSession;
+import com.qmth.themis.business.entity.TBUser;
+import com.qmth.themis.business.enums.RoleEnum;
+import com.qmth.themis.business.service.EhcacheService;
+import com.qmth.themis.business.service.TBUserService;
+import com.qmth.themis.business.util.EhcacheUtil;
+import com.qmth.themis.business.util.RedisUtil;
+import com.qmth.themis.common.enums.ExceptionResultEnum;
+import com.qmth.themis.common.enums.Platform;
+import com.qmth.themis.common.exception.BusinessException;
+import com.qmth.themis.common.signature.SignatureInfo;
+import com.qmth.themis.common.signature.SignatureType;
+import com.qmth.themis.exam.config.DictionaryConfig;
+import com.qmth.themis.exam.util.ServletUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * @Description: 鉴权拦截器
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2020/6/27
+ */
+public class AuthInterceptor implements HandlerInterceptor {
+    private final static Logger log = LoggerFactory.getLogger(AuthInterceptor.class);
+
+    @Resource
+    EhcacheService ehcacheService;
+
+    @Resource
+    DictionaryConfig dictionaryConfig;
+
+    @Resource
+    TBUserService tbUserService;
+
+    @Resource
+    RedisUtil redisUtil;
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
+        log.info("HandlerInterceptor preHandle is come in");
+        String url = request.getServletPath();
+        String method = request.getMethod();
+        Platform platform = Platform.valueOf(ServletUtil.getRequestPlatform());
+        String deviceId = ServletUtil.getRequestDeviceId();
+        if (Objects.isNull(platform) || Objects.equals(platform, "")) {
+            throw new BusinessException(ExceptionResultEnum.PLATFORM_INVALID);
+        }
+        if (Objects.isNull(deviceId) || Objects.equals(deviceId, "")) {
+            throw new BusinessException(ExceptionResultEnum.DEVICE_ID_INVALID);
+        }
+        if (url.equalsIgnoreCase(SystemConstant.ERROR)) {
+            if (response.getStatus() == HttpStatus.HTTP_NOT_FOUND) {
+                throw new BusinessException(ExceptionResultEnum.NOT_FOUND);
+            } else if (response.getStatus() == HttpStatus.HTTP_INTERNAL_ERROR) {
+                throw new BusinessException(ExceptionResultEnum.SERVICE_NOT_FOUND);
+            } else {
+                throw new BusinessException(ExceptionResultEnum.EXCEPTION_ERROR);
+            }
+        }
+        Long userId = null;
+//        Long timestamp = Long.parseLong(ServletUtil.getRequestTime(request));
+//        if (!SystemConstant.expire(timestamp.longValue())) {
+//            final SignatureInfo info = SignatureInfo
+//                    .parse(method, url, timestamp, ServletUtil.getRequestAuthorization(request));
+        //测试
+        final SignatureInfo info = SignatureInfo
+                .parse(ServletUtil.getRequestAuthorization());
+        if (Objects.nonNull(info) && info.getType() == SignatureType.TOKEN) {
+            String sessionId = info.getInvoker();
+            TBSession tbSession = (TBSession) redisUtil.getUserSession(sessionId);
+            if (Objects.isNull(tbSession)) {
+                throw new BusinessException(ExceptionResultEnum.LOGIN_NO);
+            } else {
+                if (info.validate(tbSession.getAccessToken()) && info.getTimestamp() < tbSession.getExpireTime().getTime()
+                        && platform.name().equalsIgnoreCase(tbSession.getPlatform()) && Objects.equals(deviceId, tbSession.getDeviceId())) {
+                    userId = Long.parseLong(tbSession.getIdentity());
+                    Date expireTime = tbSession.getExpireTime();
+                    //手机端的token时长为一个月,所以会出现缓存没有的情况
+                    if (expireTime.getTime() <= System.currentTimeMillis()) {//先判断时间是否过期
+                        throw new BusinessException(ExceptionResultEnum.LOGIN_NO);
+                    }
+                    TBUser tbUser = (TBUser) redisUtil.getUser(userId);
+                    if (Objects.isNull(tbUser)) {
+                        tbUser = tbUserService.getById(userId);
+                        redisUtil.setUser(tbUser.getId(), tbUser);
+                    }
+
+                    request.setAttribute(SystemConstant.SESSION, tbSession);
+                    request.setAttribute(SystemConstant.ACCOUNT, tbUser);
+
+                    AuthDto authDto = (AuthDto) EhcacheUtil.get(SystemConstant.AUTH_CACHE, userId);
+                    //验证权限
+                    if (Objects.isNull(authDto)) {
+                        authDto = ehcacheService.addAccountCache(userId);
+                    }
+                    request.setAttribute(SystemConstant.ORG, authDto.getTbOrg());
+                    //系统管理员拥有所有权限
+                    if (authDto.getRoleCodes().contains(RoleEnum.SUPER_ADMIN.name())) {
+                        return true;
+                    }
+                    //系统公用接口不拦截
+                    List<String> sysUrls = dictionaryConfig.systemUrlDomain().getUrls();
+                    int sysCount = (int) sysUrls.stream().filter(s -> {
+                        return s.equalsIgnoreCase(url);
+                    }).count();
+                    if (sysCount > 0) {
+                        return true;
+                    }
+                    Set<String> urls = authDto.getUrls();
+                    int count = (int) urls.stream().filter(s -> {
+                        return s.equalsIgnoreCase(url);
+                    }).count();
+                    if (count == 0) {
+                        throw new BusinessException(ExceptionResultEnum.UN_AUTHORIZATION);
+                    }
+                } else {
+                    throw new BusinessException(ExceptionResultEnum.AUTHORIZATION_ERROR);
+                }
+            }
+        } else {
+            throw new BusinessException(ExceptionResultEnum.AUTHORIZATION_ERROR);
+        }
+//        } else {
+//            throw new BusinessException(ExceptionResultEnum.AUTHORIZATION_ERROR);
+//        }
+        return true;
+    }
+
+    @Override
+    public void postHandle(HttpServletRequest request,
+                           HttpServletResponse response,
+                           Object o, ModelAndView modelAndView) throws Exception {
+
+    }
+
+    @Override
+    public void afterCompletion(HttpServletRequest request,
+                                HttpServletResponse response,
+                                Object o, Exception e) throws Exception {
+    }
+}

+ 182 - 0
themis-exam/src/main/java/com/qmth/themis/exam/util/ServletUtil.java

@@ -0,0 +1,182 @@
+package com.qmth.themis.exam.util;
+
+import com.qmth.themis.business.constant.SystemConstant;
+import com.qmth.themis.business.util.JacksonUtil;
+import com.qmth.themis.common.contanst.Constants;
+import com.qmth.themis.common.util.Result;
+import com.qmth.themis.common.util.ResultUtil;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * @Description: http工具
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2020/4/10
+ */
+public class ServletUtil {
+
+    /**
+     * 输出错误
+     *
+     * @param code
+     * @param message
+     * @throws IOException
+     */
+    public static void responseError(String code, String message) throws IOException {
+        Result result = ResultUtil.error(code, message);
+        String json = JacksonUtil.parseJson(result);
+        getResponse().getWriter().print(json);
+    }
+
+    /**
+     * 获取请求的accessToken
+     *
+     * @return
+     */
+    public static String getRequestToken() {
+        HttpServletRequest request = getRequest();
+        // 从header中获取token
+        String token = request.getHeader(SystemConstant.ACCESS_TOKEN);
+        // 如果header中不存在token,则从参数中获取token
+        if (Objects.isNull(token)) {
+            token = request.getParameter(SystemConstant.ACCESS_TOKEN);
+        }
+        return token;
+    }
+
+    /**
+     * 获取请求的platform
+     *
+     * @return
+     */
+    public static String getRequestPlatform() {
+        HttpServletRequest request = getRequest();
+        // 从header中获取platform
+        String platform = request.getHeader(Constants.HEADER_PLATFORM);
+        // 如果header中不存在platform,则从参数中获取platform
+        if (Objects.isNull(platform)) {
+            platform = request.getParameter(Constants.HEADER_PLATFORM);
+        }
+        return platform;
+    }
+
+    /**
+     * 获取请求的deviceId
+     *
+     * @return
+     */
+    public static String getRequestDeviceId() {
+        HttpServletRequest request = getRequest();
+        // 从header中获取deviceId
+        String deviceId = request.getHeader(Constants.HEADER_DEVICE_ID);
+        // 如果header中不存在deviceId,则从参数中获取deviceId
+        if (Objects.isNull(deviceId)) {
+            deviceId = request.getParameter(Constants.HEADER_DEVICE_ID);
+        }
+        return deviceId;
+    }
+
+    /**
+     * 获取请求的time
+     *
+     * @return
+     */
+    public static String getRequestTime() {
+        HttpServletRequest request = getRequest();
+        // 从header中获取time
+        String time = request.getHeader(Constants.HEADER_TIME);
+        // 如果header中不存在time,则从参数中获取time
+        if (Objects.isNull(time)) {
+            time = request.getParameter(Constants.HEADER_TIME);
+        }
+        return time;
+    }
+
+    /**
+     * 获取请求的Authorization
+     *
+     * @return
+     */
+    public static String getRequestAuthorization() {
+        HttpServletRequest request = getRequest();
+        // 从header中获取authorization
+        String authorization = request.getHeader(Constants.HEADER_AUTHORIZATION);
+        // 如果header中不存在authorization,则从参数中获取authorization
+        if (Objects.isNull(authorization)) {
+            authorization = request.getParameter(Constants.HEADER_AUTHORIZATION);
+        }
+        return authorization;
+    }
+
+    /**
+     * 获取请求的md5
+     *
+     * @return
+     */
+    public static String getRequestMd5() {
+        return getRequest().getHeader(SystemConstant.MD5);
+    }
+
+    /**
+     * 获取请求的path
+     *
+     * @return
+     */
+    public static String getRequestPath() {
+        return getRequest().getHeader(SystemConstant.PATH);
+    }
+
+    /**
+     * 获取请求的Session
+     *
+     * @return
+     */
+    public static Object getRequestSession() {
+        return getRequest().getAttribute(SystemConstant.SESSION);
+    }
+
+    /**
+     * 获取请求的account
+     *
+     * @return
+     */
+    public static Object getRequestAccount() {
+        return getRequest().getAttribute(SystemConstant.ACCOUNT);
+    }
+
+    /**
+     * 获取请求的org
+     *
+     * @return
+     */
+    public static Object getRequestOrg() {
+        return getRequest().getAttribute(SystemConstant.ORG);
+    }
+
+    /**
+     * 获取HttpServletRequest
+     *
+     * @return
+     */
+    public static HttpServletRequest getRequest() {
+        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        return servletRequestAttributes.getRequest();
+    }
+
+    /**
+     * 获取HttpServletResponse
+     *
+     * @return
+     */
+    public static HttpServletResponse getResponse() {
+        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        return servletRequestAttributes.getResponse();
+    }
+}

+ 213 - 0
themis-exam/src/main/java/com/qmth/themis/exam/websocket/WebSocketServer.java

@@ -0,0 +1,213 @@
+package com.qmth.themis.exam.websocket;//package com.qmth.themis.backend.websocket;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import javax.websocket.*;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @Description: websocker服务端
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2020/7/10
+ */
+@ServerEndpoint("/imserver/{userId}")
+@Component
+public class WebSocketServer
+//        implements MessageListenerConcurrently
+{
+    private final static Logger log = LoggerFactory.getLogger(WebSocketServer.class);
+    /**
+     * 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
+     */
+    private static int onlineCount = 0;
+    /**
+     * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
+     */
+    private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
+    /**
+     * 与某个客户端的连接会话,需要通过它来给客户端发送数据
+     */
+    private Session session;
+    /**
+     * 接收userId
+     */
+    private String userId = null;
+
+    /**
+     * 连接建立成功调用的方法
+     */
+    @OnOpen
+    public void onOpen(Session session, @PathParam("userId") String userId) {
+        this.session = session;
+        this.userId = userId;
+        if (webSocketMap.containsKey(userId)) {
+            webSocketMap.remove(userId);
+            webSocketMap.put(userId, this);
+            //加入set中
+        } else {
+            webSocketMap.put(userId, this);
+            //加入set中
+            addOnlineCount();
+            //在线数加1
+        }
+        log.info("用户连接:" + userId + ",当前在线人数为:" + getOnlineCount());
+        try {
+            sendMessage("连接成功");
+        } catch (IOException e) {
+            log.error("用户:" + userId + ",网络异常!!!!!!");
+        }
+    }
+
+    /**
+     * 连接关闭调用的方法
+     */
+    @OnClose
+    public void onClose() {
+        if (webSocketMap.containsKey(userId)) {
+            webSocketMap.remove(userId);
+            //从set中删除
+            subOnlineCount();
+        }
+        log.info("用户退出:" + userId + ",当前在线人数为:" + getOnlineCount());
+    }
+
+    /**
+     * 收到客户端消息后调用的方法
+     *
+     * @param message 客户端发送过来的消息
+     */
+    @OnMessage
+    public void onMessage(String message, Session session) {
+        log.info("用户消息:" + userId + ",报文:" + message);
+        //可以群发消息
+        //消息保存到数据库、redis
+        if (StringUtils.isNotBlank(message)) {
+            try {
+                //解析发送的报文
+                JSONObject jsonObject = JSON.parseObject(message);
+                //追加发送人(防止串改)
+                jsonObject.put("fromUserId", this.userId);
+                String toUserId = jsonObject.getString("toUserId");
+                //传送给对应toUserId用户的websocket
+                if (StringUtils.isNotBlank(toUserId) && webSocketMap.containsKey(toUserId)) {
+                    webSocketMap.get(toUserId).sendMessage(jsonObject.toJSONString());
+                } else {
+                    log.error("请求的userId:" + toUserId + "不在该服务器上");
+                    //否则不在这个服务器上,发送到mysql或者redis
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * @param session
+     * @param error
+     */
+    @OnError
+    public void onError(Session session, Throwable error) {
+        log.error("用户错误:" + this.userId + ",原因:" + error.getMessage());
+        error.printStackTrace();
+    }
+
+    /**
+     * 实现服务器主动推送
+     */
+    public void sendMessage(String message) throws IOException {
+        this.session.getBasicRemote().sendText(message);
+    }
+
+
+    /**
+     * 发送自定义消息
+     */
+    public static void sendInfo(String message, @PathParam("userId") String userId) throws IOException {
+        log.info("发送消息到:" + userId + ",报文:" + message);
+        if (StringUtils.isNotBlank(userId) && webSocketMap.containsKey(userId)) {
+            webSocketMap.get(userId).sendMessage(message);
+        } else {
+            log.error("用户" + userId + ",不在线!");
+        }
+    }
+
+    public void wxappPhotoReady() {
+        log.info("wxappPhotoReady is come in");
+    }
+
+    public static synchronized int getOnlineCount() {
+        return onlineCount;
+    }
+
+    public static synchronized void addOnlineCount() {
+        WebSocketServer.onlineCount++;
+    }
+
+    public static synchronized void subOnlineCount() {
+        WebSocketServer.onlineCount--;
+    }
+
+//    @Override
+//    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
+//        try {
+//            long threadId = Thread.currentThread().getId();
+//            String threadName = Thread.currentThread().getName();
+//            for (MessageExt messageExt : msgs) {
+//                log.info(":{}-:{} websocketConsumer 重试次数:{}", threadId, threadName, messageExt.getReconsumeTimes());
+//                MqDto mqDto = JacksonUtil.readJson(new String(messageExt.getBody(), Constants.CHARSET), MqDto.class);
+//                log.info(":{}-:{} websocketConsumer 接收到的消息:{}", threadId, threadName, JacksonUtil.parseJson(mqDto));
+//                log.info(":{}-:{} websocketConsumer mqDto sequence:{},tag:{}", threadId, threadName, mqDto.getSequence(), mqDto.getTag());
+//                Map map = mqDto.getProperties();
+//                String body = JacksonUtil.parseJson(mqDto.getBody());
+//                log.info("map:{},body:{}", JacksonUtil.parseJson(map), body);
+//                String model = String.valueOf(map.get("model"));
+//                MessageModel messageModel = MessageModel.valueOf(model);
+//                if (messageModel.ordinal() == MessageModel.CLUSTERING.ordinal()) {
+//                    webSocketMap.get(map.get("toUserId")).sendMessage(body);
+//                } else {
+//                    webSocketMap.forEach((k, v) -> {
+//                        try {
+//                            v.sendMessage(body);
+//                        } catch (IOException e) {
+//                            e.printStackTrace();
+//                        }
+//                    });
+//                }
+//            }
+//        } catch (Exception e) {
+//            e.printStackTrace();
+//            return ConsumeConcurrentlyStatus.RECONSUME_LATER;//重试
+//        }
+//        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;//成功
+//    }
+//
+//    @Service
+//    @RocketMQMessageListener(consumerGroup = "websocketConsumerImGroup", topic = "websocketImTopic", selectorType = SelectorType.TAG, selectorExpression = "*")
+//    public class sessionConsumerWeb implements RocketMQListener<Message>, RocketMQPushConsumerLifecycleListener {
+//
+//        @Override
+//        public void onMessage(Message message) {
+//            //实现RocketMQPushConsumerLifecycleListener监听器之后,此方法不调用
+//        }
+//
+//        @Override
+//        public void prepareStart(DefaultMQPushConsumer defaultMQPushConsumer) {
+//            defaultMQPushConsumer.setConsumeMessageBatchMaxSize(SystemConstant.CONSUME_MESSAGE_BATCH_MAX_SIZE);//每次拉取10条
+//            defaultMQPushConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
+//            defaultMQPushConsumer.setMaxReconsumeTimes(SystemConstant.MAXRECONSUMETIMES);//最大重试次数
+////            defaultMQPushConsumer.setMessageModel(MessageModel.BROADCASTING);
+//            defaultMQPushConsumer.registerMessageListener(WebSocketServer.this::consumeMessage);
+//        }
+//    }
+}
+

+ 73 - 55
themis-exam/src/main/resources/application.properties

@@ -97,53 +97,71 @@ spring.jackson.time-zone=GMT+8
 #============================================================================
 # \u914D\u7F6Erocketmq
 #============================================================================
-##namesrv\u5730\u5740
-#rocketmq.name-server=127.0.0.1:9876
-##\u53D1\u9001\u6D88\u606F\u8D85\u65F6\u65F6\u95F4\uFF0C\u5355\u4F4D\u6BEB\u79D2\u3002\u9ED8\u8BA410000
-#rocketmq.producer.send-message-timeout=300000
-##Producer\u7EC4\u540D\uFF0C\u591A\u4E2AProducer\u5982\u679C\u5C5E\u4E8E\u4E00\u4E2A\u5E94\u7528\uFF0C\u53D1\u9001\u540C\u6837\u7684\u6D88\u606F\uFF0C\u5219\u5E94\u8BE5\u5C06\u5B83\u4EEC\u5F52\u4E3A\u540C\u4E00\u7EC4\u3002\u9ED8\u8BA4DEFAULT_PRODUCER
-#rocketmq.producer.group=my-group
-##\u5BA2\u6237\u7AEF\u9650\u5236\u7684\u6D88\u606F\u5927\u5C0F\uFF0C\u8D85\u8FC7\u62A5\u9519\uFF0C\u540C\u65F6\u670D\u52A1\u7AEF\u4E5F\u4F1A\u9650\u5236\uFF0C\u9700\u8981\u8DDF\u670D\u52A1\u7AEF\u914D\u5408\u4F7F\u7528\u3002\u9ED8\u8BA44MB
-#rocketmq.producer.compress-message-body-threshold=4096
-#rocketmq.producer.max-message-size=4194304
-##\u5982\u679C\u6D88\u606F\u53D1\u9001\u5931\u8D25\uFF0C\u6700\u5927\u91CD\u8BD5\u6B21\u6570\uFF0C\u8BE5\u53C2\u6570\u53EA\u5BF9\u5F02\u6B65\u53D1\u9001\u6A21\u5F0F\u8D77\u4F5C\u7528\u3002\u9ED8\u8BA42
-#rocketmq.producer.retry-times-when-send-async-failed=3
-##\u5982\u679C\u6D88\u606F\u53D1\u9001\u5931\u8D25\uFF0C\u662F\u5426\u7EE7\u7EED\u53D1\u4E0B\u4E00\u6761
-#rocketmq.producer.retry-next-server=true
-##\u5982\u679C\u6D88\u606F\u53D1\u9001\u5931\u8D25\uFF0C\u6700\u5927\u91CD\u8BD5\u6B21\u6570\uFF0C\u8BE5\u53C2\u6570\u53EA\u5BF9\u540C\u6B65\u53D1\u9001\u6A21\u5F0F\u8D77\u4F5C\u7528\u3002\u9ED8\u8BA42
-#rocketmq.producer.retry-times-when-send-failed=3
-##ACK
-#rocketmq.producer.access-key=AK
-#rocketmq.producer.secret-key=SK
-#rocketmq.producer.enable-msg-trace=true
-#rocketmq.producer.customized-trace-topic=my-trace-topic
-#
-#mq.config.server=themis
-##session_topic\u76D1\u542C
-#mq.config.sessionTopic=${mq.config.server}-topic-session
-#mq.config.sessionConsumerGroup=${mq.config.server}-group-session
-#
-#mq.config.sessionTopicWebTag=web
-#mq.config.sessionConsumerWebGroup=${mq.config.sessionConsumerGroup}-${mq.config.sessionTopicWebTag}
-#
-#mq.config.sessionTopicWxappVideoTag=wxapp_video
-#mq.config.sessionConsumerWxappVideoGroup=${mq.config.sessionConsumerGroup}-${mq.config.sessionTopicWxappVideoTag}
-#
-#mq.config.sessionTopicWxappAnswerTag=wxapp_answer
-#mq.config.sessionConsumerWxappAnswerGroup=${mq.config.sessionConsumerGroup}-${mq.config.sessionTopicWxappAnswerTag}
-#
-#mq.config.sessionTopicPcTag=pc
-#mq.config.sessionConsumerPcGroup=${mq.config.sessionConsumerGroup}-${mq.config.sessionTopicPcTag}
-#
-##user_login\u76D1\u542C
-#mq.config.userLogTopic=${mq.config.server}-topic-userLog
-#mq.config.userLogConsumerGroup=${mq.config.server}-group-userLog
-#
-#mq.config.userLogTopicUserTag=user
-#mq.config.userLogConsumerUserGroup=${mq.config.userLogConsumerGroup}-${mq.config.userLogTopicUserTag}
-#
-#mq.config.userLogTopicStudentTag=student
-#mq.config.userLogConsumerStudentGroup=${mq.config.userLogConsumerGroup}-${mq.config.userLogTopicStudentTag}
+#namesrv\u5730\u5740
+rocketmq.name-server=127.0.0.1:9876
+#\u53D1\u9001\u6D88\u606F\u8D85\u65F6\u65F6\u95F4\uFF0C\u5355\u4F4D\u6BEB\u79D2\u3002\u9ED8\u8BA410000
+rocketmq.producer.send-message-timeout=300000
+#Producer\u7EC4\u540D\uFF0C\u591A\u4E2AProducer\u5982\u679C\u5C5E\u4E8E\u4E00\u4E2A\u5E94\u7528\uFF0C\u53D1\u9001\u540C\u6837\u7684\u6D88\u606F\uFF0C\u5219\u5E94\u8BE5\u5C06\u5B83\u4EEC\u5F52\u4E3A\u540C\u4E00\u7EC4\u3002\u9ED8\u8BA4DEFAULT_PRODUCER
+rocketmq.producer.group=my-group
+#\u5BA2\u6237\u7AEF\u9650\u5236\u7684\u6D88\u606F\u5927\u5C0F\uFF0C\u8D85\u8FC7\u62A5\u9519\uFF0C\u540C\u65F6\u670D\u52A1\u7AEF\u4E5F\u4F1A\u9650\u5236\uFF0C\u9700\u8981\u8DDF\u670D\u52A1\u7AEF\u914D\u5408\u4F7F\u7528\u3002\u9ED8\u8BA44MB
+rocketmq.producer.compress-message-body-threshold=4096
+rocketmq.producer.max-message-size=4194304
+#\u5982\u679C\u6D88\u606F\u53D1\u9001\u5931\u8D25\uFF0C\u6700\u5927\u91CD\u8BD5\u6B21\u6570\uFF0C\u8BE5\u53C2\u6570\u53EA\u5BF9\u5F02\u6B65\u53D1\u9001\u6A21\u5F0F\u8D77\u4F5C\u7528\u3002\u9ED8\u8BA42
+rocketmq.producer.retry-times-when-send-async-failed=3
+#\u5982\u679C\u6D88\u606F\u53D1\u9001\u5931\u8D25\uFF0C\u662F\u5426\u7EE7\u7EED\u53D1\u4E0B\u4E00\u6761
+rocketmq.producer.retry-next-server=true
+#\u5982\u679C\u6D88\u606F\u53D1\u9001\u5931\u8D25\uFF0C\u6700\u5927\u91CD\u8BD5\u6B21\u6570\uFF0C\u8BE5\u53C2\u6570\u53EA\u5BF9\u540C\u6B65\u53D1\u9001\u6A21\u5F0F\u8D77\u4F5C\u7528\u3002\u9ED8\u8BA42
+rocketmq.producer.retry-times-when-send-failed=3
+#ACK
+rocketmq.producer.access-key=AK
+rocketmq.producer.secret-key=SK
+#\u542F\u7528\u6D88\u606F\u8F68\u8FF9\uFF0C\u9ED8\u8BA4\u503Ctrue
+rocketmq.producer.enable-msg-trace=true
+#\u81EA\u5B9A\u4E49\u7684\u6D88\u606F\u8F68\u8FF9\u4E3B\u9898
+rocketmq.producer.customized-trace-topic=my-trace-topic
+
+mq.config.server=themis
+#session_topic\u76D1\u542C
+mq.config.sessionTopic=${mq.config.server}-topic-session
+mq.config.sessionConsumerGroup=${mq.config.server}-group-session
+
+mq.config.sessionTopicWebTag=web
+mq.config.sessionConsumerWebGroup=${mq.config.sessionConsumerGroup}-${mq.config.sessionTopicWebTag}
+
+mq.config.sessionTopicWxappMonitorTag=wxapp_monitor
+mq.config.sessionConsumerWxappMonitorGroup=${mq.config.sessionConsumerGroup}-${mq.config.sessionTopicWxappMonitorTag}
+
+mq.config.sessionTopicWxappAnswerTag=wxapp_answer
+mq.config.sessionConsumerWxappAnswerGroup=${mq.config.sessionConsumerGroup}-${mq.config.sessionTopicWxappAnswerTag}
+
+mq.config.sessionTopicPcTag=pc
+mq.config.sessionConsumerPcGroup=${mq.config.sessionConsumerGroup}-${mq.config.sessionTopicPcTag}
+
+#user_login\u76D1\u542C
+mq.config.userLogTopic=${mq.config.server}-topic-userLog
+mq.config.userLogConsumerGroup=${mq.config.server}-group-userLog
+
+mq.config.userLogTopicUserTag=user
+mq.config.userLogConsumerUserGroup=${mq.config.userLogConsumerGroup}-${mq.config.userLogTopicUserTag}
+
+mq.config.userLogTopicStudentTag=student
+mq.config.userLogConsumerStudentGroup=${mq.config.userLogConsumerGroup}-${mq.config.userLogTopicStudentTag}
+
+#task_topic\u76D1\u542C
+mq.config.taskTopic=${mq.config.server}-topic-task
+mq.config.taskConsumerGroup=${mq.config.server}-group-task
+
+#\u8003\u751F\u5BFC\u5165
+mq.config.taskTopicExamStudentImportTag=examStudentImport
+mq.config.taskConsumerExamStudentImportGroup=${mq.config.taskConsumerGroup}-${mq.config.taskTopicExamStudentImportTag}
+
+#\u8003\u573A\u5BFC\u5165
+mq.config.taskTopicRoomCodeImportTag=roomCodeImport
+mq.config.taskConsumerRoomCodeImportGroup=${mq.config.taskConsumerGroup}-${mq.config.taskTopicRoomCodeImportTag}
+
+#\u8003\u573A\u5BFC\u51FA
+mq.config.taskTopicRoomCodeExportTag=roomCodeExport
+mq.config.taskConsumerRoomCodeExportGroup=${mq.config.taskConsumerGroup}-${mq.config.taskTopicRoomCodeExportTag}
 
 #\u963F\u91CC\u4E91OSS\u914D\u7F6E
 aliyun.oss.name=oss-cn-shenzhen.aliyuncs.com
@@ -155,20 +173,20 @@ aliyun.oss.url=http://${aliyun.oss.bucket}.${aliyun.oss.name}
 
 #\u7CFB\u7EDF\u914D\u7F6E
 sys.config.oss=false
-#sys.config.localhostPath=upload
-#sys.config.attachmentType=.xlsx,.xls,.doc,.docx,.pdf,.jpg,.jpeg,.png,.html
+sys.config.attachmentType=.xlsx,.xls,.doc,.docx,.pdf,.jpg,.jpeg,.png,.html,.zip
+sys.config.serverUpload=/Users/king/git/themis-server/
 #sys.config.serverUpload=/Users/king/git/themis-service/
 #\u7F51\u5173accessKey\u548Csecret,\u6D4B\u8BD5\u7528
 #sys.config.gatewayAccessKey=LTAI4FhEmrrhh27vzPGh25xe
 #sys.config.gatewayAccessSecret=lgnWDUMRAhWBIn4bvAEg2ZC9ECB0Of
 #sys.config.deviceId=1
-#sys.config.fileHost=localhost:7001
-#sys.config.serverHost=localhost:7001
-#spring.resources.static-locations=file:${sys.config.serverUpload},classpath:/META-INF/resources/,classpath:/resources/
+sys.config.fileHost=localhost:6002${server.servlet.context-path}
+sys.config.serverHost=localhost:6002${server.servlet.context-path}
+spring.resources.static-locations=file:${sys.config.serverUpload},classpath:/META-INF/resources/,classpath:/resources/
 
 #api\u524D\u7F00
-prefix.url.exam=api/exam
+prefix.url.exam=api/oe
 
 #\u65E0\u9700\u9274\u6743\u7684url
-no.auth.urls=/webjars/**,/druid/**,/swagger-ui.html,/doc.html,/swagger-resources/**,/v2/api-docs,/webjars/springfox-swagger-ui/**,/api/admin/user/login/account
-common.system.urls=/api/admin/sys/getMenu,/api/admin/user/logout
+no.auth.urls=/webjars/**,/druid/**,/swagger-ui.html,/doc.html,/swagger-resources/**,/v2/api-docs,/webjars/springfox-swagger-ui/**,/api/oe/student/login,/file/**,/upload/**,/client/**,/base_photo/**
+common.system.urls=/api/admin/user/logout,/api/admin/sys/env