Browse Source

feat : 暂时替换redis为guava cache 缓存bok http

XuShengTao 6 năm trước cách đây
mục cha
commit
81e21fe97b

+ 4 - 0
config/application-dev.yml

@@ -58,6 +58,10 @@ aliyun:
     templateCode: SMS_181555598
 bok:
   url: http://bok.seecoder.cn
+guavaCache:
+  maximumSize: 1000
+  expireAfterAccessInSeconds: 1800
+  expireAfterWriteInSeconds: 1800
 # 自定义数据
 helper:
   mail:

+ 3 - 26
src/main/java/nju/seec/helper/controller/QuestionController.java

@@ -26,8 +26,6 @@ import org.springframework.expression.ParseException;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.*;
-
-import static nju.seec.helper.controller.QuizController.objectMapper;
 /**
  * 题库
  *
@@ -72,20 +70,9 @@ public class QuestionController {
     @Auth(roles = {UserType.TEACHER}, message = "创建题目")
     @PostMapping("")
     public QuestionVO PostQuestion(LoginUser user,  @RequestBody Map question) {
-
         if(question.get("kind")==null){throw HelperException.of(ExceptionType.PARAM_ERROR,"no kind");}
-        String kind = (String)question.get("kind");
-        QuestionVO newQuestion=null;
-        switch (kind){
-            case "CHOICE":
-                newQuestion =  (objectMapper.convertValue(question, ChoiceQuestionVO.class));
-                return questionService.newQuestion(newQuestion);
-            case "TRUE_FALSE":
-                newQuestion =  (objectMapper.convertValue(question, TrueOrFalseQuestionVO.class));
-                return questionService.newQuestion(newQuestion);
-        }
 
-        throw new ParseException('1',"parse failed:"+question.toString());
+        return questionService.newQuestion(QuestionVO.parse(question));
     }
 
     /**
@@ -94,19 +81,9 @@ public class QuestionController {
     @Auth(roles = {UserType.TEACHER}, message = "更新题目")
     @PutMapping("/{questionId}")
     public QuestionVO updateQuestion(LoginUser user, @RequestBody Map question, @PathVariable("questionId") String questionId) {
+        if(question.get("kind")==null){throw HelperException.of(ExceptionType.PARAM_ERROR,"no kind");}
 
-        if(question.get("kind")==null){throw new ParseException('1',"no kind");}
-        String kind = (String)question.get("kind");
-        QuestionVO newQuestion=null;
-        switch (kind){
-            case "CHOICE":
-                newQuestion= (objectMapper.convertValue(question, ChoiceQuestionVO.class));
-                return questionService.updateQuestion(newQuestion);
-            case "TRUE_FALSE":
-                newQuestion = (objectMapper.convertValue(question, TrueOrFalseQuestionVO.class));
-                return questionService.updateQuestion(newQuestion);
-        }
-        throw new ParseException('1',"parse failed:"+question.toString());
+        return questionService.updateQuestion(QuestionVO.parse(question));
     }
 
 

+ 0 - 1
src/main/java/nju/seec/helper/controller/QuizController.java

@@ -133,7 +133,6 @@ public class QuizController {
     /**
      * 提交答案
      */
-    static ObjectMapper objectMapper = new ObjectMapper();
 
     @Auth(roles = {UserType.STUDENT}, message = "提交答案")
     @PutMapping("/{quizId}/student-answer")

+ 3 - 3
src/main/java/nju/seec/helper/service/impl/CodeServiceImpl.java

@@ -1,7 +1,7 @@
 package nju.seec.helper.service.impl;
 
 import nju.seec.helper.service.CodeService;
-import nju.seec.helper.util.CacheUtils;
+import nju.seec.helper.util.RedisCacheUtils;
 import nju.seec.helper.util.Consts;
 import nju.seec.helper.util.MailUtils;
 import nju.seec.helper.util.SmsUtils;
