diff --git a/src/main/java/net/educoder/common/DingTalk.java b/src/main/java/net/educoder/common/DingTalk.java index bf894e8..bf7ef69 100644 --- a/src/main/java/net/educoder/common/DingTalk.java +++ b/src/main/java/net/educoder/common/DingTalk.java @@ -2,8 +2,6 @@ package net.educoder.common; import cn.hutool.core.codec.Base64; -import cn.hutool.http.HttpRequest; -import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpUtil; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/net/educoder/config/AppConfig.java b/src/main/java/net/educoder/config/AppConfig.java index 5aa19df..02a7885 100644 --- a/src/main/java/net/educoder/config/AppConfig.java +++ b/src/main/java/net/educoder/config/AppConfig.java @@ -4,7 +4,9 @@ import net.educoder.common.DingTalk; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.task.TaskExecutor; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.ThreadPoolExecutor; @@ -20,13 +22,19 @@ public class AppConfig { @Autowired private PropertiesConfig propertiesConfig; + /** + * 钉钉发消息bean配置. + * + * @return + */ @Bean("dingTalkEvaluation") public DingTalk dingTalkEvaluation() { return new DingTalk(propertiesConfig.getToken(), propertiesConfig.getSecret()); } /** - * 异步线程池配置 + * 异步线程池配置. + * * @return */ @Bean("taskExecutor") @@ -50,4 +58,27 @@ public class AppConfig { } + /** + * redisTemplate配置. + * + * @param factory 连接工厂 + * @return redisTemplate. + */ + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory factory) { + + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(factory); + // 设置key的序列化方式 + template.setKeySerializer(RedisSerializer.string()); + // 设置value的序列化方式 + template.setValueSerializer(RedisSerializer.json()); + // 设置hash的key的序列化方式 + template.setHashKeySerializer(RedisSerializer.string()); + // 设置hash的value的序列化方式 + template.setHashValueSerializer(RedisSerializer.json()); + template.afterPropertiesSet(); + return template; + } + } diff --git a/src/main/java/net/educoder/config/PropertiesConfig.java b/src/main/java/net/educoder/config/PropertiesConfig.java index 439ff9b..859c553 100644 --- a/src/main/java/net/educoder/config/PropertiesConfig.java +++ b/src/main/java/net/educoder/config/PropertiesConfig.java @@ -2,22 +2,52 @@ package net.educoder.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.PropertySource; /** * @Author: youys * @Date: 2022/4/2 - * @Description: + * @Description: 读取配置. */ @Configuration public class PropertiesConfig { + /** + * 钉钉机器人token. + */ @Value("${evaluation.token}") private String token; + /** + * 钉钉机器人secret. + */ @Value("${evaluation.secret}") private String secret; + /** + * 评测错误输出. + */ + @Value("${bridge.errorOutPuts}") + private String errorOutPuts; + + + /** + * 实训评测接口地址. + */ + @Value("${bridge.evaluation}") + private String gameEvaluationUrl; + + /** + * oj评测接口地址. + */ + @Value("${bridge.ojEvaluation}") + private String ojEvaluationUrl; + + /** + * 评测回调接口地址. + */ + @Value("${bridge.callbackUrl}") + private String callbackUrl; + public String getToken() { return token; @@ -26,4 +56,20 @@ public class PropertiesConfig { public String getSecret() { return secret; } + + public String getErrorOutPuts() { + return errorOutPuts; + } + + public String getGameEvaluationUrl() { + return gameEvaluationUrl; + } + + public String getOjEvaluationUrl() { + return ojEvaluationUrl; + } + + public String getCallbackUrl() { + return callbackUrl; + } } diff --git a/src/main/java/net/educoder/controller/CallbackController.java b/src/main/java/net/educoder/controller/CallbackController.java index b6240cb..fc79ed6 100644 --- a/src/main/java/net/educoder/controller/CallbackController.java +++ b/src/main/java/net/educoder/controller/CallbackController.java @@ -4,6 +4,8 @@ import cn.hutool.core.codec.Base64; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import net.educoder.common.DingTalk; +import net.educoder.config.PropertiesConfig; +import net.educoder.util.RedisUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -24,19 +26,17 @@ public class CallbackController { @Autowired private DingTalk dingTalk; - private static final String RESULT = "success"; + @Autowired + private PropertiesConfig propertiesConfig; + + @Autowired + private RedisUtil redisUtil; + private static final String RESULT = "success"; - private final List ERROR_MSG_LIST = Arrays.asList("系统繁忙,请稍后重试", - "程序执行失败导致评测提前终止,请稍后重试或联系系统管理员", - "本次评测网络延迟较高,资源无法正常加载,请10s后重试。", - "实验环境存在问题或已被回收,请保存数据再重置实训重新评测。", - "当前实验环境正在更新中,请稍后重试或联系系统管理员!", - "当前网络较差,代码下载超时,请稍后重试!", - "评测脚本设置异常,建议您在实训的配置页面重新选择或修改评测脚本" - ); + private static List ERROR_MSG_LIST = null; - @PostMapping("/evaluationReuslt") + @PostMapping("/evaluationResult") public String evaluation(@RequestBody Map params) { log.info("回调请求参数:{}", params); @@ -50,13 +50,21 @@ public class CallbackController { String decodeOutPut = Base64.decodeStr(outPut); log.info("tpiID:{}, decodeOutPut:{}", tpiID, decodeOutPut); - if (match(decodeOutPut)) { + + long incr; + String incrKey = String.format("evaluationResult:%s", tpiID); + boolean flag = (incr = redisUtil.increment(incrKey)) > 3; + log.info("evaluationResult, tpiID:{},第{}次评测失败", tpiID, incr); + if (matchOutPut(decodeOutPut) && flag) { log.info("tpiID:{},需要发送钉钉通知", tpiID); // 钉钉通知 StringBuilder sb = new StringBuilder(); sb.append("【自动化评测】tpiID:").append(tpiID).append(" 错误:消息").append(decodeOutPut); String result = dingTalk.sendMessage(sb.toString()); log.info("发送钉钉通知结果:{}", result); + + // 通知之后清空 + redisUtil.remove(incrKey); } return RESULT; @@ -68,7 +76,10 @@ public class CallbackController { * @param output * @return */ - private boolean match(String output) { + private boolean matchOutPut(String output) { + if (ERROR_MSG_LIST == null) { + ERROR_MSG_LIST = Arrays.asList(propertiesConfig.getErrorOutPuts().split("\n")); + } for (String s : ERROR_MSG_LIST) { if (output.contains(s)) { return true; diff --git a/src/main/java/net/educoder/model/AutoEvaParamConfig.java b/src/main/java/net/educoder/model/AutoEvaParamConfig.java index 514b549..225960a 100644 --- a/src/main/java/net/educoder/model/AutoEvaParamConfig.java +++ b/src/main/java/net/educoder/model/AutoEvaParamConfig.java @@ -1,8 +1,6 @@ package net.educoder.model; -import org.hibernate.annotations.Table; -import javax.annotation.Generated; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; diff --git a/src/main/java/net/educoder/shedule/EvaCheckWarningTask.java b/src/main/java/net/educoder/schedule/EvaCheckWarningTask.java similarity index 79% rename from src/main/java/net/educoder/shedule/EvaCheckWarningTask.java rename to src/main/java/net/educoder/schedule/EvaCheckWarningTask.java index a180636..cdcf11e 100644 --- a/src/main/java/net/educoder/shedule/EvaCheckWarningTask.java +++ b/src/main/java/net/educoder/schedule/EvaCheckWarningTask.java @@ -1,8 +1,9 @@ -package net.educoder.shedule; +package net.educoder.schedule; import cn.hutool.http.HttpUtil; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; +import net.educoder.config.PropertiesConfig; import net.educoder.model.AutoEvaParamConfig; import net.educoder.service.AutoEvaParamConfigService; import org.springframework.beans.factory.annotation.Autowired; @@ -20,43 +21,39 @@ import java.util.UUID; /** * @Author: youys * @Date: 2022/4/2 - * @Description: 评测检查告警任务 + * @Description: 评测检查告警任务. */ @Slf4j @Component public class EvaCheckWarningTask { - @Value("${bridge.evaluation}") - private String gameEvaluationUrl; - - @Value("${bridge.ojEvaluation}") - private String ojEvaluationUrl; - - @Value("${bridge.callbackUrl}") - private String callbackUrl; + @Autowired + private PropertiesConfig propertiesConfig; @Autowired private AutoEvaParamConfigService autoEvaParamConfigService; @Async - @Scheduled(initialDelay = 5 * 1000, fixedRate = 30 * 1000) + @Scheduled(cron = "${task.cron.evalCheck}") public void check() { log.info("自动化评测接口告警检查---------start "); + long startTime = System.currentTimeMillis(); + List autoEvaParamConfigList = autoEvaParamConfigService.findAll(); log.info(JSONObject.toJSONString(autoEvaParamConfigList)); for (AutoEvaParamConfig autoEvaParamConfig : autoEvaParamConfigList) { try { - if (autoEvaParamConfig.getType() == 0) { + if (autoEvaParamConfig.getType().equals(AutoEvaParamConfig.SX_EVA)) { String extras = autoEvaParamConfig.getExtras(); if (!StringUtils.hasLength(extras)) { continue; } postEvaluation(autoEvaParamConfig); - } else if (autoEvaParamConfig.getType() == 1) { + } else if (autoEvaParamConfig.getType().equals(AutoEvaParamConfig.OJ_EVA)) { if (!StringUtils.hasLength(autoEvaParamConfig.getCodeFileContent())) { continue; @@ -67,10 +64,15 @@ public class EvaCheckWarningTask { log.error("自动化评测接口告警检查任务异常", e); } } - log.info("自动化评测接口告警检查 ---------end "); + log.info("自动化评测接口告警检查 ---------end ,总耗时:{}ms", (System.currentTimeMillis() - startTime)); } + /** + * 处理实训评测. + * + * @param autoEvaParamConfig 配置 + */ private void postEvaluation(AutoEvaParamConfig autoEvaParamConfig) { JSONObject extrasObject = JSONObject.parseObject(autoEvaParamConfig.getExtras()); @@ -90,20 +92,25 @@ public class EvaCheckWarningTask { param.put("tpiGitURL", autoEvaParamConfig.getTpiGitUrl()); param.put("buildID", autoEvaParamConfig.getBuildId()); param.put("sec_key", UUID.randomUUID().toString()); - param.put("callBackUrl", callbackUrl); - String result = HttpUtil.post(gameEvaluationUrl, param); + param.put("callBackUrl", propertiesConfig.getCallbackUrl()); + String result = HttpUtil.post(propertiesConfig.getGameEvaluationUrl(), param); JSONObject jsonResult = JSONObject.parseObject(result); log.info("实训评测接口返回:{},tpiId:{}", jsonResult, autoEvaParamConfig.getTpiId()); } + /** + * 处理oj评测. + * + * @param autoEvaParamConfig 配置 + */ private void postOjEvaluation(AutoEvaParamConfig autoEvaParamConfig) { Map param = new HashMap<>(8); param.put("tpiID", autoEvaParamConfig.getTpiId()); param.put("testCases", autoEvaParamConfig.getTestCases()); param.put("codeFileContent", autoEvaParamConfig.getCodeFileContent()); param.put("sec_key", UUID.randomUUID().toString()); - param.put("callBackUrl", callbackUrl); - String result = HttpUtil.post(ojEvaluationUrl, param); + param.put("callBackUrl", propertiesConfig.getCallbackUrl()); + String result = HttpUtil.post(propertiesConfig.getOjEvaluationUrl(), param); JSONObject jsonResult = JSONObject.parseObject(result); log.info("Oj评测接口返回:{},tpiId:{}", jsonResult, autoEvaParamConfig.getTpiId()); } diff --git a/src/main/java/net/educoder/util/RedisUtil.java b/src/main/java/net/educoder/util/RedisUtil.java new file mode 100644 index 0000000..9398644 --- /dev/null +++ b/src/main/java/net/educoder/util/RedisUtil.java @@ -0,0 +1,346 @@ +package net.educoder.util; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.*; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * @Author: youys + * @Date: 2022/4/6 + * @Description: redis工具类. + */ +@Component +public class RedisUtil { + + @Autowired + private RedisTemplate redisTemplate; + + /** + * 写入缓存 + * + * @param key + * @param offset 位 8Bit=1Byte + * @return + */ + public boolean setBit(String key, long offset, boolean isShow) { + boolean result = false; + try { + ValueOperations operations = redisTemplate.opsForValue(); + operations.setBit(key, offset, isShow); + result = true; + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + + /** + * 写入缓存 + * + * @param key + * @param offset + * @return + */ + public boolean getBit(String key, long offset) { + boolean result = false; + try { + ValueOperations operations = redisTemplate.opsForValue(); + result = operations.getBit(key, offset); + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + + + /** + * 写入缓存 默认不过期. + * + * @param key + * @param value + * @return + */ + public boolean set(final String key, Object value) { + boolean result = false; + try { + ValueOperations operations = redisTemplate.opsForValue(); + operations.set(key, value); + result = true; + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + + + /** + * 写入缓存设置时效时间(单位:秒). + * + * @param key + * @param value + * @return + */ + public boolean set(final String key, Object value, Long expireTime) { + boolean result = false; + try { + ValueOperations operations = redisTemplate.opsForValue(); + operations.set(key, value); + redisTemplate.expire(key, expireTime, TimeUnit.SECONDS); + result = true; + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + + /** + * 增加(自增长1). + * + * @param key + * @return + */ + public Long increment(String key) { + return redisTemplate.opsForValue().increment(key); + } + + /** + * 增加(自增长), 负数则为自减. + * + * @param key + * @param value + * @return + */ + public Long increment(String key, long value) { + return redisTemplate.opsForValue().increment(key, value); + } + + /** + * 自减(减1). + * + * @param key + * @return + */ + public Long decrement(String key) { + return redisTemplate.opsForValue().decrement(key); + } + + + /** + * 批量删除对应的value + * + * @param keys + */ + public void remove(final String... keys) { + for (String key : keys) { + remove(key); + } + } + + + /** + * 删除对应的value + * + * @param key + */ + public void remove(final String key) { + if (exists(key)) { + redisTemplate.delete(key); + } + } + + /** + * 判断缓存中是否有对应的value + * + * @param key + * @return + */ + public boolean exists(final String key) { + return redisTemplate.hasKey(key); + } + + /** + * 读取缓存 + * + * @param key + * @return + */ + public Object get(final String key) { + Object result = null; + ValueOperations operations = redisTemplate.opsForValue(); + result = operations.get(key); + return result; + } + + /** + * 哈希 添加 + * + * @param key + * @param hashKey + * @param value + */ + public void hmSet(String key, Object hashKey, Object value) { + HashOperations hash = redisTemplate.opsForHash(); + hash.put(key, hashKey, value); + } + + /** + * 哈希获取数据 + * + * @param key + * @param hashKey + * @return + */ + public Object hmGet(String key, Object hashKey) { + HashOperations hash = redisTemplate.opsForHash(); + return hash.get(key, hashKey); + } + + /** + * 列表添加 + * + * @param k + * @param v + */ + public void lPush(String k, Object v) { + ListOperations list = redisTemplate.opsForList(); + list.rightPush(k, v); + } + + /** + * 列表获取 + * + * @param k + * @param l + * @param l1 + * @return + */ + public List lRange(String k, long l, long l1) { + ListOperations list = redisTemplate.opsForList(); + return list.range(k, l, l1); + } + + /** + * 集合添加 + * + * @param key + * @param value + */ + public void add(String key, Object value) { + SetOperations set = redisTemplate.opsForSet(); + set.add(key, value); + } + + /** + * 集合获取 + * + * @param key + * @return + */ + public Set setMembers(String key) { + SetOperations set = redisTemplate.opsForSet(); + return set.members(key); + } + + /** + * 有序集合添加 + * + * @param key + * @param value + * @param scoure + */ + public void zAdd(String key, Object value, double scoure) { + ZSetOperations zset = redisTemplate.opsForZSet(); + zset.add(key, value, scoure); + } + + /** + * 有序集合获取 + * + * @param key + * @param scoure + * @param scoure1 + * @return + */ + public Set rangeByScore(String key, double scoure, double scoure1) { + ZSetOperations zset = redisTemplate.opsForZSet(); + redisTemplate.opsForValue(); + return zset.rangeByScore(key, scoure, scoure1); + } + + + /** + * 有序集合获取排名 + * + * @param key 集合名称 + * @param value 值 + */ + public Long zRank(String key, Object value) { + ZSetOperations zset = redisTemplate.opsForZSet(); + return zset.rank(key, value); + } + + + /** + * 有序集合获取排名 + * + * @param key + */ + public Set> zRankWithScore(String key, long start, long end) { + ZSetOperations zset = redisTemplate.opsForZSet(); + Set> ret = zset.rangeWithScores(key, start, end); + return ret; + } + + /** + * 有序集合添加 + * + * @param key + * @param value + */ + public Double zSetScore(String key, Object value) { + ZSetOperations zset = redisTemplate.opsForZSet(); + return zset.score(key, value); + } + + + /** + * 有序集合添加分数 + * + * @param key + * @param value + * @param scoure + */ + public void incrementScore(String key, Object value, double scoure) { + ZSetOperations zset = redisTemplate.opsForZSet(); + zset.incrementScore(key, value, scoure); + } + + + /** + * 有序集合获取排名 + * + * @param key + */ + public Set> reverseZRankWithScore(String key, long start, long end) { + ZSetOperations zset = redisTemplate.opsForZSet(); + Set> ret = zset.reverseRangeByScoreWithScores(key, start, end); + return ret; + } + + /** + * 有序集合获取排名 + * + * @param key + */ + public Set> reverseZRankWithRank(String key, long start, long end) { + ZSetOperations zset = redisTemplate.opsForZSet(); + Set> ret = zset.reverseRangeWithScores(key, start, end); + return ret; + } + + +} \ No newline at end of file diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 35a8c0b..3b6ea0d 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -2,8 +2,19 @@ evaluation: token: 1069c45d4dfab187bb95599faeaf3687b630f79eda0d6fff914a18ee9e25db6f secret: SEC7431097248ee9c19b9d7a2c61883e29082bea79c88c3e8f02e5fa121c2837dbc - +# b端相关配置 bridge: evaluation: http://pre-bridge.educoder.net/bridge/game/gameEvaluate ojEvaluation: http://pre-bridge.educoder.net/bridge/ojs/evaluate - callbackUrl: http://1c32-120-228-142-52.ngrok.io/callback/evaluationReuslt \ No newline at end of file + callbackUrl: http://a176-120-228-142-52.ngrok.io/callback/evaluationResult + errorOutPuts: | + 系统繁忙,请稍后重试 + 程序执行失败导致评测提前终止,请稍后重试或联系系统管理员 + 本次评测网络延迟较高,资源无法正常加载,请10s后重试 + 当前实验环境正在更新中,请稍后重试或联系系统管理员! + 当前网络较差,代码下载超时,请稍后重试! + 评测脚本设置异常,建议您在实训的配置页面重新选择或修改评测脚本 +# 定时任务cron表达式 +task: + cron: + evalCheck: 0/30 * * * * ? \ No newline at end of file diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index d3e9b7d..23d668b 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -1,9 +1,20 @@ ## 评测钉钉机器人配置 evaluation: - token: 1069c45d4dfab187bb95599faeaf3687b630f79eda0d6fff914a18ee9e25db6f - secret: SEC7431097248ee9c19b9d7a2c61883e29082bea79c88c3e8f02e5fa121c2837dbc + token: a28abafca1eda791b2b09bf8a7f37d877cc27da986b92c8e1bd1157535d76fdc + secret: SEC09fedac8a866be75685ab14a5a5b80b00f48ba3da953b6af71596a4a225b9b1b bridge: evaluation: https://www.educoder.net/bridge/game/gameEvaluate ojEvaluation: https://www.educoder.net/bridge/ojs/evaluate - callbackUrl: https://1c32-120-228-142-52.ngrok.io/callback/evaluation \ No newline at end of file + callbackUrl: https://1c32-120-228-142-52.ngrok.io/callback/evaluationResult + errorOutPuts: | + 系统繁忙,请稍后重试 + 程序执行失败导致评测提前终止,请稍后重试或联系系统管理员 + 本次评测网络延迟较高,资源无法正常加载,请10s后重试 + 当前实验环境正在更新中,请稍后重试或联系系统管理员! + 当前网络较差,代码下载超时,请稍后重试! + 评测脚本设置异常,建议您在实训的配置页面重新选择或修改评测脚本 +# 定时任务cron表达式 +task: + cron: + evalCheck: 0/30 * * * * ? \ No newline at end of file diff --git a/src/test/java/net/educoder/RedisUtilTest.java b/src/test/java/net/educoder/RedisUtilTest.java new file mode 100644 index 0000000..1088ea2 --- /dev/null +++ b/src/test/java/net/educoder/RedisUtilTest.java @@ -0,0 +1,54 @@ +package net.educoder; + +import com.alibaba.fastjson.JSONObject; +import net.educoder.util.RedisUtil; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + + +/** + * @Author: youys + * @Date: 2022/4/6 + * @Description: redisUtil工具类测试. + */ +@SpringBootTest +public class RedisUtilTest { + + @Autowired + private RedisUtil redisUtil; + + + @Test + public void testSet() { + redisUtil.set("aaaaaa", "123", 30L); + String str = (String) redisUtil.get("aaaaaa"); + System.out.println(str); + + JSONObject obj = new JSONObject(); + obj.put("123", "123"); + obj.put("456", "456"); + redisUtil.set("aaaaaa2", obj, 30L); + + + JSONObject des = (JSONObject) redisUtil.get("aaaaaa2"); + System.out.println(des); + + + } + + + @Test + public void testIncrement() { + String key = "count"; + +// for (int i = 0; i < 5; i++) { +// Long increment = redisUtil.increment(key); +// System.out.println(increment); +// } + + redisUtil.decrement(key); + redisUtil.decrement(key); + + } +}