增加首页轮播图可设和ACM比赛封榜提交提示

This commit is contained in:
Himit_ZH 2021-09-04 18:08:59 +08:00
parent fc274b035c
commit 735e3c6474
28 changed files with 420 additions and 98 deletions

View File

@ -31,6 +31,21 @@
- "0.0.0.0:873:873"
```
**同时需要将MySQL的配置`MYSQL_PUBLIC_HOST`改成当前服务器的公网IP**
```shell
vim .env # 修改与docker-compose.yml同目录下的配置文件
```
```yaml
# mysql的配置
MYSQL_HOST=172.20.0.3
# 请提供当前mysql所在服务器的公网ip
MYSQL_PUBLIC_HOST=***
MYSQL_PORT=3306
MYSQL_ROOT_PASSWORD=hoj123456
```
2. 在其它服务器判题机服务器中使用docker-compose运行judgeserver服务具体操作如下
**注意如果云服务器有防火墙请开启8088端口号需要将判题服务暴露出去**

View File

@ -60,6 +60,11 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<!--分布式配置中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>

View File

@ -4,6 +4,7 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@ -13,6 +14,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
* @Date: 2020/10/22 23:25
* @Description:
*/
@EnableRetry
@EnableScheduling // 开启定时任务
@EnableDiscoveryClient // 开启注册发现
@SpringBootApplication

View File

@ -30,6 +30,7 @@ public class CorsConfig implements WebMvcConfigurer {
// /api/public/img/** /api/public/file/**
registry.addResourceHandler(Constants.File.IMG_API.getPath() + "**",Constants.File.FILE_API.getPath() + "**")
.addResourceLocations("file:" + Constants.File.USER_AVATAR_FOLDER.getPath() + File.separator,
"file:" + Constants.File.MARKDOWN_FILE_FOLDER.getPath() + File.separator);
"file:" + Constants.File.MARKDOWN_FILE_FOLDER.getPath() + File.separator,
"file:" + Constants.File.HOME_CAROUSEL_FOLDER.getPath() + File.separator);
}
}

View File

@ -1,8 +1,10 @@
package top.hcode.hoj.controller.admin;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.UnicodeUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
@ -11,14 +13,18 @@ import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import top.hcode.hoj.common.result.CommonResult;
import top.hcode.hoj.pojo.entity.File;
import top.hcode.hoj.pojo.vo.ConfigVo;
import top.hcode.hoj.service.impl.ConfigServiceImpl;
import top.hcode.hoj.service.impl.EmailServiceImpl;
import top.hcode.hoj.service.impl.FileServiceImpl;
import top.hcode.hoj.utils.Constants;
import javax.mail.MessagingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Author: Himit_ZH
@ -38,24 +44,27 @@ public class ConfigController {
@Autowired
private EmailServiceImpl emailService;
@Autowired
private FileServiceImpl fileService;
/**
* @MethodName getServiceInfo
* @Params * @param null
* @Params * @param null
* @Description 获取当前服务的相关信息以及当前系统的cpu情况内存使用情况
* @Return CommonResult
* @Since 2020/12/3
*/
@RequiresRoles(value = {"root","admin","problem_admin"},logical = Logical.OR)
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
@RequestMapping("/get-service-info")
public CommonResult getServiceInfo(){
public CommonResult getServiceInfo() {
return CommonResult.successResponse(configService.getServiceInfo());
}
@RequiresRoles(value = {"root","admin","problem_admin"},logical = Logical.OR)
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
@RequestMapping("/get-judge-service-info")
public CommonResult getJudgeServiceInfo(){
public CommonResult getJudgeServiceInfo() {
return CommonResult.successResponse(configService.getJudgeServiceInfo());
}
@ -76,14 +85,32 @@ public class ConfigController {
);
}
@RequiresPermissions("system_info_admin")
@RequestMapping(value = "/set-web-config",method = RequestMethod.PUT)
public CommonResult setWebConfig(@RequestBody HashMap<String,Object> params){
@DeleteMapping("/home-carousel")
public CommonResult deleteHomeCarousel(@RequestParam("id") Long id) {
File imgFile = fileService.getById(id);
if (imgFile == null) {
return CommonResult.errorResponse("文件id错误图片不存在");
}
boolean isOk = fileService.removeById(id);
if (isOk) {
FileUtil.del(imgFile.getFilePath());
return CommonResult.successResponse("删除成功!");
} else {
return CommonResult.errorResponse("删除失败!");
}
}
@RequiresPermissions("system_info_admin")
@RequestMapping(value = "/set-web-config", method = RequestMethod.PUT)
public CommonResult setWebConfig(@RequestBody HashMap<String, Object> params) {
boolean result = configService.setWebConfig(params);
if (result){
return CommonResult.successResponse(null,"修改网站前端配置成功!");
}else{
if (result) {
return CommonResult.successResponse(null, "修改网站前端配置成功!");
} else {
return CommonResult.errorResponse("修改失败!");
}
}
@ -103,11 +130,11 @@ public class ConfigController {
@RequiresPermissions("system_info_admin")
@PutMapping("/set-email-config")
public CommonResult setEmailConfig(@RequestBody HashMap<String,Object> params) {
public CommonResult setEmailConfig(@RequestBody HashMap<String, Object> params) {
boolean result = configService.setEmailConfig(params);
if (result) {
return CommonResult.successResponse(null,"修改邮箱配置成功!");
return CommonResult.successResponse(null, "修改邮箱配置成功!");
} else {
return CommonResult.errorResponse("修改失败!");
}
@ -115,40 +142,40 @@ public class ConfigController {
@RequiresPermissions("system_info_admin")
@PostMapping("/test-email")
public CommonResult testEmail(@RequestBody HashMap<String,Object> params) throws MessagingException {
public CommonResult testEmail(@RequestBody HashMap<String, Object> params) throws MessagingException {
String email = (String) params.get("email");
boolean isEmail = Validator.isEmail(email);
if (isEmail){
if (isEmail) {
emailService.testEmail(email);
return CommonResult.successResponse(null,"测试邮件已发送至指定邮箱!");
}else{
return CommonResult.successResponse(null, "测试邮件已发送至指定邮箱!");
} else {
return CommonResult.errorResponse("测试的邮箱不正确!");
}
}
@RequiresPermissions("system_info_admin")
@RequestMapping("/get-db-and-redis-config")
public CommonResult getDBAndRedisConfig(){
public CommonResult getDBAndRedisConfig() {
return CommonResult.successResponse(
MapUtil.builder().put("dbName",configVo.getMysqlDBName())
.put("dbHost", configVo.getMysqlHost())
.put("dbPost", configVo.getMysqlPort())
.put("dbUsername", configVo.getMysqlUsername())
.put("dbPassword", configVo.getMysqlPassword())
.put("redisHost", configVo.getRedisHost())
.put("redisPort", configVo.getRedisPort())
.put("redisPassword", configVo.getRedisPassword())
.map()
MapUtil.builder().put("dbName", configVo.getMysqlDBName())
.put("dbHost", configVo.getMysqlHost())
.put("dbPost", configVo.getMysqlPort())
.put("dbUsername", configVo.getMysqlUsername())
.put("dbPassword", configVo.getMysqlPassword())
.put("redisHost", configVo.getRedisHost())
.put("redisPort", configVo.getRedisPort())
.put("redisPassword", configVo.getRedisPassword())
.map()
);
}
@RequiresPermissions("system_info_admin")
@PutMapping("/set-db-and-redis-config")
public CommonResult setDBAndRedisConfig(@RequestBody HashMap<String,Object> params){
public CommonResult setDBAndRedisConfig(@RequestBody HashMap<String, Object> params) {
boolean result = configService.setDBAndRedisConfig(params);
if (result) {
return CommonResult.successResponse(null,"修改数据库配置成功!");
return CommonResult.successResponse(null, "修改数据库配置成功!");
} else {
return CommonResult.errorResponse("修改失败!");
}

View File

@ -189,6 +189,56 @@ public class FileController {
}
@RequestMapping(value = "/upload-carouse-img", method = RequestMethod.POST)
@RequiresAuthentication
@ResponseBody
@Transactional
@RequiresRoles("root")
public CommonResult uploadCarouselImg(@RequestParam("file") MultipartFile image, HttpServletRequest request) {
if (image == null) {
return CommonResult.errorResponse("上传的图片文件不能为空!");
}
//获取文件后缀
String suffix = image.getOriginalFilename().substring(image.getOriginalFilename().lastIndexOf(".") + 1);
if (!"jpg,jpeg,gif,png,webp,jfif,svg".toUpperCase().contains(suffix.toUpperCase())) {
return CommonResult.errorResponse("请选择jpg,jpeg,gif,png,webp,jfif,svg格式的头像图片");
}
//若不存在该目录则创建目录
FileUtil.mkdir(Constants.File.HOME_CAROUSEL_FOLDER.getPath());
//通过UUID生成唯一文件名
String filename = IdUtil.simpleUUID() + "." + suffix;
try {
//将文件保存指定目录
image.transferTo(FileUtil.file(Constants.File.HOME_CAROUSEL_FOLDER.getPath() + File.separator + filename));
} catch (Exception e) {
log.error("图片文件上传异常-------------->{}", e.getMessage());
return CommonResult.errorResponse("服务器异常:图片上传失败!", CommonResult.STATUS_ERROR);
}
// 获取当前登录用户
HttpSession session = request.getSession();
UserRolesVo userRolesVo = (UserRolesVo) session.getAttribute("userInfo");
// 插入file表记录
top.hcode.hoj.pojo.entity.File imgFile = new top.hcode.hoj.pojo.entity.File();
imgFile.setName(filename).setFolderPath(Constants.File.HOME_CAROUSEL_FOLDER.getPath())
.setFilePath(Constants.File.HOME_CAROUSEL_FOLDER.getPath() + File.separator + filename)
.setSuffix(suffix)
.setType("carousel")
.setUid(userRolesVo.getUid());
fileService.saveOrUpdate(imgFile);
return CommonResult.successResponse(MapUtil.builder()
.put("id", imgFile.getId())
.put("url", Constants.File.IMG_API.getPath() + filename)
.map(), "上传图片成功!");
}
@PostMapping("/upload-testcase-zip")
@ResponseBody
@RequiresRoles(value = {"root", "admin", "problem_admin"}, logical = Logical.OR)
@ -387,12 +437,10 @@ public class FileController {
QueryWrapper<ContestRecord> wrapper = new QueryWrapper<ContestRecord>().eq("cid", cid)
.isNotNull("status")
// 如果已经开启了封榜模式
.notBetween(isOpenSealRank, "submit_time", contest.getSealRankTime(), contest.getEndTime())
.orderByAsc("time");
List<ContestRecord> contestRecordList = contestRecordService.list(wrapper);
Assert.notEmpty(contestRecordList, "比赛暂无排行榜记录!");
List<ACMContestRankVo> acmContestRankVoList = contestRecordService.calcACMRank(contestRecordList);
List<ACMContestRankVo> acmContestRankVoList = contestRecordService.calcACMRank(isOpenSealRank,contest,contestRecordList);
EasyExcel.write(response.getOutputStream())
.head(fileService.getContestRankExcelHead(contestProblemDisplayIDList, true))
.sheet("rank")

View File

@ -589,6 +589,7 @@ public class AccountController {
.setNickname(nickname)
.setSignature((String) params.get("signature"))
.setBlog((String) params.get("blog"))
.setEmail(userRolesVo.getEmail())
.setGithub((String) params.get("github"))
.setSchool((String) params.get("school"))
.setNumber((String) params.get("number"));

View File

@ -451,7 +451,7 @@ public class ContestController {
return commonResult;
}
// 校验该比赛是否开启了封榜模式
// 校验该比赛是否开启了封榜模式超级管理员和比赛创建者可以直接看到实际榜单
boolean isOpenSealRank = contestService.isSealRank(userRolesVo.getUid(), contest, forceRefresh, isRoot);
@ -460,16 +460,13 @@ public class ContestController {
QueryWrapper<ContestRecord> wrapper = new QueryWrapper<ContestRecord>().eq("cid", cid)
.isNotNull("status")
// 如果已经开启了封榜模式
.between(isOpenSealRank, "submit_time", contest.getStartTime(), contest.getSealRankTime())
.between(!isOpenSealRank, "submit_time", contest.getStartTime(), contest.getEndTime())
.ne("username", contest.getAuthor())
.orderByAsc("time");
List<ContestRecord> contestRecordList = contestRecordService.list(wrapper);
// 进行排行榜计算以及排名分页
resultList = contestRecordService.getContestACMRank(contestRecordList, currentPage, limit);
resultList = contestRecordService.getContestACMRank(isOpenSealRank,contest, contestRecordList, currentPage, limit);
} else { //OI比赛以最后一次提交得分作为该题得分

View File

@ -2,6 +2,7 @@ package top.hcode.hoj.controller.oj;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.UnicodeUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
@ -10,11 +11,13 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import top.hcode.hoj.common.result.CommonResult;
import top.hcode.hoj.dao.ContestMapper;
import top.hcode.hoj.pojo.entity.File;
import top.hcode.hoj.pojo.vo.ACMRankVo;
import top.hcode.hoj.pojo.vo.AnnouncementVo;
import top.hcode.hoj.pojo.vo.ConfigVo;
import top.hcode.hoj.pojo.vo.ContestVo;
import top.hcode.hoj.service.impl.AnnouncementServiceImpl;
import top.hcode.hoj.service.impl.FileServiceImpl;
import top.hcode.hoj.service.impl.UserRecordServiceImpl;
import top.hcode.hoj.utils.Constants;
import top.hcode.hoj.utils.RedisUtils;
@ -23,6 +26,7 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Author: Himit_ZH
@ -49,6 +53,9 @@ public class HomeController {
@Autowired
private RedisUtils redisUtils;
@Autowired
private FileServiceImpl fileService;
/**
* @MethodName getRecentContest
* @Params * @param null
@ -65,6 +72,27 @@ public class HomeController {
/**
* @MethodName getHomeCarousel
* @Params
* @Description 获取主页轮播图
* @Return
* @Since 2021/9/4
*/
@GetMapping("/home-carousel")
public CommonResult getHomeCarousel() {
List<File> fileList = fileService.queryCarouselFileList();
List<HashMap<String, Object>> apiList = fileList.stream().map(f -> {
HashMap<String, Object> param = new HashMap<>(2);
param.put("id", f.getId());
param.put("url", Constants.File.IMG_API.getPath() + f.getName());
return param;
}).collect(Collectors.toList());
return CommonResult.successResponse(apiList);
}
/**
* @MethodName getRecentSevenACRank
* @Params * @param null
@ -118,7 +146,6 @@ public class HomeController {
return CommonResult.successResponse(announcementList);
}
/**
* @MethodName getWebConfig
* @Params * @param null

View File

@ -21,4 +21,8 @@ public interface FileMapper extends BaseMapper<File> {
List<File> queryDeleteAvatarList();
@Select("select * from file where (type = 'carousel')")
List<File> queryCarouselFileList();
}

View File

@ -14,7 +14,7 @@
(SELECT COUNT( DISTINCT pid ) FROM user_acproblem WHERE uid =u.uuid
and DATE(gmt_create) >= DATE_SUB(CURDATE(),INTERVAL 7 DAY)) AS ac,
(SELECT COUNT(uid) FROM judge WHERE uid=u.uuid AND cid=0
and DATE(submit_time) >= DATE_SUB(CURDATE(),INTERVAL 7 DAY)) AS total
and DATE(gmt_modified) >= DATE_SUB(CURDATE(),INTERVAL 7 DAY)) AS total
FROM user_info u WHERE u.status = 0
ORDER BY ac DESC,total ASC LIMIT 10
</select>

View File

@ -1,6 +1,7 @@
package top.hcode.hoj.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import top.hcode.hoj.pojo.entity.Contest;
import top.hcode.hoj.pojo.entity.UserInfo;
import top.hcode.hoj.pojo.vo.ACMContestRankVo;
import top.hcode.hoj.pojo.entity.ContestRecord;
@ -22,7 +23,7 @@ public interface ContestRecordService extends IService<ContestRecord> {
IPage<ContestRecord> getACInfo(Integer currentPage, Integer limit, Integer status, Long cid, String contestCreatorId);
IPage<ACMContestRankVo> getContestACMRank(List<ContestRecord> contestRecordList, int currentPage, int limit);
IPage<ACMContestRankVo> getContestACMRank(Boolean isOpenSealRank, Contest contest, List<ContestRecord> contestRecordList, int currentPage, int limit);
IPage<OIContestRankVo> getContestOIRank(Long cid, String contestAuthor, Boolean isOpenSealRank, Date sealTime, Date startTime, Date endTime, int currentPage, int limit);

View File

@ -12,6 +12,8 @@ public interface FileService extends IService<File> {
List<File> queryDeleteAvatarList();
List<File> queryCarouselFileList();
List<List<String>> getContestRankExcelHead(List<String> contestProblemDisplayIDList, Boolean isACM);
List<List<Object>> changeACMContestRankToExcelRowList(List<ACMContestRankVo> acmContestRankVoList, List<String> contestProblemDisplayIDList);

View File

@ -1,10 +1,12 @@
package top.hcode.hoj.service.impl;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Autowired;
import top.hcode.hoj.dao.UserInfoMapper;
import top.hcode.hoj.pojo.entity.Contest;
import top.hcode.hoj.pojo.entity.UserInfo;
import top.hcode.hoj.pojo.vo.ACMContestRankVo;
import top.hcode.hoj.pojo.entity.ContestRecord;
@ -105,10 +107,12 @@ public class ContestRecordServiceImpl extends ServiceImpl<ContestRecordMapper, C
@Override
public IPage<ACMContestRankVo> getContestACMRank(List<ContestRecord> contestRecordList, int currentPage, int limit) {
public IPage<ACMContestRankVo> getContestACMRank(Boolean isOpenSealRank, Contest contest,
List<ContestRecord> contestRecordList,
int currentPage, int limit) {
// 进行排序计算
List<ACMContestRankVo> orderResultList = calcACMRank(contestRecordList);
List<ACMContestRankVo> orderResultList = calcACMRank(isOpenSealRank, contest, contestRecordList);
// 计算好排行榜然后进行分页
Page<ACMContestRankVo> page = new Page<>(currentPage, limit);
int count = orderResultList.size();
@ -159,7 +163,7 @@ public class ContestRecordServiceImpl extends ServiceImpl<ContestRecordMapper, C
}
public List<ACMContestRankVo> calcACMRank(List<ContestRecord> contestRecordList) {
public List<ACMContestRankVo> calcACMRank(boolean isOpenSealRank, Contest contest, List<ContestRecord> contestRecordList) {
List<UserInfo> superAdminList = getSuperAdminList();
@ -203,46 +207,60 @@ public class ContestRecordServiceImpl extends ServiceImpl<ContestRecordMapper, C
HashMap<String, Object> problemSubmissionInfo = ACMContestRankVo.getSubmissionInfo().getOrDefault(contestRecord.getDisplayId(), new HashMap<>());
// 如果该题目已经AC过了那么只记录提交次数其它都不记录了
ACMContestRankVo.setTotal(ACMContestRankVo.getTotal() + 1);
if ((Boolean) problemSubmissionInfo.getOrDefault("isAC", false)) {
continue;
}
// 记录已经按题目提交耗时time升序了
// 如果是当前是开启封榜的时段和同时该提交是处于封榜时段 尝试次数+1
if (isOpenSealRank && isInSealTimeSubmission(contest, contestRecord.getSubmitTime())) {
// 通过的话
if (contestRecord.getStatus().intValue() == Constants.Contest.RECORD_AC.getCode()) {
// 总解决题目次数ac+1
ACMContestRankVo.setAc(ACMContestRankVo.getAc() + 1);
int tryNum = (int) problemSubmissionInfo.getOrDefault("tryNum", 0);
problemSubmissionInfo.put("tryNum", tryNum + 1);
// 判断是不是first AC
boolean isFirstAC = false;
Long time = firstACMap.getOrDefault(contestRecord.getDisplayId(), null);
if (time == null) {
isFirstAC = true;
firstACMap.put(contestRecord.getDisplayId(), contestRecord.getTime());
} else {
// 相同提交时间也是first AC
if (time.longValue() == contestRecord.getTime().longValue()) {
isFirstAC = true;
}
} else {
// 如果该题目已经AC过了其它都不记录了
if ((Boolean) problemSubmissionInfo.getOrDefault("isAC", false)) {
continue;
}
int errorNumber = (int) problemSubmissionInfo.getOrDefault("errorNum", 0);
problemSubmissionInfo.put("isAC", true);
problemSubmissionInfo.put("isFirstAC", isFirstAC);
problemSubmissionInfo.put("ACTime", contestRecord.getTime());
problemSubmissionInfo.put("errorNum", errorNumber);
// 记录已经按题目提交耗时time升序了
// 同时计算总耗时总耗时加上 该题目未AC前的错误次数*20*60+题目AC耗时
ACMContestRankVo.setTotalTime(ACMContestRankVo.getTotalTime() + errorNumber * 20 * 60 + contestRecord.getTime());
// 通过的话
if (contestRecord.getStatus().intValue() == Constants.Contest.RECORD_AC.getCode()) {
// 总解决题目次数ac+1
ACMContestRankVo.setAc(ACMContestRankVo.getAc() + 1);
// 未通过同时需要记录罚时次数
} else if (contestRecord.getStatus().intValue() == Constants.Contest.RECORD_NOT_AC_PENALTY.getCode()) {
// 判断是不是first AC
boolean isFirstAC = false;
Long time = firstACMap.getOrDefault(contestRecord.getDisplayId(), null);
if (time == null) {
isFirstAC = true;
firstACMap.put(contestRecord.getDisplayId(), contestRecord.getTime());
} else {
// 相同提交时间也是first AC
if (time.longValue() == contestRecord.getTime().longValue()) {
isFirstAC = true;
}
}
int errorNumber = (int) problemSubmissionInfo.getOrDefault("errorNum", 0);
problemSubmissionInfo.put("errorNum", errorNumber + 1);
int errorNumber = (int) problemSubmissionInfo.getOrDefault("errorNum", 0);
problemSubmissionInfo.put("isAC", true);
problemSubmissionInfo.put("isFirstAC", isFirstAC);
problemSubmissionInfo.put("ACTime", contestRecord.getTime());
problemSubmissionInfo.put("errorNum", errorNumber);
// 同时计算总耗时总耗时加上 该题目未AC前的错误次数*20*60+题目AC耗时
ACMContestRankVo.setTotalTime(ACMContestRankVo.getTotalTime() + errorNumber * 20 * 60 + contestRecord.getTime());
// 未通过同时需要记录罚时次数
} else if (contestRecord.getStatus().intValue() == Constants.Contest.RECORD_NOT_AC_PENALTY.getCode()) {
int errorNumber = (int) problemSubmissionInfo.getOrDefault("errorNum", 0);
problemSubmissionInfo.put("errorNum", errorNumber + 1);
}else{
int errorNumber = (int) problemSubmissionInfo.getOrDefault("errorNum", 0);
problemSubmissionInfo.put("errorNum", errorNumber);
}
}
ACMContestRankVo.getSubmissionInfo().put(contestRecord.getDisplayId(), problemSubmissionInfo);
}
@ -341,4 +359,9 @@ public class ContestRecordServiceImpl extends ServiceImpl<ContestRecordMapper, C
.collect(Collectors.toList());
return orderResultList;
}
private boolean isInSealTimeSubmission(Contest contest, Date submissionDate) {
return DateUtil.isIn(submissionDate, contest.getSealRankTime(), contest.getEndTime());
}
}

View File

@ -169,7 +169,15 @@ public class EmailServiceImpl implements EmailService {
context.setVariable(Constants.Email.OJ_SHORT_NAME.name(), ojShortName.toUpperCase());
context.setVariable(Constants.Email.OJ_URL.name(), ojAddr);
context.setVariable(Constants.Email.EMAIL_BACKGROUND_IMG.name(), ojEmailBg);
context.setVariable("RESET_URL", Constants.Email.OJ_URL.getValue() + "/reset-password?username=" + username + "&code=" + code);
String resetUrl;
if (ojAddr.endsWith("/")) {
resetUrl = ojAddr + "reset-password?username=" + username + "&code=" + code;
} else {
resetUrl = ojAddr + "/reset-password?username=" + username + "&code=" + code;
}
context.setVariable("RESET_URL", resetUrl);
context.setVariable("EXPIRE_TIME", expireTime.toString());
context.setVariable("USERNAME", username);

View File

@ -33,6 +33,11 @@ public class FileServiceImpl extends ServiceImpl<FileMapper, File> implements Fi
return fileMapper.queryDeleteAvatarList();
}
@Override
public List<File> queryCarouselFileList(){
return fileMapper.queryCarouselFileList();
}
@Override
public List<List<String>> getContestRankExcelHead(List<String> contestProblemDisplayIDList, Boolean isACM) {
List<List<String>> headList = new LinkedList<>();

View File

@ -10,6 +10,8 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@ -23,6 +25,7 @@ import top.hcode.hoj.utils.Constants;
import top.hcode.hoj.utils.JsoupUtils;
import top.hcode.hoj.utils.RedisUtils;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
@ -51,7 +54,6 @@ import java.util.concurrent.TimeUnit;
* "C" 代表Calendar的意思它的意思是计划所关联的日期如果日期没有被关联则相当于日历中所有日期
*/
@Service
@Async
@Slf4j(topic = "hoj")
public class ScheduleServiceImpl implements ScheduleService {
@ -203,14 +205,8 @@ public class ScheduleServiceImpl implements ScheduleService {
// 格式化api
String ratingAPI = String.format(codeforcesUserInfoAPI, cfUsername);
try {
// 防止cf的频率限制
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 连接api获取json格式对象
JSONObject resultObject = JsoupUtils.getJsonFromConnection(JsoupUtils.getConnectionFromUrl(ratingAPI, null, null));
JSONObject resultObject = getCFUserInfo(ratingAPI);
// 获取状态码
String status = resultObject.getStr("status");
// 如果查无此用户则跳过
@ -236,4 +232,11 @@ public class ScheduleServiceImpl implements ScheduleService {
log.info("获取Codeforces Rating成功");
}
@Retryable(value = Exception.class,
maxAttempts = 5,
backoff = @Backoff(delay = 500))
public JSONObject getCFUserInfo(String url) throws Exception {
return JsoupUtils.getJsonFromConnection(JsoupUtils.getConnectionFromUrl(url, null, null));
}
}

View File

@ -153,6 +153,8 @@ public class Constants {
USER_AVATAR_FOLDER("/hoj/file/avatar"),
HOME_CAROUSEL_FOLDER("/hoj/file/carousel"),
MARKDOWN_FILE_FOLDER("/hoj/file/md"),
IMG_API("/api/public/img/"),

View File

@ -163,7 +163,7 @@ public class HduJudge implements RemoteJudgeStrategy {
// TODO 添加结果对应的状态
private static final Map<String, Constants.Judge> statusTypeMap = new HashMap<String, Constants.Judge>() {
{
put("Submitted", Constants.Judge.STATUS_SUBMITTING);
put("Submitted", Constants.Judge.STATUS_PENDING);
put("Accepted", Constants.Judge.STATUS_ACCEPTED);
put("Wrong Answer", Constants.Judge.STATUS_WRONG_ANSWER);
put("Compilation Error", Constants.Judge.STATUS_COMPILE_ERROR);
@ -171,6 +171,7 @@ public class HduJudge implements RemoteJudgeStrategy {
put("Running", Constants.Judge.STATUS_JUDGING);
put("Compiling", Constants.Judge.STATUS_COMPILING);
put("Time Limit Exceeded", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED);
put("Memory Limit Exceeded", Constants.Judge.STATUS_MEMORY_LIMIT_EXCEEDED);
put("Presentation Error", Constants.Judge.STATUS_PRESENTATION_ERROR);
}
};

View File

@ -1,9 +1,6 @@
package top.hcode.hoj.pojo.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ -21,6 +18,7 @@ import java.util.Date;
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="File对象", description="")
@TableName("`file`")
public class File {
private static final long serialVersionUID = 1L;
@ -31,6 +29,7 @@ public class File {
private String uid;
@ApiModelProperty(value = "文件所属类型例如avatar")
@TableField("`type`")
private String type;
@ApiModelProperty(value = "文件名")
@ -46,6 +45,7 @@ public class File {
private String filePath;
@ApiModelProperty(value = "是否删除")
@TableField("`delete`")
private Boolean delete;
@ApiModelProperty(value = "创建时间")

View File

@ -254,6 +254,10 @@ a:hover {
background-color: #a9f5af;
color: #3c763d;
}
.try {
background-color: #ff9800;
color: #fff;
}
.oi-0,
.wa {

View File

@ -118,6 +118,10 @@ const ojApi = {
return ajax('/api/get-website-config', 'get', {
})
},
getHomeCarousel(){
return ajax('/api/home-carousel', 'get', {
})
},
getRecentContests(){
return ajax('/api/get-recent-contest', 'get', {
})
@ -654,6 +658,7 @@ const adminApi = {
data
})
},
// 系统配置
admin_getSMTPConfig () {
return ajax('/api/admin/config/get-email-config', 'get')
@ -663,6 +668,15 @@ const adminApi = {
data
})
},
admin_deleteHomeCarousel(id){
return ajax('/api/admin/config/home-carousel', 'delete', {
params:{
id
}
})
},
admin_testSMTPConfig (email) {
return ajax('/api/admin/config/test-email', 'post', {
data: {

View File

@ -111,7 +111,7 @@ export default {
},
countDown() {
let i = this.time;
this.resetText = i + 's, ' + this.$i18n.t('m.Watting_Can_Resend_Email');
this.resetText = i + 's, ' + this.$i18n.t('m.Waiting_Can_Resend_Email');
if (i == 0) {
this.btnResetPwdDisabled = false;
this.resetText = this.$i18n.t('m.Send_Password_Reset_Email');

View File

@ -114,6 +114,7 @@ export const m = {
Project_Url:'Project Url',
Web_Desc: 'Web Desc',
Allow_Register: 'Allow Register',
Home_Rotation_Chart:'Home Rotation Chart',
SMTP_Config: 'SMTP Config',
Email_BG:'BG IMG',
Email_BG_Desc:'SMTP Template Background IMG Address',

View File

@ -114,6 +114,7 @@ export const m = {
Project_Url:'项目地址',
Web_Desc: '网站简介',
Allow_Register: '是否允许注册',
Home_Rotation_Chart:'首页轮播图',
SMTP_Config: 'SMTP 设置',
Email_BG:'邮件背景',
Email_BG_Desc:'请输入邮件背景图的URL链接',

View File

@ -100,6 +100,70 @@
>
</el-card>
<el-card style="margin-top:15px">
<div slot="header">
<span class="panel-title home-title">{{
$t('m.Home_Rotation_Chart')
}}</span>
</div>
<ul class="el-upload-list el-upload-list--picture-card">
<li
tabindex="0"
class="el-upload-list__item is-ready"
v-for="(img, index) in carouselImgList"
:key="index"
>
<div>
<img
:src="img.url"
alt="load faild"
style="height:146px;width:146x"
class="el-upload-list__item-thumbnail"
/><span class="el-upload-list__item-actions">
<span
class="el-upload-list__item-preview"
@click="handlePictureCardPreview(img)"
>
<i class="el-icon-zoom-in"></i>
</span>
<span
v-if="!disabled"
class="el-upload-list__item-delete"
@click="handleDownload(img)"
>
<i class="el-icon-download"></i>
</span>
<span
v-if="!disabled"
class="el-upload-list__item-delete"
@click="handleRemove(img, index)"
>
<i class="el-icon-delete"></i>
</span>
</span>
</div>
</li>
</ul>
<el-upload
action="/api/file/upload-carouse-img"
list-type="picture-card"
accept="image/gif,image/jpeg,image/jpg,image/png,image/svg,image/jfif,image/webp"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
style="display: inline;"
>
<i class="el-icon-plus"></i>
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="dialogImageUrl" alt="" />
</el-dialog>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="dialogImageUrl" alt="" />
</el-dialog>
</el-card>
<el-card style="margin-top:15px">
<div slot="header">
<span class="panel-title home-title">{{ $t('m.SMTP_Config') }}</span>
@ -279,6 +343,7 @@
<script>
import api from '@/common/api';
import myMessage from '@/common/message';
import utils from '@/common/utils';
export default {
name: 'SystemConfig',
data() {
@ -296,6 +361,10 @@ export default {
},
websiteConfig: {},
databaseConfig: {},
dialogImageUrl: '',
dialogVisible: false,
disabled: false,
carouselImgList: [],
};
},
mounted() {
@ -307,6 +376,11 @@ export default {
myMessage.warning('No STMP Config');
}
});
api.getHomeCarousel().then((res) => {
this.carouselImgList = res.data.data;
});
api
.admin_getWebsiteConfig()
.then((res) => {
@ -321,6 +395,25 @@ export default {
.catch(() => {});
},
methods: {
handleRemove(file, index = undefined) {
let id = file.id;
if (file.response != null) {
id = file.response.data.id;
}
api.admin_deleteHomeCarousel(id).then((res) => {
myMessage.success(this.$i18n.t('m.Delete_successfully'));
if (index != undefined) {
this.carouselImgList.splice(index, 1);
}
});
},
handlePictureCardPreview(file) {
this.dialogImageUrl = file.url;
this.dialogVisible = true;
},
handleDownload(file) {
utils.downloadFile(file.url);
},
saveSMTPConfig() {
api.admin_editSMTPConfig(this.smtp).then(
(res) => {

View File

@ -83,8 +83,8 @@
arrow="always"
indicator-position="outside"
>
<el-carousel-item v-for="item in srcList" :key="item">
<el-image :src="item" fit="fill"></el-image>
<el-carousel-item v-for="item in carouselImgList" :key="item">
<el-image :src="item.url" fit="fill"></el-image>
</el-carousel-item>
</el-carousel>
</el-card>
@ -224,7 +224,7 @@ export default {
},
data() {
return {
interval: 6000,
interval: 4000,
otherContests: [],
recentUserACRecord: [],
CONTEST_STATUS_REVERSE: {},
@ -234,9 +234,13 @@ export default {
recent7ACRankLoading: false,
recentOtherContestsLoading: false,
},
srcList: [
'https://cdn.jsdelivr.net/gh/HimitZH/CDN/images/home1.jfif',
'https://cdn.jsdelivr.net/gh/HimitZH/CDN/images/home2.jpeg',
carouselImgList: [
{
url: 'https://cdn.jsdelivr.net/gh/HimitZH/CDN/images/home1.jfif',
},
{
url: 'https://cdn.jsdelivr.net/gh/HimitZH/CDN/images/home2.jpeg',
},
],
srcHight: '440px',
};
@ -249,11 +253,20 @@ export default {
this.srcHight = '440px';
}
this.CONTEST_STATUS_REVERSE = Object.assign({}, CONTEST_STATUS_REVERSE);
this.getHomeCarousel();
this.getRecentContests();
this.getRecent7ACRank();
this.getRecentOtherContests();
},
methods: {
getHomeCarousel() {
api.getHomeCarousel().then((res) => {
if (res.data.data != null && res.data.data.length > 0) {
this.carouselImgList = res.data.data;
}
});
},
getRecentContests() {
api.getRecentContests().then((res) => {
this.contests = res.data.data;

View File

@ -77,7 +77,6 @@
</vxe-table-column>
<vxe-table-column
field="realname"
fixed="left"
min-width="96"
:title="$t('m.RealName')"
v-if="isContestAdmin"
@ -128,9 +127,32 @@
<span v-if="row.submissionInfo[problem.displayId].isAC"
>{{ row.submissionInfo[problem.displayId].ACTime }}<br
/></span>
<span v-if="row.submissionInfo[problem.displayId].errorNum != 0"
>(-{{ row.submissionInfo[problem.displayId].errorNum }})</span
<span
v-if="
row.submissionInfo[problem.displayId].tryNum == null &&
row.submissionInfo[problem.displayId].errorNum != 0
"
>
(-{{
row.submissionInfo[problem.displayId].errorNum > 0
? row.submissionInfo[problem.displayId].errorNum
: 0
}})
</span>
<span v-if="row.submissionInfo[problem.displayId].tryNum != null"
><template
v-if="row.submissionInfo[problem.displayId].errorNum > 0"
>
(-{{
row.submissionInfo[problem.displayId].errorNum
}})+</template
>({{ row.submissionInfo[problem.displayId].tryNum
}}{{
row.submissionInfo[problem.displayId].tryNum > 1
? ' tries'
: ' try'
}})
</span>
</span>
</template>
</vxe-table-column>
@ -301,6 +323,8 @@ export default {
cellClass[problemID] = 'first-ac';
} else if (status.isAC) {
cellClass[problemID] = 'ac';
} else if (status.tryNum != null && status.tryNum > 0) {
cellClass[problemID] = 'try';
} else if (status.errorNum != 0) {
cellClass[problemID] = 'wa';
}