diff --git a/src/main/java/io/v/nutz/MainLauncher.java b/src/main/java/io/v/nutz/MainLauncher.java index a22354f..091737a 100644 --- a/src/main/java/io/v/nutz/MainLauncher.java +++ b/src/main/java/io/v/nutz/MainLauncher.java @@ -84,8 +84,7 @@ public class MainLauncher { } public void init() { - Mvcs.X_POWERED_BY = "BudWK-V5 "; - + Mvcs.DISABLE_X_POWERED_BY = true; Globals.AppBase = Mvcs.getServletContext().getContextPath(); Globals.AppRoot = Mvcs.getServletContext().getRealPath("/"); //注册自定义标签 diff --git a/src/main/java/io/v/nutz/member/controller/MemberCommonController.java b/src/main/java/io/v/nutz/member/controller/MemberCommonController.java index a6afb69..79afe26 100644 --- a/src/main/java/io/v/nutz/member/controller/MemberCommonController.java +++ b/src/main/java/io/v/nutz/member/controller/MemberCommonController.java @@ -2,6 +2,7 @@ package io.v.nutz.member.controller; import cn.wizzer.framework.base.Result; import io.v.nutz.annontation.ViReturn; +import io.v.nutz.base.utils.Vi; import io.v.nutz.member.UserMode; import io.v.nutz.member.model.MemberApplyRecord; import io.v.nutz.member.model.MemberChangeRecord; @@ -9,6 +10,7 @@ import io.v.nutz.member.service.MemberService; import io.v.nutz.service.ViService; import io.v.nutz.sys.models.Sys_user; import io.v.nutz.sys.services.SysUserService; +import io.v.nutz.web.commons.utils.ShiroUtil; import org.apache.shiro.authz.annotation.RequiresAuthentication; import org.nutz.dao.Cnd; import org.nutz.dao.Sqls; @@ -59,6 +61,9 @@ public class MemberCommonController { @ViReturn @Ok("json:{locked:'password|salt',ignoreNull:false}") public Object userInfo(String userId, String source) { + if (!ShiroUtil.hasAnyRoles("sysadmin,A06")){ + userId= ShiroUtil.getUserId(); + } Sql sql = Sqls.create(""" SELECT u.*,unit.`name` unitname,un.unionname,unit.unionid diff --git a/src/main/java/io/v/nutz/member/controller/info/MemberGroupInfoController.java b/src/main/java/io/v/nutz/member/controller/info/MemberGroupInfoController.java index 9558101..44d0a1e 100644 --- a/src/main/java/io/v/nutz/member/controller/info/MemberGroupInfoController.java +++ b/src/main/java/io/v/nutz/member/controller/info/MemberGroupInfoController.java @@ -47,7 +47,6 @@ import java.util.List; @At("/platform/member/group/info") @Ok("json:full") @IocBean -@RequiresAuthentication public class MemberGroupInfoController { @At("") @Ok("beetl:/platform/member/info/GroupInfo.html") @@ -111,11 +110,13 @@ public class MemberGroupInfoController { } @At + @RequiresPermissions("member.group.person") public Integer getMemberHistoryNum(Integer year) { return memberHistoryService.count(Cnd.where("year", "=", year)); } @At + @RequiresPermissions("member.group.person") public Object archive(Integer year) { try { @@ -139,12 +140,14 @@ public class MemberGroupInfoController { @At @ViReturn + @RequiresPermissions("member.group.person") public Object pageData(PageForm pageForm, String unionId, String unitId, String[] personTypes, String[] userStates, String campus, String sex, Integer memberStatus, String position, String jobTitle) { return memberService.list(pageForm, dataSql(pageForm, unionId, unitId, personTypes, userStates, campus, sex, memberStatus, position, jobTitle)); } @At @ViReturn + @RequiresPermissions("member.group.person") public Object delete(String userId) { UserMode.removeMemberRole(userId); return null; @@ -152,7 +155,7 @@ public class MemberGroupInfoController { @At @Ok("void") - @RequiresAuthentication + @RequiresPermissions("member.group.person") public void export(HttpServletResponse response, String props, PageForm pageForm, String unionId, String unitId, String personTypes, String userStates, String campus, String sex, Integer memberStatus, String position, String jobTitle) throws IOException { ViTool.excelResponse(response, "会员名单.xls"); diff --git a/src/main/java/io/v/nutz/member/controller/inquire/MemberInquireIntegrateController.java b/src/main/java/io/v/nutz/member/controller/inquire/MemberInquireIntegrateController.java index 799a544..960e63d 100644 --- a/src/main/java/io/v/nutz/member/controller/inquire/MemberInquireIntegrateController.java +++ b/src/main/java/io/v/nutz/member/controller/inquire/MemberInquireIntegrateController.java @@ -45,7 +45,6 @@ import java.util.Map; @At("/platform/member/inquire/integrate") @Ok("json:full") @IocBean -@RequiresAuthentication public class MemberInquireIntegrateController { @At("") @@ -65,6 +64,7 @@ public class MemberInquireIntegrateController { @At @ViReturn + @RequiresPermissions("member.inquire.Integrate") public Object pageData(PageForm pageForm, Integer year, String unionId, String unitId, String[] personTypes, String[] userStates, String[] memberStatus, String campus) { int yyyy = Calendar.getInstance().get(Calendar.YEAR); @@ -328,6 +328,7 @@ public class MemberInquireIntegrateController { @At @Ok("void") + @RequiresPermissions("member.inquire.Integrate") public void doExport(HttpServletResponse response, Integer year, String unionId, String unitId, String memberStatus, String personTypes, String userStates) { try { int yyyy = Calendar.getInstance().get(Calendar.YEAR); @@ -385,6 +386,7 @@ public class MemberInquireIntegrateController { @At @Ok("void") + @RequiresPermissions("member.inquire.Integrate") public void doExportByUnion(HttpServletResponse response, Integer year, String unionId, String unitId, String memberStatus, String personTypes, String userStates, String props) { try { int yyyy = Calendar.getInstance().get(Calendar.YEAR); @@ -465,5 +467,4 @@ public class MemberInquireIntegrateController { } } - } diff --git a/src/main/java/io/v/nutz/question/controller/QuestionH5Controller.java b/src/main/java/io/v/nutz/question/controller/QuestionH5Controller.java index 2539f6e..7afb27f 100644 --- a/src/main/java/io/v/nutz/question/controller/QuestionH5Controller.java +++ b/src/main/java/io/v/nutz/question/controller/QuestionH5Controller.java @@ -1,5 +1,7 @@ package io.v.nutz.question.controller; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.collection.ListUtil; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjectUtil; @@ -31,6 +33,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * 问卷H5 @@ -286,6 +289,12 @@ public class QuestionH5Controller { //判断是否将题目加进去了 Sql issueSql = Sqls.create(" SELECT iid FROM `question_issue` $condition"); + issueSql = Sqls.create(""" + SELECT iid FROM ( + SELECT * FROM `question_issue` $condition + ) AS subquery ORDER BY ipxbh ASC; + """); + Cnd commonIssueCnd = Cnd.NEW(); commonIssueCnd.and("iqid", "=", id); @@ -305,12 +314,17 @@ public class QuestionH5Controller { QuestionRandomIssue maxQuestionRandomIssue = randomIssues.stream().filter(randomIssue -> randomIssue.getNum() == finalMaxAnswer).findFirst().orElse(null); if (randomIssues.size() < ranDomSum && Lang.isNotEmpty(maxQuestionRandomIssue) && maxQuestionRandomIssue.getIsFinish()) { List allIssueIds = randomIssues.stream().flatMap(randomIssue -> randomIssue.getIssueIds().stream()).collect(Collectors.toList()); - commonIssueCnd.andEX("iid", "not in", allIssueIds); + //每次都从20题里面去找 +// commonIssueCnd.andEX("iid", "not in", allIssueIds); }else{ needFor = false; } } + //固定3题 + List fixedIssueIds = new java.util.ArrayList<>(List.of("52e2a5c9d54e4a3ab3e60443b1c961aa", "140388aa1a10445bbcd9e6356c28ef2b", "5f04a8e4f43d46378f012a573001bea9","c81599b8bf5e424bba619de39c5860ca","f701af5766244d788513439b9379f3f0")); + commonIssueCnd.and("iid","not in",fixedIssueIds); + if (ObjectUtil.isAllNotEmpty(ranDomSum, ranDomSingleIssueNum)) { commonIssueCnd.and(new Static("1=1 order by rand() limit " + ranDomSingleIssueNum)); } @@ -320,48 +334,31 @@ public class QuestionH5Controller { issueSql.setCallback(Sqls.callback.strList()); dao.execute(issueSql); List randomIssueIds = (List) issueSql.getResult(); - Collections.shuffle(randomIssueIds); +// Collections.shuffle(randomIssueIds); + + List allIssueIds = Stream.concat(fixedIssueIds.stream(), randomIssueIds.stream()).collect(Collectors.toList()); + QuestionRandomIssue questionRandomIssue = new QuestionRandomIssue(); questionRandomIssue.setQuestionId(id); questionRandomIssue.setUserId(ShiroUtil.getUserId()); - questionRandomIssue.setIssueIds(randomIssueIds); + questionRandomIssue.setIssueIds(allIssueIds); questionRandomIssue.setNum(maxAnswer + 1); questionRandomIssue.setIsFinish(false); - dao.insert(questionRandomIssue); - issueCnd.and("iid", "in", randomIssueIds); + if(maxAnswer==0){ + dao.insert(questionRandomIssue); + } + issueCnd.and("iid", "in", allIssueIds); }else{ // List allIssueIds = randomIssues.stream().flatMap(randomIssue -> randomIssue.getIssueIds().stream()).collect(Collectors.toList()); int finalMaxAnswer1 = maxAnswer; List allIssueIds = randomIssues.stream().filter(randomIssue -> randomIssue.getNum() == finalMaxAnswer1).flatMap(randomIssue -> randomIssue.getIssueIds().stream()).collect(Collectors.toList()); issueCnd.and("iid", "in", allIssueIds); } - -// if (Lang.isEmpty(randomIssue)) { -// -// Sql issueSql = Sqls.create(" SELECT iid FROM `question_issue` where iqid = @qid $randLimit"); -// issueSql.setParam("qid", id); -// if (ranDomSingleIssueNum != null && ranDomSingleIssueNum > 0) { -// issueSql.setVar("randLimit", " order by rand() limit " + ranDomSingleIssueNum); -// } -// -// issueSql.setCallback(Sqls.callback.strList()); -// dao.execute(issueSql); -// List randomIssueIds = (List) issueSql.getResult(); -// Collections.shuffle(randomIssueIds); -// QuestionRandomIssue questionRandomIssue = new QuestionRandomIssue(); -// questionRandomIssue.setQuestionId(id); -// questionRandomIssue.setUserId(ShiroUtil.getUserId()); -// questionRandomIssue.setIssueIds(randomIssueIds); -// questionRandomIssue.setNum(null); -// questionRandomIssue.setIsFinish(false); -// dao.insert(questionRandomIssue); -// issueCnd.and("iid", "in", randomIssueIds); -// } else { -// issueCnd.and("iid", "in", randomIssue.getIssueIds()); -// } } -// issueCnd.asc("ipxbh"); +// issueCnd.and(new Static("1=1 ORDER BY FIELD(iid,'52e2a5c9d54e4a3ab3e60443b1c961aa', '140388aa1a10445bbcd9e6356c28ef2b', '5f04a8e4f43d46378f012a573001bea9', 'c81599b8bf5e424bba619de39c5860ca', 'f701af5766244d788513439b9379f3f0') DESC")); + + issueCnd.desc("ipxbh"); questionService.fetchLinks(question, "issues", issueCnd); for (Question_issue issue : question.getIssues()) { questionService.fetchLinks(issue, "wors", Cnd.NEW().asc("wpxbh")); @@ -435,6 +432,80 @@ public class QuestionH5Controller { return Result.success(); } + @At + public Result answerAgainCommon(String id){ + Question question = questionService.fetch(id); + Cnd issueCnd = Cnd.NEW(); + Sql issueSql = Sqls.create(" SELECT iid FROM `question_issue` $condition"); + issueSql = Sqls.create(""" + SELECT * FROM ( + SELECT * FROM `question_issue` $condition + ) AS subquery ORDER BY ipxbh ASC; + """); + + Cnd commonIssueCnd = Cnd.NEW(); + commonIssueCnd.and("iqid", "=", id); + + //判断是否要随机 + Integer ranDomSum = question.getRanDomSum(); + Integer ranDomSingleIssueNum = question.getRanDomSingleIssueNum(); + + //已经回答最大次数 + int maxAnswer = 0; + + boolean needFor = true; + + List randomIssues = dao.query(QuestionRandomIssue.class, Cnd.where("questionId", "=", question.getQid()).and("userId", "=", ShiroUtil.getUserId())); + if (Lang.isNotEmpty(randomIssues)) { + maxAnswer = randomIssues.stream().mapToInt(QuestionRandomIssue::getNum).max().orElse(0); + int finalMaxAnswer = maxAnswer; + QuestionRandomIssue maxQuestionRandomIssue = randomIssues.stream().filter(randomIssue -> randomIssue.getNum() == finalMaxAnswer).findFirst().orElse(null); + if (randomIssues.size() < ranDomSum && Lang.isNotEmpty(maxQuestionRandomIssue) && maxQuestionRandomIssue.getIsFinish()) { + List allIssueIds = randomIssues.stream().flatMap(randomIssue -> randomIssue.getIssueIds().stream()).collect(Collectors.toList()); + //每次都从20题里面去找 +// commonIssueCnd.andEX("iid", "not in", allIssueIds); + }else{ + needFor = false; + } + } + + if (ObjectUtil.isAllNotEmpty(ranDomSum, ranDomSingleIssueNum)) { + commonIssueCnd.and(new Static("1=1 order by rand() limit " + ranDomSingleIssueNum)); + } + + if(needFor){ + issueSql.setCondition(commonIssueCnd); + issueSql.setCallback(Sqls.callback.strList()); + dao.execute(issueSql); + List randomIssueIds = (List) issueSql.getResult(); + Collections.shuffle(randomIssueIds); + QuestionRandomIssue questionRandomIssue = new QuestionRandomIssue(); + questionRandomIssue.setQuestionId(id); + questionRandomIssue.setUserId(ShiroUtil.getUserId()); + questionRandomIssue.setIssueIds(randomIssueIds); + questionRandomIssue.setNum(maxAnswer + 1); + questionRandomIssue.setIsFinish(false); + dao.insert(questionRandomIssue); + issueCnd.and("iid", "in", randomIssueIds); + }else{ +// List allIssueIds = randomIssues.stream().flatMap(randomIssue -> randomIssue.getIssueIds().stream()).collect(Collectors.toList()); + int finalMaxAnswer1 = maxAnswer; + List allIssueIds = randomIssues.stream().filter(randomIssue -> randomIssue.getNum() == finalMaxAnswer1).flatMap(randomIssue -> randomIssue.getIssueIds().stream()).collect(Collectors.toList()); + issueCnd.and("iid", "in", allIssueIds); + } + questionService.fetchLinks(question, "issues", issueCnd); + for (Question_issue issue : question.getIssues()) { + questionService.fetchLinks(issue, "wors", Cnd.NEW().asc("wpxbh")); + } + + if (question.getDisruptingOrder()) { + Collections.shuffle(question.getIssues()); + } + + return Result.success(question); + } + + @At("/winInfo") public Object winInfo(String questionId) { // List winners = dao.query(QuestionWinner.class, Cnd.where("questionId", "=", questionId)); @@ -581,12 +652,26 @@ public class QuestionH5Controller { } else if (question.getCtMode() == 3) { QuestionUserScore userScore = dao.fetch(QuestionUserScore.class, Cnd.where("questionId", "=", questionId) .and("userId", "=", ShiroUtil.getUserId()) - .desc(question.getCalcScoreMode() == 1 ? "score" : "answerNum")); +// .desc(question.getCalcScoreMode() == 1 ? "score" : "answerNum")); + .desc("answerNum")); return Result.success().addData(Optional.ofNullable(userScore).map(v -> v.getScore()).orElse(0)); } return Result.success(); } + /** + * 仅限常规模式 + * @param questionId + * @return + */ + @At + @RequiresAuthentication + public Result historyScore(String questionId){ + List scores = dao.query(QuestionUserScore.class, Cnd.where("questionId", "=", questionId) + .and("userId","=",ShiroUtil.getUserId()).asc("answerNum")); + return Result.success(scores); + } + /** * 获取用户已回答的轮数 针对单天随机 * diff --git a/src/main/java/io/v/nutz/trainEducation/controller/statistics/TrainEduStatisticsVideoController.java b/src/main/java/io/v/nutz/trainEducation/controller/statistics/TrainEduStatisticsVideoController.java index 1abbe79..cd2fe2b 100644 --- a/src/main/java/io/v/nutz/trainEducation/controller/statistics/TrainEduStatisticsVideoController.java +++ b/src/main/java/io/v/nutz/trainEducation/controller/statistics/TrainEduStatisticsVideoController.java @@ -97,12 +97,44 @@ public class TrainEduStatisticsVideoController { LEFT JOIN `user` u ON u.id = tevwr.userId $condition """); + sql = Sqls.create(""" + SELECT + u.username, + u.loginname, + u.unitname, + u.unionname, + tev.qualifiedNum, + SUM(CASE + WHEN sub.cnt > tev.everyDayMaxWatchNum THEN tev.everyDayMaxWatchNum + ELSE sub.cnt + END) AS watchCount + FROM + `train_edu_video_watch_record` tevwr + LEFT JOIN train_edu_video tev ON tev.id = tevwr.videoId + LEFT JOIN train_edu_album tea ON tea.id = tevwr.albumId + LEFT JOIN `user` u ON u.id = tevwr.userId + LEFT JOIN ( + SELECT + userId, + albumId, + watchTime, + COUNT(*) AS cnt + FROM + `train_edu_video_watch_record` + WHERE + albumId = @albumId + GROUP BY + userId, + watchTime + ) AS sub ON tevwr.userId = sub.userId AND tevwr.albumId = sub.albumId AND tevwr.watchTime = sub.watchTime + $condition + """).setParam("albumId", albumId); Cnd cnd = Cnd.NEW(); cnd.and("tevwr.albumId", "=", albumId); cnd.and(new Static("IF(tea.createType = 1, u.unionId = tea.unionId , 1=1)")); cnd.and(new Static("tevwr.watchTime >= tev.startDate")); cnd.and(new Static("tevwr.watchTime <= tev.endDate")); - cnd.groupBy("tevwr.userId"); + cnd.groupBy("u.username,u.loginname,u.unitname,u.unionname,tev.qualifiedNum"); sql.setCondition(cnd); Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); return pagination; @@ -152,57 +184,70 @@ public class TrainEduStatisticsVideoController { @At @Ok("void") @RequiresPermissions("trainEdu.statistics.video") - public void exportXlsx(String albumId,HttpServletResponse response){ + public void exportXlsx(String albumId, HttpServletResponse response) { Sql sql = Sqls.create(""" SELECT - u.username, - u.loginname, - u.unitname, - u.unionname, - tev.qualifiedNum, - ( - SELECT - SUM(IF( cnt > tev.everyDayMaxWatchNum, tev.everyDayMaxWatchNum, cnt )) AS count - FROM - ( SELECT watchTime, COUNT( 1 ) AS cnt FROM train_edu_video_watch_record where userId = tevwr.userId and albumId = tevwr.albumId GROUP BY watchTime ) AS subquery - ) as watchCount - + u.username, + u.loginname, + u.unitname, + u.unionname, + tev.qualifiedNum, + SUM(CASE + WHEN sub.cnt > tev.everyDayMaxWatchNum THEN tev.everyDayMaxWatchNum + ELSE sub.cnt + END) AS watchCount FROM - `train_edu_video_watch_record` tevwr - LEFT JOIN train_edu_video tev ON tev.id = tevwr.videoId - LEFT JOIN train_edu_album tea on tea.id = tevwr.albumId - LEFT JOIN `user` u ON u.id = tevwr.userId + `train_edu_video_watch_record` tevwr + LEFT JOIN train_edu_video tev ON tev.id = tevwr.videoId + LEFT JOIN train_edu_album tea ON tea.id = tevwr.albumId + LEFT JOIN `user` u ON u.id = tevwr.userId + LEFT JOIN ( + SELECT + userId, + albumId, + watchTime, + COUNT(*) AS cnt + FROM + `train_edu_video_watch_record` + WHERE + albumId = @albumId + GROUP BY + userId, + watchTime + ) AS sub ON tevwr.userId = sub.userId AND tevwr.albumId = sub.albumId AND tevwr.watchTime = sub.watchTime $condition - """); + """).setParam("albumId", albumId); Cnd cnd = Cnd.NEW(); cnd.and("tevwr.albumId", "=", albumId); cnd.and(new Static("IF(tea.createType = 1, u.unionId = tea.unionId , 1=1)")); cnd.and(new Static("tevwr.watchTime >= tev.startDate")); cnd.and(new Static("tevwr.watchTime <= tev.endDate")); - cnd.groupBy("tevwr.userId"); + cnd.groupBy("u.username,u.loginname,u.unitname,u.unionname,tev.qualifiedNum"); sql.setCondition(cnd); List list = baseService.listMap(sql); List excelExportEntities = new ArrayList<>(); - excelExportEntities.add(new ExcelExportEntity("序号","no",10)); - excelExportEntities.add(new ExcelExportEntity("姓名","username",20)); - excelExportEntities.add(new ExcelExportEntity("工号","loginname",20)); - excelExportEntities.add(new ExcelExportEntity("单位","unitname",20)); - excelExportEntities.add(new ExcelExportEntity("分工会","unionname",20)); - excelExportEntities.add(new ExcelExportEntity("学习总次数","watchCount",20)); - excelExportEntities.add(new ExcelExportEntity("是否达标","qualifiedNum",20)); + excelExportEntities.add(new ExcelExportEntity("序号", "no", 10)); + excelExportEntities.add(new ExcelExportEntity("姓名", "username", 20)); + excelExportEntities.add(new ExcelExportEntity("工号", "loginname", 20)); + excelExportEntities.add(new ExcelExportEntity("单位", "unitname", 20)); + excelExportEntities.add(new ExcelExportEntity("分工会", "unionname", 20)); + excelExportEntities.add(new ExcelExportEntity("学习总次数", "watchCount", 20)); + excelExportEntities.add(new ExcelExportEntity("是否达标", "qualified", 20)); for (int i = 0; i < list.size(); i++) { - list.get(i).put("no",i+1); - list.get(i).put("qualifiedNum",list.get(i).getInt("qualifiedNum",0)==1?"是":"否"); + list.get(i).put("no", i + 1); + int watchCount = list.get(i).getInt("watchCount", 0); + int qualifiedNum = list.get(i).getInt("qualifiedNum", 0); + list.get(i).put("qualified", watchCount >= qualifiedNum ? "是" : "否"); } - try{ + try { ViTool.excelResponse(response, "获奖人员名单.xls"); Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), excelExportEntities, list); workbook.write(response.getOutputStream()); - }catch (Exception e){ - e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); } } diff --git a/src/main/java/io/v/nutz/web/commons/proc/GlobalsSettingProcessor.java b/src/main/java/io/v/nutz/web/commons/proc/GlobalsSettingProcessor.java index 70ec442..bcf0b44 100644 --- a/src/main/java/io/v/nutz/web/commons/proc/GlobalsSettingProcessor.java +++ b/src/main/java/io/v/nutz/web/commons/proc/GlobalsSettingProcessor.java @@ -33,27 +33,29 @@ public class GlobalsSettingProcessor extends AbstractProcessor { @SuppressWarnings("rawtypes") public void process(ActionContext ac) throws Throwable { - ac.getRequest().setAttribute("AppRoot", Globals.AppRoot); - ac.getRequest().setAttribute("AppBase", Globals.AppBase); - ac.getRequest().setAttribute("AppName", Globals.AppName); - ac.getRequest().setAttribute("AppDomain", Globals.AppDomain); - ac.getRequest().setAttribute("AppShrotName", Globals.AppShrotName); - ac.getRequest().setAttribute("AppFileDomain", Globals.AppFileDomain); - ac.getRequest().setAttribute("config", Globals.MyConfig); - ac.getRequest().setAttribute("shiro", ShiroUtil); - ac.getRequest().setAttribute("date", dateUtil); - ac.getRequest().setAttribute("string", stringUtil); - ac.getRequest().setAttribute("BrandActivityName", Globals.BrandActivityName); - // 如果url中有语言属性则设置 - String lang = ac.getRequest().getParameter("lang"); - if (!Strings.isEmpty(lang)) { - Mvcs.setLocalizationKey(lang); - } else { - // Mvcs.getLocalizationKey() 1.r.56 版本是null,所以要做两次判断, 1.r.57已修复为默认值 Nutz:Fix issue 1072 - lang = Strings.isBlank(Mvcs.getLocalizationKey()) ? Mvcs.getDefaultLocalizationKey() : Mvcs.getLocalizationKey(); - } - ac.getRequest().setAttribute("lang", lang); - doNext(ac); + ac.getRequest().setAttribute("AppRoot", Globals.AppRoot); + ac.getRequest().setAttribute("AppBase", Globals.AppBase); + ac.getRequest().setAttribute("AppName", Globals.AppName); + ac.getRequest().setAttribute("AppDomain", Globals.AppDomain); + ac.getRequest().setAttribute("AppShrotName", Globals.AppShrotName); + ac.getRequest().setAttribute("AppFileDomain", Globals.AppFileDomain); + ac.getRequest().setAttribute("config", Globals.MyConfig); + ac.getRequest().setAttribute("shiro", ShiroUtil); + ac.getRequest().setAttribute("date", dateUtil); + ac.getRequest().setAttribute("string", stringUtil); + ac.getRequest().setAttribute("BrandActivityName", Globals.BrandActivityName); + // 如果url中有语言属性则设置 + String lang = ac.getRequest().getParameter("lang"); + if (!Strings.isEmpty(lang)) { + Mvcs.setLocalizationKey(lang); + } else { + // Mvcs.getLocalizationKey() 1.r.56 版本是null,所以要做两次判断, 1.r.57已修复为默认值 Nutz:Fix issue 1072 + lang = Strings.isBlank(Mvcs.getLocalizationKey()) ? Mvcs.getDefaultLocalizationKey() : Mvcs.getLocalizationKey(); + } + ac.getRequest().setAttribute("lang", lang); + doNext(ac); + + } } diff --git a/src/main/java/io/v/nutz/web/commons/proc/WkFailProcessor.java b/src/main/java/io/v/nutz/web/commons/proc/WkFailProcessor.java index 34e5cd9..50eac60 100644 --- a/src/main/java/io/v/nutz/web/commons/proc/WkFailProcessor.java +++ b/src/main/java/io/v/nutz/web/commons/proc/WkFailProcessor.java @@ -1,6 +1,7 @@ package io.v.nutz.web.commons.proc; import io.v.nutz.base.result.Result; +import org.eclipse.jetty.http.BadMessageException; import org.nutz.integration.shiro.NutShiro; import org.nutz.ioc.IocException; import org.nutz.log.Log; @@ -35,6 +36,12 @@ public class WkFailProcessor extends ViewProcessor { new ForwardView(errorUri).render(ac.getRequest(), ac.getResponse(), Mvcs.getMessage(ac.getRequest(), "system.exception")); } } + if(ac.getError() instanceof BadMessageException){ + NutShiro.rendAjaxResp(ac.getRequest(), ac.getResponse(), Result.error("表单参数错误")); + } + if(ac.getError() instanceof RuntimeException){ + NutShiro.rendAjaxResp(ac.getRequest(), ac.getResponse(), Result.error("程序异常")); + } super.process(ac); } } diff --git a/src/main/java/io/v/nutz/web/commons/proc/XssSqlFilterProcessor.java b/src/main/java/io/v/nutz/web/commons/proc/XssSqlFilterProcessor.java index 9855fc6..aa1e6b4 100644 --- a/src/main/java/io/v/nutz/web/commons/proc/XssSqlFilterProcessor.java +++ b/src/main/java/io/v/nutz/web/commons/proc/XssSqlFilterProcessor.java @@ -1,6 +1,8 @@ package io.v.nutz.web.commons.proc; import cn.hutool.http.HtmlUtil; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import io.v.nutz.base.result.Result; import org.apache.commons.lang3.StringUtils; import org.nutz.integration.shiro.NutShiro; @@ -16,9 +18,11 @@ import org.nutz.mvc.impl.processor.AbstractProcessor; import org.nutz.mvc.view.ForwardView; import javax.servlet.http.HttpServletRequest; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.regex.Pattern; /** * SQL XSS拦截 @@ -37,6 +41,13 @@ public class XssSqlFilterProcessor extends AbstractProcessor { private final static String regxpForImaTagSrcAttrib = "src=\"([^\"]+)\""; // 找出IMG标签的SRC属性 + //合法字段 + private static final Set ALLOWED_ORDER_FIELDS = new HashSet<>(Arrays.asList( + "createdat", "updatedat" + )); + + private static final ObjectMapper objectMapper = new ObjectMapper(); + @Override public void init(NutConfig config, ActionInfo ai) throws Throwable { try { @@ -66,64 +77,57 @@ public class XssSqlFilterProcessor extends AbstractProcessor { protected boolean checkParams(ActionContext ac) { HttpServletRequest req = ac.getRequest(); + + /* String pageOrderName = req.getParameter("pageOrderName"); + if (pageOrderName != null && ALLOWED_ORDER_FIELDS.contains(pageOrderName.toLowerCase())) { + // 如果是预定义的排序字段,直接放行 + return false; + }*/ + + Iterator values = req.getParameterMap().values().iterator();// 获取所有的表单参数 Iterator values2 = req.getParameterMap().values().iterator();// 因为是游标所以要重新获取 boolean isError = false; - //use remove -|, - String regEx_sql = """ - select|update|and|or|delete|insert|trancate|char|chr|into|substr|ascii|declare|exec|count|master|drop|execute - """; - String regEx_xss = "script|iframe|img"; + // XSS过滤 + String regEx_xss = "script|iframe"; //SQL过滤 while (values.hasNext()) { String[] valueArray = (String[]) values.next(); for (int i = 0; i < valueArray.length; i++) { - String value = valueArray[i].toLowerCase(); + // 不转换为小写,保持原始值进行检查 + String value = valueArray[i]; - //排除掉base64 - if (value.startsWith("data:image/png;base64,")) { + // URL解码 + try { + value = URLDecoder.decode(value, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + log.error("URL解码失败", e); + isError = true; break; } - //分拆关键字 - String[] inj_stra = StringUtils.split(regEx_sql, "\\|"); - for (int j = 0; j < inj_stra.length; j++) { - // 判断如果路径参数值中含有关键字则返回true,并且结束循环 -// if (value.contains(inj_stra[j] + " ") || value.contains(" " + inj_stra[j] + " ") || value.contains(" " + inj_stra[j])) { -// isError = true; -// log.debugf("[%-4s]URI=%s %s", req.getMethod(), req.getRequestURI(), "SQL关键字过滤:" + value); -// break; -// } - - if ("and".equals(inj_stra[j]) || "or".equals(inj_stra[j]) || "into".equals(inj_stra[j])) { - if (value.contains(" " + inj_stra[j] + " ")) { - isError = true; - log.debugf("[%-4s]URI=%s %s", req.getMethod(), req.getRequestURI(), "SQL关键字过滤:" + value); - break; - } - } else { - if (value.contains(" " + inj_stra[j] + " ") - || value.contains(inj_stra[j] + " ") - || value.contains(inj_stra[j] + "/**/") - || value.contains(inj_stra[j] + "/*") - || value.contains("/**/" + inj_stra[j] + "/**/") - || value.contains(inj_stra[j] + "*/") - ) { - isError = true; - log.debugf("[%-4s]URI=%s %s", req.getMethod(), req.getRequestURI(), "SQL关键字过滤:" + value); - break; - } + // 检查是否是JSON格式 + if (isValidJson(value)) { + // 对JSON内容进行安全检查 + if (checkJsonContent(value)) { + isError = true; + log.debugf("[%-4s]URI=%s %s", req.getMethod(), req.getRequestURI(), "JSON内容包含危险字符:" + value); + break; } + // JSON格式验证通过,继续处理下一个参数 + continue; } - if (isError) { + + // 非JSON格式,进行普通的SQL注入检查 + if (containsSqlInjection(value, false)) { + isError = true; + log.debugf("[%-4s]URI=%s %s", req.getMethod(), req.getRequestURI(), "SQL注入风险:" + value); break; } } - if (isError) { - break; - } + if (isError) break; } if (!isError) { // XSS漏洞过滤 @@ -131,14 +135,6 @@ public class XssSqlFilterProcessor extends AbstractProcessor { String[] valueArray = (String[]) values2.next(); for (int i = 0; i < valueArray.length; i++) { String value = valueArray[i].toLowerCase(); - - value = HtmlUtil.escape(value); - - if (value.trim().startsWith("<") || value.trim().endsWith(">") || - value.trim().endsWith("/")) { - log.debugf("[%-4s]URI=%s %s", req.getMethod(), req.getRequestURI(), "XSS关键字过滤(已被我转义):" + value); - } - // 分拆关键字 String[] inj_stra = StringUtils.split(regEx_xss, "|"); for (int j = 0; j < inj_stra.length; j++) { @@ -162,4 +158,197 @@ public class XssSqlFilterProcessor extends AbstractProcessor { } return isError; } + + + /** + * 验证是否为有效的JSON + */ + private boolean isValidJson(String value) { + try { + objectMapper.readTree(value); + return true; + } catch (Exception e) { + return false; + } + } + + /** + * 检查JSON内容是否包含危险字符 + */ + private boolean checkJsonContent(String jsonStr) { + try { + JsonNode rootNode = objectMapper.readTree(jsonStr); + return checkJsonNode(rootNode, true); + } catch (Exception e) { + log.error("JSON内容检查失败", e); + return true; // 解析失败当作危险处理 + } + } + + /** + * 递归检查JSON节点 + */ + private boolean checkJsonNode(JsonNode node, boolean isJsonContext) { + if (node.isObject()) { + Iterator> fields = node.fields(); + while (fields.hasNext()) { + Map.Entry entry = fields.next(); + // JSON对象的键名不需要进行特殊字符检查,因为它们是受控的标识符 + // 递归检查值 + if (checkJsonNode(entry.getValue(), true)) { + return true; + } + } + } else if (node.isArray()) { + for (JsonNode element : node) { + if (checkJsonNode(element, true)) { + return true; + } + } + } else if (node.isTextual()) { + String value = node.asText(); + // 在JSON上下文中使用更宽松的规则 + return containsSqlInjection(value, true); + } + return false; + } + + + /** + * 检查是否包含SQL注入风险 + * + * @param value 要检查的值 + * @param isJsonContext 是否在JSON上下文中 + */ + private boolean containsSqlInjection(String value, boolean isJsonContext) { + if (StringUtils.isBlank(value)) { + return false; + } + // 规范化输入:移除多余的空白字符 + value = value.replaceAll("\\s+", " ").trim(); + + // 检查编码后的特殊字符 + String decodedValue = value.replaceAll("%20", " ") + .replaceAll("%2B", "+") + .replaceAll("%2F", "/") + .replaceAll("%3D", "="); + + // 检查注释符号和其他危险字符 + if (decodedValue.contains("--") || + decodedValue.contains("/*") || + decodedValue.contains("*/") || + decodedValue.contains("#") || + Pattern.compile("[;'\"\\\\]").matcher(decodedValue).find()) { + return true; + } + + // 如果是UUID格式,直接放行 + if (value.matches("[a-fA-F0-9]{32}") || value.matches("[a-fA-F0-9-]{36}")) { + return false; + } + + // 如果包含HTML标签,使用更宽松的检查规则 + if (value.contains("<") && value.contains(">")) { + return checkHtmlContent(value); + } + + // 添加对常见标识符的检查 + if (isCommonIdentifier(value) || ALLOWED_ORDER_FIELDS.contains(value.toLowerCase())) { + return false; + } + + // 检查注释符号 + if (value.contains("--") || value.contains("/*") || value.contains("*/")) { + return true; + } + + // 检查危险的特殊字符(针对非JSON数据) + if (!isValidJson(value) && containsDangerousChars(value)) { + return true; + } + + // 检查SQL关键字,但要避免误判 + String valueLower = value.toLowerCase(); + for (String keyword : SQL_KEYWORDS) { + // 在JSON上下文中,只有当关键字作为独立单词出现时才判定为危险 + if (isJsonContext) { + if (isStandaloneWord(valueLower, keyword)) { + return true; + } + } else if (valueLower.contains(keyword)) { + return true; + } + } + + return false; + } + + /** + * 判断是否为常见的UUID或标识符格式 + */ + private boolean isCommonIdentifier(String value) { + // UUID格式 + if (value.matches("[a-fA-F0-9]{32}") || value.matches("[a-fA-F0-9-]{36}")) { + return true; + } + // 常见的标识符格式(字母、数字、下划线、中划线) + if (value.matches("^[a-zA-Z0-9_-]+$")) { + return true; + } + return false; + } + + + /** + * 检查HTML内容是否包含危险脚本 + */ + private boolean checkHtmlContent(String html) { + String lowerHtml = html.toLowerCase(); + // 只检查真正危险的脚本标签 + return lowerHtml.contains(" SQL_KEYWORDS = new HashSet<>(Arrays.asList( + "select", "update", "delete", "insert", "truncate", + "drop", "execute", "exec", "declare", "union", + "create", "alter", "database", "table", "varchar", + "extractvalue", "concat", "benchmark", "sleep", "delay", + "waitfor", "pg_sleep", "information_schema", "sysobjects", + "load_file", "outfile", "dumpfile" + )); + + /** + * 检查关键字是否作为独立单词出现 + */ + private boolean isStandaloneWord(String text, String keyword) { + String wordBoundary = "\\b" + keyword + "\\b"; + return Pattern.compile(wordBoundary).matcher(text).find(); + } + } diff --git a/src/main/resources/static/assets/mobile/css/theme.css b/src/main/resources/static/assets/mobile/css/theme.css index caead95..182f51c 100644 --- a/src/main/resources/static/assets/mobile/css/theme.css +++ b/src/main/resources/static/assets/mobile/css/theme.css @@ -32,7 +32,7 @@ } .van-tabs__line { - background-color: #1867b0; + background-color: #1867b0!important; } .font-theme-color { diff --git a/src/main/resources/static/assets/platform/js/main.js b/src/main/resources/static/assets/platform/js/main.js index cac3d2b..c62d883 100644 --- a/src/main/resources/static/assets/platform/js/main.js +++ b/src/main/resources/static/assets/platform/js/main.js @@ -273,6 +273,18 @@ var sublime = function () { }, //2022/02/14 zxy pjax跳转自定义页面 jumpPagePjax(pageUrl) { + // const url = new URL(location.protocol + "//" + location.host + jump_url) + // const pathname = url.pathname + // headerNavBarVueApp.matchMenu(pathname) + // setTimeout(() => { + // const a = document.querySelector('a[href="' + pathname + '"][data-pjax]') + // if (a) { + // a.href = jump_url + // a.click() + // a.href = pathname + // // leftMenuApp.assignmentMenu() + // } + // }, 0) const index = pageUrl.indexOf("?") const doMainUrl = index > -1 ? pageUrl.substring(0, index) : pageUrl const {protocol, host} = window.location diff --git a/src/main/resources/views/layouts/platform.html b/src/main/resources/views/layouts/platform.html index 6fd793f..73ede49 100644 --- a/src/main/resources/views/layouts/platform.html +++ b/src/main/resources/views/layouts/platform.html @@ -67,8 +67,8 @@ - - + + @@ -441,7 +441,7 @@ secondMenus: {}, userDefaultModuleId: null, menuPopover: false, - appModuleId:"" + appModuleId: "" } }, methods: { @@ -609,13 +609,12 @@ const selectEntranceModuleStr = window.sessionStorage.getItem('selectEntranceModule') if (selectEntranceModuleStr) { this.selectEntranceModule = JSON.parse(selectEntranceModuleStr) + this.appModuleId = this.selectEntranceModule.id } - this.appModuleId=this.selectEntranceModule.id await this.getEntranceModule() await this.getFirstMenus(this.selectEntranceModule.id) await this.getSecondMenus() this.setAttr() - // let pjax_url = window.sessionStorage.getItem('pjax_url') // pjax_url = pjax_url.replace(window.location.origin, '') // $.pjax({ diff --git a/src/main/resources/views/mobile/platform.html b/src/main/resources/views/mobile/platform.html index 04afd2e..244458d 100644 --- a/src/main/resources/views/mobile/platform.html +++ b/src/main/resources/views/mobile/platform.html @@ -53,8 +53,8 @@ - + + diff --git a/src/main/resources/views/mobile/question/answer.html b/src/main/resources/views/mobile/question/answer.html index 7a7fd12..771c432 100644 --- a/src/main/resources/views/mobile/question/answer.html +++ b/src/main/resources/views/mobile/question/answer.html @@ -157,26 +157,30 @@ layout("/mobile/platform.html"){
-
- +
+ + + + + + + + + + + + +
- {{questionData.calcScoreMode==1?'今日最高成绩':'得分'}}{{currentScore}} + 最新得分{{currentScore}} +
-
- 剩余时间:{{remainder}} +
+ 查看历史得分
+ + +
@@ -196,8 +200,8 @@ layout("/mobile/platform.html"){ {{issue.iisdx?'单选':'多选'}} - - + {{issue.fraction}}分 +

@@ -267,6 +271,15 @@ layout("/mobile/platform.html"){ close-on-click-action > + + + +
第{{item.answerNum}}次
+
{{item.score}}分
+
+
+
+

@@ -310,7 +323,11 @@ layout("/mobile/platform.html"){ //答题时间(使用秒) countDownSeconds: 0, //剩余时间 - remainder: null + remainder: null, + + //历史分数 + historyScores:[], + historyScorePopup:false } }, @@ -362,6 +379,14 @@ layout("/mobile/platform.html"){ }, methods: { + viewHistoryScore(){ + $.get(BASE_URL+"/historyScore",{questionId:this.id}).then(res=>{ + if(res.code===0){ + this.historyScores = res.data + this.historyScorePopup = true + } + }) + }, openRandomCount() { this.randomCountSheetShow = true this.randomActions = [] @@ -573,7 +598,20 @@ layout("/mobile/platform.html"){ answerAgain() { if(this.questionData.ctMode===3){ - location.reload() + $.post(BASE_URL+'/answerAgainCommon',{id:this.id}).then(resp=>{ + if(resp.code===0){ + this.questionData = resp.data + this.finishAnswer = false + setTimeout(() => { + this.questionData.issues.forEach(issue => { + issue.wors.forEach(wor => { + this.$refs['checkboxes' + wor.wid][0].toggle(false) + }) + }) + }, 0) + this.checkTimer() + } + }) }else{ this.finishAnswer = false setTimeout(() => { @@ -653,7 +691,16 @@ layout("/mobile/platform.html"){ } else { this.countDownSeconds = this.countDownSeconds - 1 } + }, + + getHistoryScore(){ + $.get(BASE_URL+"/historyScore",{questionId:this.id}).then(res=>{ + if(res.code===0){ + this.historyScores = res.data + } + }) } + }, async created() { if (this.timerInterval) { @@ -666,6 +713,7 @@ layout("/mobile/platform.html"){ await this.checkUserFinishAnswer() await this.getScore() await this.checkTimer() + this.getHistoryScore() this.mLoading = false } }) diff --git a/src/main/resources/views/platform/question/components/answer.js b/src/main/resources/views/platform/question/components/answer.js index 6f42bd7..46a07cc 100644 --- a/src/main/resources/views/platform/question/components/answer.js +++ b/src/main/resources/views/platform/question/components/answer.js @@ -63,7 +63,7 @@ const onlineAnswer = { ({{issue.row.fraction}}分) - + ({{issue.row.iisdx ? '单选' : '多选'}}) @@ -74,7 +74,7 @@ const onlineAnswer = {
- +
-
{{index+1}}、
-
{{issue.iname}}
+
{{index+1}}、{{issue.iname}}
diff --git a/src/main/resources/views/platform/question/question.html b/src/main/resources/views/platform/question/question.html index 0b478b9..03034b7 100644 --- a/src/main/resources/views/platform/question/question.html +++ b/src/main/resources/views/platform/question/question.html @@ -539,7 +539,7 @@ layout("/layouts/platform.html"){
- + + v-for="item in [{x:1,y:'选择题'},{x:2,y:'填空题'},{x:3,y:'判断题'}]">
@@ -928,9 +928,9 @@ layout("/layouts/platform.html"){ -