From 3425888c9142bde4bdee80c4f9e912b41c5a7814 Mon Sep 17 00:00:00 2001 From: zhouhefeng Date: Wed, 15 Jul 2026 11:47:47 +0800 Subject: [PATCH] commit --- .../sys/controller/SysLoginController.java | 130 +++- .../TeacherCongressInstitutionController.java | 50 +- ...TeacherCongressInstitutionUserService.java | 2 +- ...herCongressInstitutionUserServiceImpl.java | 25 +- .../resources/views/layouts/v4/home/act.js | 25 +- .../resources/views/layouts/v4/home/entry.js | 45 +- .../views/platform/sys/data/tool/index.html | 604 +++++++++++------- .../resources/views/platform/sys/login.html | 28 +- .../activity/sports/infoManage/addActivity.js | 9 + .../zhgh/activity/sports/reading/index.html | 8 +- .../teachercongress/institution/basicTable.js | 162 ++--- .../teachercongress/institution/index.html | 60 +- .../teachercongress/institution/tree.js | 23 +- .../teachercongress/institution/userTable.js | 109 +--- 14 files changed, 691 insertions(+), 589 deletions(-) diff --git a/src/main/java/com/budwk/app/sys/controller/SysLoginController.java b/src/main/java/com/budwk/app/sys/controller/SysLoginController.java index e78a0a5d..7ad931a5 100644 --- a/src/main/java/com/budwk/app/sys/controller/SysLoginController.java +++ b/src/main/java/com/budwk/app/sys/controller/SysLoginController.java @@ -41,15 +41,11 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.security.KeyFactory; import java.security.KeyPair; -import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.util.Base64; import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; @IocBean @@ -58,6 +54,11 @@ import java.util.UUID; @Api(tags = "登录") public class SysLoginController { private static final Log log = Logs.get(); + private static final LoginRsaKeyRing LOGIN_RSA_KEY_RING = new LoginRsaKeyRing(); + /** + * TODO: 临时屏蔽 localhost 平台登录验证码,恢复时改为 false。 + */ + private static final boolean DISABLE_LOGIN_CAPTCHA = true; @Inject private SysUserService sysUserService; @Inject @@ -123,14 +124,20 @@ public class SysLoginController { } try { // 验证码校验 - try { - validateService.checkCode(captchaKey, captchaCode); - } catch (BaseException e) { - return Result.error(e.getMessage()); + if (!isLoginCaptchaDisabled(req)) { + try { + validateService.checkCode(captchaKey, captchaCode); + } catch (BaseException e) { + return Result.error(e.getMessage()); + } } // 解密密码 - String decryptPwd = validateService.decryptPwd(keyId, password); + LoginRsaKey rsaKey = LOGIN_RSA_KEY_RING.getPrivateKey(keyId); + if (rsaKey == null) { + throw new BaseException("Login key expired, please refresh the login page"); + } + String decryptPwd = RsaUtils.decrypt(password, rsaKey.privateKey); if (decryptPwd == null) { throw new BaseException("用户登录失败"); } @@ -153,6 +160,17 @@ public class SysLoginController { } } + private boolean isLoginCaptchaDisabled(HttpServletRequest req) { + if (!DISABLE_LOGIN_CAPTCHA || req == null) { + return false; + } + String serverName = req.getServerName(); + return "localhost".equalsIgnoreCase(serverName) + || "127.0.0.1".equals(serverName) + || "0:0:0:0:0:0:0:1".equals(serverName) + || "::1".equals(serverName); + } + @At(value = "/platform/sso/login", top = true) @Ok("re") @ApiOperation("用户cas登录统一入口") @@ -267,25 +285,89 @@ public class SysLoginController { @ApiOperation("获取公钥") public Object publicKey() { try { - // 生成密钥对 - KeyPair keyPair = RsaUtils.generateKeyPair(); - String publicKeyStr = RsaUtils.getPublicKeyBase64(keyPair.getPublic()); - String privateKeyStr = RsaUtils.getPrivateKeyBase64(keyPair.getPrivate()); - - // 生成 UUID 作为 keyId - String keyId = UUID.randomUUID().toString().replace("-", ""); - - // 私钥存入 Redis,5 分钟过期,也防止恶意刷密钥 - redisService.setex(RedisConstant.RSA_KEY_PREFIX + keyId, 5 * 60, privateKeyStr); - - // 返回给前端 + LoginRsaKey rsaKey = LOGIN_RSA_KEY_RING.currentKey(); return Result.success(Map.of( - "publicKey", publicKeyStr, - "keyId", keyId + "publicKey", rsaKey.publicKey, + "keyId", rsaKey.keyId, + "expireAt", rsaKey.encryptExpireAt )); } catch (Exception e) { log.error("生成 RSA 密钥失败", e); return Result.error("系统异常"); } } + + private static class LoginRsaKeyRing { + private static final long ACTIVE_MILLIS = 30 * 60 * 1000L; + private static final long DECRYPT_GRACE_MILLIS = 10 * 60 * 1000L; + private final ConcurrentHashMap keys = new ConcurrentHashMap<>(); + private volatile LoginRsaKey current; + + LoginRsaKey currentKey() throws Exception { + long now = System.currentTimeMillis(); + LoginRsaKey key = current; + if (key != null && now < key.encryptExpireAt) { + return key; + } + synchronized (this) { + key = current; + now = System.currentTimeMillis(); + if (key == null || now >= key.encryptExpireAt) { + key = generateKey(now); + current = key; + keys.put(key.keyId, key); + cleanup(now); + } + return key; + } + } + + LoginRsaKey getPrivateKey(String keyId) { + if (StrUtil.isBlank(keyId)) { + return null; + } + long now = System.currentTimeMillis(); + LoginRsaKey key = keys.get(keyId); + if (key == null) { + return null; + } + if (now >= key.decryptExpireAt) { + keys.remove(keyId); + return null; + } + return key; + } + + private LoginRsaKey generateKey(long now) throws Exception { + KeyPair keyPair = RsaUtils.generateKeyPair(); + String keyId = UUID.randomUUID().toString().replace("-", ""); + return new LoginRsaKey( + keyId, + RsaUtils.getPublicKeyBase64(keyPair.getPublic()), + keyPair.getPrivate(), + now + ACTIVE_MILLIS, + now + ACTIVE_MILLIS + DECRYPT_GRACE_MILLIS + ); + } + + private void cleanup(long now) { + keys.entrySet().removeIf(entry -> now >= entry.getValue().decryptExpireAt); + } + } + + private static class LoginRsaKey { + private final String keyId; + private final String publicKey; + private final PrivateKey privateKey; + private final long encryptExpireAt; + private final long decryptExpireAt; + + private LoginRsaKey(String keyId, String publicKey, PrivateKey privateKey, long encryptExpireAt, long decryptExpireAt) { + this.keyId = keyId; + this.publicKey = publicKey; + this.privateKey = privateKey; + this.encryptExpireAt = encryptExpireAt; + this.decryptExpireAt = decryptExpireAt; + } + } } diff --git a/src/main/java/com/budwk/app/zhgh/democratic/teachercongress/institution/controller/TeacherCongressInstitutionController.java b/src/main/java/com/budwk/app/zhgh/democratic/teachercongress/institution/controller/TeacherCongressInstitutionController.java index 7e574d3c..2dcb71d7 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/teachercongress/institution/controller/TeacherCongressInstitutionController.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/teachercongress/institution/controller/TeacherCongressInstitutionController.java @@ -25,7 +25,6 @@ import org.nutz.dao.util.cri.SqlExpressionGroup; import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; -import org.nutz.lang.Strings; import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.Ok; @@ -69,15 +68,11 @@ public class TeacherCongressInstitutionController { Teacher_congress_institution rootInstitution = new Teacher_congress_institution(); rootInstitution.setId("0"); rootInstitution.setParentId(""); - rootInstitution.setName("两代会组织机构"); + rootInstitution.setName("教代会机构"); rootInstitution.setLocation(0); institutionList.add(rootInstitution); - List> treeNodes = institutionList.stream().map(institution -> { - TreeNode treeNode = new TreeNode<>(institution.getId(), institution.getParentId(), institution.getName(), institution.getLocation()); - treeNode.setExtra(Map.of("code", Strings.sNull(institution.getCode()))); - return treeNode; - }).toList(); + List> treeNodes = institutionList.stream().map(institution -> new TreeNode<>(institution.getId(), institution.getParentId(), institution.getName(), institution.getLocation())).toList(); List> treeList = TreeUtil.build(treeNodes, ""); return Result.success(treeList); } @@ -102,16 +97,6 @@ public class TeacherCongressInstitutionController { return Result.success(Map.of("treeFlat", children, "treeList", treeList)); } - @At - @SaCheckPermission("tc.institution") - public Result specialCommitteeRoleOptions() { - Sys_dict parent = sysDictService.fetch(Cnd.where(Sys_dict::getCode, "=", "SPECIAL_COMMITTEE_ROLES")); - if (ObjectUtil.isEmpty(parent)) { - return Result.success(List.of()); - } - List children = sysDictService.query(Cnd.where(Sys_dict::getPath, "like", parent.getPath() + "%").and(Sys_dict::getId, "!=", parent.getId()).asc(Sys_dict::getLocation)); - return Result.success(children); - } /** * 机构分页查询 @@ -128,7 +113,6 @@ public class TeacherCongressInstitutionController { cnd.and("parentId", "=", parentId); cnd.and("sessionId", "=", sessionId); cnd.asc("location"); - cnd.asc("code"); Pagination pagination = baseService.listPage(pageForm.getPageNumber(), pageForm.getPageSize(), "teacher_congress_institution", cnd); return Result.success(pagination); } @@ -145,7 +129,7 @@ public class TeacherCongressInstitutionController { if (dao.count(Teacher_congress_institution.class, Cnd.where(Teacher_congress_institution::getCode, "=", institution.getCode()).and(Teacher_congress_institution::getSessionId, "=", institution.getSessionId())) > 0) { return Result.error("机构已存在"); } - if (StrUtil.isNotBlank(institution.getParentId()) && !institution.getParentId().equals("0") && dao.count(Teacher_congress_institution.class, Cnd.where(Teacher_congress_institution::getId, "=", institution.getParentId()).and(Teacher_congress_institution::getSessionId, "=", institution.getSessionId())) == 0) { + if (StrUtil.isNotBlank(institution.getParentId()) && !institution.getParentId().equals("0")) { Sys_dict sysDict = sysDictService.fetch(Cnd.where(Sys_dict::getId, "=", institution.getParentId())); Teacher_congress_institution parentInstitution = dao.fetch(Teacher_congress_institution.class, Cnd.where(Teacher_congress_institution::getCode, "=", sysDict.getCode()) @@ -167,34 +151,10 @@ public class TeacherCongressInstitutionController { * @param id * @return */ - @At - @SaCheckPermission("tc.institution") - public Result update(Teacher_congress_institution institution) { - Teacher_congress_institution dbInstitution = dao.fetch(Teacher_congress_institution.class, institution.getId()); - if (ObjectUtil.isEmpty(dbInstitution)) { - return Result.error("机构不存在"); - } - dbInstitution.setName(institution.getName()); - dbInstitution.setIntroduce(institution.getIntroduce()); - dbInstitution.setLocation(institution.getLocation()); - dao.update(dbInstitution); - return Result.success(); - } - @At @SaCheckPermission("tc.institution") @Aop(TransAop.READ_COMMITTED) public Result delete(@Valid String id) { - Teacher_congress_institution institution = dao.fetch(Teacher_congress_institution.class, id); - if (ObjectUtil.isEmpty(institution)) { - return Result.error("机构不存在"); - } - List siblingList = dao.query(Teacher_congress_institution.class, Cnd.where(Teacher_congress_institution::getParentId, "=", institution.getParentId()).and(Teacher_congress_institution::getSessionId, "=", institution.getSessionId()).asc(Teacher_congress_institution::getLocation).asc(Teacher_congress_institution::getCode)); - for (int i = 0; i < siblingList.size() && i < 6; i++) { - if (id.equals(siblingList.get(i).getId())) { - return Result.error("你选择的组织机构是两代会基本机构,不允许删除。"); - } - } dao.delete(Teacher_congress_institution.class, id); dao.clear(Teacher_congress_institution_user.class, Cnd.where("institutionId", "=", id)); return Result.success(); @@ -254,8 +214,8 @@ public class TeacherCongressInstitutionController { @SaCheckPermission("tc.institution") @SLog(tag = "教代会-机构设置", msg = "添加人员") @Aop(TransAop.READ_COMMITTED) - public Result userInsert(@Valid String userId, @Valid String institutionId, @Valid String sessionId, @Valid String identity, String roleCode) { - teacherCongressInstitutionUserService.userInsert(userId, institutionId, sessionId, identity, roleCode); + public Result userInsert(@Valid String userId, @Valid String institutionId, @Valid String sessionId, @Valid String identity) { + teacherCongressInstitutionUserService.userInsert(userId, institutionId, sessionId, identity); return Result.success(); } diff --git a/src/main/java/com/budwk/app/zhgh/democratic/teachercongress/institution/service/TeacherCongressInstitutionUserService.java b/src/main/java/com/budwk/app/zhgh/democratic/teachercongress/institution/service/TeacherCongressInstitutionUserService.java index 83472c4b..302aa97f 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/teachercongress/institution/service/TeacherCongressInstitutionUserService.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/teachercongress/institution/service/TeacherCongressInstitutionUserService.java @@ -14,7 +14,7 @@ public interface TeacherCongressInstitutionUserService extends BaseService
- + +
{{ item.name }}
@@ -167,7 +168,7 @@ const entry = { .app-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(108px, 1fr)); justify-items: center; justify-content: start; gap: 12px 18px; @@ -185,13 +186,17 @@ const entry = { display: flex; flex-direction: column; align-items: center; + justify-content: flex-start; border-radius: 8px; background: #fff; width: 100%; - max-width: 100%; + max-width: 108px; + min-width: 0; + box-sizing: border-box; padding: 6px; cursor: pointer; transition: transform 0.2s ease, box-shadow 0.2s ease; + overflow: hidden; } .app-item:hover { @@ -200,24 +205,46 @@ const entry = { } .app-icon { - width: 46px; - height: 46px; + width: 56px; + height: 56px; margin-bottom: 8px; + display: flex; + align-items: center; + justify-content: center; + flex: 0 0 56px; + overflow: hidden; + border-radius: 8px; + background: #f5f8ff; } - .app-icon img { - width: 100%; - height: 100%; - object-fit: contain; + .app-icon img, + .app-icon-img { + display: block; + width: 56px !important; + height: 56px !important; + max-width: 56px !important; + max-height: 56px !important; + object-fit: contain !important; + border-radius: 8px; + } + + .app-icon-fallback { + color: #409eff; + font-size: 24px; } .app-name { min-height: 34px; + width: 100%; font-size: 12px; color: #333; line-height: 1.4; word-break: break-word; text-align: center; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; } .app-empty { diff --git a/src/main/resources/views/platform/sys/data/tool/index.html b/src/main/resources/views/platform/sys/data/tool/index.html index c632e51b..b7ec0121 100644 --- a/src/main/resources/views/platform/sys/data/tool/index.html +++ b/src/main/resources/views/platform/sys/data/tool/index.html @@ -4,225 +4,268 @@ layout("/layouts/platform.html"){
- -
- 高级工具 + +
+ DataKid
-
-
- - - - - - +
+
+ + + + + + -
-
-
- - - 搜索 - 当前选择:{{ formData.tableName }} -
- - -
- {{ item.table_name }} - ({{ item.table_comment }}) + + + +
+ + + + + 当前选中:{{ formData.tableName }} + +
+ + + + {{ item.table_name }}{{ item.table_comment ? " ( " + item.table_comment + " )" : "" }} rows: {{ item.table_rows || 0 }} -
-
-
-
+ + + + -
-
- - - 搜索 - {{ formData.tableName }} -
- - -
- {{ item.column_name }} - ({{ item.column_comment }}) + + +
+ + + + + {{ formData.tableName }} + +
+ + + + {{ item.column_name }}{{ item.column_comment ? " ( " + item.column_comment + " )" : "" }} {{ item.column_type }} -
-
-
-
+ + + + -
- - - - - - - - - - - -
+ + + + + + + + + + + + + + + -
-
- - + + + + + + 追加或更新 仅追加 @@ -230,13 +273,8 @@ layout("/layouts/platform.html"){ - - + + - + - +
将文件拖到此处,或点击上传
-
仅支持 `xls`、`xlsx` 文件
+
只能上传 xls/xlsx 文件
-
-
+ + -
-
- -
- 共 {{ result.total || 0 }} 条,成功 {{ result.success || 0 }} 条 + + + +
{{ resultSummary }}
+
+ + 下载错误记录 +
-
- 下载错误记录 -
-
-
-
+ + + -
@@ -316,11 +359,16 @@ layout("/layouts/platform.html"){ relation: [], plugins: [], fileList: [], + cacheTableName: "", result: { total: 0, success: 0, cacheKey: "" }, + formRules: { + method: [{ required: true, message: "必填", trigger: ["blur", "change"] }], + field: [{ required: true, message: "必填", trigger: ["blur", "change"] }] + }, formData: { tableName: "", columns: [], @@ -331,6 +379,29 @@ layout("/layouts/platform.html"){ } }, computed: { + primaryButtonText() { + if (this.active === 3 && this.formData.method === 3) { + return "更新" + } + return "下一步" + }, + resultSummary() { + const total = this.result.total || 0 + const success = this.result.success || 0 + const fail = Math.max(total - success, 0) + if (fail > 0) { + return "总记录数 " + total + " 条,成功 " + success + " 条,失败 " + fail + " 条!" + } + return "总记录数 " + total + " 条,成功 " + success + " 条!" + }, + resultProgressStatus() { + const total = this.result.total || 0 + const success = this.result.success || 0 + if (total > 0 && success < total) { + return "exception" + } + return "success" + }, resultPercentage() { if (!this.result.total) { return 0 @@ -339,6 +410,7 @@ layout("/layouts/platform.html"){ } }, methods: { + loc, normalizeRecord(item) { const normalized = {} Object.keys(item || {}).forEach(key => { @@ -398,66 +470,75 @@ layout("/layouts/platform.html"){ .filter(item => this.formData.columns.includes(item.column_name)) .map(item => Object.assign({}, item, { relation: "" })) }, - async nextStep() { + async next() { if (this.active === 0) { if (!this.formData.tableName) { - this.$message.warning("请选择数据表") + this.$notify({ + title: "警告", + message: "请选择数据表", + type: "warning" + }) return } - await this.loadColumns() - this.formData.columns = [] - this.formData.field = "" + if (this.cacheTableName !== this.formData.tableName) { + await this.loadColumns() + this.formData.columns = [] + this.formData.field = "" + this.cacheTableName = this.formData.tableName + } } else if (this.active === 1) { if (!this.formData.columns.length) { - this.$message.warning("请至少选择一个字段") + this.$notify({ + title: "警告", + message: "请选择数据列", + type: "warning" + }) return } this.buildRelation() } else if (this.active === 2) { - const hasEmptyRelation = this.relation.some(item => !item.relation) - if (hasEmptyRelation) { - this.$message.warning("请填写所有 Excel 表头对应关系") + if (this.relation.find(item => !item.relation)) { + this.$notify({ + title: "警告", + message: "存在未填写的目标列", + type: "warning" + }) return } } else if (this.active === 3) { - const success = await this.submitImport() + const success = await this.doSubmit() if (!success) { return } } - this.active += 1 + + this.$refs.carousel.next() + this.active++ }, - prevStep() { - if (this.active > 0) { - this.active -= 1 + prev() { + if (this.active <= 0) { + return } + this.$refs.carousel.prev() + this.active-- }, - handleFileRemove(file, fileList) { - this.fileList = fileList - }, - handleFileChange(file, fileList) { - const extension = ((file.name || "").split(".").pop() || "").toLowerCase() - const removeIndex = fileList.findIndex(item => item.uid === file.uid) - if (!["xls", "xlsx"].includes(extension)) { - this.$message.warning("仅支持 xls、xlsx 文件") - if (removeIndex > -1) { - fileList.splice(removeIndex, 1) - } - } else if (file.size === 0) { - this.$message.warning("上传文件不能为空") - if (removeIndex > -1) { - fileList.splice(removeIndex, 1) - } - } - this.fileList = fileList - }, - async submitImport() { - if ([1, 3].includes(this.formData.method) && !this.formData.field) { - this.$message.warning("请选择关键字段") + validateSubmitForm() { + let validForm = true + this.$refs.form.validate(valid => { + validForm = valid + }) + if (!validForm || !this.fileList.length) { + this.$notify({ + title: "警告", + message: "存在未填写的必填项", + type: "warning" + }) return false } - if (!this.fileList.length) { - this.$message.warning("请上传 Excel 文件") + return true + }, + async doSubmit() { + if (!this.validateSubmitForm()) { return false } @@ -476,18 +557,45 @@ layout("/layouts/platform.html"){ "Content-Type": "multipart/form-data" } }) + if (resp.code !== 0) { - this.$message.error(resp.msg) + this.$notify.error({ + title: "错误", + message: resp.msg + }) + this.result = { total: 0, success: 0, cacheKey: "" } return false } + this.result = resp.data || { total: 0, success: 0, cacheKey: "" } - this.$message.success(resp.msg) return true } finally { this.loading = false } }, - restart() { + handleFileRemove(file, fileList) { + this.fileList = fileList + }, + handleFileChange(file, fileList) { + const extension = ((file.name || "").split(".").pop() || "").toLowerCase() + const remove = () => { + const removeIndex = fileList.findIndex(item => item.uid === file.uid) + if (removeIndex > -1) { + fileList.splice(removeIndex, 1) + } + } + + if (file.size === 0) { + this.$message.warning("您选择的是空文件") + remove() + } else if (!["xls", "xlsx"].includes(extension)) { + this.$message.warning("上传文件只能是 xls/xlsx 格式") + remove() + } + + this.fileList = fileList + }, + carry() { this.active = 0 this.tableKeyword = "" this.columnKeyword = "" @@ -495,6 +603,7 @@ layout("/layouts/platform.html"){ this.filteredColumns = [] this.relation = [] this.fileList = [] + this.cacheTableName = "" this.result = { total: 0, success: 0, @@ -511,6 +620,7 @@ layout("/layouts/platform.html"){ this.$refs.upload.clearFiles() } this.filterTables() + this.$refs.carousel.setActiveItem(0) } }, async created() { diff --git a/src/main/resources/views/platform/sys/login.html b/src/main/resources/views/platform/sys/login.html index bb969228..7c89de05 100644 --- a/src/main/resources/views/platform/sys/login.html +++ b/src/main/resources/views/platform/sys/login.html @@ -52,7 +52,7 @@ prefix-icon="el-icon-lock" > - +