From db9a7ac8e51832155930cb049444737842fc7661 Mon Sep 17 00:00:00 2001 From: xiaofeng Date: Sun, 16 Nov 2025 20:10:58 +0800 Subject: [PATCH 1/7] =?UTF-8?q?2025=E5=B9=B411=E6=9C=8816=E6=97=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/FUNDING.yml | 1 - .vscode/settings.json | 3 - .../psychology/PsyAssessmentController.java | 210 ++++++++++-- .../psychology/PsyQrcodeController.java | 276 +++++++++++++++- .../psychology/PsyScaleController.java | 3 +- .../PsyScalePermissionController.java | 85 ++++- .../psychology/PsyUserProfileController.java | 170 ++++++++-- .../controller/system/SysLoginController.java | 22 +- .../web/exception/GlobalExceptionHandler.java | 56 ++++ .../web/service/SysLoginService.java | 4 +- .../domain/psychology/PsyUserProfile.java | 39 +++ .../impl/psychology/PsyQrcodeServiceImpl.java | 82 ++++- .../PsyScalePermissionServiceImpl.java | 59 +++- .../psychology/PsyUserProfileServiceImpl.java | 138 +++++++- .../service/psychology/IPsyQrcodeService.java | 9 + .../psychology/PsyUserProfileMapper.xml | 58 +++- xinli-ui/src/api/psychology/permission.js | 23 +- xinli-ui/src/api/psychology/profile.js | 42 +++ xinli-ui/src/api/psychology/qrcode.js | 16 + xinli-ui/src/store/modules/user.js | 5 +- xinli-ui/src/views/login.vue | 117 +------ .../src/views/psychology/permission/user.vue | 7 +- .../src/views/psychology/profile/index.vue | 302 ++++++++++++++++-- xinli-ui/src/views/psychology/qrcode/scan.vue | 122 ++++++- xinli-ui/src/views/student/tests.vue | 20 +- xinli-ui/vue.config.js | 7 +- 26 files changed, 1617 insertions(+), 259 deletions(-) delete mode 100644 .github/FUNDING.yml delete mode 100644 .vscode/settings.json diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 7e8207d3..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -custom: http://doc.ruoyi.vip/ruoyi-vue/other/donate.html diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 8f2b7113..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "java.compile.nullAnalysis.mode": "disabled" -} \ No newline at end of file diff --git a/ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyAssessmentController.java b/ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyAssessmentController.java index befa96ed..8f2bcc46 100644 --- a/ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyAssessmentController.java +++ b/ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyAssessmentController.java @@ -90,42 +90,88 @@ public class PsyAssessmentController extends BaseController /** * 根据测评ID获取详细信息 + * 允许管理员和学员访问(学员只能访问自己的测评) */ - @PreAuthorize("@ss.hasPermi('psychology:assessment:query')") @GetMapping(value = "/{assessmentId}") public AjaxResult getInfo(@PathVariable Long assessmentId) { - return success(assessmentService.selectAssessmentById(assessmentId)); + PsyAssessment assessment = assessmentService.selectAssessmentById(assessmentId); + if (assessment == null) + { + return error("测评不存在"); + } + + // 检查权限:管理员可以访问所有测评,学员只能访问自己的测评 + if (!hasAccessPermission(assessment)) + { + return error("无权访问该测评"); + } + + return success(assessment); } /** * 开始测评 + * 允许管理员和学员访问 */ @PostMapping("/start") public AjaxResult start(@Validated @RequestBody AssessmentStartVO startVO) { - PsyAssessment assessment = new PsyAssessment(); - assessment.setScaleId(startVO.getScaleId()); - assessment.setAssesseeName(startVO.getAssesseeName()); - assessment.setAssesseeGender(startVO.getAssesseeGender()); - assessment.setAssesseeAge(startVO.getAssesseeAge()); - assessment.setAssesseePhone(startVO.getAssesseePhone()); - assessment.setStatus("0"); - assessment.setStartTime(new Date()); - assessment.setIpAddress(IpUtils.getIpAddr()); - assessment.setUserAgent(ServletUtils.getRequest().getHeader("User-Agent")); - - // 直接设置用户ID,移除可能导致空指针的匿名检查 - assessment.setUserId(SecurityUtils.getUserId()); - - assessment.setCreateBy(SecurityUtils.getUsername()); - int result = assessmentService.insertAssessment(assessment); - - if (result > 0) - { - return success(assessment.getAssessmentId()); + try { + Long userId = null; + String username = null; + + // 尝试获取当前登录用户信息 + try { + userId = SecurityUtils.getUserId(); + username = SecurityUtils.getUsername(); + } catch (Exception e) { + logger.warn("获取用户信息失败,可能是匿名访问: {}", e.getMessage()); + // 如果是匿名访问,userId 和 username 可以为 null + } + + PsyAssessment assessment = new PsyAssessment(); + assessment.setScaleId(startVO.getScaleId()); + assessment.setAssesseeName(startVO.getAssesseeName()); + assessment.setAssesseeGender(startVO.getAssesseeGender()); + assessment.setAssesseeAge(startVO.getAssesseeAge()); + assessment.setAssesseePhone(startVO.getAssesseePhone()); + assessment.setStatus("0"); + assessment.setStartTime(new Date()); + assessment.setIpAddress(IpUtils.getIpAddr()); + assessment.setUserAgent(ServletUtils.getRequest().getHeader("User-Agent")); + + // 设置用户ID(如果获取到了) + if (userId != null) { + assessment.setUserId(userId); + } + + // 设置创建者(如果获取到了) + if (username != null && !username.isEmpty()) { + assessment.setCreateBy(username); + } else { + assessment.setCreateBy("system"); // 默认值 + } + + logger.info("创建测评 - scaleId: {}, userId: {}, assesseeName: {}", + startVO.getScaleId(), userId, startVO.getAssesseeName()); + + int result = assessmentService.insertAssessment(assessment); + + if (result > 0 && assessment.getAssessmentId() != null) + { + logger.info("测评创建成功 - assessmentId: {}", assessment.getAssessmentId()); + return success(assessment.getAssessmentId()); + } + else + { + logger.error("创建测评失败 - result: {}, assessmentId: {}", result, assessment.getAssessmentId()); + return error("开始测评失败,请检查量表是否存在"); + } + } catch (Exception e) { + logger.error("创建测评异常", e); + return error("开始测评失败:" + (e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName())); } - return error("开始测评失败"); } /** @@ -153,30 +199,53 @@ public class PsyAssessmentController extends BaseController /** * 暂停测评 + * 允许管理员和学员访问(学员只能暂停自己的测评) */ @PostMapping("/pause/{assessmentId}") public AjaxResult pause(@PathVariable Long assessmentId) { - PsyAssessment assessment = new PsyAssessment(); - assessment.setAssessmentId(assessmentId); + PsyAssessment assessment = assessmentService.selectAssessmentById(assessmentId); + if (assessment == null) + { + return error("测评不存在"); + } + + // 检查权限:管理员可以访问所有测评,学员只能访问自己的测评 + if (!hasAccessPermission(assessment)) + { + return error("无权访问该测评"); + } + assessment.setUpdateBy(SecurityUtils.getUsername()); return toAjax(assessmentService.pauseAssessment(assessment)); } /** * 恢复测评 + * 允许管理员和学员访问(学员只能恢复自己的测评) */ @PostMapping("/resume/{assessmentId}") public AjaxResult resume(@PathVariable Long assessmentId) { - PsyAssessment assessment = new PsyAssessment(); - assessment.setAssessmentId(assessmentId); + PsyAssessment assessment = assessmentService.selectAssessmentById(assessmentId); + if (assessment == null) + { + return error("测评不存在"); + } + + // 检查权限:管理员可以访问所有测评,学员只能访问自己的测评 + if (!hasAccessPermission(assessment)) + { + return error("无权访问该测评"); + } + assessment.setUpdateBy(SecurityUtils.getUsername()); return toAjax(assessmentService.resumeAssessment(assessment)); } /** * 获取测评的题目列表 + * 允许管理员和学员访问(学员只能访问自己的测评) */ @GetMapping("/items/{assessmentId}") public AjaxResult getItems(@PathVariable Long assessmentId) @@ -187,26 +256,59 @@ public class PsyAssessmentController extends BaseController return error("测评不存在"); } + // 检查权限:管理员可以访问所有测评,学员只能访问自己的测评 + if (!hasAccessPermission(assessment)) + { + return error("无权访问该测评"); + } + List items = itemService.selectItemListByScaleId(assessment.getScaleId()); return success(items); } /** * 获取测评的答案列表 + * 允许管理员和学员访问(学员只能访问自己的测评) */ @GetMapping("/answers/{assessmentId}") public AjaxResult getAnswers(@PathVariable Long assessmentId) { + PsyAssessment assessment = assessmentService.selectAssessmentById(assessmentId); + if (assessment == null) + { + return error("测评不存在"); + } + + // 检查权限:管理员可以访问所有测评,学员只能访问自己的测评 + if (!hasAccessPermission(assessment)) + { + return error("无权访问该测评"); + } + List answers = answerService.selectAnswerListByAssessmentId(assessmentId); return success(answers); } /** * 保存答案 + * 允许管理员和学员访问(学员只能保存自己测评的答案) */ @PostMapping("/answer") public AjaxResult saveAnswer(@Validated @RequestBody AssessmentAnswerVO answerVO) { + // 检查测评是否存在 + PsyAssessment assessment = assessmentService.selectAssessmentById(answerVO.getAssessmentId()); + if (assessment == null) + { + return error("测评不存在"); + } + + // 检查权限:管理员可以访问所有测评,学员只能访问自己的测评 + if (!hasAccessPermission(assessment)) + { + return error("无权访问该测评"); + } + PsyAssessmentAnswer answer = new PsyAssessmentAnswer(); answer.setAssessmentId(answerVO.getAssessmentId()); answer.setItemId(answerVO.getItemId()); @@ -229,6 +331,7 @@ public class PsyAssessmentController extends BaseController /** * 提交测评 + * 允许管理员和学员访问(学员只能提交自己的测评) */ @PostMapping("/submit/{assessmentId}") public AjaxResult submit(@PathVariable Long assessmentId) @@ -240,6 +343,12 @@ public class PsyAssessmentController extends BaseController { return error("测评不存在"); } + + // 检查权限:管理员可以访问所有测评,学员只能访问自己的测评 + if (!hasAccessPermission(assessment)) + { + return error("无权访问该测评"); + } if (!"0".equals(assessment.getStatus()) && !"3".equals(assessment.getStatus())) { @@ -280,5 +389,52 @@ public class PsyAssessmentController extends BaseController return error("提交失败:" + e.getMessage()); } } + + /** + * 检查用户是否有权限访问该测评 + * 管理员可以访问所有测评,学员只能访问自己的测评 + */ + private boolean hasAccessPermission(PsyAssessment assessment) + { + try + { + // 获取当前登录用户ID + Long currentUserId = SecurityUtils.getUserId(); + + // 如果是管理员(userId=1),可以访问所有测评 + if (currentUserId != null && currentUserId.equals(1L)) + { + return true; + } + + // 检查是否有管理员权限 + try + { + if (SecurityUtils.hasPermi("psychology:assessment:query")) + { + return true; + } + } + catch (Exception e) + { + // 权限检查失败,继续检查是否是自己的测评 + } + + // 学员只能访问自己的测评(通过 userId 匹配) + if (assessment.getUserId() != null && currentUserId != null) + { + return assessment.getUserId().equals(currentUserId); + } + + // 如果测评没有 userId 或当前用户没有 userId,拒绝访问 + return false; + } + catch (Exception e) + { + logger.warn("检查测评访问权限失败: {}", e.getMessage()); + // 如果获取用户信息失败,拒绝访问 + return false; + } + } } diff --git a/ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyQrcodeController.java b/ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyQrcodeController.java index 8e99c1a2..12f69a32 100644 --- a/ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyQrcodeController.java +++ b/ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyQrcodeController.java @@ -21,6 +21,7 @@ import com.ddnai.common.core.domain.AjaxResult; import com.ddnai.common.core.page.TableDataInfo; import com.ddnai.common.enums.BusinessType; import com.ddnai.common.utils.StringUtils; +import com.ddnai.common.utils.ServletUtils; import com.ddnai.common.utils.poi.ExcelUtil; import com.ddnai.system.domain.psychology.PsyQrcode; import com.ddnai.system.service.psychology.IPsyQrcodeService; @@ -96,6 +97,7 @@ public class PsyQrcodeController extends BaseController /** * 根据二维码ID获取详细信息 + * 注意:此方法必须放在所有具体路径之后,避免路径冲突 */ @PreAuthorize("@ss.hasPermi('psychology:qrcode:query')") @GetMapping(value = "/{qrcodeId}") @@ -118,7 +120,25 @@ public class PsyQrcodeController extends BaseController { if (qrcode != null) { - String qrcodeBase64 = qrcodeService.generateQrcode(qrcode); + // 构建实际服务器地址(使用当前请求的服务器地址) + String serverAddress = buildServerAddress(); + // 确保serverAddress不包含端口号(如果是30081)或双斜杠 + if (serverAddress != null && !serverAddress.isEmpty()) + { + // 移除可能存在的30081端口(无论出现在哪里) + serverAddress = serverAddress.replace(":30081", ""); + // 移除末尾的斜杠 + if (serverAddress.endsWith("/")) + { + serverAddress = serverAddress.substring(0, serverAddress.length() - 1); + } + } + else + { + // 如果无法获取服务器地址,使用默认值(本地开发环境) + serverAddress = "http://localhost"; + } + String qrcodeBase64 = qrcodeService.generateQrcode(qrcode, serverAddress); if (StringUtils.isNotEmpty(qrcodeBase64)) { qrcode.setQrcodeUrl("data:image/png;base64," + qrcodeBase64); @@ -130,6 +150,196 @@ public class PsyQrcodeController extends BaseController } } + /** + * 构建服务器地址(使用当前请求的服务器地址,但用于前端页面访问) + * 二维码URL应该指向前端页面,而不是后端API + * + * 本地环境:前端运行在80端口,后端运行在30081端口 + * 二维码URL应该使用前端地址(localhost或本机IP,80端口) + * + * 注意:如果使用手机扫码,需要使用本机局域网IP地址(如192.168.x.x),而不是localhost + * + * @return 服务器地址(如:http://localhost 或 http://192.168.1.100) + */ + private String buildServerAddress() + { + javax.servlet.http.HttpServletRequest request = ServletUtils.getRequest(); + if (request != null) + { + String scheme = request.getScheme(); // http 或 https + String serverName = request.getServerName(); // 主机名或IP + int serverPort = request.getServerPort(); // 后端端口(通常是30081) + + // 优先从Referer头获取前端地址 + String referer = request.getHeader("Referer"); + if (referer != null && !referer.isEmpty()) + { + try + { + java.net.URL refererUrl = new java.net.URL(referer); + String refererHost = refererUrl.getHost(); + int refererPort = refererUrl.getPort(); + String refererScheme = refererUrl.getProtocol(); + + // 如果Referer的端口是80、443或-1(默认端口),说明是前端地址 + if (refererPort == -1 || refererPort == 80 || refererPort == 443) + { + StringBuilder url = new StringBuilder(); + url.append(refererScheme).append("://").append(refererHost); + // 标准端口不显示端口号 + String result = url.toString(); + + // 如果结果是localhost,尝试获取本机局域网IP(用于手机扫码) + if ("localhost".equals(refererHost) || "127.0.0.1".equals(refererHost)) + { + String localIp = getLocalNetworkIp(); + if (localIp != null && !localIp.isEmpty()) + { + result = refererScheme + "://" + localIp; + } + } + + return result; + } + } + catch (Exception e) + { + // 解析失败,继续使用默认逻辑 + } + } + + // 如果没有Referer或解析失败,使用请求的服务器地址 + // 但如果是后端端口(30081),改为前端端口(80,不显示) + StringBuilder url = new StringBuilder(); + url.append(scheme).append("://").append(serverName); + + // 如果是后端端口(30081),不显示端口号(默认使用80端口,前端地址) + // 本地环境:前端运行在80端口,后端运行在30081端口 + // 二维码URL应该使用前端地址(80端口),而不是后端地址(30081端口) + if (serverPort == 30081) + { + // 前端运行在80端口,不显示端口号 + // 本地环境:localhost:30081 -> localhost(前端80端口) + // 不追加端口号,表示使用默认的80端口 + } + else if (serverPort == 80 || serverPort == 443) + { + // 已经是标准端口,不显示端口号 + } + else + { + // 非标准端口,显示端口号 + url.append(":").append(serverPort); + } + + String result = url.toString(); + // 再次确保不包含30081端口(防止遗漏) + result = result.replace(":30081", ""); + + // 如果结果是localhost,尝试获取本机局域网IP(用于手机扫码) + if (result.contains("localhost") || result.contains("127.0.0.1")) + { + String localIp = getLocalNetworkIp(); + if (localIp != null && !localIp.isEmpty()) + { + // 替换localhost为局域网IP + result = result.replace("localhost", localIp); + result = result.replace("127.0.0.1", localIp); + } + } + + return result; + } + + // 如果无法获取请求信息,尝试获取本机局域网IP + String localIp = getLocalNetworkIp(); + if (localIp != null && !localIp.isEmpty()) + { + return "http://" + localIp; + } + + // 如果无法获取局域网IP,返回默认值(前端地址,本地开发环境) + return "http://localhost"; + } + + /** + * 获取本机局域网IP地址(用于手机扫码) + * + * @return 局域网IP地址(如:192.168.1.100),如果无法获取则返回null + */ + private String getLocalNetworkIp() + { + try + { + java.util.Enumeration networkInterfaces = + java.net.NetworkInterface.getNetworkInterfaces(); + + while (networkInterfaces.hasMoreElements()) + { + java.net.NetworkInterface networkInterface = networkInterfaces.nextElement(); + + // 跳过回环接口和未启用的接口 + if (networkInterface.isLoopback() || !networkInterface.isUp()) + { + continue; + } + + java.util.Enumeration addresses = + networkInterface.getInetAddresses(); + + while (addresses.hasMoreElements()) + { + java.net.InetAddress address = addresses.nextElement(); + + // 只返回IPv4地址,且不是回环地址 + if (address instanceof java.net.Inet4Address && !address.isLoopbackAddress()) + { + String ip = address.getHostAddress(); + + // 优先返回局域网IP(192.168.x.x, 10.x.x.x, 172.16-31.x.x) + if (ip.startsWith("192.168.") || + ip.startsWith("10.") || + (ip.startsWith("172.") && + Integer.parseInt(ip.split("\\.")[1]) >= 16 && + Integer.parseInt(ip.split("\\.")[1]) <= 31)) + { + return ip; + } + } + } + } + + // 如果没有找到局域网IP,返回第一个非回环的IPv4地址 + networkInterfaces = java.net.NetworkInterface.getNetworkInterfaces(); + while (networkInterfaces.hasMoreElements()) + { + java.net.NetworkInterface networkInterface = networkInterfaces.nextElement(); + if (networkInterface.isLoopback() || !networkInterface.isUp()) + { + continue; + } + + java.util.Enumeration addresses = + networkInterface.getInetAddresses(); + + while (addresses.hasMoreElements()) + { + java.net.InetAddress address = addresses.nextElement(); + if (address instanceof java.net.Inet4Address && !address.isLoopbackAddress()) + { + return address.getHostAddress(); + } + } + } + } + catch (Exception e) + { + logger.warn("获取本机局域网IP失败: {}", e.getMessage()); + } + + return null; + } + /** * 新增二维码 */ @@ -248,6 +458,9 @@ public class PsyQrcodeController extends BaseController // 构建跳转地址 String redirectUrl = buildRedirectUrl(qrcode); + // 构建完整的跳转URL(包含域名和端口) + String fullRedirectUrl = buildFullRedirectUrl(redirectUrl); + // 只返回必要的跳转信息,不返回Base64数据以提升性能 java.util.Map data = new java.util.HashMap<>(); // 创建一个简化的二维码信息对象,不包含Base64数据 @@ -260,6 +473,7 @@ public class PsyQrcodeController extends BaseController qrcodeInfo.put("scanCount", qrcode.getScanCount()); data.put("qrcode", qrcodeInfo); data.put("redirectUrl", redirectUrl); + data.put("fullRedirectUrl", fullRedirectUrl); return AjaxResult.success(data); } @@ -269,6 +483,65 @@ public class PsyQrcodeController extends BaseController } } + /** + * 构建完整的跳转URL(包含域名和端口) + * 注意:跳转URL应该指向前端页面,而不是后端API + * + * @param redirectUrl 相对路径 + * @return 完整URL + */ + private String buildFullRedirectUrl(String redirectUrl) + { + if (redirectUrl == null || redirectUrl.isEmpty()) + { + return redirectUrl; + } + + // 如果已经是完整URL,直接返回 + if (redirectUrl.startsWith("http://") || redirectUrl.startsWith("https://")) + { + return redirectUrl; + } + + // 使用与二维码生成相同的逻辑,构建前端地址 + String serverAddress = buildServerAddress(); + + // 确保serverAddress不包含端口号(如果是30081)或双斜杠 + if (serverAddress != null && !serverAddress.isEmpty()) + { + // 移除可能存在的30081端口(无论出现在哪里) + serverAddress = serverAddress.replace(":30081", ""); + // 移除末尾的斜杠 + if (serverAddress.endsWith("/")) + { + serverAddress = serverAddress.substring(0, serverAddress.length() - 1); + } + } + else + { + // 如果无法获取服务器地址,使用默认值(本地开发环境) + serverAddress = "http://localhost"; + } + + // 构建完整URL + StringBuilder fullUrl = new StringBuilder(); + fullUrl.append(serverAddress); + + // 添加相对路径,确保以/开头 + String redirect = redirectUrl; + if (!redirect.startsWith("/")) + { + redirect = "/" + redirect; + } + fullUrl.append(redirect); + + // 替换可能出现的双斜杠为单斜杠(除了http://或https://) + String result = fullUrl.toString(); + result = result.replaceAll("([^:])//+", "$1/"); + + return result; + } + /** * 构建跳转地址 * @@ -480,5 +753,6 @@ public class PsyQrcodeController extends BaseController return error("生成二维码失败:" + e.getMessage()); } } + } diff --git a/ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyScaleController.java b/ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyScaleController.java index 01f9075a..91df8a8c 100644 --- a/ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyScaleController.java +++ b/ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyScaleController.java @@ -50,8 +50,9 @@ public class PsyScaleController extends BaseController /** * 获取量表列表(包含问卷) + * 允许管理员和学员访问 */ - @PreAuthorize("@ss.hasPermi('psychology:scale:list')") + @PreAuthorize("@ss.hasPermi('psychology:scale:list') or @ss.hasAnyRoles('student')") @GetMapping("/list") public TableDataInfo list(PsyScale scale, @RequestParam(required = false, defaultValue = "true") Boolean includeQuestionnaire) { diff --git a/ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyScalePermissionController.java b/ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyScalePermissionController.java index e30be9cb..7aebcc79 100644 --- a/ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyScalePermissionController.java +++ b/ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyScalePermissionController.java @@ -144,19 +144,90 @@ public class PsyScalePermissionController extends BaseController public AjaxResult assignUserScales(@RequestBody java.util.Map params) { try { - Long userId = Long.valueOf(params.get("userId").toString()); - @SuppressWarnings("unchecked") - List scaleIdList = (List) params.get("scaleIds"); - Long[] scaleIds = scaleIdList != null ? scaleIdList.toArray(new Long[0]) : new Long[0]; + // 参数验证 + if (params == null) { + return error("请求参数不能为空"); + } + + Object userIdObj = params.get("userId"); + if (userIdObj == null) { + return error("用户ID不能为空"); + } + + Long userId; + try { + if (userIdObj instanceof Number) { + userId = ((Number) userIdObj).longValue(); + } else { + userId = Long.valueOf(userIdObj.toString()); + } + } catch (NumberFormatException e) { + logger.error("用户ID格式错误: {}", userIdObj, e); + return error("用户ID格式错误: " + userIdObj); + } + + // 处理量表ID列表 + Object scaleIdsObj = params.get("scaleIds"); + Long[] scaleIds = new Long[0]; + + if (scaleIdsObj != null) { + if (scaleIdsObj instanceof java.util.List) { + @SuppressWarnings("unchecked") + java.util.List scaleIdList = (java.util.List) scaleIdsObj; + java.util.List longList = new java.util.ArrayList<>(); + for (Object item : scaleIdList) { + if (item != null) { + try { + if (item instanceof Number) { + longList.add(((Number) item).longValue()); + } else { + longList.add(Long.valueOf(item.toString())); + } + } catch (NumberFormatException e) { + logger.warn("跳过无效的量表ID: {}", item); + } + } + } + scaleIds = longList.toArray(new Long[0]); + } else if (scaleIdsObj instanceof Object[]) { + Object[] array = (Object[]) scaleIdsObj; + java.util.List longList = new java.util.ArrayList<>(); + for (Object item : array) { + if (item != null) { + try { + if (item instanceof Number) { + longList.add(((Number) item).longValue()); + } else { + longList.add(Long.valueOf(item.toString())); + } + } catch (NumberFormatException e) { + logger.warn("跳过无效的量表ID: {}", item); + } + } + } + scaleIds = longList.toArray(new Long[0]); + } + } + + logger.info("分配权限 - 用户ID: {}, 量表数量: {}", userId, scaleIds.length); + int result = permissionService.batchAssignUserScalePermission(userId, scaleIds); - if (result > 0) { + + // 如果 scaleIds 为空,表示移除所有权限,这也是合法的操作 + if (scaleIds.length == 0) { + return success("已移除该用户的所有量表权限"); + } else if (result > 0) { return success("分配权限成功,共分配 " + result + " 个权限"); } else { - return error("分配权限失败,可能所有量表都不存在"); + return error("分配权限失败,可能所有量表都不存在或已被删除"); } } catch (Exception e) { logger.error("分配权限失败", e); - return error("分配权限失败:" + e.getMessage()); + String errorMsg = e.getMessage(); + if (errorMsg == null || errorMsg.isEmpty()) { + errorMsg = e.getClass().getSimpleName(); + } + return error("分配权限失败:" + errorMsg); } } } diff --git a/ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyUserProfileController.java b/ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyUserProfileController.java index e1fac377..f186ba73 100644 --- a/ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyUserProfileController.java +++ b/ry-xinli-admin/src/main/java/com/ddnai/web/controller/psychology/PsyUserProfileController.java @@ -15,9 +15,16 @@ import org.springframework.web.bind.annotation.RestController; import com.ddnai.common.annotation.Log; import com.ddnai.common.core.controller.BaseController; import com.ddnai.common.core.domain.AjaxResult; +import com.ddnai.common.core.domain.entity.SysUser; import com.ddnai.common.core.page.TableDataInfo; import com.ddnai.common.enums.BusinessType; +import com.ddnai.common.utils.SecurityUtils; +import com.ddnai.common.utils.StringUtils; import com.ddnai.system.domain.psychology.PsyUserProfile; +import com.ddnai.system.service.ISysDeptService; +import com.ddnai.system.service.ISysPostService; +import com.ddnai.system.service.ISysRoleService; +import com.ddnai.system.service.ISysUserService; import com.ddnai.system.service.psychology.IPsyUserProfileService; @@ -32,7 +39,136 @@ public class PsyUserProfileController extends BaseController { @Autowired private IPsyUserProfileService profileService; + + @Autowired + private ISysUserService userService; + + @Autowired + private ISysRoleService roleService; + + @Autowired + private ISysDeptService deptService; + + @Autowired + private ISysPostService postService; + // ==================== 用户管理相关接口(使用 /manage/user/ 前缀,完全避免路径冲突) ==================== + + /** + * 获取用户信息(用于创建用户对话框) + */ + @PreAuthorize("@ss.hasPermi('psychology:profile:add')") + @GetMapping("/manage/user/create/info") + public AjaxResult getUserInfo() + { + AjaxResult ajax = AjaxResult.success(); + ajax.put("roles", roleService.selectRoleAll()); + ajax.put("posts", postService.selectPostAll()); + return ajax; + } + + /** + * 根据用户ID获取用户信息(用于修改用户) + */ + @PreAuthorize("@ss.hasPermi('psychology:profile:edit')") + @GetMapping("/manage/user/edit/{userId}") + public AjaxResult getUserInfoById(@PathVariable Long userId) + { + AjaxResult ajax = AjaxResult.success(); + SysUser sysUser = userService.selectUserById(userId); + userService.checkUserDataScope(userId); + ajax.put(AjaxResult.DATA_TAG, sysUser); + ajax.put("postIds", postService.selectPostListByUserId(userId)); + ajax.put("roleIds", sysUser.getRoles().stream().map(r -> r.getRoleId()).collect(java.util.stream.Collectors.toList())); + ajax.put("roles", roleService.selectRoleAll()); + ajax.put("posts", postService.selectPostAll()); + return ajax; + } + + /** + * 根据用户ID获取档案信息 + */ + @PreAuthorize("@ss.hasPermi('psychology:profile:query')") + @GetMapping("/manage/user/profile/{userId}") + public AjaxResult getInfoByUserId(@PathVariable Long userId) + { + return success(profileService.selectProfileByUserId(userId)); + } + + /** + * 创建用户(复用系统用户管理的逻辑) + */ + @PreAuthorize("@ss.hasPermi('psychology:profile:add')") + @Log(title = "用户档案-创建用户", businessType = BusinessType.INSERT) + @PostMapping("/manage/user/create") + public AjaxResult addUser(@Validated @RequestBody SysUser user) + { + // 复用系统用户管理的创建逻辑 + deptService.checkDeptDataScope(user.getDeptId()); + roleService.checkRoleDataScope(user.getRoleIds()); + if (!userService.checkUserNameUnique(user)) + { + return error("新增用户'" + user.getUserName() + "'失败,登录账号已存在"); + } + else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) + { + return error("新增用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) + { + return error("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + user.setCreateBy(getUsername()); + user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); + return toAjax(userService.insertUser(user)); + } + + /** + * 修改用户(复用系统用户管理的逻辑) + */ + @PreAuthorize("@ss.hasPermi('psychology:profile:edit')") + @Log(title = "用户档案-修改用户", businessType = BusinessType.UPDATE) + @PutMapping("/manage/user/update") + public AjaxResult updateUser(@Validated @RequestBody SysUser user) + { + // 复用系统用户管理的修改逻辑 + userService.checkUserAllowed(user); + userService.checkUserDataScope(user.getUserId()); + deptService.checkDeptDataScope(user.getDeptId()); + roleService.checkRoleDataScope(user.getRoleIds()); + if (!userService.checkUserNameUnique(user)) + { + return error("修改用户'" + user.getUserName() + "'失败,登录账号已存在"); + } + else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) + { + return error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) + { + return error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + user.setUpdateBy(getUsername()); + return toAjax(userService.updateUser(user)); + } + + /** + * 删除用户(复用系统用户管理的逻辑) + */ + @PreAuthorize("@ss.hasPermi('psychology:profile:remove')") + @Log(title = "用户档案-删除用户", businessType = BusinessType.DELETE) + @DeleteMapping("/manage/user/delete/{userIds}") + public AjaxResult removeUser(@PathVariable Long[] userIds) + { + if (java.util.Arrays.asList(userIds).contains(getUserId())) + { + return error("当前用户不能删除"); + } + return toAjax(userService.deleteUserByIds(userIds)); + } + + // ==================== 档案管理相关接口 ==================== + /** * 获取档案列表 */ @@ -45,26 +181,6 @@ public class PsyUserProfileController extends BaseController return getDataTable(list); } - /** - * 根据档案ID获取详细信息 - */ - @PreAuthorize("@ss.hasPermi('psychology:profile:query')") - @GetMapping(value = "/{profileId}") - public AjaxResult getInfo(@PathVariable Long profileId) - { - return success(profileService.selectProfileById(profileId)); - } - - /** - * 根据用户ID获取档案信息 - */ - @PreAuthorize("@ss.hasPermi('psychology:profile:query')") - @GetMapping(value = "/user/{userId}") - public AjaxResult getInfoByUserId(@PathVariable Long userId) - { - return success(profileService.selectProfileByUserId(userId)); - } - /** * 新增档案 */ @@ -89,6 +205,19 @@ public class PsyUserProfileController extends BaseController return toAjax(profileService.updateProfile(profile)); } + // ==================== 通配符路径(必须放在最后) ==================== + + /** + * 根据档案ID获取详细信息 + * 注意:此路径必须放在所有具体路径之后,作为兜底路径 + */ + @PreAuthorize("@ss.hasPermi('psychology:profile:query')") + @GetMapping(value = "/{profileId}") + public AjaxResult getInfo(@PathVariable Long profileId) + { + return success(profileService.selectProfileById(profileId)); + } + /** * 删除档案 */ @@ -100,4 +229,3 @@ public class PsyUserProfileController extends BaseController return toAjax(profileService.deleteProfileByIds(profileIds)); } } - diff --git a/ry-xinli-admin/src/main/java/com/ddnai/web/controller/system/SysLoginController.java b/ry-xinli-admin/src/main/java/com/ddnai/web/controller/system/SysLoginController.java index 67b09f04..8882c286 100644 --- a/ry-xinli-admin/src/main/java/com/ddnai/web/controller/system/SysLoginController.java +++ b/ry-xinli-admin/src/main/java/com/ddnai/web/controller/system/SysLoginController.java @@ -163,13 +163,29 @@ public class SysLoginController return AjaxResult.error("学员编号不能为空"); } - // 根据学员编号查找用户(使用user_name作为学员编号) - SysUser user = userService.selectUserByUserName(studentNo.trim()); + // 根据学员编号查找用户 + // 支持两种方式:1. 通过user_name查找 2. 如果是数字,通过user_id查找 + String studentNoTrimmed = studentNo.trim(); + SysUser user = userService.selectUserByUserName(studentNoTrimmed); + + // 如果通过user_name找不到,且输入的是数字,尝试通过user_id查找 + if (user == null && StringUtils.isNumeric(studentNoTrimmed)) + { + try + { + Long userId = Long.parseLong(studentNoTrimmed); + user = userService.selectUserById(userId); + } + catch (NumberFormatException e) + { + // 数字格式错误,忽略 + } + } // 检查系统有效时间(系统管理员不受限制) try { - loginService.validateSystemExpireTime(studentNo, user != null ? user.getUserId() : null); + loginService.validateSystemExpireTime(studentNoTrimmed, user != null ? user.getUserId() : null); } catch (ServiceException e) { diff --git a/ry-xinli-framework/src/main/java/com/ddnai/framework/web/exception/GlobalExceptionHandler.java b/ry-xinli-framework/src/main/java/com/ddnai/framework/web/exception/GlobalExceptionHandler.java index 22e0a0f5..868c9c74 100644 --- a/ry-xinli-framework/src/main/java/com/ddnai/framework/web/exception/GlobalExceptionHandler.java +++ b/ry-xinli-framework/src/main/java/com/ddnai/framework/web/exception/GlobalExceptionHandler.java @@ -14,6 +14,7 @@ import org.springframework.web.method.annotation.MethodArgumentTypeMismatchExcep import com.ddnai.common.constant.HttpStatus; import com.ddnai.common.core.domain.AjaxResult; import com.ddnai.common.core.text.Convert; +import java.sql.SQLIntegrityConstraintViolationException; import com.ddnai.common.exception.DemoModeException; import com.ddnai.common.exception.ServiceException; import com.ddnai.common.utils.StringUtils; @@ -142,5 +143,60 @@ public class GlobalExceptionHandler { return AjaxResult.error("演示模式,不允许操作"); } + + /** + * SQL完整性约束违反异常(外键约束等) + */ + @ExceptionHandler(SQLIntegrityConstraintViolationException.class) + public AjaxResult handleSQLIntegrityConstraintViolationException(SQLIntegrityConstraintViolationException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生数据库约束违反异常.", requestURI, e); + + String errorMessage = e.getMessage(); + // 解析错误信息,提供更友好的提示 + if (errorMessage != null) + { + if (errorMessage.contains("foreign key constraint fails")) + { + // 外键约束失败 + if (errorMessage.contains("fk_profile_user")) + { + return AjaxResult.error("用户不存在,无法创建档案。请先在用户管理中创建该用户,或使用\"创建用户\"功能创建用户"); + } + return AjaxResult.error("数据关联错误,请检查关联的数据是否存在"); + } + else if (errorMessage.contains("Duplicate entry")) + { + // 唯一约束违反 + return AjaxResult.error("数据已存在,无法重复创建"); + } + } + + return AjaxResult.error("数据库操作失败:" + (errorMessage != null ? errorMessage : "未知错误")); + } + + /** + * 处理 MyBatis 包装的 SQL 异常 + */ + @ExceptionHandler(org.apache.ibatis.exceptions.PersistenceException.class) + public AjaxResult handlePersistenceException(org.apache.ibatis.exceptions.PersistenceException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生持久化异常.", requestURI, e); + + Throwable cause = e.getCause(); + // 查找 SQLIntegrityConstraintViolationException + while (cause != null) + { + if (cause instanceof SQLIntegrityConstraintViolationException) + { + return handleSQLIntegrityConstraintViolationException((SQLIntegrityConstraintViolationException) cause, request); + } + cause = cause.getCause(); + } + + return AjaxResult.error("数据库操作失败:" + e.getMessage()); + } } diff --git a/ry-xinli-framework/src/main/java/com/ddnai/framework/web/service/SysLoginService.java b/ry-xinli-framework/src/main/java/com/ddnai/framework/web/service/SysLoginService.java index afe682a1..52cfb2ce 100644 --- a/ry-xinli-framework/src/main/java/com/ddnai/framework/web/service/SysLoginService.java +++ b/ry-xinli-framework/src/main/java/com/ddnai/framework/web/service/SysLoginService.java @@ -68,8 +68,8 @@ public class SysLoginService */ public String login(String username, String password, String code, String uuid) { - // 验证码校验 - validateCaptcha(username, code, uuid); + // 验证码校验 - 已禁用验证码功能 + // validateCaptcha(username, code, uuid); // 登录前置校验 loginPreCheck(username, password); // 用户验证 diff --git a/ry-xinli-system/src/main/java/com/ddnai/system/domain/psychology/PsyUserProfile.java b/ry-xinli-system/src/main/java/com/ddnai/system/domain/psychology/PsyUserProfile.java index 0371d310..c1f45722 100644 --- a/ry-xinli-system/src/main/java/com/ddnai/system/domain/psychology/PsyUserProfile.java +++ b/ry-xinli-system/src/main/java/com/ddnai/system/domain/psychology/PsyUserProfile.java @@ -60,6 +60,15 @@ public class PsyUserProfile extends BaseEntity /** 病史 */ private String medicalHistory; + /** 用户状态(0正常 1停用) */ + private String status; + + /** 部门ID */ + private Long deptId; + + /** 部门名称 */ + private String deptName; + public Long getProfileId() { return profileId; @@ -210,6 +219,36 @@ public class PsyUserProfile extends BaseEntity this.phone = phone; } + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + public String getDeptName() + { + return deptName; + } + + public void setDeptName(String deptName) + { + this.deptName = deptName; + } + @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) diff --git a/ry-xinli-system/src/main/java/com/ddnai/system/service/impl/psychology/PsyQrcodeServiceImpl.java b/ry-xinli-system/src/main/java/com/ddnai/system/service/impl/psychology/PsyQrcodeServiceImpl.java index e14fc8fb..bbaf8816 100644 --- a/ry-xinli-system/src/main/java/com/ddnai/system/service/impl/psychology/PsyQrcodeServiceImpl.java +++ b/ry-xinli-system/src/main/java/com/ddnai/system/service/impl/psychology/PsyQrcodeServiceImpl.java @@ -221,11 +221,24 @@ public class PsyQrcodeServiceImpl implements IPsyQrcodeService */ @Override public String generateQrcode(PsyQrcode qrcode) + { + return generateQrcode(qrcode, null); + } + + /** + * 生成二维码(使用指定的服务器地址) + * + * @param qrcode 二维码信息 + * @param serverAddress 服务器地址(完整URL,如:http://192.168.1.100:30081) + * @return 二维码图片Base64 + */ + @Override + public String generateQrcode(PsyQrcode qrcode, String serverAddress) { try { // 构建二维码内容URL - String content = buildQrcodeContent(qrcode); + String content = buildQrcodeContent(qrcode, serverAddress); if (StringUtils.isEmpty(content)) { @@ -248,28 +261,75 @@ public class PsyQrcodeServiceImpl implements IPsyQrcodeService * @return URL字符串 */ private String buildQrcodeContent(PsyQrcode qrcode) + { + return buildQrcodeContent(qrcode, null); + } + + /** + * 构建二维码内容URL + * + * @param qrcode 二维码信息 + * @param serverAddress 服务器地址(完整URL,如:http://192.168.1.100:30081,如果为null则使用配置的地址) + * @return URL字符串 + */ + private String buildQrcodeContent(PsyQrcode qrcode, String serverAddress) { if (StringUtils.isEmpty(qrcode.getShortUrl())) { // 如果没有短链接,构建完整URL - // 由于Service层无法直接获取HTTP请求信息,使用配置的服务器地址 StringBuilder url = new StringBuilder(); - // 判断是HTTP还是HTTPS(可以通过配置获取,这里默认HTTP) - url.append("http://"); - url.append(serverAddress); - url.append(":"); - url.append(serverPort); - - if (StringUtils.isNotEmpty(contextPath)) + if (StringUtils.isNotEmpty(serverAddress)) { - url.append(contextPath); + // 使用传入的服务器地址,确保不以斜杠结尾 + String address = serverAddress; + if (address.endsWith("/")) + { + address = address.substring(0, address.length() - 1); + } + url.append(address); + } + else + { + // 使用配置的服务器地址(兼容旧代码) + url.append("http://"); + url.append(this.serverAddress); + url.append(":"); + url.append(serverPort); } + // 添加上下文路径(如果有),确保路径拼接正确 + if (StringUtils.isNotEmpty(contextPath)) + { + String path = contextPath.trim(); + // 如果contextPath是"/",忽略它(避免产生双斜杠) + if (!"/".equals(path)) + { + // 确保contextPath以/开头,不以/结尾 + if (!path.startsWith("/")) + { + path = "/" + path; + } + if (path.endsWith("/")) + { + path = path.substring(0, path.length() - 1); + } + url.append(path); + } + } + + // 添加扫描路径,确保以/开头 url.append("/psychology/qrcode/scan/"); url.append(qrcode.getQrcodeCode()); - return url.toString(); + // 替换可能出现的双斜杠为单斜杠(除了http://或https://) + String result = url.toString(); + // 先移除30081端口(防止遗漏) + result = result.replace(":30081", ""); + // 替换所有双斜杠或多斜杠为单斜杠(除了http://或https://) + result = result.replaceAll("([^:])//+", "$1/"); + + return result; } else { diff --git a/ry-xinli-system/src/main/java/com/ddnai/system/service/impl/psychology/PsyScalePermissionServiceImpl.java b/ry-xinli-system/src/main/java/com/ddnai/system/service/impl/psychology/PsyScalePermissionServiceImpl.java index ec2ffcc1..727a39d1 100644 --- a/ry-xinli-system/src/main/java/com/ddnai/system/service/impl/psychology/PsyScalePermissionServiceImpl.java +++ b/ry-xinli-system/src/main/java/com/ddnai/system/service/impl/psychology/PsyScalePermissionServiceImpl.java @@ -1,6 +1,8 @@ package com.ddnai.system.service.impl.psychology; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -18,6 +20,8 @@ import com.ddnai.system.service.psychology.IPsyScaleService; @Service public class PsyScalePermissionServiceImpl implements IPsyScalePermissionService { + private static final Logger log = LoggerFactory.getLogger(PsyScalePermissionServiceImpl.class); + @Autowired private PsyScalePermissionMapper permissionMapper; @@ -174,30 +178,59 @@ public class PsyScalePermissionServiceImpl implements IPsyScalePermissionService @Transactional public int batchAssignUserScalePermission(Long userId, Long[] scaleIds) { + if (userId == null) { + throw new IllegalArgumentException("用户ID不能为空"); + } + // 先删除该用户的所有现有权限 - permissionMapper.deletePermissionByUserId(userId); + try { + permissionMapper.deletePermissionByUserId(userId); + } catch (Exception e) { + log.error("删除用户现有权限失败,userId: {}", userId, e); + throw new RuntimeException("删除用户现有权限失败: " + (e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName()), e); + } // 批量插入新权限 int count = 0; if (scaleIds != null && scaleIds.length > 0) { String username = SecurityUtils.getUsername(); + if (username == null || username.isEmpty()) { + username = "system"; // 如果获取不到用户名,使用默认值 + } + for (Long scaleId : scaleIds) { - // 验证 scale_id 是否存在 - com.ddnai.system.domain.psychology.PsyScale scale = scaleService.selectScaleById(scaleId); - if (scale == null) - { - System.err.println("警告:量表不存在,跳过该权限分配,scaleId: " + scaleId); - continue; // 跳过不存在的量表 + if (scaleId == null) { + log.warn("跳过空的量表ID"); + continue; } - PsyScalePermission permission = new PsyScalePermission(); - permission.setUserId(userId); - permission.setScaleId(scaleId); - permission.setStatus("0"); - permission.setCreateBy(username); - count += permissionMapper.insertPermission(permission); + try { + // 验证 scale_id 是否存在 + com.ddnai.system.domain.psychology.PsyScale scale = scaleService.selectScaleById(scaleId); + if (scale == null) + { + log.warn("量表不存在,跳过该权限分配,scaleId: {}", scaleId); + continue; // 跳过不存在的量表 + } + + PsyScalePermission permission = new PsyScalePermission(); + permission.setUserId(userId); + permission.setScaleId(scaleId); + permission.setStatus("0"); + permission.setCreateBy(username); + + int insertResult = permissionMapper.insertPermission(permission); + if (insertResult > 0) { + count++; + } else { + log.warn("插入权限失败,userId: {}, scaleId: {}", userId, scaleId); + } + } catch (Exception e) { + log.error("处理量表权限失败,userId: {}, scaleId: {}", userId, scaleId, e); + // 继续处理下一个,不中断整个流程 + } } } return count; diff --git a/ry-xinli-system/src/main/java/com/ddnai/system/service/impl/psychology/PsyUserProfileServiceImpl.java b/ry-xinli-system/src/main/java/com/ddnai/system/service/impl/psychology/PsyUserProfileServiceImpl.java index d5b9b0e5..cf9abcad 100644 --- a/ry-xinli-system/src/main/java/com/ddnai/system/service/impl/psychology/PsyUserProfileServiceImpl.java +++ b/ry-xinli-system/src/main/java/com/ddnai/system/service/impl/psychology/PsyUserProfileServiceImpl.java @@ -1,10 +1,18 @@ package com.ddnai.system.service.impl.psychology; +import java.sql.SQLIntegrityConstraintViolationException; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; import org.springframework.stereotype.Service; +import com.ddnai.common.core.domain.entity.SysUser; +import com.ddnai.common.exception.ServiceException; +import com.ddnai.common.utils.StringUtils; import com.ddnai.system.domain.psychology.PsyUserProfile; import com.ddnai.system.mapper.psychology.PsyUserProfileMapper; +import com.ddnai.system.service.ISysUserService; import com.ddnai.system.service.psychology.IPsyUserProfileService; /** @@ -15,8 +23,13 @@ import com.ddnai.system.service.psychology.IPsyUserProfileService; @Service public class PsyUserProfileServiceImpl implements IPsyUserProfileService { + private static final Logger log = LoggerFactory.getLogger(PsyUserProfileServiceImpl.class); + @Autowired private PsyUserProfileMapper profileMapper; + + @Autowired + private ISysUserService userService; /** * 查询档案信息 @@ -49,6 +62,7 @@ public class PsyUserProfileServiceImpl implements IPsyUserProfileService * @return 档案集合 */ @Override + @com.ddnai.common.annotation.DataScope(deptAlias = "d", userAlias = "u") public List selectProfileList(PsyUserProfile profile) { return profileMapper.selectProfileList(profile); @@ -63,7 +77,110 @@ public class PsyUserProfileServiceImpl implements IPsyUserProfileService @Override public int insertProfile(PsyUserProfile profile) { - return profileMapper.insertProfile(profile); + log.info("开始创建用户档案,userId: {}", profile.getUserId()); + + // 验证用户ID是否为空 + if (profile.getUserId() == null) + { + log.error("创建用户档案失败:用户ID为空"); + throw new ServiceException("用户ID不能为空"); + } + + Long userId = profile.getUserId(); + log.debug("验证用户是否存在,userId: {}", userId); + + // 验证用户是否存在 + SysUser user = userService.selectUserById(userId); + if (user == null) + { + log.error("创建用户档案失败:用户ID {} 不存在", userId); + throw new ServiceException("用户ID " + userId + " 不存在,无法创建档案。请先在用户管理中创建该用户"); + } + + log.debug("用户存在,userName: {}, delFlag: {}", user.getUserName(), user.getDelFlag()); + + // 检查用户是否已被删除 + if ("2".equals(user.getDelFlag())) + { + log.error("创建用户档案失败:用户ID {} 已被删除", userId); + throw new ServiceException("用户已被删除,无法创建档案"); + } + + // 检查该用户是否已经存在档案(表中user_id有唯一约束) + PsyUserProfile existingProfile = profileMapper.selectProfileByUserId(userId); + if (existingProfile != null) + { + log.error("创建用户档案失败:用户ID {} 已存在档案,profileId: {}", userId, existingProfile.getProfileId()); + throw new ServiceException("用户ID " + userId + " 已存在档案,无法重复创建。请使用修改功能更新档案"); + } + + log.info("验证通过,开始插入用户档案,userId: {}", userId); + try + { + int result = profileMapper.insertProfile(profile); + log.info("用户档案创建成功,userId: {}, profileId: {}", userId, profile.getProfileId()); + return result; + } + catch (Exception e) + { + log.error("插入用户档案时发生异常,userId: {}", userId, e); + + // 检查异常消息中是否包含外键约束错误 + String errorMessage = e.getMessage(); + Throwable cause = e.getCause(); + Throwable rootCause = e; + + // 查找根本原因异常 + while (rootCause.getCause() != null && rootCause.getCause() != rootCause) + { + rootCause = rootCause.getCause(); + } + + // 检查当前异常和原因异常的消息 + while (errorMessage == null && cause != null) + { + errorMessage = cause.getMessage(); + cause = cause.getCause(); + } + + // 如果根原因是 SQLIntegrityConstraintViolationException,提供友好提示 + if (rootCause instanceof SQLIntegrityConstraintViolationException) + { + log.error("检测到SQL完整性约束违反异常,userId: {}", userId); + throw new ServiceException("用户ID " + userId + " 不存在,无法创建档案。请先在用户管理中创建该用户,或使用\"创建用户\"功能创建用户"); + } + + // 如果是 DataAccessException,检查其根本原因 + if (e instanceof DataAccessException) + { + Throwable daeCause = e.getCause(); + while (daeCause != null) + { + if (daeCause instanceof SQLIntegrityConstraintViolationException) + { + log.error("检测到DataAccessException包装的SQL完整性约束违反异常,userId: {}", userId); + throw new ServiceException("用户ID " + userId + " 不存在,无法创建档案。请先在用户管理中创建该用户,或使用\"创建用户\"功能创建用户"); + } + daeCause = daeCause.getCause(); + } + } + + // 如果是外键约束异常,提供更友好的提示 + if (errorMessage != null && errorMessage.contains("foreign key constraint")) + { + log.error("检测到外键约束错误,userId: {}, 错误信息: {}", userId, errorMessage); + throw new ServiceException("用户ID " + userId + " 不存在,无法创建档案。请先在用户管理中创建该用户,或使用\"创建用户\"功能创建用户"); + } + + // 如果是 SQLIntegrityConstraintViolationException,也提供友好提示 + if (e instanceof SQLIntegrityConstraintViolationException) + { + log.error("检测到SQL完整性约束违反异常,userId: {}", userId); + throw new ServiceException("用户ID " + userId + " 不存在,无法创建档案。请先在用户管理中创建该用户,或使用\"创建用户\"功能创建用户"); + } + + throw e; + } } /** @@ -75,6 +192,25 @@ public class PsyUserProfileServiceImpl implements IPsyUserProfileService @Override public int updateProfile(PsyUserProfile profile) { + // 验证用户ID是否为空 + if (profile.getUserId() == null) + { + throw new ServiceException("用户ID不能为空"); + } + + // 验证用户是否存在 + SysUser user = userService.selectUserById(profile.getUserId()); + if (user == null) + { + throw new ServiceException("用户ID " + profile.getUserId() + " 不存在,无法更新档案"); + } + + // 检查用户是否已被删除 + if ("2".equals(user.getDelFlag())) + { + throw new ServiceException("用户已被删除,无法更新档案"); + } + return profileMapper.updateProfile(profile); } diff --git a/ry-xinli-system/src/main/java/com/ddnai/system/service/psychology/IPsyQrcodeService.java b/ry-xinli-system/src/main/java/com/ddnai/system/service/psychology/IPsyQrcodeService.java index f176cbbf..6512b951 100644 --- a/ry-xinli-system/src/main/java/com/ddnai/system/service/psychology/IPsyQrcodeService.java +++ b/ry-xinli-system/src/main/java/com/ddnai/system/service/psychology/IPsyQrcodeService.java @@ -90,5 +90,14 @@ public interface IPsyQrcodeService * @return 二维码图片Base64 */ public String generateQrcode(PsyQrcode qrcode); + + /** + * 生成二维码(使用指定的服务器地址) + * + * @param qrcode 二维码信息 + * @param serverAddress 服务器地址(如:http://192.168.1.100:30081) + * @return 二维码图片Base64 + */ + public String generateQrcode(PsyQrcode qrcode, String serverAddress); } diff --git a/ry-xinli-system/src/main/resources/mapper/system/psychology/PsyUserProfileMapper.xml b/ry-xinli-system/src/main/resources/mapper/system/psychology/PsyUserProfileMapper.xml index 146be80b..c259e946 100644 --- a/ry-xinli-system/src/main/resources/mapper/system/psychology/PsyUserProfileMapper.xml +++ b/ry-xinli-system/src/main/resources/mapper/system/psychology/PsyUserProfileMapper.xml @@ -20,6 +20,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + + @@ -31,29 +34,64 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" select p.profile_id, p.user_id, p.profile_type, p.profile_data, p.avatar, p.id_card, p.birthday, p.education, p.occupation, p.address, p.emergency_contact, p.emergency_phone, p.medical_history, p.create_by, p.create_time, p.update_by, p.update_time, p.remark, - u.user_name, u.phonenumber as phone - from psy_user_profile p - left join sys_user u on p.user_id = u.user_id + u.user_name, u.phonenumber as phone, u.nick_name, u.email, u.sex, u.status, u.dept_id, u.create_time as user_create_time + diff --git a/xinli-ui/src/api/psychology/permission.js b/xinli-ui/src/api/psychology/permission.js index 6fd429fa..aa0bcc4e 100644 --- a/xinli-ui/src/api/psychology/permission.js +++ b/xinli-ui/src/api/psychology/permission.js @@ -67,12 +67,31 @@ export function assignUserScales(userId, scaleIds) { if (!userId || userId === 'undefined' || userId === 'null' || isNaN(userId)) { return Promise.reject(new Error('用户ID无效')); } + // 确保 userId 是数字类型 + const numUserId = typeof userId === 'number' ? userId : parseInt(userId); + if (isNaN(numUserId)) { + return Promise.reject(new Error('用户ID格式错误')); + } + // 确保 scaleIds 是数字数组 + let numScaleIds = []; + if (scaleIds && Array.isArray(scaleIds)) { + numScaleIds = scaleIds.map(id => { + if (typeof id === 'number') { + return id; + } else if (typeof id === 'string') { + const numId = parseInt(id); + return isNaN(numId) ? null : numId; + } else { + return null; + } + }).filter(id => id !== null); + } return request({ url: '/psychology/permission/assign', method: 'post', data: { - userId: userId, - scaleIds: scaleIds || [] + userId: numUserId, + scaleIds: numScaleIds } }) } diff --git a/xinli-ui/src/api/psychology/profile.js b/xinli-ui/src/api/psychology/profile.js index b6cba324..8daab987 100644 --- a/xinli-ui/src/api/psychology/profile.js +++ b/xinli-ui/src/api/psychology/profile.js @@ -51,3 +51,45 @@ export function delProfile(profileId) { }) } +// 获取用户信息(用于创建用户对话框) +export function getUserInfo() { + return request({ + url: '/psychology/profile/manage/user/create/info', + method: 'get' + }) +} + +// 创建用户(在用户档案中) +export function addUserInProfile(data) { + return request({ + url: '/psychology/profile/manage/user/create', + method: 'post', + data: data + }) +} + +// 根据用户ID获取用户信息(用于修改用户) +export function getUserInfoById(userId) { + return request({ + url: '/psychology/profile/manage/user/edit/' + userId, + method: 'get' + }) +} + +// 修改用户(在用户档案中) +export function updateUserInProfile(data) { + return request({ + url: '/psychology/profile/manage/user/update', + method: 'put', + data: data + }) +} + +// 删除用户(在用户档案中) +export function delUserInProfile(userIds) { + return request({ + url: '/psychology/profile/manage/user/delete/' + (Array.isArray(userIds) ? userIds.join(',') : userIds), + method: 'delete' + }) +} + diff --git a/xinli-ui/src/api/psychology/qrcode.js b/xinli-ui/src/api/psychology/qrcode.js index 283e4f95..dd9c11a9 100644 --- a/xinli-ui/src/api/psychology/qrcode.js +++ b/xinli-ui/src/api/psychology/qrcode.js @@ -94,3 +94,19 @@ export function getQrcodeImage(qrcodeId) { }) } +// 生成注册二维码 +export function generateRegisterQrcode() { + return request({ + url: '/psychology/qrcode/quick/register', + method: 'post' + }) +} + +// 生成登录二维码 +export function generateLoginQrcode() { + return request({ + url: '/psychology/qrcode/quick/login', + method: 'post' + }) +} + diff --git a/xinli-ui/src/store/modules/user.js b/xinli-ui/src/store/modules/user.js index 6595cd73..63205561 100644 --- a/xinli-ui/src/store/modules/user.js +++ b/xinli-ui/src/store/modules/user.js @@ -66,8 +66,9 @@ const user = { Login({ commit }, userInfo) { const username = userInfo.username.trim() const password = userInfo.password - const code = userInfo.code - const uuid = userInfo.uuid + // 验证码功能已禁用,传递空字符串 + const code = userInfo.code || "" + const uuid = userInfo.uuid || "" return new Promise((resolve, reject) => { login(username, password, code, uuid).then(res => { setToken(res.token) diff --git a/xinli-ui/src/views/login.vue b/xinli-ui/src/views/login.vue index 9ada5473..dfab5209 100644 --- a/xinli-ui/src/views/login.vue +++ b/xinli-ui/src/views/login.vue @@ -60,35 +60,6 @@ - - - - - - 记住密码 { - this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled - if (this.captchaEnabled) { - if (res.img && res.uuid) { - this.codeUrl = "data:image/gif;base64," + res.img - this.loginForm.uuid = res.uuid - } else { - console.warn('验证码数据不完整:', res) - this.codeUrl = "" - this.$message.warning('验证码数据不完整,请重试') - } - } else { - // 如果后端返回验证码已禁用,清空验证码相关数据 - this.codeUrl = "" - this.loginForm.code = "" - this.loginForm.uuid = "" - } - }).catch(error => { - console.error('获取验证码失败:', error) - // 即使获取失败,也保持验证码输入框显示,让用户可以重试 - this.captchaEnabled = true - this.codeUrl = "" - this.loginForm.code = "" - this.loginForm.uuid = "" - this.$message.error('获取验证码失败,请点击验证码区域重试') - }) }, // 获取Cookie中保存的登录信息 getCookie() { @@ -292,9 +220,6 @@ export default { this.$router.push({ path: this.redirect || "/" }).catch(()=>{}) }).catch(() => { this.loading = false - if (this.captchaEnabled) { - this.getCode() - } }) } else { // 学员登录 @@ -361,37 +286,6 @@ export default { text-align: center; color: #bfbfbf; } -.login-code { - width: 33%; - height: 38px; - float: right; - img { - cursor: pointer; - vertical-align: middle; - } -} -.login-code-placeholder { - width: 100%; - height: 38px; - border: 1px solid #dcdfe6; - border-radius: 4px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - background-color: #f5f7fa; - color: #909399; - font-size: 12px; - transition: all 0.3s; - i { - margin-right: 4px; - } - &:hover { - background-color: #ecf5ff; - border-color: #409EFF; - color: #409EFF; - } -} .el-login-footer { height: 40px; line-height: 40px; @@ -404,9 +298,6 @@ export default { font-size: 12px; letter-spacing: 1px; } -.login-code-img { - height: 38px; -} .login-btn { width: 70% !important; margin: 0 auto; diff --git a/xinli-ui/src/views/psychology/permission/user.vue b/xinli-ui/src/views/psychology/permission/user.vue index 2cc4b75a..f2594fb8 100644 --- a/xinli-ui/src/views/psychology/permission/user.vue +++ b/xinli-ui/src/views/psychology/permission/user.vue @@ -111,10 +111,13 @@ export default { } this.loading = true; assignUserScales(this.userId, this.selectedScaleIds).then(response => { - this.$modal.msgSuccess("分配成功"); + this.$modal.msgSuccess(response.msg || "分配成功"); this.loading = false; this.handleBack(); - }).catch(() => { + }).catch(error => { + console.error("分配权限失败:", error); + const errorMsg = error && error.msg ? error.msg : (error && error.message ? error.message : "分配权限失败,请稍后重试"); + this.$modal.msgError(errorMsg); this.loading = false; }); }, diff --git a/xinli-ui/src/views/psychology/profile/index.vue b/xinli-ui/src/views/psychology/profile/index.vue index 3467888e..9ac4a6f1 100644 --- a/xinli-ui/src/views/psychology/profile/index.vue +++ b/xinli-ui/src/views/psychology/profile/index.vue @@ -1,6 +1,14 @@