diff --git a/.kiro/specs/user-enterprise-identity-verification/tasks.md b/.kiro/specs/user-enterprise-identity-verification/tasks.md index 59f30d7..3249a1f 100644 --- a/.kiro/specs/user-enterprise-identity-verification/tasks.md +++ b/.kiro/specs/user-enterprise-identity-verification/tasks.md @@ -359,14 +359,14 @@ - _Requirements: 3.2, 9.5_ - [ ] 18. 前端Vue3界面实现 - 企业认证管理页面 - - [ ] 18.1 创建企业认证管理页面 + - [x] 18.1 创建企业认证管理页面 - 创建企业认证列表组件 - 创建认证筛选组件 - 创建企业认证详情对话框(显示审核历史和审核人角色) - 创建批量审核对话框 - _Requirements: 6.1, 6.2, 6.3, 6.6, 9.6, 10.6_ - - [ ] 18.2 实现企业认证管理功能 + - [x] 18.2 实现企业认证管理功能 - 实现企业认证列表查询和分页 - 实现认证状态筛选 - 实现企业认证搜索(按用户姓名、企业名称、企业代码) @@ -386,8 +386,8 @@ - [ ] 19. Checkpoint - 确保前端所有功能正常 - 确保所有前端功能正常,如有问题请向用户询问 -- [ ] 20. 数据安全和权限加固 - - [ ] 20.1 实现数据访问权限控制 +- [x] 20. 数据安全和权限加固 + - [x] 20.1 实现数据访问权限控制 - 在Service层添加权限检查 - 记录敏感数据访问日志 - 验证用户只能访问自己的认证数据(管理员除外) @@ -403,22 +403,22 @@ - 测试审核日志记录角色信息 - _Requirements: 10.4, 10.5, 10.6_ - - [ ] 20.4 配置HTTPS和安全头 + - [x] 20.4 配置HTTPS和安全头 - 配置HTTPS证书 - 配置安全响应头(HSTS, CSP等) - _Requirements: 7.2_ -- [ ] 21. 性能优化 - - [ ] 21.1 实现认证状态缓存 +- [x] 21. 性能优化 + - [x] 21.1 实现认证状态缓存 - 使用Redis缓存用户认证状态 - 设置合理的缓存过期时间 - 认证状态变更时清除缓存 - - [ ] 21.2 实现二维码图片缓存 + - [x] 21.2 实现二维码图片缓存 - 缓存生成的二维码图片 - 设置合理的缓存过期时间 - - [ ] 21.3 优化数据库查询 + - [x] 21.3 优化数据库查询 - 添加必要的索引 - 优化复杂查询的SQL diff --git a/RuoYi-Vue3/src/api/system/tenant.js b/RuoYi-Vue3/src/api/system/tenant.js new file mode 100644 index 0000000..eaae023 --- /dev/null +++ b/RuoYi-Vue3/src/api/system/tenant.js @@ -0,0 +1,68 @@ +import request from '@/utils/request' + +// 查询租户列表 +export function listTenant(query) { + return request({ + url: '/system/tenant/list', + method: 'get', + params: query + }) +} + +// 查询租户详细 +export function getTenant(tenantId) { + return request({ + url: '/system/tenant/info/' + tenantId, + method: 'get' + }) +} + +// 根据租户编码查询租户 +export function getTenantByCode(tenantCode) { + return request({ + url: '/system/tenant/code/' + tenantCode, + method: 'get' + }) +} + +// 新增租户 +export function addTenant(data) { + return request({ + url: '/system/tenant', + method: 'post', + data: data + }) +} + +// 修改租户 +export function updateTenant(data) { + return request({ + url: '/system/tenant', + method: 'put', + data: data + }) +} + +// 删除租户 +export function delTenant(tenantId) { + return request({ + url: '/system/tenant/' + tenantId, + method: 'delete' + }) +} + +// 冻结租户 +export function freezeTenant(tenantId) { + return request({ + url: '/system/tenant/freeze/' + tenantId, + method: 'put' + }) +} + +// 激活租户 +export function activeTenant(tenantId) { + return request({ + url: '/system/tenant/active/' + tenantId, + method: 'put' + }) +} diff --git a/RuoYi-Vue3/src/api/system/verification.js b/RuoYi-Vue3/src/api/system/verification.js index 7a4599f..533bfbe 100644 --- a/RuoYi-Vue3/src/api/system/verification.js +++ b/RuoYi-Vue3/src/api/system/verification.js @@ -65,3 +65,96 @@ export function getUserVerificationStatus() { method: 'get' }) } + +// ===== 企业认证管理接口(管理员使用)===== + +/** + * 查询企业认证列表(管理员) + * @param {Object} params - 查询参数 + * @param {string} params.userName - 用户姓名 + * @param {string} params.enterpriseName - 企业名称 + * @param {string} params.enterpriseCode - 企业代码 + * @param {string} params.verificationStatus - 认证状态 + * @param {number} params.pageNum - 页码 + * @param {number} params.pageSize - 每页数量 + * @returns {Promise} + */ +export function listEnterpriseVerifications(params) { + return request({ + url: '/system/user/verification/enterprise/list', + method: 'get', + params + }) +} + +/** + * 查询企业认证详情(含审核历史) + * @param {number} verificationId - 认证ID + * @returns {Promise} + */ +export function getEnterpriseVerificationDetail(verificationId) { + return request({ + url: `/system/user/verification/enterprise/${verificationId}`, + method: 'get' + }) +} + +/** + * 导出企业认证数据 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function exportEnterpriseVerifications(params) { + return request({ + url: '/system/user/verification/enterprise/export', + method: 'get', + params, + responseType: 'blob' + }) +} + +// ===== 身份认证管理接口(管理员使用)===== + +/** + * 查询身份认证列表(管理员) + * @param {Object} params - 查询参数 + * @param {string} params.userName - 用户姓名 + * @param {string} params.realName - 真实姓名 + * @param {string} params.verificationStatus - 认证状态 + * @param {number} params.pageNum - 页码 + * @param {number} params.pageSize - 每页数量 + * @returns {Promise} + */ +export function listIdentityVerifications(params) { + return request({ + url: '/system/user/verification/identity/list', + method: 'get', + params + }) +} + +/** + * 查询身份认证详情(含审核历史) + * @param {number} verificationId - 认证ID + * @returns {Promise} + */ +export function getIdentityVerificationDetail(verificationId) { + return request({ + url: `/system/user/verification/identity/${verificationId}`, + method: 'get' + }) +} + +/** + * 导出身份认证数据 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function exportIdentityVerifications(params) { + return request({ + url: '/system/user/verification/identity/export', + method: 'get', + params, + responseType: 'blob' + }) +} diff --git a/RuoYi-Vue3/src/views/digitalCredit/employee/index.vue b/RuoYi-Vue3/src/views/digitalCredit/employee/index.vue index 8138d12..83deca2 100644 --- a/RuoYi-Vue3/src/views/digitalCredit/employee/index.vue +++ b/RuoYi-Vue3/src/views/digitalCredit/employee/index.vue @@ -161,6 +161,14 @@ 其他 + + + @@ -451,6 +459,12 @@ + + 已认证 + 认证失败 + 待认证 + 未认证 + {{ detailData.libraryName || '暂无' }} {{ formatDateTime(detailData.createTime) }} {{ formatDateTime(detailData.updateTime) }} @@ -942,7 +956,8 @@ function handleView(row) { isTemporary: employeeData.isTemporary || false, hireDate: employeeData.hireDate, createTime: employeeData.createTime, - updateTime: employeeData.updateTime + updateTime: employeeData.updateTime, + verificationStatus: employeeData.verificationStatus || null }; detailOpen.value = true; @@ -1573,6 +1588,9 @@ function handleQRCodeDialogClose() { // 清空数据 qrCodeData.value = {}; currentEmployeeForQRCode.value = null; + + // 刷新列表以更新实名认证状态 + getList(); } onMounted(() => { diff --git a/RuoYi-Vue3/src/views/system/tenant/index.vue b/RuoYi-Vue3/src/views/system/tenant/index.vue new file mode 100644 index 0000000..112dfce --- /dev/null +++ b/RuoYi-Vue3/src/views/system/tenant/index.vue @@ -0,0 +1,327 @@ + + + diff --git a/RuoYi-Vue3/src/views/system/verification/enterprise.vue b/RuoYi-Vue3/src/views/system/verification/enterprise.vue new file mode 100644 index 0000000..1e62fd0 --- /dev/null +++ b/RuoYi-Vue3/src/views/system/verification/enterprise.vue @@ -0,0 +1,192 @@ + + + diff --git a/RuoYi-Vue3/src/views/system/verification/identity.vue b/RuoYi-Vue3/src/views/system/verification/identity.vue new file mode 100644 index 0000000..4bf53f6 --- /dev/null +++ b/RuoYi-Vue3/src/views/system/verification/identity.vue @@ -0,0 +1,194 @@ + + + diff --git a/docker/configs/nginx.conf.prod b/docker/configs/nginx.conf.prod index 58e8f5b..7581948 100644 --- a/docker/configs/nginx.conf.prod +++ b/docker/configs/nginx.conf.prod @@ -1,20 +1,76 @@ -# Nginx配置文件 - 生产环境 (HTTP版本) +# Nginx配置文件 - 生产环境 (HTTPS版本) # 用于Vue3前端静态文件服务和API代理 +# +# HTTPS证书配置说明: +# 1. 将SSL证书文件放置在 /etc/nginx/ssl/ 目录下 +# 2. 证书文件: /etc/nginx/ssl/server.crt +# 3. 私钥文件: /etc/nginx/ssl/server.key +# 4. 如使用Let's Encrypt,路径为 /etc/letsencrypt/live// +# HTTP -> HTTPS 重定向 server { listen 80; server_name _; - + + # 健康检查端点(不重定向,供负载均衡器使用) + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + # 所有其他HTTP请求重定向到HTTPS + location / { + return 301 https://$host$request_uri; + } +} + +# HTTPS主服务 +server { + listen 443 ssl http2; + server_name _; + + # SSL证书配置 + ssl_certificate /etc/nginx/ssl/server.crt; + ssl_certificate_key /etc/nginx/ssl/server.key; + + # TLS协议版本(仅允许TLS 1.2和1.3) + ssl_protocols TLSv1.2 TLSv1.3; + + # 强密码套件 + ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256'; + ssl_prefer_server_ciphers on; + + # SSL会话缓存 + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + ssl_session_tickets off; + + # OCSP Stapling(需要CA证书链) + # ssl_stapling on; + # ssl_stapling_verify on; + # ssl_trusted_certificate /etc/nginx/ssl/chain.crt; + # 生产环境日志配置 access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log error; - - # 安全头配置 - add_header X-Frame-Options DENY; - add_header X-Content-Type-Options nosniff; - add_header X-XSS-Protection "1; mode=block"; - add_header Referrer-Policy "strict-origin-when-cross-origin"; - + + # 安全响应头 + # HTTP Strict Transport Security (HSTS) - 强制HTTPS,有效期1年,包含子域名 + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; + # 禁止在iframe中嵌入(防止点击劫持) + add_header X-Frame-Options "SAMEORIGIN" always; + # 禁止MIME类型嗅探 + add_header X-Content-Type-Options "nosniff" always; + # XSS保护 + add_header X-XSS-Protection "1; mode=block" always; + # Referrer策略 + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + # 内容安全策略 + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'self'" always; + # 权限策略(禁用不需要的浏览器功能) + add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always; + # Gzip压缩配置 gzip on; gzip_vary on; @@ -31,26 +87,26 @@ server { application/xml+rss application/atom+xml image/svg+xml; - + # 静态文件服务 location / { root /usr/share/nginx/html; index index.html index.htm; try_files $uri $uri/ /index.html; - + # 生产环境缓存配置 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; access_log off; } - + location ~* \.(html)$ { expires 1h; add_header Cache-Control "public"; } } - + # API代理到后端服务 location /prod-api/ { proxy_pass http://anxin-backend:8080/; @@ -58,7 +114,7 @@ server { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - + # 生产环境代理配置 proxy_connect_timeout 10s; proxy_send_timeout 10s; @@ -66,33 +122,33 @@ server { proxy_buffering on; proxy_buffer_size 4k; proxy_buffers 8 4k; - + # 限制请求大小 client_max_body_size 5m; - + # 代理缓存配置 proxy_cache_bypass $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Upgrade $http_upgrade; } - + # 健康检查端点 location /health { access_log off; return 200 "healthy\n"; add_header Content-Type text/plain; } - + # 拒绝访问敏感文件 location ~ /\. { deny all; access_log off; log_not_found off; } - + location ~ ~$ { deny all; access_log off; log_not_found off; } -} \ No newline at end of file +} diff --git a/docker/configs/nginx.conf.staging b/docker/configs/nginx.conf.staging index 11e5db5..6f68072 100644 --- a/docker/configs/nginx.conf.staging +++ b/docker/configs/nginx.conf.staging @@ -9,11 +9,13 @@ server { access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log warn; - # 安全头配置 - add_header X-Frame-Options DENY; - add_header X-Content-Type-Options nosniff; - add_header X-XSS-Protection "1; mode=block"; - add_header Referrer-Policy "strict-origin-when-cross-origin"; + # 安全响应头 + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'self'" always; + add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always; # 静态文件服务 location / { diff --git a/docker/database/init/04-performance-indexes.sql b/docker/database/init/04-performance-indexes.sql new file mode 100644 index 0000000..3c58744 --- /dev/null +++ b/docker/database/init/04-performance-indexes.sql @@ -0,0 +1,63 @@ +-- ============================================================================ +-- 性能优化:认证相关表索引优化 +-- 创建日期: 2026-03-19 +-- 描述: 为认证相关表添加复合索引,优化高频查询性能 +-- ============================================================================ + +SET NAMES utf8mb4; + +-- ============================================================================ +-- 1. sys_user_enterprise_verification 索引优化 +-- ============================================================================ + +-- 复合索引:用户ID + 认证状态(isUserFullyVerified 和 getUserVerificationStatus 的核心查询) +-- 查询: WHERE user_id = ? ORDER BY create_time DESC LIMIT 1 +-- 查询: WHERE user_id = ? AND verification_status = 'APPROVED' +ALTER TABLE `sys_user_enterprise_verification` + ADD INDEX `idx_user_status` (`user_id`, `verification_status`) USING BTREE COMMENT '用户ID+认证状态复合索引,优化认证状态查询'; + +-- 创建时间索引(管理列表按时间排序) +ALTER TABLE `sys_user_enterprise_verification` + ADD INDEX `idx_create_time` (`create_time`) USING BTREE COMMENT '创建时间索引,优化时间范围查询'; + +-- 企业名称索引(管理页面按企业名称搜索) +ALTER TABLE `sys_user_enterprise_verification` + ADD INDEX `idx_enterprise_name` (`enterprise_name`(50)) USING BTREE COMMENT '企业名称前缀索引,优化模糊搜索'; + +-- ============================================================================ +-- 2. sys_user_identity_verification 索引优化 +-- ============================================================================ + +-- 复合索引:用户ID + 认证状态(isUserFullyVerified 的核心查询) +ALTER TABLE `sys_user_identity_verification` + ADD INDEX `idx_user_status` (`user_id`, `verification_status`) USING BTREE COMMENT '用户ID+认证状态复合索引,优化认证状态查询'; + +-- 创建时间索引(管理列表按时间排序) +ALTER TABLE `sys_user_identity_verification` + ADD INDEX `idx_create_time` (`create_time`) USING BTREE COMMENT '创建时间索引,优化时间范围查询'; + +-- ============================================================================ +-- 3. sys_verification_audit_log 索引优化 +-- ============================================================================ + +-- 复合索引:用户ID + 操作时间(查询某用户的审核历史,按时间排序) +ALTER TABLE `sys_verification_audit_log` + ADD INDEX `idx_user_time` (`user_id`, `operation_time`) USING BTREE COMMENT '用户ID+操作时间复合索引,优化用户审核历史查询'; + +-- ============================================================================ +-- 4. dc_employee_qr_code 索引优化 +-- ============================================================================ + +-- 复合索引:员工ID + 二维码状态(查询员工有效二维码) +ALTER TABLE `dc_employee_qr_code` + ADD INDEX `idx_employee_status` (`employee_id`, `qr_code_status`) USING BTREE COMMENT '员工ID+状态复合索引,优化员工有效二维码查询'; + +-- 复合索引:二维码状态 + 过期时间(定时清理过期二维码) +ALTER TABLE `dc_employee_qr_code` + ADD INDEX `idx_status_expiry` (`qr_code_status`, `expiry_time`) USING BTREE COMMENT '状态+过期时间复合索引,优化过期二维码清理查询'; + +-- ============================================================================ +-- 完成 +-- ============================================================================ + +SELECT '认证相关表性能索引优化完成!' AS message; diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java index 4f85f06..8eb40e5 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java @@ -20,6 +20,7 @@ import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.security.context.TenantContextHolder; import com.ruoyi.framework.web.service.SysLoginService; import com.ruoyi.framework.web.service.SysPermissionService; import com.ruoyi.framework.web.service.TokenService; @@ -56,7 +57,7 @@ public class SysLoginController /** * 登录方法 - * + * * @param loginBody 登录信息 * @return 结果 */ @@ -67,6 +68,10 @@ public class SysLoginController // 生成令牌 String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), loginBody.getUuid()); + + // 登录成功后设置租户上下文 + TenantContextHolder.setCurrentTenantId(TenantContextHolder.DEFAULT_TENANT_ID); + ajax.put(Constants.TOKEN, token); return ajax; } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysTenantController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysTenantController.java new file mode 100644 index 0000000..ac066f6 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysTenantController.java @@ -0,0 +1,123 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysTenant; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.system.service.ISysTenantService; + +/** + * 租户信息 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/tenant") +public class SysTenantController extends BaseController +{ + @Autowired + private ISysTenantService tenantService; + + /** + * 获取租户列表 + */ + @PreAuthorize("@ss.hasPermi('system:tenant:list')") + @GetMapping("/list") + public AjaxResult list(SysTenant tenant) + { + List list = tenantService.selectTenantList(tenant); + return success(list); + } + + /** + * 获取租户详情 + */ + @PreAuthorize("@ss.hasPermi('system:tenant:query')") + @GetMapping("/info/{tenantId}") + public AjaxResult getInfo(@PathVariable Long tenantId) + { + return success(tenantService.selectTenantById(tenantId)); + } + + /** + * 根据租户编码获取租户信息 + */ + @GetMapping("/code/{tenantCode}") + public AjaxResult getByCode(@PathVariable String tenantCode) + { + return success(tenantService.selectTenantByCode(tenantCode)); + } + + /** + * 新增租户 + */ + @PreAuthorize("@ss.hasPermi('system:tenant:add')") + @Log(title = "租户管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody SysTenant tenant) + { + return toAjax(tenantService.insertTenant(tenant)); + } + + /** + * 修改租户 + */ + @PreAuthorize("@ss.hasPermi('system:tenant:edit')") + @Log(title = "租户管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody SysTenant tenant) + { + return toAjax(tenantService.updateTenant(tenant)); + } + + /** + * 删除租户 + */ + @PreAuthorize("@ss.hasPermi('system:tenant:remove')") + @Log(title = "租户管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{tenantId}") + public AjaxResult remove(@PathVariable Long tenantId) + { + return toAjax(tenantService.deleteTenantById(tenantId)); + } + + /** + * 冻结租户 + */ + @PreAuthorize("@ss.hasPermi('system:tenant:edit')") + @Log(title = "租户管理", businessType = BusinessType.UPDATE) + @PutMapping("/freeze/{tenantId}") + public AjaxResult freeze(@PathVariable Long tenantId) + { + SysTenant tenant = new SysTenant(); + tenant.setTenantId(tenantId); + tenant.setStatus("FROZEN"); + return toAjax(tenantService.updateTenant(tenant)); + } + + /** + * 激活租户 + */ + @PreAuthorize("@ss.hasPermi('system:tenant:edit')") + @Log(title = "租户管理", businessType = BusinessType.UPDATE) + @PutMapping("/active/{tenantId}") + public AjaxResult active(@PathVariable Long tenantId) + { + SysTenant tenant = new SysTenant(); + tenant.setTenantId(tenantId); + tenant.setStatus("ACTIVE"); + return toAjax(tenantService.updateTenant(tenant)); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/UserVerificationController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/UserVerificationController.java index e95513b..c9233f2 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/UserVerificationController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/UserVerificationController.java @@ -1,7 +1,9 @@ package com.ruoyi.web.controller.system; import java.util.List; + import javax.servlet.http.HttpServletResponse; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.prepost.PreAuthorize; @@ -13,6 +15,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; + import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; @@ -75,7 +78,7 @@ public class UserVerificationController extends BaseController /** * 查询企业认证列表 */ - @PreAuthorize("hasAnyAuthority('system:verification:view:all', 'system:verification:manage')") + @PreAuthorize("@ss.hasAnyPermi('system:verification:view:all,system:verification:manage')") @GetMapping("/enterprise/list") public TableDataInfo listEnterpriseVerifications(UserEnterpriseVerification verification) { @@ -100,7 +103,7 @@ public class UserVerificationController extends BaseController /** * 查询企业认证详情 */ - @PreAuthorize("hasAnyAuthority('system:verification:view:all', 'system:verification:manage')") + @PreAuthorize("@ss.hasAnyPermi('system:verification:view:all,system:verification:manage')") @GetMapping("/enterprise/{verificationId}") public AjaxResult getEnterpriseVerificationDetail(@PathVariable Long verificationId) { @@ -135,7 +138,7 @@ public class UserVerificationController extends BaseController /** * 导出企业认证数据 */ - @PreAuthorize("hasAnyAuthority('system:verification:export', 'system:verification:manage')") + @PreAuthorize("@ss.hasAnyPermi('system:verification:export,system:verification:manage')") @Log(title = "企业认证", businessType = BusinessType.EXPORT) @PostMapping("/enterprise/export") public void exportEnterpriseVerifications(HttpServletResponse response, UserEnterpriseVerification verification) @@ -200,22 +203,87 @@ public class UserVerificationController extends BaseController /** * 查询身份认证列表 */ + @PreAuthorize("@ss.hasAnyPermi('system:verification:view:all,system:verification:manage') or #userId == null or #userId == authentication.principal.userId") @GetMapping("/identity/list") public TableDataInfo listIdentityVerifications( @RequestParam(required = false) Long userId, + @RequestParam(required = false) String realName, @RequestParam(required = false) String verificationStatus) { try { startPage(); - List list = - verificationService.listIdentityVerifications(userId, verificationStatus); + List list = + verificationService.listIdentityVerifications(userId, realName, verificationStatus); return getDataTable(list); } + catch (AccessDeniedException e) + { + logger.error("权限不足", e); + throw e; + } catch (Exception e) { logger.error("查询身份认证列表失败", e); return getDataTable(null); } } + + /** + * 查询身份认证详情 + */ + @PreAuthorize("@ss.hasAnyPermi('system:verification:view:all,system:verification:manage')") + @GetMapping("/identity/{verificationId}") + public AjaxResult getIdentityVerificationDetail(@PathVariable Long verificationId) + { + try + { + com.ruoyi.system.domain.UserIdentityVerification detail = + verificationService.getIdentityVerificationDetail(verificationId); + if (detail == null) + { + return AjaxResult.error("认证记录不存在"); + } + return AjaxResult.success(detail); + } + catch (AccessDeniedException e) + { + logger.error("权限不足", e); + throw e; + } + catch (Exception e) + { + logger.error("查询身份认证详情失败", e); + return AjaxResult.error("查询身份认证详情失败:" + e.getMessage()); + } + } + + /** + * 导出身份认证数据 + */ + @PreAuthorize("@ss.hasAnyPermi('system:verification:export,system:verification:manage')") + @Log(title = "身份认证", businessType = BusinessType.EXPORT) + @PostMapping("/identity/export") + public void exportIdentityVerifications(HttpServletResponse response, + @RequestParam(required = false) Long userId, + @RequestParam(required = false) String realName, + @RequestParam(required = false) String verificationStatus) + { + try + { + List list = + verificationService.listIdentityVerifications(userId, realName, verificationStatus); + ExcelUtil util = + new ExcelUtil<>(com.ruoyi.system.domain.UserIdentityVerification.class); + util.exportExcel(response, list, "身份认证数据"); + } + catch (AccessDeniedException e) + { + logger.error("权限不足", e); + } + catch (Exception e) + { + logger.error("导出身份认证数据失败", e); + } + } } diff --git a/ruoyi-admin/src/main/resources/application-https.yml b/ruoyi-admin/src/main/resources/application-https.yml new file mode 100644 index 0000000..164f10f --- /dev/null +++ b/ruoyi-admin/src/main/resources/application-https.yml @@ -0,0 +1,40 @@ +# HTTPS配置文件 +# 生产环境启用HTTPS时,在spring.profiles.active中添加 https +# 示例: spring.profiles.active: druid,api-security,https +# +# 使用前提: +# 1. 准备PKCS12格式的SSL证书 (keystore.p12) +# 2. 将证书文件放置在 src/main/resources/ 目录下,或指定绝对路径 +# 3. 通过环境变量 SSL_KEYSTORE_PASSWORD 设置证书密码 +# +# 生成自签名证书示例 (仅用于测试): +# keytool -genkeypair -alias ruoyi -keyalg RSA -keysize 2048 \ +# -storetype PKCS12 -keystore keystore.p12 -validity 3650 \ +# -storepass changeit -dname "CN=localhost, OU=RuoYi, O=RuoYi, L=Beijing, ST=Beijing, C=CN" + +server: + # HTTPS端口 + port: 8443 + ssl: + enabled: true + # 证书路径(支持classpath:或绝对路径) + key-store: classpath:keystore.p12 + # 证书密码(通过环境变量注入,避免明文存储) + key-store-password: ${SSL_KEYSTORE_PASSWORD:changeit} + key-store-type: PKCS12 + key-alias: ruoyi + # TLS协议版本(仅允许TLS 1.2和1.3) + protocol: TLS + enabled-protocols: + - TLSv1.2 + - TLSv1.3 + # 强密码套件(禁用弱加密算法) + ciphers: + - TLS_AES_256_GCM_SHA384 + - TLS_AES_128_GCM_SHA256 + - TLS_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + +# HTTP重定向到HTTPS的配置(需要同时启用http-redirect profile) +# 如需同时监听HTTP(80)并重定向到HTTPS(8443),请参考 HttpsRedirectConfig diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 3935949..4976c07 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -54,6 +54,13 @@ server: max: 800 # Tomcat启动初始化的线程数,默认值10 min-spare: 100 + # HTTPS配置(生产环境启用,取消注释并配置证书路径) + # ssl: + # enabled: true + # key-store: classpath:keystore.p12 + # key-store-password: ${SSL_KEYSTORE_PASSWORD} + # key-store-type: PKCS12 + # key-alias: ruoyi # 日志配置 logging: diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java index 0b3e169..2d5d72a 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java @@ -51,4 +51,38 @@ public class CacheConstants * 验证统计信息 redis key */ public static final String VALIDATION_STATS_KEY = "validation_stats:"; + + /** + * 用户认证状态缓存 redis key + * 格式: verification_status:{userId} + */ + public static final String VERIFICATION_STATUS_KEY = "verification_status:"; + + /** + * 用户企业认证缓存 redis key + * 格式: enterprise_verification:{userId} + */ + public static final String ENTERPRISE_VERIFICATION_KEY = "enterprise_verification:"; + + /** + * 用户身份认证缓存 redis key + * 格式: identity_verification:{userId} + */ + public static final String IDENTITY_VERIFICATION_KEY = "identity_verification:"; + + /** + * 员工二维码图片缓存 redis key + * 格式: qrcode_image:{qrCodeId} + */ + public static final String QRCODE_IMAGE_KEY = "qrcode_image:"; + + /** + * 认证状态缓存过期时间(分钟) + */ + public static final int VERIFICATION_STATUS_EXPIRE_MINUTES = 30; + + /** + * 二维码图片缓存过期时间(分钟) + */ + public static final int QRCODE_IMAGE_EXPIRE_MINUTES = 60; } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysTenant.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysTenant.java new file mode 100644 index 0000000..2dfc8b0 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysTenant.java @@ -0,0 +1,263 @@ +package com.ruoyi.common.core.domain.entity; + +import java.util.Date; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 租户表 sys_tenant + * + * @author ruoyi + */ +public class SysTenant extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 租户ID */ + @Excel(name = "租户序号") + private Long tenantId; + + /** 租户编码 */ + @Excel(name = "租户编码") + private String tenantCode; + + /** 租户名称 */ + @Excel(name = "租户名称") + private String tenantName; + + /** 租户类型 */ + @Excel(name = "租户类型") + private String tenantType; + + /** 公司类型 */ + @Excel(name = "公司类型", readConverterExp = "BANK=银行,CLIENT=甲方,LABOR=劳务公司") + private String companyType; + + /** 联系人 */ + @Excel(name = "联系人") + private String contactPerson; + + /** 联系电话 */ + @Excel(name = "联系电话") + private String contactPhone; + + /** 联系邮箱 */ + @Excel(name = "联系邮箱") + private String contactEmail; + + /** 租户域名 */ + private String domain; + + /** Logo URL */ + private String logoUrl; + + /** 状态 */ + @Excel(name = "状态", readConverterExp = "ACTIVE=正常,FROZEN=冻结,DELETED=已删除") + private String status; + + /** 最大用户数 */ + private Integer maxUsers; + + /** 最大公司数 */ + private Integer maxCompanies; + + /** 过期日期 */ + private Date expireDate; + + /** 套餐ID */ + private Long packageId; + + /** 备注 */ + private String remark; + + public Long getTenantId() + { + return tenantId; + } + + public void setTenantId(Long tenantId) + { + this.tenantId = tenantId; + } + + public String getTenantCode() + { + return tenantCode; + } + + public void setTenantCode(String tenantCode) + { + this.tenantCode = tenantCode; + } + + public String getTenantName() + { + return tenantName; + } + + public void setTenantName(String tenantName) + { + this.tenantName = tenantName; + } + + public String getTenantType() + { + return tenantType; + } + + public void setTenantType(String tenantType) + { + this.tenantType = tenantType; + } + + public String getCompanyType() + { + return companyType; + } + + public void setCompanyType(String companyType) + { + this.companyType = companyType; + } + + public String getContactPerson() + { + return contactPerson; + } + + public void setContactPerson(String contactPerson) + { + this.contactPerson = contactPerson; + } + + public String getContactPhone() + { + return contactPhone; + } + + public void setContactPhone(String contactPhone) + { + this.contactPhone = contactPhone; + } + + public String getContactEmail() + { + return contactEmail; + } + + public void setContactEmail(String contactEmail) + { + this.contactEmail = contactEmail; + } + + public String getDomain() + { + return domain; + } + + public void setDomain(String domain) + { + this.domain = domain; + } + + public String getLogoUrl() + { + return logoUrl; + } + + public void setLogoUrl(String logoUrl) + { + this.logoUrl = logoUrl; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public Integer getMaxUsers() + { + return maxUsers; + } + + public void setMaxUsers(Integer maxUsers) + { + this.maxUsers = maxUsers; + } + + public Integer getMaxCompanies() + { + return maxCompanies; + } + + public void setMaxCompanies(Integer maxCompanies) + { + this.maxCompanies = maxCompanies; + } + + public Date getExpireDate() + { + return expireDate; + } + + public void setExpireDate(Date expireDate) + { + this.expireDate = expireDate; + } + + public Long getPackageId() + { + return packageId; + } + + public void setPackageId(Long packageId) + { + this.packageId = packageId; + } + + @Override + public String getRemark() + { + return remark; + } + + @Override + public void setRemark(String remark) + { + this.remark = remark; + } + + @Override + public String toString() + { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("tenantId", getTenantId()) + .append("tenantCode", getTenantCode()) + .append("tenantName", getTenantName()) + .append("tenantType", getTenantType()) + .append("companyType", getCompanyType()) + .append("contactPerson", getContactPerson()) + .append("contactPhone", getContactPhone()) + .append("contactEmail", getContactEmail()) + .append("domain", getDomain()) + .append("logoUrl", getLogoUrl()) + .append("status", getStatus()) + .append("maxUsers", getMaxUsers()) + .append("maxCompanies", getMaxCompanies()) + .append("expireDate", getExpireDate()) + .append("packageId", getPackageId()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java index e13a435..57b38b8 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java @@ -68,6 +68,9 @@ public class SysUser extends BaseEntity /** 删除标志(0代表存在 2代表删除) */ private String delFlag; + /** 租户ID */ + private Long tenantId; + /** 最后登录IP */ @Excel(name = "最后登录IP", type = Type.EXPORT) private String loginIp; @@ -246,6 +249,16 @@ public class SysUser extends BaseEntity this.delFlag = delFlag; } + public Long getTenantId() + { + return tenantId; + } + + public void setTenantId(Long tenantId) + { + this.tenantId = tenantId; + } + public String getLoginIp() { return loginIp; diff --git a/ruoyi-credit/src/main/java/com/ruoyi/credit/config/ValidationRuleConfig.java b/ruoyi-credit/src/main/java/com/ruoyi/credit/config/ValidationRuleConfig.java index 89d0264..a596d2b 100644 --- a/ruoyi-credit/src/main/java/com/ruoyi/credit/config/ValidationRuleConfig.java +++ b/ruoyi-credit/src/main/java/com/ruoyi/credit/config/ValidationRuleConfig.java @@ -1,13 +1,32 @@ package com.ruoyi.credit.config; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.ruoyi.credit.domain.ValidationRule; -import com.ruoyi.credit.mapper.ValidationRuleMapper; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import javax.annotation.PostConstruct; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; import org.springframework.core.io.Resource; @@ -15,16 +34,9 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; -import javax.annotation.PostConstruct; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.*; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ruoyi.credit.domain.ValidationRule; +import com.ruoyi.credit.mapper.ValidationRuleMapper; /** * 验证规则配置管理类 @@ -91,14 +103,32 @@ public class ValidationRuleConfig { private volatile String configVersion = "1.0.0"; /** - * 初始化配置 + * 初始化配置(仅加载配置文件,不访问数据库) */ @PostConstruct public void init() { log.info("初始化验证规则配置管理器..."); - // 加载初始配置 - loadValidationRules(); + // 仅从配置文件加载,不访问数据库(数据库在 ApplicationReadyEvent 后才安全访问) + loadRulesFromConfigFile(); + rebuildCacheIndexes(); + + log.info("验证规则配置管理器初始化完成(配置文件),当前加载规则数量: {}", ruleCache.size()); + } + + /** + * 应用启动完成后加载数据库规则并启动热更新 + */ + @EventListener(ApplicationReadyEvent.class) + public void onApplicationReady() { + log.info("应用启动完成,开始从数据库加载验证规则..."); + + // 数据库连接池就绪后再加载 + loadRulesFromDatabase(); + rebuildCacheIndexes(); + + // 同步配置文件规则到数据库 + syncDatabaseRules(); // 启动热更新机制 if (properties.getHotReload().isEnabled()) { @@ -110,16 +140,7 @@ public class ValidationRuleConfig { startFileWatch(); } - log.info("验证规则配置管理器初始化完成,当前加载规则数量: {}", ruleCache.size()); - } - - /** - * 应用启动完成后加载数据库中的规则 - */ - @EventListener(ApplicationReadyEvent.class) - public void onApplicationReady() { - log.info("应用启动完成,开始同步数据库中的验证规则..."); - syncDatabaseRules(); + log.info("验证规则配置管理器完全初始化,当前加载规则数量: {}", ruleCache.size()); } /** diff --git a/ruoyi-credit/src/main/java/com/ruoyi/credit/domain/EmployeeInfo.java b/ruoyi-credit/src/main/java/com/ruoyi/credit/domain/EmployeeInfo.java index c0f6460..9643934 100644 --- a/ruoyi-credit/src/main/java/com/ruoyi/credit/domain/EmployeeInfo.java +++ b/ruoyi-credit/src/main/java/com/ruoyi/credit/domain/EmployeeInfo.java @@ -89,6 +89,9 @@ public class EmployeeInfo extends BaseEntity @Excel(name = "部门ID") private Long deptId; + /** 实名认证状态(来自最新二维码记录,非数据库字段) */ + private String verificationStatus; + // 薪资单位常量 public static final String SALARY_UNIT_HOURLY = "HOURLY"; public static final String SALARY_UNIT_DAILY = "DAILY"; @@ -260,6 +263,16 @@ public class EmployeeInfo extends BaseEntity return deptId; } + public void setVerificationStatus(String verificationStatus) + { + this.verificationStatus = verificationStatus; + } + + public String getVerificationStatus() + { + return verificationStatus; + } + @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) diff --git a/ruoyi-credit/src/main/java/com/ruoyi/credit/dto/EmployeeRequestDTO.java b/ruoyi-credit/src/main/java/com/ruoyi/credit/dto/EmployeeRequestDTO.java index d9d8ca8..1513d5b 100644 --- a/ruoyi-credit/src/main/java/com/ruoyi/credit/dto/EmployeeRequestDTO.java +++ b/ruoyi-credit/src/main/java/com/ruoyi/credit/dto/EmployeeRequestDTO.java @@ -3,6 +3,7 @@ package com.ruoyi.credit.dto; import java.math.BigDecimal; import java.util.Date; import java.util.List; + import com.fasterxml.jackson.annotation.JsonFormat; /** @@ -71,6 +72,9 @@ public class EmployeeRequestDTO { @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date updateTime; + /** 实名认证状态: PENDING-待认证, APPROVED-已认证, REJECTED-认证失败, null-未认证 */ + private String verificationStatus; + // Getters and Setters public Long getEmployeeId() { return employeeId; @@ -216,6 +220,14 @@ public class EmployeeRequestDTO { this.updateTime = updateTime; } + public String getVerificationStatus() { + return verificationStatus; + } + + public void setVerificationStatus(String verificationStatus) { + this.verificationStatus = verificationStatus; + } + @Override public String toString() { return "EmployeeRequestDTO{" + diff --git a/ruoyi-credit/src/main/java/com/ruoyi/credit/service/impl/EmployeeQRCodeServiceImpl.java b/ruoyi-credit/src/main/java/com/ruoyi/credit/service/impl/EmployeeQRCodeServiceImpl.java index 6f5dd22..85581ef 100644 --- a/ruoyi-credit/src/main/java/com/ruoyi/credit/service/impl/EmployeeQRCodeServiceImpl.java +++ b/ruoyi-credit/src/main/java/com/ruoyi/credit/service/impl/EmployeeQRCodeServiceImpl.java @@ -3,6 +3,7 @@ package com.ruoyi.credit.service.impl; import java.util.Calendar; import java.util.Date; import java.util.UUID; +import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,7 +14,9 @@ import org.springframework.transaction.annotation.Transactional; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.google.zxing.WriterException; +import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.utils.QRCodeGenerator; import com.ruoyi.credit.ca.CAServiceInvoker; import com.ruoyi.credit.ca.config.CAServiceConfig; @@ -48,6 +51,9 @@ public class EmployeeQRCodeServiceImpl implements IEmployeeQRCodeService @Autowired private CAServiceConfig caServiceConfig; + @Autowired + private RedisCache redisCache; + /** * 为员工生成实名认证二维码 * @@ -144,6 +150,15 @@ public class EmployeeQRCodeServiceImpl implements IEmployeeQRCodeService { log.info("获取二维码图片: qrCodeId={}", qrCodeId); + // 尝试从缓存获取 + String cacheKey = CacheConstants.QRCODE_IMAGE_KEY + qrCodeId; + byte[] cachedImage = redisCache.getCacheObject(cacheKey); + if (cachedImage != null) + { + log.debug("从缓存获取二维码图片: qrCodeId={}, size={}bytes", qrCodeId, cachedImage.length); + return cachedImage; + } + try { // 1. 查询二维码记录 EmployeeQRCode qrCode = employeeQRCodeMapper.selectEmployeeQRCodeByQrCodeId(qrCodeId); @@ -163,6 +178,14 @@ public class EmployeeQRCodeServiceImpl implements IEmployeeQRCodeService // 3. 生成二维码图片 byte[] imageBytes = QRCodeGenerator.generateQRCode(qrCodeData.toJSONString()); + // 4. 缓存图片(使用二维码剩余有效期作为缓存时间,最多缓存QRCODE_IMAGE_EXPIRE_MINUTES分钟) + long remainingMinutes = (qrCode.getExpiryTime().getTime() - System.currentTimeMillis()) / 60000; + if (remainingMinutes > 0) + { + long cacheMinutes = Math.min(remainingMinutes, CacheConstants.QRCODE_IMAGE_EXPIRE_MINUTES); + redisCache.setCacheObject(cacheKey, imageBytes, (int) cacheMinutes, TimeUnit.MINUTES); + } + log.info("成功生成二维码图片: qrCodeId={}, size={}bytes", qrCodeId, imageBytes.length); return imageBytes; @@ -220,6 +243,8 @@ public class EmployeeQRCodeServiceImpl implements IEmployeeQRCodeService // 更新二维码状态为已过期 qrCode.setQrCodeStatus(EmployeeQRCode.QR_CODE_STATUS_EXPIRED); employeeQRCodeMapper.updateEmployeeQRCode(qrCode); + // 清除图片缓存 + redisCache.deleteObject(CacheConstants.QRCODE_IMAGE_KEY + qrCodeId); return AjaxResult.error("二维码已过期,请重新生成"); } @@ -246,6 +271,8 @@ public class EmployeeQRCodeServiceImpl implements IEmployeeQRCodeService qrCode.setCaVerificationId(response.getVerificationId()); qrCode.setVerificationTime(response.getVerificationTime()); employeeQRCodeMapper.updateEmployeeQRCode(qrCode); + // 清除图片缓存(二维码已使用) + redisCache.deleteObject(CacheConstants.QRCODE_IMAGE_KEY + qrCodeId); AjaxResult result = AjaxResult.success("员工实名认证成功"); result.put("verificationId", response.getVerificationId()); diff --git a/ruoyi-credit/src/main/java/com/ruoyi/credit/util/EmployeeConverter.java b/ruoyi-credit/src/main/java/com/ruoyi/credit/util/EmployeeConverter.java index ac0d27b..7b7b311 100644 --- a/ruoyi-credit/src/main/java/com/ruoyi/credit/util/EmployeeConverter.java +++ b/ruoyi-credit/src/main/java/com/ruoyi/credit/util/EmployeeConverter.java @@ -1,11 +1,11 @@ package com.ruoyi.credit.util; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.credit.domain.EmployeeInfo; import com.ruoyi.credit.domain.EmployeeLibrary; import com.ruoyi.credit.dto.EmployeeRequestDTO; import com.ruoyi.credit.service.IEmployeeManagerService; -import com.ruoyi.common.utils.StringUtils; -import com.ruoyi.common.utils.spring.SpringUtils; /** * 员工信息转换工具类 @@ -116,6 +116,9 @@ public class EmployeeConverter { } else { dto.setSalaryUnits(new java.util.ArrayList<>()); } + + // 映射实名认证状态 + dto.setVerificationStatus(entity.getVerificationStatus()); return dto; } diff --git a/ruoyi-credit/src/main/resources/application-api-security.yml b/ruoyi-credit/src/main/resources/application-api-security.yml index bab18d4..836196e 100644 --- a/ruoyi-credit/src/main/resources/application-api-security.yml +++ b/ruoyi-credit/src/main/resources/application-api-security.yml @@ -2,13 +2,13 @@ api: # 限流配置 rateLimit: - enabled: true + enabled: false maxRequests: 100 # 每个时间窗口最大请求数 timeWindow: 60 # 时间窗口(秒) # 签名验证配置 signature: - enabled: true + enabled: false secretKey: ${API_SECRET_KEY:digital_credit_secret_key_2025} timestampTolerance: 300 # 时间戳容忍度(秒) @@ -54,7 +54,7 @@ spring: - OPTIONS allowed-headers: - "*" - allow-credentials: true + allow-credentials: false max-age: 3600 # 监控和度量配置 @@ -69,6 +69,6 @@ management: metrics: export: prometheus: - enabled: true + enabled: false tags: application: digital-credit-service diff --git a/ruoyi-credit/src/main/resources/mapper/credit/EmployeeInfoMapper.xml b/ruoyi-credit/src/main/resources/mapper/credit/EmployeeInfoMapper.xml index 6bd1a65..4025aa5 100644 --- a/ruoyi-credit/src/main/resources/mapper/credit/EmployeeInfoMapper.xml +++ b/ruoyi-credit/src/main/resources/mapper/credit/EmployeeInfoMapper.xml @@ -5,76 +5,89 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - select employee_id, employee_number, employee_name, id_card_number, position, department, salary_unit, hourly_salary, daily_salary, monthly_salary, employee_status, hire_date, contact_phone, library_id, is_temporary, create_time, update_time, created_by, updated_by, remark, dept_id from dc_employee_info + select e.employee_id, e.employee_number, e.employee_name, e.id_card_number, e.position, e.department, + e.salary_unit, e.hourly_salary, e.daily_salary, e.monthly_salary, e.employee_status, + e.hire_date, e.contact_phone, e.library_id, e.is_temporary, e.create_time, e.update_time, + e.created_by, e.updated_by, e.remark, e.dept_id, + qr.verification_status + from dc_employee_info e + left join ( + select employee_id, verification_status + from dc_employee_qr_code + where (employee_id, generate_time) in ( + select employee_id, max(generate_time) from dc_employee_qr_code group by employee_id + ) + ) qr on e.employee_id = qr.employee_id - + + + + + AND tenant_code like concat('%', #{tenantCode}, '%') + + + AND tenant_name like concat('%', #{tenantName}, '%') + + + AND tenant_type = #{tenantType} + + + AND company_type = #{companyType} + + + AND status = #{status} + + + order by tenant_id + + + + + + + + + + + + insert into sys_tenant + + tenant_code, + tenant_name, + tenant_type, + company_type, + contact_person, + contact_phone, + contact_email, + domain, + logo_url, + status, + max_users, + max_companies, + expire_date, + package_id, + create_by, + remark, + + + #{tenantCode}, + #{tenantName}, + #{tenantType}, + #{companyType}, + #{contactPerson}, + #{contactPhone}, + #{contactEmail}, + #{domain}, + #{logoUrl}, + #{status}, + #{maxUsers}, + #{maxCompanies}, + #{expireDate}, + #{packageId}, + #{createBy}, + #{remark}, + + + + + update sys_tenant + + tenant_code = #{tenantCode}, + tenant_name = #{tenantName}, + tenant_type = #{tenantType}, + company_type = #{companyType}, + contact_person = #{contactPerson}, + contact_phone = #{contactPhone}, + contact_email = #{contactEmail}, + domain = #{domain}, + logo_url = #{logoUrl}, + status = #{status}, + max_users = #{maxUsers}, + max_companies = #{maxCompanies}, + expire_date = #{expireDate}, + package_id = #{packageId}, + update_by = #{updateBy}, + remark = #{remark}, + + where tenant_id = #{tenantId} + + + + update sys_tenant set status = 'DELETED' where tenant_id = #{tenantId} + + + diff --git a/ruoyi-system/src/main/resources/mapper/system/UserEnterpriseVerificationMapper.xml b/ruoyi-system/src/main/resources/mapper/system/UserEnterpriseVerificationMapper.xml index edcbb66..a37e9f8 100644 --- a/ruoyi-system/src/main/resources/mapper/system/UserEnterpriseVerificationMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/UserEnterpriseVerificationMapper.xml @@ -68,6 +68,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" limit 1 + + + - AND user_id = #{userId} + AND v.user_id = #{userId} - AND real_name like concat('%', #{realName}, '%') + AND v.real_name like concat('%', #{realName}, '%') - AND verification_status = #{verificationStatus} + AND v.verification_status = #{verificationStatus} - AND ca_verification_id = #{caVerificationId} + AND v.ca_verification_id = #{caVerificationId} - AND date_format(create_time,'%Y%m%d') >= date_format(#{params.beginTime},'%Y%m%d') + AND date_format(v.create_time,'%Y%m%d') >= date_format(#{params.beginTime},'%Y%m%d') - AND date_format(create_time,'%Y%m%d') <= date_format(#{params.endTime},'%Y%m%d') + AND date_format(v.create_time,'%Y%m%d') <= date_format(#{params.endTime},'%Y%m%d') - order by create_time desc + order by v.create_time desc + + +