@@ -20,9 +20,9 @@ public class CodeServiceImpl implements CodeService {
 
     private final MailUtils mailUtils;
     private final SmsUtils smsUtils;
-    private final CacheUtils cacheUtils;
+    private final RedisCacheUtils cacheUtils;
 
-    public CodeServiceImpl(MailUtils mailUtils, SmsUtils smsUtils, CacheUtils cacheUtils) {
+    public CodeServiceImpl(MailUtils mailUtils, SmsUtils smsUtils, RedisCacheUtils cacheUtils) {
         this.mailUtils = mailUtils;
         this.smsUtils = smsUtils;
         this.cacheUtils = cacheUtils;

+ 3 - 3
src/main/java/nju/seec/helper/service/impl/SlideServiceImpl.java

@@ -16,7 +16,7 @@ import nju.seec.helper.service.MessageService;
 import nju.seec.helper.service.QuizService;
 import nju.seec.helper.service.SlideService;
 import nju.seec.helper.service.util.AuthUtils;
-import nju.seec.helper.util.CacheUtils;
+import nju.seec.helper.util.RedisCacheUtils;
 import nju.seec.helper.util.Consts;
 import nju.seec.helper.util.FileUtils;
 import nju.seec.helper.util.enums.ExceptionType;
@@ -50,12 +50,12 @@ public class SlideServiceImpl implements SlideService {
     private final ChooseDAO chooseDAO;
     private final QuizService quizService;
     private final FileUtils fileUtils;
-    private final CacheUtils cacheUtils;
+    private final RedisCacheUtils cacheUtils;
 
     private final MessageService messageService;
 
     @Autowired
-    public SlideServiceImpl(CourseDAO courseDAO, SlideDAO slideDAO, ChooseDAO chooseDAO, FileUtils fileUtils, CacheUtils cacheUtils, MessageService messageService, QuizService quizService) {
+    public SlideServiceImpl(CourseDAO courseDAO, SlideDAO slideDAO, ChooseDAO chooseDAO, FileUtils fileUtils, RedisCacheUtils cacheUtils, MessageService messageService, QuizService quizService) {
         this.courseDAO = courseDAO;
         this.slideDAO = slideDAO;
         this.chooseDAO = chooseDAO;

+ 3 - 3
src/main/java/nju/seec/helper/service/impl/UserServiceImpl.java

@@ -9,7 +9,7 @@ import nju.seec.helper.entity.User;
 import nju.seec.helper.service.UserService;
 import nju.seec.helper.service.util.AuthUtils;
 import nju.seec.helper.service.util.RefreshUtils;
-import nju.seec.helper.util.CacheUtils;
+import nju.seec.helper.util.RedisCacheUtils;
 import nju.seec.helper.util.Consts;
 import nju.seec.helper.util.EncryptUtils;
 import nju.seec.helper.util.enums.ExceptionType;
@@ -26,7 +26,7 @@ import java.util.Map;
  */
 @Service
 public class UserServiceImpl implements UserService {
-    private final CacheUtils cacheUtils;
+    private final RedisCacheUtils cacheUtils;
     private final RefreshUtils refreshUtils;
     private final UserDAO userDAO;
     private final static Map<String, UserType> EMAIL_SUFFIX_AND_USER_TYPE_MAP =
@@ -35,7 +35,7 @@ public class UserServiceImpl implements UserService {
                     "nju.edu.cn", UserType.TEACHER
             );
 
-    public UserServiceImpl(UserDAO userDAO, CacheUtils cacheUtils, RefreshUtils refreshUtils) {
+    public UserServiceImpl(UserDAO userDAO, RedisCacheUtils cacheUtils, RefreshUtils refreshUtils) {
         this.userDAO = userDAO;
         this.cacheUtils = cacheUtils;
         this.refreshUtils = refreshUtils;

+ 48 - 43
src/main/java/nju/seec/helper/util/BOKUtil.java

@@ -2,6 +2,7 @@ package nju.seec.helper.util;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.collect.ImmutableMap;
 import lombok.Data;
 import nju.seec.helper.util.enums.ExceptionType;
 import nju.seec.helper.util.enums.QuestionKindState;
@@ -27,13 +28,14 @@ public class BOKUtil {
     @Autowired
     private RestRequestUtil restRequestUtil;
     @Autowired
-    private CacheUtils cacheUtils;
+    private GuavaCacheUtil cacheUtils;
     @Value("${bok.url}")
     private String bokUrl="http://bok.seecoder.cn";
     private String searchUrl=bokUrl+"/api/tq/search/";
     private String tqUrl=bokUrl+"/api/tq/";
     // 应该做成bok的库
     public void BokFindByStemLike(String stem, List<QuestionVO> Questions, Map page, Pageable pageable) {
+
         Map<String,String> urlParams=new HashMap<>();
         urlParams.put("content",stem);
         urlParams.put("sort",pageable.getSort().toString().replaceAll(" ","").replaceAll(":",","));
@@ -58,25 +60,26 @@ public class BOKUtil {
     }
     private static String CACHE_NAME="BOK";
     public List<QuestionVO> BokFindByIdIn(List<String> Ids) {
-        //todo: 缓存Ids
-        List<String> newIds = new ArrayList<>(Ids);
-        List<String> bok = cacheUtils.multiGet(CACHE_NAME, newIds);
-        Map<String, QuestionVO> forSortMap = new HashMap<>(Ids.size());
-        bok.forEach(json -> {
-            if (json != null && !json.equals("null")) {
-                Map voMap = JsonUtils.fromJson(json, Map.class);
-                QuestionVO questionVO = parseRedisRet(voMap);
-                forSortMap.put(questionVO.getQuestionId(), questionVO);
-                newIds.remove(questionVO.getQuestionId());
+        //todo: 缓存中取Ids
+        Set<String> requestIds = new HashSet<>(Ids);
+        final ImmutableMap<Object, Object> bok = cacheUtils.multiGet(CACHE_NAME,requestIds );
+
+        Map<String, QuestionVO> QuestionsMap = new HashMap<>(Ids.size());
+        bok.values().forEach(vo -> {
+            if (vo instanceof QuestionVO) {
+                QuestionVO questionVO = (QuestionVO) vo;
+                QuestionsMap.put(questionVO.getQuestionId(), questionVO);
+                requestIds.remove(questionVO.getQuestionId());
             }
         });
-        if(newIds.size()>0){
+        //未缓存的去这里拿
+        if(requestIds.size()>0){
             Map<String, String> urlParams = new HashMap<>();
-            String idsStr = newIds.stream().reduce((a, b) -> {
+            String idsStr = requestIds.stream().reduce((a, b) -> {
                 return a + "," + b;
             }).orElse("");
             urlParams.put("id", idsStr);
-            urlParams.put("size", String.valueOf(newIds.size()));
+            urlParams.put("size", String.valueOf(requestIds.size()));
             urlParams.put("page", "1");
             List<Map> tqs = null;
             try {
@@ -85,19 +88,21 @@ public class BOKUtil {
                 tqs = ((List) ((Map) ret.get("_embedded")).get("choiceQuestions"));
             } catch (Exception e) {
                 e.printStackTrace();
-                throw new RuntimeException("bok request error!for findByIdIn:" + Arrays.toString(newIds.toArray()));
+                throw new RuntimeException("bok request error!for findByIdIn:" + Arrays.toString(requestIds.toArray()));
             }
             List<QuestionVO> collect = parseBody(tqs);
+            Map<String,Object> newGet=new HashMap<>(collect.size());
             for (QuestionVO vo : collect) {
-                forSortMap.put(vo.getQuestionId(), vo);
-                //todo:异步批量存储
-                cacheUtils.set(CACHE_NAME, vo.getQuestionId(), JsonUtils.toJson(vo), 12, TimeUnit.HOURS);
+                QuestionsMap.put(vo.getQuestionId(), vo);
+                newGet.put(vo.getQuestionId(),vo);
             }
+            cacheUtils.setAll(CACHE_NAME,newGet);
         }
-        // todo: 重排序
+
+        // todo: 按照Ids重排序
         List<QuestionVO> ret=new ArrayList<>();
         for (String id:Ids){
-            ret.add(forSortMap.get(id));
+            ret.add(QuestionsMap.get(id));
         }
         return (ret);
     }
@@ -107,21 +112,21 @@ public class BOKUtil {
         return collect;
     }
 
-    private static QuestionVO parseRedisRet(Map q){
-        switch ((String) q.get("kind")) {
-            case "CHOICE":
-                return ChoiceQuestionVO.builder().stem((String) q.get("stem")).kind(QuestionKindState.CHOICE)
-                        .options((Map<String, String>) q.get("options")).answer(((String) q.get("answer")))
-                        .analysis((String) q.get("analysis")).QuestionId((String) q.get("QuestionId")).build();
-
-            case "TRUE_FALSE":
-                return TrueOrFalseQuestionVO.builder().stem((String) q.get("stem")).kind(QuestionKindState.TRUE_FALSE)
-                        .answer(Boolean.valueOf((String) q.get("answer")))
-                        .analysis((String) q.get("analysis")).QuestionId((String) q.get("QuestionId")).build();
-
-        }
-        throw HelperException.of(ExceptionType.ERROR,"BOK-unknown type:" + (String) q.get("type"));
-    }
+//    private static QuestionVO parseRedisRet(Map q){
+//        switch ((String) q.get("kind")) {
+//            case "CHOICE":
+//                return ChoiceQuestionVO.builder().stem((String) q.get("stem")).kind(QuestionKindState.CHOICE)
+//                        .options((Map<String, String>) q.get("options")).answer(((String) q.get("answer")))
+//                        .analysis((String) q.get("analysis")).QuestionId((String) q.get("QuestionId")).build();
+//
+//            case "TRUE_FALSE":
+//                return TrueOrFalseQuestionVO.builder().stem((String) q.get("stem")).kind(QuestionKindState.TRUE_FALSE)
+//                        .answer(Boolean.valueOf((String) q.get("answer")))
+//                        .analysis((String) q.get("analysis")).QuestionId((String) q.get("QuestionId")).build();
+//
+//        }
+//        throw HelperException.of(ExceptionType.ERROR,"BOK-unknown type:" + (String) q.get("type"));
+//    }
     private static QuestionVO parseOneQuestion(Map q){
         switch ((String) q.get("type")) {
             case "choice":
@@ -139,15 +144,14 @@ public class BOKUtil {
     }
     public QuestionVO BokFindById(String questionId)  {
         Map<String,String> urlParams=new HashMap<>();
-        String json = cacheUtils.get(CACHE_NAME, questionId);
-        if (json != null && !json.equals("null")) {
-            Map voMap = JsonUtils.fromJson(json, Map.class);
-            return parseRedisRet(voMap);
+        Object cachedVo = cacheUtils.get(CACHE_NAME, questionId);
+        if (cachedVo instanceof QuestionVO) {
+            return (QuestionVO) cachedVo;
         }
         try {
             Map ret = (Map)restRequestUtil.sendGetRequest(tqUrl+questionId, urlParams);
             final QuestionVO vo = parseOneQuestion(ret);
-            cacheUtils.set(CACHE_NAME, vo.getQuestionId(), JsonUtils.toJson(vo), 12, TimeUnit.HOURS);
+            cacheUtils.set(CACHE_NAME, vo.getQuestionId(), vo);
             return vo;
         } catch (JsonProcessingException e) {
             throw HelperException.of(ExceptionType.ERROR,"请求BOK题库数据错误:"+e.getMessage());
@@ -205,9 +209,9 @@ public class BOKUtil {
                 throw HelperException.of(ExceptionType.PARAM_ERROR,"should POST a present question");
             }
         }
+        restRequestUtil.sendPutRequest(tqUrl+questionId, new BOK_TQ(vo));
 
-        BOK_TQ bok_tq=new BOK_TQ(vo);
-        restRequestUtil.sendPutRequest(tqUrl+questionId, bok_tq);
+        cacheUtils.remove(CACHE_NAME,questionId);
         return vo;
     }
     public void deleteQuestion(String questionId) {
@@ -216,8 +220,9 @@ public class BOKUtil {
         } catch (JsonProcessingException e) {
             throw HelperException.of(ExceptionType.ERROR,"请求BOK题库数据错误:"+e.getMessage());
         }
+        cacheUtils.remove(CACHE_NAME,questionId);
     }
-
+    // 来自restBok接口的对象
     @JsonInclude(JsonInclude.Include.NON_NULL)
     @Data
     class BOK_TQ{

+ 8 - 33
src/main/java/nju/seec/helper/util/CacheUtils.java

@@ -1,42 +1,17 @@
 package nju.seec.helper.util;
 
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.stereotype.Component;
-import sun.reflect.generics.reflectiveObjects.NotImplementedException;
-
 import java.util.List;
 import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-
-/**
- * use redis
- *
- * @author cst
- */
-@Component
-public class CacheUtils {
-    private final RedisTemplate<String, String> redisTemplate;
 
-    public CacheUtils(RedisTemplate<String, String> redisTemplate) {
-        this.redisTemplate = redisTemplate;
-    }
+public interface CacheUtils {
     //todo: async
-    public void set(String cacheName, String key, String value, long expireTime, TimeUnit timeUnit) {
-        redisTemplate.opsForValue().set(cacheName + ":" + key, value, expireTime, timeUnit);
-    }
-    public void setAll(String cacheName, String key, String value, long expireTime, TimeUnit timeUnit) {
-        throw new NotImplementedException();
-    }
-    public String get(String cacheName, String key) {
-        return redisTemplate.opsForValue().get(cacheName + ":" + key);
-    }
+    void set(String cacheName, String key, String value, long expireTime, TimeUnit timeUnit);
+
+    void setAll(String cacheName, String key, String value, long expireTime, TimeUnit timeUnit);
+
+    String get(String cacheName, String key);
 
-    public List<String> multiGet(String cacheName, List<String> keys) {
-        List<String> body=keys.stream().map(s->cacheName + ":" + s).collect(Collectors.toList());
-        return redisTemplate.opsForValue().multiGet(body);
-    }
+    List<String> multiGet(String cacheName, List<String> keys);
 
-    public void remove(String cacheName, String key) {
-        redisTemplate.delete(cacheName + ":" + key);
-    }
+    void remove(String cacheName, String key);
 }

+ 64 - 0
src/main/java/nju/seec/helper/util/GuavaCacheUtil.java

@@ -0,0 +1,64 @@
+package nju.seec.helper.util;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.collect.ImmutableMap;
+import org.springframework.beans.factory.BeanNameAware;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+@Component
+public class GuavaCacheUtil {
+    private Cache<Object, Object> cache;
+
+    @Value("${guavaCache.maximumSize}")
+    private Long maximumSize=1000L;
+
+    @Value("${guavaCache.expireAfterAccessInSeconds}")
+    private Long expireAfterAccessInSeconds=1800L;
+
+    @Value("${guavaCache.expireAfterWriteInSeconds}")
+    private Long expireAfterWriteInSeconds=3600L;
+
+    public GuavaCacheUtil() {
+        CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
+        builder.maximumSize(maximumSize);
+        builder.expireAfterAccess(expireAfterAccessInSeconds, TimeUnit.SECONDS);
+        builder.expireAfterWrite(expireAfterWriteInSeconds, TimeUnit.SECONDS);
+        cache = builder.build();
+    }
+
+    public void set(String cacheName, String key, Object value) {
+        cache.put(combineKey(cacheName,key),value);
+    }
+
+    public void setAll(String cacheName, Map<String,Object> putAll) {
+        final Map<String, Object> all = putAll.entrySet().stream().collect(Collectors.toMap(entry -> {
+            return combineKey(cacheName , entry.getKey());
+        }, Map.Entry::getValue));
+        cache.putAll(all);
+    }
+    public Object get(String cacheName, String key) {
+        return cache.getIfPresent(combineKey(cacheName,key));
+    }
+
+    public ImmutableMap<Object, Object> multiGet(String cacheName, Collection<String> keys) {
+        Collection<String> body=keys.stream().map(s->combineKey(cacheName,s)).collect(Collectors.toList());
+        return cache.getAllPresent(body);
+    }
+
+    public void remove(String cacheName, String key) {
+        cache.invalidate(combineKey(cacheName,key));
+    }
+
+    private String combineKey(String cacheName, String key){
+        return cacheName + ":" + key;
+    }
+}

+ 42 - 0
src/main/java/nju/seec/helper/util/RedisCacheUtils.java

@@ -0,0 +1,42 @@
+package nju.seec.helper.util;
+
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+import sun.reflect.generics.reflectiveObjects.NotImplementedException;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * use redis
+ *
+ * @author cst
+ */
+@Component
+public class RedisCacheUtils {
+    private final RedisTemplate<String, String> redisTemplate;
+
+    public RedisCacheUtils(RedisTemplate<String, String> redisTemplate) {
+        this.redisTemplate = redisTemplate;
+    }
+    //todo: async
+    public void set(String cacheName, String key, String value, long expireTime, TimeUnit timeUnit) {
+        redisTemplate.opsForValue().set(cacheName + ":" + key, value, expireTime, timeUnit);
+    }
+    public void setAll(String cacheName, String key, String value, long expireTime, TimeUnit timeUnit) {
+        throw new NotImplementedException();
+    }
+    public String get(String cacheName, String key) {
+        return redisTemplate.opsForValue().get(cacheName + ":" + key);
+    }
+
+    public List<String> multiGet(String cacheName, List<String> keys) {
+        List<String> body=keys.stream().map(s->cacheName + ":" + s).collect(Collectors.toList());
+        return redisTemplate.opsForValue().multiGet(body);
+    }
+
+    public void remove(String cacheName, String key) {
+        redisTemplate.delete(cacheName + ":" + key);
+    }
+}

+ 18 - 0
src/main/java/nju/seec/helper/vo/quiz/QuestionVO.java

@@ -1,15 +1,19 @@
 package nju.seec.helper.vo.quiz;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 import lombok.experimental.SuperBuilder;
 import nju.seec.helper.entity.QuizStudentAnswer;
+import nju.seec.helper.util.enums.ExceptionType;
 import nju.seec.helper.util.enums.QuestionKindState;
+import nju.seec.helper.util.exception.HelperException;
 import nju.seec.helper.vo.CourseVO;
 import org.springframework.beans.BeanUtils;
+import org.springframework.expression.ParseException;
 
 import java.util.Map;
 
@@ -29,6 +33,20 @@ public abstract class QuestionVO {
     private String stem;
     public abstract void exAddAnswer(QuizStudentAnswer answer);
     public abstract boolean checkAnswer(Object answer);
+    private static ObjectMapper objectMapper = new ObjectMapper();
+    public static QuestionVO parse(Map question){
+        String kind = (String)question.get("kind");
+        QuestionVO newQuestion=null;
+        switch (kind){
+            case "CHOICE":
+                newQuestion= (objectMapper.convertValue(question, ChoiceQuestionVO.class));
+                return newQuestion;
+            case "TRUE_FALSE":
+                newQuestion = (objectMapper.convertValue(question, TrueOrFalseQuestionVO.class));
+                return newQuestion;
+        }
+        throw HelperException.of(ExceptionType.PARAM_ERROR,"parse failed:"+question.toString());
+    }
 }
 //QuestionSerializer
 //问题的详细定义