feat: 认证管理页面
This commit is contained in:
parent
52367cb030
commit
c5e49b0bbc
@ -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
|
||||
|
||||
|
||||
68
RuoYi-Vue3/src/api/system/tenant.js
Normal file
68
RuoYi-Vue3/src/api/system/tenant.js
Normal file
@ -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'
|
||||
})
|
||||
}
|
||||
@ -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'
|
||||
})
|
||||
}
|
||||
|
||||
@ -161,6 +161,14 @@
|
||||
<el-tag v-else type="warning">其他</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实名认证" align="center" prop="verificationStatus" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.verificationStatus === 'APPROVED'" type="success">已认证</el-tag>
|
||||
<el-tag v-else-if="scope.row.verificationStatus === 'REJECTED'" type="danger">认证失败</el-tag>
|
||||
<el-tag v-else-if="scope.row.verificationStatus === 'PENDING'" type="warning">待认证</el-tag>
|
||||
<el-tag v-else type="info">未认证</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="160" />
|
||||
<el-table-column label="更新时间" align="center" prop="updateTime" width="160" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="300" fixed="right">
|
||||
@ -451,6 +459,12 @@
|
||||
<el-tag v-if="detailData.isTemporary" type="warning">是</el-tag>
|
||||
<el-tag v-else type="success">否</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="实名认证状态">
|
||||
<el-tag v-if="detailData.verificationStatus === 'APPROVED'" type="success">已认证</el-tag>
|
||||
<el-tag v-else-if="detailData.verificationStatus === 'REJECTED'" type="danger">认证失败</el-tag>
|
||||
<el-tag v-else-if="detailData.verificationStatus === 'PENDING'" type="warning">待认证</el-tag>
|
||||
<el-tag v-else type="info">未认证</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="员工库">{{ detailData.libraryName || '暂无' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ formatDateTime(detailData.createTime) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">{{ formatDateTime(detailData.updateTime) }}</el-descriptions-item>
|
||||
@ -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(() => {
|
||||
|
||||
327
RuoYi-Vue3/src/views/system/tenant/index.vue
Normal file
327
RuoYi-Vue3/src/views/system/tenant/index.vue
Normal file
@ -0,0 +1,327 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="租户名称" prop="tenantName">
|
||||
<el-input
|
||||
v-model="queryParams.tenantName"
|
||||
placeholder="请输入租户名称"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="租户编码" prop="tenantCode">
|
||||
<el-input
|
||||
v-model="queryParams.tenantCode"
|
||||
placeholder="请输入租户编码"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="公司类型" prop="companyType">
|
||||
<el-select v-model="queryParams.companyType" placeholder="请选择公司类型" clearable style="width: 200px">
|
||||
<el-option label="银行" value="BANK" />
|
||||
<el-option label="甲方" value="CLIENT" />
|
||||
<el-option label="劳务公司" value="LABOR" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="租户状态" clearable style="width: 200px">
|
||||
<el-option label="正常" value="ACTIVE" />
|
||||
<el-option label="冻结" value="FROZEN" />
|
||||
<el-option label="已删除" value="DELETED" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="Plus"
|
||||
@click="handleAdd"
|
||||
v-hasPermi="['system:tenant:add']"
|
||||
>新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="Delete"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
v-hasPermi="['system:tenant:remove']"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="tenantList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="租户ID" align="center" prop="tenantId" width="80" />
|
||||
<el-table-column label="租户编码" align="center" prop="tenantCode" width="150" />
|
||||
<el-table-column label="租户名称" align="center" prop="tenantName" min-width="150" />
|
||||
<el-table-column label="公司类型" align="center" prop="companyType" width="120">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.companyType === 'BANK'" type="success">银行</el-tag>
|
||||
<el-tag v-else-if="scope.row.companyType === 'CLIENT'" type="warning">甲方</el-tag>
|
||||
<el-tag v-else-if="scope.row.companyType === 'LABOR'" type="primary">劳务公司</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="联系人" align="center" prop="contactPerson" width="100" />
|
||||
<el-table-column label="联系电话" align="center" prop="contactPhone" width="130" />
|
||||
<el-table-column label="状态" align="center" prop="status" width="80">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.status === 'ACTIVE'" type="success">正常</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 'FROZEN'" type="warning">冻结</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 'DELETED'" type="danger">已删除</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最大用户数" align="center" prop="maxUsers" width="100" />
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:tenant:edit']">修改</el-button>
|
||||
<el-button v-if="scope.row.tenantId !== 1" link type="primary" icon="Lock" @click="handleFreeze(scope.row)" v-hasPermi="['system:tenant:edit']">
|
||||
{{ scope.row.status === 'ACTIVE' ? '冻结' : '激活' }}
|
||||
</el-button>
|
||||
<el-button v-if="scope.row.tenantId !== 1" link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:tenant:remove']">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<!-- 添加或修改租户对话框 -->
|
||||
<el-dialog :title="title" v-model="open" width="600px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="租户编码" prop="tenantCode">
|
||||
<el-input v-model="form.tenantCode" placeholder="请输入租户编码" :disabled="form.tenantId !== undefined" />
|
||||
</el-form-item>
|
||||
<el-form-item label="租户名称" prop="tenantName">
|
||||
<el-input v-model="form.tenantName" placeholder="请输入租户名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="公司类型" prop="companyType">
|
||||
<el-select v-model="form.companyType" placeholder="请选择公司类型" style="width: 100%">
|
||||
<el-option label="银行" value="BANK" />
|
||||
<el-option label="甲方" value="CLIENT" />
|
||||
<el-option label="劳务公司" value="LABOR" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="联系人" prop="contactPerson">
|
||||
<el-input v-model="form.contactPerson" placeholder="请输入联系人" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话" prop="contactPhone">
|
||||
<el-input v-model="form.contactPhone" placeholder="请输入联系电话" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系邮箱" prop="contactEmail">
|
||||
<el-input v-model="form.contactEmail" placeholder="请输入联系邮箱" />
|
||||
</el-form-item>
|
||||
<el-form-item label="最大用户数" prop="maxUsers">
|
||||
<el-input-number v-model="form.maxUsers" :min="1" :max="10000" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="最大公司数" prop="maxCompanies">
|
||||
<el-input-number v-model="form.maxCompanies" :min="1" :max="1000" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="过期日期" prop="expireDate">
|
||||
<el-date-picker
|
||||
v-model="form.expireDate"
|
||||
type="date"
|
||||
placeholder="选择过期日期"
|
||||
style="width: 100%"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio label="ACTIVE">正常</el-radio>
|
||||
<el-radio label="FROZEN">冻结</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="Tenant">
|
||||
import { listTenant, getTenant, addTenant, updateTenant, delTenant, freezeTenant, activeTenant } from '@/api/system/tenant'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const loading = ref(true)
|
||||
const showSearch = ref(true)
|
||||
const tenantList = ref([])
|
||||
const total = ref(0)
|
||||
const multiple = ref(true)
|
||||
const ids = ref([])
|
||||
|
||||
const title = ref('')
|
||||
const open = ref(false)
|
||||
const formRef = ref()
|
||||
const form = ref({})
|
||||
const rules = ref({
|
||||
tenantCode: [{ required: true, message: '租户编码不能为空', trigger: 'blur' }],
|
||||
tenantName: [{ required: true, message: '租户名称不能为空', trigger: 'blur' }],
|
||||
companyType: [{ required: true, message: '请选择公司类型', trigger: 'change' }],
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
|
||||
})
|
||||
|
||||
const queryParams = ref({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
tenantCode: undefined,
|
||||
tenantName: undefined,
|
||||
companyType: undefined,
|
||||
status: undefined
|
||||
})
|
||||
|
||||
/** 查询租户列表 */
|
||||
function getList() {
|
||||
loading.value = true
|
||||
listTenant(queryParams.value).then(response => {
|
||||
tenantList.value = response.rows
|
||||
total.value = response.total
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
/** 取消按钮 */
|
||||
function cancel() {
|
||||
open.value = false
|
||||
reset()
|
||||
}
|
||||
|
||||
/** 表单重置 */
|
||||
function reset() {
|
||||
form.value = {
|
||||
tenantId: undefined,
|
||||
tenantCode: undefined,
|
||||
tenantName: undefined,
|
||||
tenantType: 'COMPANY',
|
||||
companyType: undefined,
|
||||
contactPerson: undefined,
|
||||
contactPhone: undefined,
|
||||
contactEmail: undefined,
|
||||
maxUsers: 10,
|
||||
maxCompanies: 5,
|
||||
expireDate: undefined,
|
||||
status: 'ACTIVE',
|
||||
remark: undefined
|
||||
}
|
||||
if (formRef.value) {
|
||||
formRef.value.resetFields()
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
function handleQuery() {
|
||||
queryParams.value.pageNum = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
function resetQuery() {
|
||||
proxy.$refs.queryRef.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 多选框选中数据 */
|
||||
function handleSelectionChange(selection) {
|
||||
ids.value = selection.map(item => item.tenantId)
|
||||
multiple.value = !selection.length
|
||||
}
|
||||
|
||||
/** 新增按钮操作 */
|
||||
function handleAdd() {
|
||||
reset()
|
||||
open.value = true
|
||||
title.value = '添加租户'
|
||||
}
|
||||
|
||||
/** 修改按钮操作 */
|
||||
function handleUpdate(row) {
|
||||
reset()
|
||||
const tenantId = row.tenantId || ids.value
|
||||
getTenant(tenantId).then(response => {
|
||||
form.value = response.data
|
||||
open.value = true
|
||||
title.value = '修改租户'
|
||||
})
|
||||
}
|
||||
|
||||
/** 冻结/激活按钮操作 */
|
||||
function handleFreeze(row) {
|
||||
const tenantId = row.tenantId
|
||||
const status = row.status === 'ACTIVE' ? '冻结' : '激活'
|
||||
proxy.$modal.confirm('确认要' + status + '租户【' + row.tenantName + '】吗?').then(() => {
|
||||
if (row.status === 'ACTIVE') {
|
||||
return freezeTenant(tenantId)
|
||||
} else {
|
||||
return activeTenant(tenantId)
|
||||
}
|
||||
}).then(() => {
|
||||
getList()
|
||||
proxy.$modal.msgSuccess(status + '成功')
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
function handleDelete(row) {
|
||||
const tenantIds = row.tenantId || ids.value
|
||||
proxy.$modal.confirm('是否确认删除租户编号为"' + tenantIds + '"的数据项?').then(() => {
|
||||
return delTenant(tenantIds)
|
||||
}).then(() => {
|
||||
getList()
|
||||
proxy.$modal.msgSuccess('删除成功')
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
/** 提交按钮 */
|
||||
function submitForm() {
|
||||
proxy.$refs.formRef.validate(valid => {
|
||||
if (valid) {
|
||||
if (form.value.tenantId !== undefined) {
|
||||
updateTenant(form.value).then(response => {
|
||||
proxy.$modal.msgSuccess('修改成功')
|
||||
open.value = false
|
||||
getList()
|
||||
})
|
||||
} else {
|
||||
addTenant(form.value).then(response => {
|
||||
proxy.$modal.msgSuccess('新增成功')
|
||||
open.value = false
|
||||
getList()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getList()
|
||||
</script>
|
||||
192
RuoYi-Vue3/src/views/system/verification/enterprise.vue
Normal file
192
RuoYi-Vue3/src/views/system/verification/enterprise.vue
Normal file
@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 搜索筛选区 -->
|
||||
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="80px">
|
||||
<el-form-item label="用户姓名" prop="userName">
|
||||
<el-input v-model="queryParams.userName" placeholder="请输入用户姓名" clearable style="width: 200px" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="企业名称" prop="enterpriseName">
|
||||
<el-input v-model="queryParams.enterpriseName" placeholder="请输入企业名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="企业代码" prop="enterpriseCode">
|
||||
<el-input v-model="queryParams.enterpriseCode" placeholder="请输入统一社会信用代码" clearable style="width: 220px" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="认证状态" prop="verificationStatus">
|
||||
<el-select v-model="queryParams.verificationStatus" placeholder="请选择认证状态" clearable style="width: 160px">
|
||||
<el-option label="待认证" value="PENDING" />
|
||||
<el-option label="已认证" value="APPROVED" />
|
||||
<el-option label="认证失败" value="REJECTED" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 操作按钮区 -->
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:verification:export']">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-table v-loading="loading" :data="verificationList">
|
||||
<el-table-column label="认证ID" align="center" prop="verificationId" width="80" />
|
||||
<el-table-column label="用户姓名" align="center" prop="userName" min-width="100" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="企业名称" align="center" prop="enterpriseName" min-width="160" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="统一社会信用代码" align="center" prop="enterpriseCode" min-width="180" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="法人代表" align="center" prop="legalPerson" min-width="100" />
|
||||
<el-table-column label="认证状态" align="center" prop="verificationStatus" width="110">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusTagType(scope.row.verificationStatus)">
|
||||
{{ getStatusText(scope.row.verificationStatus) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="CA认证时间" align="center" prop="caVerificationTime" min-width="160">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.caVerificationTime ? parseTime(scope.row.caVerificationTime) : '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="提交时间" align="center" prop="createTime" min-width="160">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="100" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
<el-button link type="primary" icon="View" @click="handleDetail(scope.row)" v-hasPermi="['system:verification:view:all']" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
|
||||
<!-- 企业认证详情对话框 -->
|
||||
<el-dialog title="企业认证详情" v-model="detailOpen" width="700px" append-to-body>
|
||||
<el-descriptions :column="2" border v-if="currentDetail">
|
||||
<el-descriptions-item label="认证ID">{{ currentDetail.verificationId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="用户姓名">{{ currentDetail.userName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="企业名称" :span="2">{{ currentDetail.enterpriseName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="统一社会信用代码" :span="2">{{ currentDetail.enterpriseCode }}</el-descriptions-item>
|
||||
<el-descriptions-item label="法人代表">{{ currentDetail.legalPerson || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="认证状态">
|
||||
<el-tag :type="getStatusTagType(currentDetail.verificationStatus)">
|
||||
{{ getStatusText(currentDetail.verificationStatus) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="CA认证ID" :span="2">{{ currentDetail.caVerificationId || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="CA认证时间" :span="2">{{ currentDetail.caVerificationTime ? parseTime(currentDetail.caVerificationTime) : '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2">{{ currentDetail.auditRemark || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="提交时间">{{ parseTime(currentDetail.createTime) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">{{ parseTime(currentDetail.updateTime) }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<!-- 审核历史 -->
|
||||
<div v-if="currentDetail && currentDetail.auditLogs && currentDetail.auditLogs.length > 0" style="margin-top: 20px">
|
||||
<div style="font-weight: bold; margin-bottom: 10px; color: #606266;">审核历史</div>
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="log in currentDetail.auditLogs"
|
||||
:key="log.logId"
|
||||
:timestamp="parseTime(log.operationTime)"
|
||||
placement="top"
|
||||
>
|
||||
<el-card shadow="never" style="padding: 8px 12px;">
|
||||
<div>
|
||||
<span>状态变更:</span>
|
||||
<el-tag size="small" :type="getStatusTagType(log.oldStatus)" style="margin-right: 4px">{{ getStatusText(log.oldStatus) }}</el-tag>
|
||||
<el-icon><ArrowRight /></el-icon>
|
||||
<el-tag size="small" :type="getStatusTagType(log.newStatus)" style="margin-left: 4px">{{ getStatusText(log.newStatus) }}</el-tag>
|
||||
</div>
|
||||
<div style="margin-top: 6px; color: #909399; font-size: 13px;">
|
||||
操作人:{{ log.operatorName }}
|
||||
<span v-if="log.operatorRoles" style="margin-left: 8px">({{ log.operatorRoles }})</span>
|
||||
</div>
|
||||
<div v-if="log.auditRemark" style="margin-top: 4px; color: #606266; font-size: 13px;">备注:{{ log.auditRemark }}</div>
|
||||
</el-card>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="detailOpen = false">关 闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="EnterpriseVerification">
|
||||
import { listEnterpriseVerifications, getEnterpriseVerificationDetail, exportEnterpriseVerifications } from '@/api/system/verification'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const verificationList = ref([])
|
||||
const loading = ref(true)
|
||||
const showSearch = ref(true)
|
||||
const total = ref(0)
|
||||
const detailOpen = ref(false)
|
||||
const currentDetail = ref(null)
|
||||
|
||||
const data = reactive({
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
userName: undefined,
|
||||
enterpriseName: undefined,
|
||||
enterpriseCode: undefined,
|
||||
verificationStatus: undefined
|
||||
}
|
||||
})
|
||||
|
||||
const { queryParams } = toRefs(data)
|
||||
|
||||
function getStatusText(status) {
|
||||
const map = { PENDING: '待认证', APPROVED: '已认证', REJECTED: '认证失败' }
|
||||
return map[status] || status || '-'
|
||||
}
|
||||
|
||||
function getStatusTagType(status) {
|
||||
const map = { PENDING: 'warning', APPROVED: 'success', REJECTED: 'danger' }
|
||||
return map[status] || 'info'
|
||||
}
|
||||
|
||||
function getList() {
|
||||
loading.value = true
|
||||
listEnterpriseVerifications(queryParams.value).then(res => {
|
||||
verificationList.value = res.rows
|
||||
total.value = res.total
|
||||
loading.value = false
|
||||
}).catch(() => { loading.value = false })
|
||||
}
|
||||
|
||||
function handleQuery() {
|
||||
queryParams.value.pageNum = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
function resetQuery() {
|
||||
proxy.resetForm('queryRef')
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
function handleDetail(row) {
|
||||
getEnterpriseVerificationDetail(row.verificationId).then(res => {
|
||||
currentDetail.value = res.data
|
||||
detailOpen.value = true
|
||||
})
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
proxy.download('system/user/verification/enterprise/export', { ...queryParams.value }, `enterprise_verification_${new Date().getTime()}.xlsx`)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
194
RuoYi-Vue3/src/views/system/verification/identity.vue
Normal file
194
RuoYi-Vue3/src/views/system/verification/identity.vue
Normal file
@ -0,0 +1,194 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 搜索筛选区 -->
|
||||
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="80px">
|
||||
<el-form-item label="用户姓名" prop="userName">
|
||||
<el-input v-model="queryParams.userName" placeholder="请输入用户姓名" clearable style="width: 200px" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="真实姓名" prop="realName">
|
||||
<el-input v-model="queryParams.realName" placeholder="请输入真实姓名" clearable style="width: 200px" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="认证状态" prop="verificationStatus">
|
||||
<el-select v-model="queryParams.verificationStatus" placeholder="请选择认证状态" clearable style="width: 160px">
|
||||
<el-option label="待认证" value="PENDING" />
|
||||
<el-option label="已认证" value="APPROVED" />
|
||||
<el-option label="认证失败" value="REJECTED" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 操作按钮区 -->
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:verification:export']">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-table v-loading="loading" :data="verificationList">
|
||||
<el-table-column label="认证ID" align="center" prop="verificationId" width="80" />
|
||||
<el-table-column label="用户姓名" align="center" prop="userName" min-width="100" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="真实姓名" align="center" prop="realName" min-width="100" />
|
||||
<el-table-column label="认证状态" align="center" prop="verificationStatus" width="110">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusTagType(scope.row.verificationStatus)">
|
||||
{{ getStatusText(scope.row.verificationStatus) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="CA认证ID" align="center" prop="caVerificationId" min-width="200" :show-overflow-tooltip="true">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.caVerificationId || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="认证时间" align="center" prop="verificationTime" min-width="160">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.verificationTime ? parseTime(scope.row.verificationTime) : '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="拒绝原因" align="center" prop="rejectReason" min-width="160" :show-overflow-tooltip="true">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.rejectReason || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="提交时间" align="center" prop="createTime" min-width="160">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="100" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
<el-button link type="primary" icon="View" @click="handleDetail(scope.row)" v-hasPermi="['system:verification:view:all']" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
|
||||
<!-- 身份认证详情对话框 -->
|
||||
<el-dialog title="身份认证详情" v-model="detailOpen" width="650px" append-to-body>
|
||||
<el-descriptions :column="2" border v-if="currentDetail">
|
||||
<el-descriptions-item label="认证ID">{{ currentDetail.verificationId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="用户姓名">{{ currentDetail.userName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="真实姓名" :span="2">{{ currentDetail.realName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="认证状态" :span="2">
|
||||
<el-tag :type="getStatusTagType(currentDetail.verificationStatus)">
|
||||
{{ getStatusText(currentDetail.verificationStatus) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="CA认证ID" :span="2">{{ currentDetail.caVerificationId || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="认证时间" :span="2">{{ currentDetail.verificationTime ? parseTime(currentDetail.verificationTime) : '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="拒绝原因" :span="2">{{ currentDetail.rejectReason || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="提交时间">{{ parseTime(currentDetail.createTime) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">{{ parseTime(currentDetail.updateTime) }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<!-- 审核历史 -->
|
||||
<div v-if="currentDetail && currentDetail.auditLogs && currentDetail.auditLogs.length > 0" style="margin-top: 20px">
|
||||
<div style="font-weight: bold; margin-bottom: 10px; color: #606266;">审核历史</div>
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="log in currentDetail.auditLogs"
|
||||
:key="log.logId"
|
||||
:timestamp="parseTime(log.operationTime)"
|
||||
placement="top"
|
||||
>
|
||||
<el-card shadow="never" style="padding: 8px 12px;">
|
||||
<div>
|
||||
<span>状态变更:</span>
|
||||
<el-tag size="small" :type="getStatusTagType(log.oldStatus)" style="margin-right: 4px">{{ getStatusText(log.oldStatus) }}</el-tag>
|
||||
<el-icon><ArrowRight /></el-icon>
|
||||
<el-tag size="small" :type="getStatusTagType(log.newStatus)" style="margin-left: 4px">{{ getStatusText(log.newStatus) }}</el-tag>
|
||||
</div>
|
||||
<div style="margin-top: 6px; color: #909399; font-size: 13px;">
|
||||
操作人:{{ log.operatorName }}
|
||||
<span v-if="log.operatorRoles" style="margin-left: 8px">({{ log.operatorRoles }})</span>
|
||||
</div>
|
||||
<div v-if="log.auditRemark" style="margin-top: 4px; color: #606266; font-size: 13px;">备注:{{ log.auditRemark }}</div>
|
||||
</el-card>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="detailOpen = false">关 闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="IdentityVerification">
|
||||
import { listIdentityVerifications, getIdentityVerificationDetail, exportIdentityVerifications } from '@/api/system/verification'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const verificationList = ref([])
|
||||
const loading = ref(true)
|
||||
const showSearch = ref(true)
|
||||
const total = ref(0)
|
||||
const detailOpen = ref(false)
|
||||
const currentDetail = ref(null)
|
||||
|
||||
const data = reactive({
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
userName: undefined,
|
||||
realName: undefined,
|
||||
verificationStatus: undefined
|
||||
}
|
||||
})
|
||||
|
||||
const { queryParams } = toRefs(data)
|
||||
|
||||
function getStatusText(status) {
|
||||
const map = { PENDING: '待认证', APPROVED: '已认证', REJECTED: '认证失败' }
|
||||
return map[status] || status || '-'
|
||||
}
|
||||
|
||||
function getStatusTagType(status) {
|
||||
const map = { PENDING: 'warning', APPROVED: 'success', REJECTED: 'danger' }
|
||||
return map[status] || 'info'
|
||||
}
|
||||
|
||||
function getList() {
|
||||
loading.value = true
|
||||
listIdentityVerifications(queryParams.value).then(res => {
|
||||
verificationList.value = res.rows
|
||||
total.value = res.total
|
||||
loading.value = false
|
||||
}).catch(() => { loading.value = false })
|
||||
}
|
||||
|
||||
function handleQuery() {
|
||||
queryParams.value.pageNum = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
function resetQuery() {
|
||||
proxy.resetForm('queryRef')
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
function handleDetail(row) {
|
||||
getIdentityVerificationDetail(row.verificationId).then(res => {
|
||||
currentDetail.value = res.data
|
||||
detailOpen.value = true
|
||||
})
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
proxy.download('system/user/verification/identity/export', { ...queryParams.value }, `identity_verification_${new Date().getTime()}.xlsx`)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@ -1,19 +1,75 @@
|
||||
# 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/<domain>/
|
||||
|
||||
# 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;
|
||||
|
||||
@ -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 / {
|
||||
|
||||
63
docker/database/init/04-performance-indexes.sql
Normal file
63
docker/database/init/04-performance-indexes.sql
Normal file
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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<SysTenant> 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));
|
||||
}
|
||||
}
|
||||
@ -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<com.ruoyi.system.domain.UserIdentityVerification> list =
|
||||
verificationService.listIdentityVerifications(userId, verificationStatus);
|
||||
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<com.ruoyi.system.domain.UserIdentityVerification> list =
|
||||
verificationService.listIdentityVerifications(userId, realName, verificationStatus);
|
||||
ExcelUtil<com.ruoyi.system.domain.UserIdentityVerification> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
40
ruoyi-admin/src/main/resources/application-https.yml
Normal file
40
ruoyi-admin/src/main/resources/application-https.yml
Normal file
@ -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
|
||||
@ -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:
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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{" +
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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;
|
||||
|
||||
/**
|
||||
* 员工信息转换工具类
|
||||
@ -117,6 +117,9 @@ public class EmployeeConverter {
|
||||
dto.setSalaryUnits(new java.util.ArrayList<>());
|
||||
}
|
||||
|
||||
// 映射实名认证状态
|
||||
dto.setVerificationStatus(entity.getVerificationStatus());
|
||||
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -5,76 +5,89 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<mapper namespace="com.ruoyi.credit.mapper.EmployeeInfoMapper">
|
||||
|
||||
<resultMap type="EmployeeInfo" id="EmployeeInfoResult">
|
||||
<result property="employeeId" column="employee_id" />
|
||||
<result property="employeeNumber" column="employee_number" />
|
||||
<result property="employeeName" column="employee_name" />
|
||||
<result property="idCardNumber" column="id_card_number" />
|
||||
<result property="position" column="position" />
|
||||
<result property="department" column="department" />
|
||||
<result property="salaryUnit" column="salary_unit" />
|
||||
<result property="hourlySalary" column="hourly_salary" />
|
||||
<result property="dailySalary" column="daily_salary" />
|
||||
<result property="monthlySalary" column="monthly_salary" />
|
||||
<result property="employeeStatus" column="employee_status" />
|
||||
<result property="hireDate" column="hire_date" />
|
||||
<result property="contactPhone" column="contact_phone" />
|
||||
<result property="libraryId" column="library_id" />
|
||||
<result property="isTemporary" column="is_temporary" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="createBy" column="created_by" />
|
||||
<result property="updateBy" column="updated_by" />
|
||||
<result property="remark" column="remark" />
|
||||
<result property="deptId" column="dept_id" />
|
||||
<result property="employeeId" column="employee_id" />
|
||||
<result property="employeeNumber" column="employee_number" />
|
||||
<result property="employeeName" column="employee_name" />
|
||||
<result property="idCardNumber" column="id_card_number" />
|
||||
<result property="position" column="position" />
|
||||
<result property="department" column="department" />
|
||||
<result property="salaryUnit" column="salary_unit" />
|
||||
<result property="hourlySalary" column="hourly_salary" />
|
||||
<result property="dailySalary" column="daily_salary" />
|
||||
<result property="monthlySalary" column="monthly_salary" />
|
||||
<result property="employeeStatus" column="employee_status" />
|
||||
<result property="hireDate" column="hire_date" />
|
||||
<result property="contactPhone" column="contact_phone" />
|
||||
<result property="libraryId" column="library_id" />
|
||||
<result property="isTemporary" column="is_temporary" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="createBy" column="created_by" />
|
||||
<result property="updateBy" column="updated_by" />
|
||||
<result property="remark" column="remark" />
|
||||
<result property="deptId" column="dept_id" />
|
||||
<result property="verificationStatus" column="verification_status"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectEmployeeInfoVo">
|
||||
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
|
||||
</sql>
|
||||
|
||||
<select id="selectEmployeeInfoList" parameterType="EmployeeInfo" resultMap="EmployeeInfoResult">
|
||||
<include refid="selectEmployeeInfoVo"/>
|
||||
<where>
|
||||
<if test="employeeNumber != null and employeeNumber != ''"> and employee_number like concat('%', #{employeeNumber}, '%')</if>
|
||||
<if test="employeeName != null and employeeName != ''"> and employee_name like concat('%', #{employeeName}, '%')</if>
|
||||
<if test="idCardNumber != null and idCardNumber != ''"> and id_card_number like concat('%', #{idCardNumber}, '%')</if>
|
||||
<if test="position != null and position != ''"> and position = #{position}</if>
|
||||
<if test="department != null and department != ''"> and department = #{department}</if>
|
||||
<if test="salaryUnit != null and salaryUnit != ''"> and salary_unit = #{salaryUnit}</if>
|
||||
<if test="employeeStatus != null and employeeStatus != ''"> and employee_status = #{employeeStatus}</if>
|
||||
<if test="hireDate != null "> and hire_date = #{hireDate}</if>
|
||||
<if test="libraryId != null "> and library_id = #{libraryId}</if>
|
||||
<if test="isTemporary != null "> and is_temporary = #{isTemporary}</if>
|
||||
<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
|
||||
and date_format(create_time,'%y%m%d') >= date_format(#{params.beginTime},'%y%m%d')
|
||||
<if test="employeeNumber != null and employeeNumber != ''"> and e.employee_number like concat('%', #{employeeNumber}, '%')</if>
|
||||
<if test="employeeName != null and employeeName != ''"> and e.employee_name like concat('%', #{employeeName}, '%')</if>
|
||||
<if test="idCardNumber != null and idCardNumber != ''"> and e.id_card_number like concat('%', #{idCardNumber}, '%')</if>
|
||||
<if test="position != null and position != ''"> and e.position = #{position}</if>
|
||||
<if test="department != null and department != ''"> and e.department = #{department}</if>
|
||||
<if test="salaryUnit != null and salaryUnit != ''"> and e.salary_unit = #{salaryUnit}</if>
|
||||
<if test="employeeStatus != null and employeeStatus != ''"> and e.employee_status = #{employeeStatus}</if>
|
||||
<if test="hireDate != null "> and e.hire_date = #{hireDate}</if>
|
||||
<if test="libraryId != null "> and e.library_id = #{libraryId}</if>
|
||||
<if test="isTemporary != null "> and e.is_temporary = #{isTemporary}</if>
|
||||
<if test="params.beginTime != null and params.beginTime != ''">
|
||||
and date_format(e.create_time,'%y%m%d') >= date_format(#{params.beginTime},'%y%m%d')
|
||||
</if>
|
||||
<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
|
||||
and date_format(create_time,'%y%m%d') <= date_format(#{params.endTime},'%y%m%d')
|
||||
<if test="params.endTime != null and params.endTime != ''">
|
||||
and date_format(e.create_time,'%y%m%d') <= date_format(#{params.endTime},'%y%m%d')
|
||||
</if>
|
||||
</where>
|
||||
order by create_time desc
|
||||
order by e.create_time desc
|
||||
</select>
|
||||
|
||||
<select id="selectEmployeeInfoByEmployeeId" parameterType="Long" resultMap="EmployeeInfoResult">
|
||||
<include refid="selectEmployeeInfoVo"/>
|
||||
where employee_id = #{employeeId}
|
||||
where e.employee_id = #{employeeId}
|
||||
</select>
|
||||
|
||||
<select id="selectEmployeeInfoByEmployeeNumber" parameterType="String" resultMap="EmployeeInfoResult">
|
||||
<include refid="selectEmployeeInfoVo"/>
|
||||
where employee_number = #{employeeNumber}
|
||||
where e.employee_number = #{employeeNumber}
|
||||
</select>
|
||||
|
||||
<select id="selectEmployeeInfosByLibraryId" parameterType="Long" resultMap="EmployeeInfoResult">
|
||||
<include refid="selectEmployeeInfoVo"/>
|
||||
where library_id = #{libraryId}
|
||||
order by create_time desc
|
||||
where e.library_id = #{libraryId}
|
||||
order by e.create_time desc
|
||||
</select>
|
||||
|
||||
<select id="selectEmployeeInfosByPosition" parameterType="String" resultMap="EmployeeInfoResult">
|
||||
<include refid="selectEmployeeInfoVo"/>
|
||||
where position = #{position}
|
||||
order by create_time desc
|
||||
where e.position = #{position}
|
||||
order by e.create_time desc
|
||||
</select>
|
||||
|
||||
<select id="countEmployeesByLibraryId" parameterType="Long" resultType="int">
|
||||
|
||||
@ -0,0 +1,79 @@
|
||||
package com.ruoyi.framework.config;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.connector.Connector;
|
||||
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
|
||||
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* HTTPS重定向配置
|
||||
* <p>
|
||||
* 当启用HTTPS时,同时开启HTTP监听并将所有HTTP请求重定向到HTTPS。
|
||||
* 通过配置项 security.https.redirect.enabled=true 启用。
|
||||
* </p>
|
||||
*
|
||||
* 使用方式:在 application-https.yml 中添加:
|
||||
* <pre>
|
||||
* security:
|
||||
* https:
|
||||
* redirect:
|
||||
* enabled: true
|
||||
* http-port: 8080
|
||||
* https-port: 8443
|
||||
* </pre>
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty(value = "security.https.redirect.enabled", havingValue = "true")
|
||||
public class HttpsRedirectConfig
|
||||
{
|
||||
@Value("${security.https.redirect.http-port:8080}")
|
||||
private int httpPort;
|
||||
|
||||
@Value("${security.https.redirect.https-port:8443}")
|
||||
private int httpsPort;
|
||||
|
||||
/**
|
||||
* 配置额外的HTTP连接器,将HTTP请求重定向到HTTPS
|
||||
*/
|
||||
@Bean
|
||||
public ServletWebServerFactory servletContainer()
|
||||
{
|
||||
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory()
|
||||
{
|
||||
@Override
|
||||
protected void postProcessContext(Context context)
|
||||
{
|
||||
// 强制所有HTTP请求使用HTTPS
|
||||
SecurityConstraint securityConstraint = new SecurityConstraint();
|
||||
securityConstraint.setUserConstraint("CONFIDENTIAL");
|
||||
SecurityCollection collection = new SecurityCollection();
|
||||
collection.addPattern("/*");
|
||||
securityConstraint.addCollection(collection);
|
||||
context.addConstraint(securityConstraint);
|
||||
}
|
||||
};
|
||||
tomcat.addAdditionalTomcatConnectors(createHttpConnector());
|
||||
return tomcat;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建HTTP连接器,监听HTTP端口并重定向到HTTPS
|
||||
*/
|
||||
private Connector createHttpConnector()
|
||||
{
|
||||
Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
|
||||
connector.setScheme("http");
|
||||
connector.setPort(httpPort);
|
||||
connector.setSecure(false);
|
||||
connector.setRedirectPort(httpsPort);
|
||||
return connector;
|
||||
}
|
||||
}
|
||||
@ -26,7 +26,7 @@ import org.springframework.util.ClassUtils;
|
||||
import org.apache.ibatis.plugin.Interceptor;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.framework.interceptor.DepartmentDataInterceptor;
|
||||
import com.ruoyi.framework.interceptor.CompanyScopeDataInterceptor;
|
||||
import com.ruoyi.framework.interceptor.TenantDataInterceptor;
|
||||
import com.ruoyi.framework.service.IDepartmentDataPerformanceService;
|
||||
import com.ruoyi.framework.service.impl.DepartmentDataPerformanceServiceImpl;
|
||||
|
||||
@ -45,7 +45,7 @@ public class MyBatisConfig
|
||||
private DepartmentDataInterceptor departmentDataInterceptor;
|
||||
|
||||
@Autowired
|
||||
private CompanyScopeDataInterceptor companyScopeDataInterceptor;
|
||||
private TenantDataInterceptor tenantDataInterceptor;
|
||||
|
||||
/**
|
||||
* 部门数据隔离性能监控服务Bean
|
||||
@ -150,12 +150,9 @@ public class MyBatisConfig
|
||||
sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
|
||||
sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
|
||||
|
||||
// 注册数据隔离拦截器
|
||||
// 配置拦截器执行顺序:公司数据隔离拦截器优先级最高,然后是部门数据隔离拦截器
|
||||
sessionFactory.setPlugins(new Interceptor[]{
|
||||
companyScopeDataInterceptor, // 新的公司数据隔离拦截器
|
||||
departmentDataInterceptor // 原有的部门数据隔离拦截器(向后兼容)
|
||||
});
|
||||
// 注册部门数据隔离拦截器和租户数据隔离拦截器
|
||||
// 配置拦截器执行顺序:租户隔离 -> 部门隔离
|
||||
sessionFactory.setPlugins(new Interceptor[]{tenantDataInterceptor, departmentDataInterceptor});
|
||||
|
||||
return sessionFactory.getObject();
|
||||
}
|
||||
|
||||
@ -15,7 +15,9 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.logout.LogoutFilter;
|
||||
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
import com.ruoyi.framework.config.properties.PermitAllUrlProperties;
|
||||
import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter;
|
||||
import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl;
|
||||
@ -99,9 +101,25 @@ public class SecurityConfig
|
||||
return httpSecurity
|
||||
// CSRF禁用,因为不使用session
|
||||
.csrf(csrf -> csrf.disable())
|
||||
// 禁用HTTP响应标头
|
||||
// 配置安全响应头
|
||||
.headers((headersCustomizer) -> {
|
||||
headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());
|
||||
headersCustomizer
|
||||
.cacheControl(cache -> cache.disable())
|
||||
.frameOptions(options -> options.sameOrigin())
|
||||
// HTTP Strict Transport Security (HSTS)
|
||||
.httpStrictTransportSecurity(hsts -> hsts
|
||||
.includeSubDomains(true)
|
||||
.maxAgeInSeconds(31536000))
|
||||
// X-Content-Type-Options: nosniff
|
||||
.contentTypeOptions(contentType -> {})
|
||||
// X-XSS-Protection
|
||||
.xssProtection(xss -> xss.block(true))
|
||||
// Referrer-Policy
|
||||
.referrerPolicy(referrer -> referrer
|
||||
.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN))
|
||||
// Content-Security-Policy
|
||||
.contentSecurityPolicy(csp -> csp
|
||||
.policyDirectives("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'"));
|
||||
})
|
||||
// 认证失败处理类
|
||||
.exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
|
||||
|
||||
@ -0,0 +1,504 @@
|
||||
package com.ruoyi.framework.interceptor;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.ibatis.executor.Executor;
|
||||
import org.apache.ibatis.executor.statement.StatementHandler;
|
||||
import org.apache.ibatis.mapping.BoundSql;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.mapping.SqlCommandType;
|
||||
import org.apache.ibatis.plugin.Interceptor;
|
||||
import org.apache.ibatis.plugin.Intercepts;
|
||||
import org.apache.ibatis.plugin.Invocation;
|
||||
import org.apache.ibatis.plugin.Plugin;
|
||||
import org.apache.ibatis.plugin.Signature;
|
||||
import org.apache.ibatis.session.ResultHandler;
|
||||
import org.apache.ibatis.session.RowBounds;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ruoyi.framework.security.context.TenantContextHolder;
|
||||
|
||||
/**
|
||||
* 租户数据隔离拦截器
|
||||
*
|
||||
* 自动为涉及租户隔离的表添加租户过滤条件,确保用户只能访问自己租户的数据。
|
||||
* 超级管理员不受此限制,可以访问所有租户的数据。
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Component
|
||||
@Intercepts({
|
||||
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
|
||||
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
|
||||
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
|
||||
})
|
||||
public class TenantDataInterceptor implements Interceptor
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(TenantDataInterceptor.class);
|
||||
|
||||
/**
|
||||
* 需要进行租户隔离的表名集合
|
||||
*/
|
||||
private static final Set<String> TARGET_TABLES = new HashSet<>(Arrays.asList(
|
||||
"sys_user",
|
||||
"sys_dept",
|
||||
"dc_contract",
|
||||
"dc_service_contract",
|
||||
"dc_service_period",
|
||||
"dc_service_period_loan",
|
||||
"dc_employee_info",
|
||||
"dc_employee_library",
|
||||
"dc_credit",
|
||||
"dc_bank_institution",
|
||||
"dc_financing",
|
||||
"dc_company_relationship"
|
||||
));
|
||||
|
||||
/**
|
||||
* 忽略租户隔离的系统表
|
||||
*/
|
||||
private static final Set<String> IGNORE_TABLES = new HashSet<>(Arrays.asList(
|
||||
"sys_tenant",
|
||||
"sys_tenant_package",
|
||||
"sys_role",
|
||||
"sys_menu",
|
||||
"sys_dict_data",
|
||||
"sys_dict_type",
|
||||
"sys_config",
|
||||
"sys_notice",
|
||||
"sys_post",
|
||||
"sys_login_log",
|
||||
"sys_oper_log"
|
||||
));
|
||||
|
||||
/**
|
||||
* SQL注入检测模式
|
||||
*/
|
||||
private static final Pattern SQL_INJECTION_PATTERN = Pattern.compile(
|
||||
"('.+(\\s)*(or|and)(\\s)+.+=')|('.+(\\s)*(or|and)(\\s)+.+\\s*=\\s*.+)",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
);
|
||||
|
||||
/**
|
||||
* 是否启用租户隔离(可通过配置覆盖)
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
@Override
|
||||
public Object intercept(Invocation invocation) throws Throwable
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查功能是否启用
|
||||
if (!enabled)
|
||||
{
|
||||
log.debug("租户数据隔离功能已禁用,跳过拦截");
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
// 超级管理员跳过租户隔离
|
||||
if (TenantContextHolder.isSuperAdmin())
|
||||
{
|
||||
log.debug("超级管理员访问,跳过租户数据隔离");
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
// 获取当前租户ID
|
||||
Long currentTenantId = TenantContextHolder.getCurrentTenantId();
|
||||
if (currentTenantId == null)
|
||||
{
|
||||
log.warn("无法获取当前租户ID,跳过租户数据隔离");
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
Object target = invocation.getTarget();
|
||||
Object[] args = invocation.getArgs();
|
||||
|
||||
if (target instanceof Executor)
|
||||
{
|
||||
return handleExecutorIntercept(invocation, args, currentTenantId);
|
||||
}
|
||||
else if (target instanceof StatementHandler)
|
||||
{
|
||||
return handleStatementHandlerIntercept(invocation, args, currentTenantId);
|
||||
}
|
||||
|
||||
return invocation.proceed();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("租户数据隔离拦截器执行异常", e);
|
||||
// 发生异常时继续执行原始操作,避免影响业务
|
||||
return invocation.proceed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理Executor拦截
|
||||
*/
|
||||
private Object handleExecutorIntercept(Invocation invocation, Object[] args, Long currentTenantId) throws Throwable
|
||||
{
|
||||
MappedStatement mappedStatement = (MappedStatement) args[0];
|
||||
Object parameter = args[1];
|
||||
|
||||
// 检查是否需要添加租户过滤
|
||||
if (needTenantFilter(mappedStatement))
|
||||
{
|
||||
log.debug("为SQL操作添加租户过滤条件,租户ID: {}", currentTenantId);
|
||||
addTenantFilter(parameter, currentTenantId);
|
||||
}
|
||||
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理StatementHandler拦截
|
||||
*/
|
||||
private Object handleStatementHandlerIntercept(Invocation invocation, Object[] args, Long currentTenantId) throws Throwable
|
||||
{
|
||||
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
|
||||
BoundSql boundSql = statementHandler.getBoundSql();
|
||||
String originalSql = boundSql.getSql();
|
||||
|
||||
// 检查SQL是否涉及目标表
|
||||
if (containsTargetTables(originalSql))
|
||||
{
|
||||
String modifiedSql = addTenantConditionToSql(originalSql, currentTenantId);
|
||||
if (!modifiedSql.equals(originalSql))
|
||||
{
|
||||
log.debug("修改SQL添加租户过滤条件: {}", modifiedSql);
|
||||
// 通过反射修改BoundSql中的SQL
|
||||
java.lang.reflect.Field sqlField = BoundSql.class.getDeclaredField("sql");
|
||||
sqlField.setAccessible(true);
|
||||
sqlField.set(boundSql, modifiedSql);
|
||||
}
|
||||
}
|
||||
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否需要添加租户过滤
|
||||
*/
|
||||
private boolean needTenantFilter(MappedStatement mappedStatement)
|
||||
{
|
||||
// 获取SQL命令类型
|
||||
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
|
||||
|
||||
// 只对SELECT、UPDATE、DELETE操作进行过滤
|
||||
if (sqlCommandType != SqlCommandType.SELECT &&
|
||||
sqlCommandType != SqlCommandType.UPDATE &&
|
||||
sqlCommandType != SqlCommandType.DELETE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查SQL是否涉及目标表
|
||||
BoundSql boundSql = mappedStatement.getBoundSql(null);
|
||||
if (boundSql == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
String sql = boundSql.getSql();
|
||||
return containsTargetTables(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查SQL是否包含目标表
|
||||
*/
|
||||
private boolean containsTargetTables(String sql)
|
||||
{
|
||||
if (sql == null || sql.trim().isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
String lowerSql = sql.toLowerCase();
|
||||
for (String table : TARGET_TABLES)
|
||||
{
|
||||
// 使用正则表达式匹配表名,确保是完整的表名而不是子字符串
|
||||
Pattern pattern = Pattern.compile("\\b" + table + "\\b", Pattern.CASE_INSENSITIVE);
|
||||
if (pattern.matcher(lowerSql).find())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为参数添加租户过滤条件
|
||||
*/
|
||||
private void addTenantFilter(Object parameter, Long currentTenantId)
|
||||
{
|
||||
if (parameter instanceof Map)
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> paramMap = (Map<String, Object>) parameter;
|
||||
|
||||
// 如果参数中还没有tenantId,则添加
|
||||
if (!paramMap.containsKey("tenantId"))
|
||||
{
|
||||
paramMap.put("tenantId", currentTenantId);
|
||||
log.debug("添加租户过滤参数: tenantId = {}", currentTenantId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 为SQL添加租户条件
|
||||
*/
|
||||
private String addTenantConditionToSql(String originalSql, Long currentTenantId)
|
||||
{
|
||||
if (originalSql == null || originalSql.trim().isEmpty())
|
||||
{
|
||||
return originalSql;
|
||||
}
|
||||
|
||||
// 防止SQL注入
|
||||
if (SQL_INJECTION_PATTERN.matcher(String.valueOf(currentTenantId)).find())
|
||||
{
|
||||
log.warn("检测到潜在的SQL注入风险,跳过租户条件添加");
|
||||
return originalSql;
|
||||
}
|
||||
|
||||
String sql = originalSql.trim();
|
||||
String lowerSql = sql.toLowerCase();
|
||||
|
||||
// 检查是否需要忽略此表
|
||||
for (String ignoreTable : IGNORE_TABLES)
|
||||
{
|
||||
if (lowerSql.contains(ignoreTable))
|
||||
{
|
||||
return originalSql;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 处理SELECT语句
|
||||
if (lowerSql.startsWith("select"))
|
||||
{
|
||||
return addTenantConditionToSelect(sql, currentTenantId);
|
||||
}
|
||||
// 处理UPDATE语句
|
||||
else if (lowerSql.startsWith("update"))
|
||||
{
|
||||
return addTenantConditionToUpdate(sql, currentTenantId);
|
||||
}
|
||||
// 处理DELETE语句
|
||||
else if (lowerSql.startsWith("delete"))
|
||||
{
|
||||
return addTenantConditionToDelete(sql, currentTenantId);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.warn("添加租户条件时发生异常,使用原始SQL: {}", e.getMessage());
|
||||
return originalSql;
|
||||
}
|
||||
|
||||
return originalSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为SELECT语句添加租户条件
|
||||
*/
|
||||
private String addTenantConditionToSelect(String sql, Long currentTenantId)
|
||||
{
|
||||
String lowerSql = sql.toLowerCase();
|
||||
|
||||
// 查找WHERE子句的位置
|
||||
int whereIndex = lowerSql.indexOf(" where ");
|
||||
|
||||
if (whereIndex != -1)
|
||||
{
|
||||
// 已有WHERE子句,添加AND条件
|
||||
StringBuilder sb = new StringBuilder(sql);
|
||||
int insertIndex = whereIndex + 7; // " where ".length()
|
||||
|
||||
// 为每个目标表添加租户条件
|
||||
String tableAlias = findTableAlias(lowerSql, TARGET_TABLES);
|
||||
if (tableAlias != null)
|
||||
{
|
||||
String condition = tableAlias + ".tenant_id = " + currentTenantId + " AND ";
|
||||
sb.insert(insertIndex, condition);
|
||||
|
||||
log.debug("添加租户过滤条件到现有WHERE子句: {}", condition);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 没有WHERE子句,添加WHERE条件
|
||||
// 查找ORDER BY, GROUP BY, HAVING, LIMIT等子句的位置
|
||||
String[] keywords = {" order by ", " group by ", " having ", " limit ", " offset "};
|
||||
int insertIndex = sql.length();
|
||||
|
||||
for (String keyword : keywords)
|
||||
{
|
||||
int keywordIndex = lowerSql.indexOf(keyword);
|
||||
if (keywordIndex != -1 && keywordIndex < insertIndex)
|
||||
{
|
||||
insertIndex = keywordIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// 为第一个找到的目标表添加WHERE条件
|
||||
String tableAlias = findTableAlias(lowerSql, TARGET_TABLES);
|
||||
if (tableAlias != null)
|
||||
{
|
||||
String whereClause = " WHERE " + tableAlias + ".tenant_id = " + currentTenantId;
|
||||
String modifiedSql = sql.substring(0, insertIndex) + whereClause + sql.substring(insertIndex);
|
||||
|
||||
log.debug("添加新的WHERE子句: {}", whereClause);
|
||||
|
||||
return modifiedSql;
|
||||
}
|
||||
}
|
||||
|
||||
log.warn("无法为SQL添加租户过滤条件,未找到目标表");
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找表的别名
|
||||
*/
|
||||
private String findTableAlias(String lowerSql, Set<String> targetTables)
|
||||
{
|
||||
for (String table : targetTables)
|
||||
{
|
||||
if (lowerSql.contains(table))
|
||||
{
|
||||
// 对于UPDATE语句,表名在UPDATE关键字之后
|
||||
if (lowerSql.trim().startsWith("update"))
|
||||
{
|
||||
String updatePattern = "update\\s+" + table + "\\s+(as\\s+)?(\\w+)\\s+set";
|
||||
java.util.regex.Pattern p = java.util.regex.Pattern.compile(updatePattern, java.util.regex.Pattern.CASE_INSENSITIVE);
|
||||
java.util.regex.Matcher m = p.matcher(lowerSql);
|
||||
|
||||
if (m.find())
|
||||
{
|
||||
String alias = m.group(2);
|
||||
if (alias != null && !isReservedKeyword(alias))
|
||||
{
|
||||
return alias;
|
||||
}
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 对于SELECT语句,查找表名后的别名
|
||||
String pattern = "\\b" + table + "\\s+(as\\s+)?(\\w+)";
|
||||
java.util.regex.Pattern p = java.util.regex.Pattern.compile(pattern, java.util.regex.Pattern.CASE_INSENSITIVE);
|
||||
java.util.regex.Matcher m = p.matcher(lowerSql);
|
||||
|
||||
if (m.find())
|
||||
{
|
||||
String alias = m.group(2);
|
||||
if (!isReservedKeyword(alias))
|
||||
{
|
||||
return alias;
|
||||
}
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为SQL保留关键字
|
||||
*/
|
||||
private boolean isReservedKeyword(String word)
|
||||
{
|
||||
String[] keywords = {"inner", "left", "right", "join", "on", "where", "and", "or", "order", "by", "group", "having", "limit", "offset", "set", "from", "into", "values"};
|
||||
for (String keyword : keywords)
|
||||
{
|
||||
if (keyword.equalsIgnoreCase(word))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为UPDATE语句添加租户条件
|
||||
*/
|
||||
private String addTenantConditionToUpdate(String sql, Long currentTenantId)
|
||||
{
|
||||
String lowerSql = sql.toLowerCase();
|
||||
int whereIndex = lowerSql.indexOf(" where ");
|
||||
|
||||
if (whereIndex != -1)
|
||||
{
|
||||
// 已有WHERE子句,添加AND条件
|
||||
String tableAlias = findTableAlias(lowerSql, TARGET_TABLES);
|
||||
if (tableAlias != null)
|
||||
{
|
||||
String condition = " AND " + tableAlias + ".tenant_id = " + currentTenantId;
|
||||
return sql + condition;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 没有WHERE子句,添加WHERE条件
|
||||
String tableAlias = findTableAlias(lowerSql, TARGET_TABLES);
|
||||
if (tableAlias != null)
|
||||
{
|
||||
String whereClause = " WHERE " + tableAlias + ".tenant_id = " + currentTenantId;
|
||||
return sql + whereClause;
|
||||
}
|
||||
}
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为DELETE语句添加租户条件
|
||||
*/
|
||||
private String addTenantConditionToDelete(String sql, Long currentTenantId)
|
||||
{
|
||||
return addTenantConditionToUpdate(sql, currentTenantId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object plugin(Object target)
|
||||
{
|
||||
if (target instanceof Executor || target instanceof StatementHandler)
|
||||
{
|
||||
return Plugin.wrap(target, this);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperties(Properties properties)
|
||||
{
|
||||
// 可以通过properties配置拦截器参数
|
||||
log.info("租户数据隔离拦截器初始化完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否启用租户隔离
|
||||
*/
|
||||
public void setEnabled(boolean enabled)
|
||||
{
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,172 @@
|
||||
package com.ruoyi.framework.security.context;
|
||||
|
||||
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
|
||||
/**
|
||||
* 租户上下文管理器
|
||||
*
|
||||
* 提供线程安全的租户上下文管理,支持当前租户ID的获取和设置,
|
||||
* 以及超级管理员权限判断功能。
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class TenantContextHolder
|
||||
{
|
||||
/**
|
||||
* 默认租户ID(兼容历史数据)
|
||||
*/
|
||||
public static final Long DEFAULT_TENANT_ID = 1L;
|
||||
|
||||
/**
|
||||
* 租户上下文ThreadLocal存储
|
||||
*/
|
||||
private static final ThreadLocal<Long> TENANT_CONTEXT = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 设置当前线程的租户ID
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
*/
|
||||
public static void setCurrentTenantId(Long tenantId)
|
||||
{
|
||||
TENANT_CONTEXT.set(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前线程的租户ID
|
||||
*
|
||||
* 优先从ThreadLocal获取,如果没有则返回默认租户ID
|
||||
*
|
||||
* @return 租户ID
|
||||
*/
|
||||
public static Long getCurrentTenantId()
|
||||
{
|
||||
Long tenantId = TENANT_CONTEXT.get();
|
||||
if (tenantId != null)
|
||||
{
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
// 如果ThreadLocal中没有,则从SecurityUtils获取当前用户租户ID
|
||||
try
|
||||
{
|
||||
tenantId = getTenantIdFromUser();
|
||||
if (tenantId != null)
|
||||
{
|
||||
return tenantId;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// 忽略异常
|
||||
}
|
||||
|
||||
// 返回默认租户ID(兼容历史数据)
|
||||
return DEFAULT_TENANT_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从当前用户获取租户ID
|
||||
*
|
||||
* @return 租户ID,如果无法获取则返回null
|
||||
*/
|
||||
private static Long getTenantIdFromUser()
|
||||
{
|
||||
try
|
||||
{
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (loginUser != null && loginUser.getUser() != null)
|
||||
{
|
||||
return loginUser.getUser().getTenantId();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// 忽略异常
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除当前线程的租户上下文
|
||||
*/
|
||||
public static void clear()
|
||||
{
|
||||
TENANT_CONTEXT.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前用户是否为超级管理员
|
||||
*
|
||||
* 超级管理员可以切换到任意租户,不受租户隔离限制
|
||||
*
|
||||
* @return true-超级管理员,false-普通用户
|
||||
*/
|
||||
public static boolean isSuperAdmin()
|
||||
{
|
||||
try
|
||||
{
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (loginUser == null || loginUser.getUser() == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用SysUser的isAdmin方法判断是否为超级管理员
|
||||
return loginUser.getUser().isAdmin();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// 如果获取用户信息失败,默认不是超级管理员
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否启用租户隔离
|
||||
*
|
||||
* 超级管理员可以不受租户隔离限制
|
||||
*
|
||||
* @return true-启用租户隔离,false-禁用租户隔离
|
||||
*/
|
||||
public static boolean isTenantEnabled()
|
||||
{
|
||||
// 非超级管理员都启用租户隔离
|
||||
return !isSuperAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户ID
|
||||
*
|
||||
* @return 用户ID,如果无法获取则返回null
|
||||
*/
|
||||
public static Long getCurrentUserId()
|
||||
{
|
||||
try
|
||||
{
|
||||
return SecurityUtils.getUserId();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户名
|
||||
*
|
||||
* @return 用户名,如果无法获取则返回null
|
||||
*/
|
||||
public static String getCurrentUsername()
|
||||
{
|
||||
try
|
||||
{
|
||||
return SecurityUtils.getUsername();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -16,6 +16,7 @@ import com.ruoyi.common.core.domain.model.LoginUser;
|
||||
import com.ruoyi.common.event.UserAuthenticationEvent;
|
||||
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.TokenService;
|
||||
|
||||
/**
|
||||
@ -40,6 +41,14 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
|
||||
if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
|
||||
{
|
||||
tokenService.verifyToken(loginUser);
|
||||
|
||||
// 设置租户上下文
|
||||
Long tenantId = loginUser.getUser().getTenantId();
|
||||
if (tenantId != null)
|
||||
{
|
||||
TenantContextHolder.setCurrentTenantId(tenantId);
|
||||
}
|
||||
|
||||
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
|
||||
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.ruoyi.system.domain;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
@ -30,6 +31,10 @@ public class UserIdentityVerification extends BaseEntity
|
||||
@NotNull(message = "用户ID不能为空")
|
||||
private Long userId;
|
||||
|
||||
/** 用户名(关联查询,非数据库字段) */
|
||||
@Excel(name = "用户名")
|
||||
private String userName;
|
||||
|
||||
/** 真实姓名 */
|
||||
@Excel(name = "真实姓名")
|
||||
@NotBlank(message = "真实姓名不能为空")
|
||||
@ -61,6 +66,9 @@ public class UserIdentityVerification extends BaseEntity
|
||||
@Excel(name = "CA认证时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date verificationTime;
|
||||
|
||||
/** 审核日志列表(非数据库字段,详情查询时附加) */
|
||||
private List<VerificationAuditLog> auditLogs;
|
||||
|
||||
public Long getVerificationId()
|
||||
{
|
||||
return verificationId;
|
||||
@ -81,6 +89,16 @@ public class UserIdentityVerification extends BaseEntity
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public String getUserName()
|
||||
{
|
||||
return userName;
|
||||
}
|
||||
|
||||
public void setUserName(String userName)
|
||||
{
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
public String getRealName()
|
||||
{
|
||||
return realName;
|
||||
@ -141,12 +159,23 @@ public class UserIdentityVerification extends BaseEntity
|
||||
this.verificationTime = verificationTime;
|
||||
}
|
||||
|
||||
public List<VerificationAuditLog> getAuditLogs()
|
||||
{
|
||||
return auditLogs;
|
||||
}
|
||||
|
||||
public void setAuditLogs(List<VerificationAuditLog> auditLogs)
|
||||
{
|
||||
this.auditLogs = auditLogs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
|
||||
.append("verificationId", getVerificationId())
|
||||
.append("userId", getUserId())
|
||||
.append("userName", getUserName())
|
||||
.append("realName", getRealName())
|
||||
.append("idCardNumber", "[MASKED]")
|
||||
.append("verificationStatus", getVerificationStatus())
|
||||
|
||||
@ -0,0 +1,76 @@
|
||||
package com.ruoyi.system.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.common.core.domain.entity.SysTenant;
|
||||
|
||||
/**
|
||||
* 租户管理 数据层
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public interface SysTenantMapper
|
||||
{
|
||||
/**
|
||||
* 查询租户管理数据
|
||||
*
|
||||
* @param tenant 租户信息
|
||||
* @return 租户信息集合
|
||||
*/
|
||||
public List<SysTenant> selectTenantList(SysTenant tenant);
|
||||
|
||||
/**
|
||||
* 根据租户ID查询信息
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @return 租户信息
|
||||
*/
|
||||
public SysTenant selectTenantById(Long tenantId);
|
||||
|
||||
/**
|
||||
* 根据租户编码查询信息
|
||||
*
|
||||
* @param tenantCode 租户编码
|
||||
* @return 租户信息
|
||||
*/
|
||||
public SysTenant selectTenantByCode(String tenantCode);
|
||||
|
||||
/**
|
||||
* 校验租户编码是否唯一
|
||||
*
|
||||
* @param tenantCode 租户编码
|
||||
* @return 结果
|
||||
*/
|
||||
public int checkTenantCodeUnique(String tenantCode);
|
||||
|
||||
/**
|
||||
* 新增租户信息
|
||||
*
|
||||
* @param tenant 租户信息
|
||||
* @return 结果
|
||||
*/
|
||||
public int insertTenant(SysTenant tenant);
|
||||
|
||||
/**
|
||||
* 修改租户信息
|
||||
*
|
||||
* @param tenant 租户信息
|
||||
* @return 结果
|
||||
*/
|
||||
public int updateTenant(SysTenant tenant);
|
||||
|
||||
/**
|
||||
* 删除租户管理信息
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteTenantById(Long tenantId);
|
||||
|
||||
/**
|
||||
* 根据用户ID查询租户信息
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 租户信息
|
||||
*/
|
||||
public SysTenant selectTenantByUserId(Long userId);
|
||||
}
|
||||
@ -37,6 +37,14 @@ public interface UserEnterpriseVerificationMapper
|
||||
*/
|
||||
public UserEnterpriseVerification selectVerificationByUserId(Long userId);
|
||||
|
||||
/**
|
||||
* 根据用户ID查询企业认证状态(轻量级查询,用于权限检查)
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 认证状态字符串
|
||||
*/
|
||||
public String selectVerificationStatusByUserId(Long userId);
|
||||
|
||||
/**
|
||||
* 根据 sys_user_company.company_id 查询企业认证信息
|
||||
*
|
||||
|
||||
@ -35,6 +35,14 @@ public interface UserIdentityVerificationMapper
|
||||
*/
|
||||
public UserIdentityVerification selectVerificationByUserId(Long userId);
|
||||
|
||||
/**
|
||||
* 根据用户ID查询身份认证状态(轻量级查询,用于权限检查)
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 认证状态字符串
|
||||
*/
|
||||
public String selectVerificationStatusByUserId(Long userId);
|
||||
|
||||
/**
|
||||
* 新增用户身份认证
|
||||
*
|
||||
|
||||
@ -0,0 +1,83 @@
|
||||
package com.ruoyi.system.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.common.core.domain.entity.SysTenant;
|
||||
|
||||
/**
|
||||
* 租户管理 服务层
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public interface ISysTenantService
|
||||
{
|
||||
/**
|
||||
* 查询租户管理数据
|
||||
*
|
||||
* @param tenant 租户信息
|
||||
* @return 租户信息集合
|
||||
*/
|
||||
public List<SysTenant> selectTenantList(SysTenant tenant);
|
||||
|
||||
/**
|
||||
* 根据租户ID查询信息
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @return 租户信息
|
||||
*/
|
||||
public SysTenant selectTenantById(Long tenantId);
|
||||
|
||||
/**
|
||||
* 根据租户编码查询信息
|
||||
*
|
||||
* @param tenantCode 租户编码
|
||||
* @return 租户信息
|
||||
*/
|
||||
public SysTenant selectTenantByCode(String tenantCode);
|
||||
|
||||
/**
|
||||
* 根据用户ID查询租户信息
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 租户信息
|
||||
*/
|
||||
public SysTenant selectTenantByUserId(Long userId);
|
||||
|
||||
/**
|
||||
* 校验租户编码是否唯一
|
||||
*
|
||||
* @param tenant 租户信息
|
||||
* @return 结果
|
||||
*/
|
||||
public boolean checkTenantCodeUnique(SysTenant tenant);
|
||||
|
||||
/**
|
||||
* 新增保存租户信息
|
||||
*
|
||||
* @param tenant 租户信息
|
||||
* @return 结果
|
||||
*/
|
||||
public int insertTenant(SysTenant tenant);
|
||||
|
||||
/**
|
||||
* 修改保存租户信息
|
||||
*
|
||||
* @param tenant 租户信息
|
||||
* @return 结果
|
||||
*/
|
||||
public int updateTenant(SysTenant tenant);
|
||||
|
||||
/**
|
||||
* 删除租户管理信息
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteTenantById(Long tenantId);
|
||||
|
||||
/**
|
||||
* 校验租户是否允许删除
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
*/
|
||||
public void checkTenantCanDelete(Long tenantId);
|
||||
}
|
||||
@ -78,10 +78,19 @@ public interface IUserVerificationService
|
||||
* 查询身份认证列表
|
||||
*
|
||||
* @param userId 用户ID(可选)
|
||||
* @param realName 真实姓名(可选)
|
||||
* @param verificationStatus 认证状态(可选)
|
||||
* @return 身份认证集合
|
||||
*/
|
||||
public List<com.ruoyi.system.domain.UserIdentityVerification> listIdentityVerifications(Long userId, String verificationStatus);
|
||||
public List<com.ruoyi.system.domain.UserIdentityVerification> listIdentityVerifications(Long userId, String realName, String verificationStatus);
|
||||
|
||||
/**
|
||||
* 查询身份认证详情(含审核历史)
|
||||
*
|
||||
* @param verificationId 认证ID
|
||||
* @return 身份认证详情
|
||||
*/
|
||||
public com.ruoyi.system.domain.UserIdentityVerification getIdentityVerificationDetail(Long verificationId);
|
||||
|
||||
// ==================== 认证状态查询方法 ====================
|
||||
|
||||
|
||||
@ -0,0 +1,150 @@
|
||||
package com.ruoyi.system.service.impl;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ruoyi.common.core.domain.entity.SysTenant;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.system.mapper.SysTenantMapper;
|
||||
import com.ruoyi.system.service.ISysTenantService;
|
||||
|
||||
/**
|
||||
* 租户管理 服务实现
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Service
|
||||
public class SysTenantServiceImpl implements ISysTenantService
|
||||
{
|
||||
@Autowired
|
||||
private SysTenantMapper tenantMapper;
|
||||
|
||||
/**
|
||||
* 查询租户管理数据
|
||||
*
|
||||
* @param tenant 租户信息
|
||||
* @return 租户信息集合
|
||||
*/
|
||||
@Override
|
||||
public List<SysTenant> selectTenantList(SysTenant tenant)
|
||||
{
|
||||
return tenantMapper.selectTenantList(tenant);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据租户ID查询信息
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @return 租户信息
|
||||
*/
|
||||
@Override
|
||||
public SysTenant selectTenantById(Long tenantId)
|
||||
{
|
||||
return tenantMapper.selectTenantById(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据租户编码查询信息
|
||||
*
|
||||
* @param tenantCode 租户编码
|
||||
* @return 租户信息
|
||||
*/
|
||||
@Override
|
||||
public SysTenant selectTenantByCode(String tenantCode)
|
||||
{
|
||||
return tenantMapper.selectTenantByCode(tenantCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户ID查询租户信息
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 租户信息
|
||||
*/
|
||||
@Override
|
||||
public SysTenant selectTenantByUserId(Long userId)
|
||||
{
|
||||
return tenantMapper.selectTenantByUserId(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验租户编码是否唯一
|
||||
*
|
||||
* @param tenant 租户信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public boolean checkTenantCodeUnique(SysTenant tenant)
|
||||
{
|
||||
Long tenantId = tenant.getTenantId() == null ? -1L : tenant.getTenantId();
|
||||
SysTenant info = tenantMapper.selectTenantByCode(tenant.getTenantCode());
|
||||
if (info != null && info.getTenantId().longValue() != tenantId.longValue())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增保存租户信息
|
||||
*
|
||||
* @param tenant 租户信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int insertTenant(SysTenant tenant)
|
||||
{
|
||||
// 校验租户编码唯一性
|
||||
if (!checkTenantCodeUnique(tenant))
|
||||
{
|
||||
throw new ServiceException("租户编码已存在");
|
||||
}
|
||||
return tenantMapper.insertTenant(tenant);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改保存租户信息
|
||||
*
|
||||
* @param tenant 租户信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int updateTenant(SysTenant tenant)
|
||||
{
|
||||
// 校验租户编码唯一性
|
||||
if (!checkTenantCodeUnique(tenant))
|
||||
{
|
||||
throw new ServiceException("租户编码已存在");
|
||||
}
|
||||
return tenantMapper.updateTenant(tenant);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除租户管理信息
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int deleteTenantById(Long tenantId)
|
||||
{
|
||||
// 校验租户是否允许删除
|
||||
checkTenantCanDelete(tenantId);
|
||||
return tenantMapper.deleteTenantById(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验租户是否允许删除
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
*/
|
||||
@Override
|
||||
public void checkTenantCanDelete(Long tenantId)
|
||||
{
|
||||
// 默认租户不允许删除
|
||||
if (tenantId != null && tenantId == 1L)
|
||||
{
|
||||
throw new ServiceException("默认租户不允许删除");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@ package com.ruoyi.system.service.impl;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@ -12,9 +13,13 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.ruoyi.common.config.EncryptionConfig;
|
||||
import com.ruoyi.common.constant.CacheConstants;
|
||||
import com.ruoyi.common.constant.VerificationPermissions;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.entity.SysRole;
|
||||
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.EncryptionUtil;
|
||||
import com.ruoyi.common.utils.IdCardValidator;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
@ -56,6 +61,9 @@ public class UserVerificationServiceImpl implements IUserVerificationService
|
||||
@Autowired
|
||||
private VerificationNotificationService notificationService;
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
/**
|
||||
* 提交企业法人CA认证申请
|
||||
*
|
||||
@ -170,11 +178,15 @@ public class UserVerificationServiceImpl implements IUserVerificationService
|
||||
if ("APPROVED".equals(verification.getVerificationStatus()))
|
||||
{
|
||||
notificationService.sendEnterpriseApprovalNotification(verification);
|
||||
// 清除用户认证状态缓存
|
||||
evictVerificationCache(request.getUserId());
|
||||
return AjaxResult.success("法人CA认证成功,企业认证已自动完成");
|
||||
}
|
||||
else
|
||||
{
|
||||
notificationService.sendEnterpriseRejectionNotification(verification);
|
||||
// 清除用户认证状态缓存
|
||||
evictVerificationCache(request.getUserId());
|
||||
return AjaxResult.error("法人CA认证失败: " + caResponse.getRejectReason());
|
||||
}
|
||||
}
|
||||
@ -222,6 +234,18 @@ public class UserVerificationServiceImpl implements IUserVerificationService
|
||||
@Override
|
||||
public AjaxResult getEnterpriseVerificationStatus(Long userId)
|
||||
{
|
||||
// 检查数据访问权限:用户只能查询自己的认证状态
|
||||
checkDataAccessPermission(userId);
|
||||
logSensitiveDataAccess("查询企业认证状态", userId);
|
||||
|
||||
// 尝试从缓存获取
|
||||
String cacheKey = CacheConstants.ENTERPRISE_VERIFICATION_KEY + userId;
|
||||
UserEnterpriseVerification cached = redisCache.getCacheObject(cacheKey);
|
||||
if (cached != null)
|
||||
{
|
||||
return AjaxResult.success(cached);
|
||||
}
|
||||
|
||||
UserEnterpriseVerification verification = enterpriseVerificationMapper.selectVerificationByUserId(userId);
|
||||
|
||||
if (verification == null)
|
||||
@ -229,6 +253,9 @@ public class UserVerificationServiceImpl implements IUserVerificationService
|
||||
return AjaxResult.success("未认证", null);
|
||||
}
|
||||
|
||||
// 缓存结果
|
||||
redisCache.setCacheObject(cacheKey, verification, CacheConstants.VERIFICATION_STATUS_EXPIRE_MINUTES, TimeUnit.MINUTES);
|
||||
|
||||
return AjaxResult.success(verification);
|
||||
}
|
||||
|
||||
@ -277,6 +304,69 @@ public class UserVerificationServiceImpl implements IUserVerificationService
|
||||
return auditLogMapper.selectAuditLogByVerificationId(verificationType, verificationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查数据访问权限:用户只能访问自己的认证数据,管理员可访问所有数据
|
||||
*
|
||||
* @param targetUserId 目标用户ID
|
||||
* @throws ServiceException 权限不足时抛出异常
|
||||
*/
|
||||
private void checkDataAccessPermission(Long targetUserId)
|
||||
{
|
||||
try
|
||||
{
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
Long currentUserId = loginUser.getUserId();
|
||||
|
||||
// 超级管理员可访问所有数据
|
||||
if (SecurityUtils.isAdmin(currentUserId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 具有查看所有认证信息权限的用户可访问所有数据
|
||||
if (SecurityUtils.hasPermi(VerificationPermissions.VIEW_ALL_VERIFICATION)
|
||||
|| SecurityUtils.hasPermi(VerificationPermissions.MANAGE_VERIFICATION))
|
||||
{
|
||||
log.info("管理员用户ID: {} 访问用户ID: {} 的认证数据", currentUserId, targetUserId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 普通用户只能访问自己的数据
|
||||
if (!currentUserId.equals(targetUserId))
|
||||
{
|
||||
log.warn("用户ID: {} 尝试访问用户ID: {} 的认证数据,权限不足", currentUserId, targetUserId);
|
||||
throw new ServiceException("无权访问其他用户的认证数据");
|
||||
}
|
||||
}
|
||||
catch (ServiceException e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("检查数据访问权限时发生异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录敏感数据访问日志
|
||||
*
|
||||
* @param operation 操作描述
|
||||
* @param targetUserId 目标用户ID
|
||||
*/
|
||||
private void logSensitiveDataAccess(String operation, Long targetUserId)
|
||||
{
|
||||
try
|
||||
{
|
||||
Long currentUserId = SecurityUtils.getUserId();
|
||||
log.info("[敏感数据访问] 操作: {}, 操作人ID: {}, 目标用户ID: {}", operation, currentUserId, targetUserId);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// 日志记录失败不影响主流程
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录审核日志
|
||||
*
|
||||
@ -427,11 +517,15 @@ public class UserVerificationServiceImpl implements IUserVerificationService
|
||||
if ("APPROVED".equals(verification.getVerificationStatus()))
|
||||
{
|
||||
notificationService.sendIdentityApprovalNotification(verification);
|
||||
// 清除用户认证状态缓存
|
||||
evictVerificationCache(userId);
|
||||
return AjaxResult.success("身份认证成功");
|
||||
}
|
||||
else
|
||||
{
|
||||
notificationService.sendIdentityRejectionNotification(verification);
|
||||
// 清除用户认证状态缓存
|
||||
evictVerificationCache(userId);
|
||||
return AjaxResult.error("身份认证失败: " + verification.getRejectReason());
|
||||
}
|
||||
}
|
||||
@ -476,6 +570,18 @@ public class UserVerificationServiceImpl implements IUserVerificationService
|
||||
@Override
|
||||
public AjaxResult getIdentityVerificationStatus(Long userId)
|
||||
{
|
||||
// 检查数据访问权限:用户只能查询自己的认证状态
|
||||
checkDataAccessPermission(userId);
|
||||
logSensitiveDataAccess("查询身份认证状态", userId);
|
||||
|
||||
// 尝试从缓存获取(不含身份证号)
|
||||
String cacheKey = CacheConstants.IDENTITY_VERIFICATION_KEY + userId;
|
||||
UserIdentityVerification cached = redisCache.getCacheObject(cacheKey);
|
||||
if (cached != null)
|
||||
{
|
||||
return AjaxResult.success(cached);
|
||||
}
|
||||
|
||||
UserIdentityVerification verification = identityVerificationMapper.selectVerificationByUserId(userId);
|
||||
|
||||
if (verification == null)
|
||||
@ -486,6 +592,9 @@ public class UserVerificationServiceImpl implements IUserVerificationService
|
||||
// 不返回加密的身份证号
|
||||
verification.setIdCardNumber(null);
|
||||
|
||||
// 缓存结果
|
||||
redisCache.setCacheObject(cacheKey, verification, CacheConstants.VERIFICATION_STATUS_EXPIRE_MINUTES, TimeUnit.MINUTES);
|
||||
|
||||
return AjaxResult.success(verification);
|
||||
}
|
||||
|
||||
@ -493,14 +602,34 @@ public class UserVerificationServiceImpl implements IUserVerificationService
|
||||
* 查询身份认证列表
|
||||
*
|
||||
* @param userId 用户ID(可选)
|
||||
* @param realName 真实姓名(可选)
|
||||
* @param verificationStatus 认证状态(可选)
|
||||
* @return 身份认证集合
|
||||
*/
|
||||
@Override
|
||||
public List<UserIdentityVerification> listIdentityVerifications(Long userId, String verificationStatus)
|
||||
public List<UserIdentityVerification> listIdentityVerifications(Long userId, String realName, String verificationStatus)
|
||||
{
|
||||
// 如果指定了userId,检查数据访问权限
|
||||
if (userId != null)
|
||||
{
|
||||
checkDataAccessPermission(userId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 查询所有用户数据需要管理员权限
|
||||
if (!SecurityUtils.isAdmin(SecurityUtils.getUserId())
|
||||
&& !SecurityUtils.hasPermi(VerificationPermissions.VIEW_ALL_VERIFICATION)
|
||||
&& !SecurityUtils.hasPermi(VerificationPermissions.MANAGE_VERIFICATION))
|
||||
{
|
||||
// 普通用户只能查询自己的数据
|
||||
userId = SecurityUtils.getUserId();
|
||||
}
|
||||
}
|
||||
logSensitiveDataAccess("查询身份认证列表", userId);
|
||||
|
||||
UserIdentityVerification query = new UserIdentityVerification();
|
||||
query.setUserId(userId);
|
||||
query.setRealName(realName);
|
||||
query.setVerificationStatus(verificationStatus);
|
||||
|
||||
List<UserIdentityVerification> list = identityVerificationMapper.selectVerificationList(query);
|
||||
@ -511,6 +640,28 @@ public class UserVerificationServiceImpl implements IUserVerificationService
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询身份认证详情(含审核历史)
|
||||
*
|
||||
* @param verificationId 认证ID
|
||||
* @return 身份认证详情
|
||||
*/
|
||||
@Override
|
||||
public UserIdentityVerification getIdentityVerificationDetail(Long verificationId)
|
||||
{
|
||||
UserIdentityVerification verification = identityVerificationMapper.selectVerificationById(verificationId);
|
||||
if (verification == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// 不返回加密的身份证号
|
||||
verification.setIdCardNumber(null);
|
||||
// 附加审核历史
|
||||
List<VerificationAuditLog> auditLogs = auditLogMapper.selectAuditLogByVerificationId("IDENTITY", verificationId);
|
||||
verification.setAuditLogs(auditLogs);
|
||||
return verification;
|
||||
}
|
||||
|
||||
// ==================== 认证状态查询方法实现 ====================
|
||||
|
||||
/**
|
||||
@ -528,20 +679,31 @@ public class UserVerificationServiceImpl implements IUserVerificationService
|
||||
return true;
|
||||
}
|
||||
|
||||
// 查询企业认证状态
|
||||
UserEnterpriseVerification enterpriseVerification = enterpriseVerificationMapper.selectVerificationByUserId(userId);
|
||||
if (enterpriseVerification == null || !"APPROVED".equals(enterpriseVerification.getVerificationStatus()))
|
||||
// 尝试从缓存获取认证状态
|
||||
String cacheKey = CacheConstants.VERIFICATION_STATUS_KEY + userId;
|
||||
Boolean cachedStatus = redisCache.getCacheObject(cacheKey);
|
||||
if (cachedStatus != null)
|
||||
{
|
||||
return cachedStatus;
|
||||
}
|
||||
|
||||
// 查询企业认证状态(使用轻量级状态查询)
|
||||
String enterpriseStatus = enterpriseVerificationMapper.selectVerificationStatusByUserId(userId);
|
||||
if (!"APPROVED".equals(enterpriseStatus))
|
||||
{
|
||||
redisCache.setCacheObject(cacheKey, Boolean.FALSE, CacheConstants.VERIFICATION_STATUS_EXPIRE_MINUTES, TimeUnit.MINUTES);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 查询身份认证状态
|
||||
UserIdentityVerification identityVerification = identityVerificationMapper.selectVerificationByUserId(userId);
|
||||
if (identityVerification == null || !"APPROVED".equals(identityVerification.getVerificationStatus()))
|
||||
// 查询身份认证状态(使用轻量级状态查询)
|
||||
String identityStatus = identityVerificationMapper.selectVerificationStatusByUserId(userId);
|
||||
if (!"APPROVED".equals(identityStatus))
|
||||
{
|
||||
redisCache.setCacheObject(cacheKey, Boolean.FALSE, CacheConstants.VERIFICATION_STATUS_EXPIRE_MINUTES, TimeUnit.MINUTES);
|
||||
return false;
|
||||
}
|
||||
|
||||
redisCache.setCacheObject(cacheKey, Boolean.TRUE, CacheConstants.VERIFICATION_STATUS_EXPIRE_MINUTES, TimeUnit.MINUTES);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -557,6 +719,14 @@ public class UserVerificationServiceImpl implements IUserVerificationService
|
||||
// 检查是否为超级管理员
|
||||
boolean isSuperAdmin = (userId != null && userId == 1L);
|
||||
|
||||
// 尝试从缓存获取完整状态DTO
|
||||
String cacheKey = CacheConstants.VERIFICATION_STATUS_KEY + "dto:" + userId;
|
||||
com.ruoyi.system.domain.dto.VerificationStatusDTO cachedDto = redisCache.getCacheObject(cacheKey);
|
||||
if (cachedDto != null)
|
||||
{
|
||||
return cachedDto;
|
||||
}
|
||||
|
||||
// 查询企业认证状态
|
||||
UserEnterpriseVerification enterpriseVerification = enterpriseVerificationMapper.selectVerificationByUserId(userId);
|
||||
String enterpriseStatus = enterpriseVerification != null ? enterpriseVerification.getVerificationStatus() : "PENDING";
|
||||
@ -569,12 +739,35 @@ public class UserVerificationServiceImpl implements IUserVerificationService
|
||||
boolean fullyVerified = isSuperAdmin ||
|
||||
("APPROVED".equals(enterpriseStatus) && "APPROVED".equals(identityStatus));
|
||||
|
||||
return new com.ruoyi.system.domain.dto.VerificationStatusDTO(
|
||||
com.ruoyi.system.domain.dto.VerificationStatusDTO dto = new com.ruoyi.system.domain.dto.VerificationStatusDTO(
|
||||
userId,
|
||||
enterpriseStatus,
|
||||
identityStatus,
|
||||
fullyVerified,
|
||||
isSuperAdmin
|
||||
);
|
||||
|
||||
// 缓存结果
|
||||
redisCache.setCacheObject(cacheKey, dto, CacheConstants.VERIFICATION_STATUS_EXPIRE_MINUTES, TimeUnit.MINUTES);
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除用户认证状态相关缓存
|
||||
*
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
private void evictVerificationCache(Long userId)
|
||||
{
|
||||
if (userId == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
redisCache.deleteObject(CacheConstants.VERIFICATION_STATUS_KEY + userId);
|
||||
redisCache.deleteObject(CacheConstants.VERIFICATION_STATUS_KEY + "dto:" + userId);
|
||||
redisCache.deleteObject(CacheConstants.ENTERPRISE_VERIFICATION_KEY + userId);
|
||||
redisCache.deleteObject(CacheConstants.IDENTITY_VERIFICATION_KEY + userId);
|
||||
log.debug("已清除用户认证状态缓存: userId={}", userId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,145 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.system.mapper.SysTenantMapper">
|
||||
|
||||
<resultMap type="SysTenant" id="SysTenantResult">
|
||||
<id property="tenantId" column="tenant_id" />
|
||||
<result property="tenantCode" column="tenant_code" />
|
||||
<result property="tenantName" column="tenant_name" />
|
||||
<result property="tenantType" column="tenant_type" />
|
||||
<result property="companyType" column="company_type" />
|
||||
<result property="contactPerson" column="contact_person" />
|
||||
<result property="contactPhone" column="contact_phone" />
|
||||
<result property="contactEmail" column="contact_email" />
|
||||
<result property="domain" column="domain" />
|
||||
<result property="logoUrl" column="logo_url" />
|
||||
<result property="status" column="status" />
|
||||
<result property="maxUsers" column="max_users" />
|
||||
<result property="maxCompanies" column="max_companies" />
|
||||
<result property="expireDate" column="expire_date" />
|
||||
<result property="packageId" column="package_id" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="remark" column="remark" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectTenantVo">
|
||||
select tenant_id, 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, create_time, update_by, update_time, remark
|
||||
from sys_tenant
|
||||
</sql>
|
||||
|
||||
<select id="selectTenantList" parameterType="SysTenant" resultMap="SysTenantResult">
|
||||
<include refid="selectTenantVo"/>
|
||||
<where>
|
||||
<if test="tenantCode != null and tenantCode != ''">
|
||||
AND tenant_code like concat('%', #{tenantCode}, '%')
|
||||
</if>
|
||||
<if test="tenantName != null and tenantName != ''">
|
||||
AND tenant_name like concat('%', #{tenantName}, '%')
|
||||
</if>
|
||||
<if test="tenantType != null and tenantType != ''">
|
||||
AND tenant_type = #{tenantType}
|
||||
</if>
|
||||
<if test="companyType != null and companyType != ''">
|
||||
AND company_type = #{companyType}
|
||||
</if>
|
||||
<if test="status != null and status != ''">
|
||||
AND status = #{status}
|
||||
</if>
|
||||
</where>
|
||||
order by tenant_id
|
||||
</select>
|
||||
|
||||
<select id="selectTenantById" parameterType="Long" resultMap="SysTenantResult">
|
||||
<include refid="selectTenantVo"/>
|
||||
where tenant_id = #{tenantId}
|
||||
</select>
|
||||
|
||||
<select id="selectTenantByCode" parameterType="String" resultMap="SysTenantResult">
|
||||
<include refid="selectTenantVo"/>
|
||||
where tenant_code = #{tenantCode}
|
||||
</select>
|
||||
|
||||
<select id="checkTenantCodeUnique" parameterType="String" resultType="int">
|
||||
select count(1) from sys_tenant where tenant_code = #{tenantCode}
|
||||
</select>
|
||||
|
||||
<select id="selectTenantByUserId" parameterType="Long" resultMap="SysTenantResult">
|
||||
<include refid="selectTenantVo"/>
|
||||
where tenant_id = (select tenant_id from sys_user where user_id = #{userId} limit 1)
|
||||
</select>
|
||||
|
||||
<insert id="insertTenant" parameterType="SysTenant" useGeneratedKeys="true" keyProperty="tenantId">
|
||||
insert into sys_tenant
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="tenantCode != null and tenantCode != ''">tenant_code,</if>
|
||||
<if test="tenantName != null and tenantName != ''">tenant_name,</if>
|
||||
<if test="tenantType != null">tenant_type,</if>
|
||||
<if test="companyType != null">company_type,</if>
|
||||
<if test="contactPerson != null">contact_person,</if>
|
||||
<if test="contactPhone != null">contact_phone,</if>
|
||||
<if test="contactEmail != null">contact_email,</if>
|
||||
<if test="domain != null">domain,</if>
|
||||
<if test="logoUrl != null">logo_url,</if>
|
||||
<if test="status != null">status,</if>
|
||||
<if test="maxUsers != null">max_users,</if>
|
||||
<if test="maxCompanies != null">max_companies,</if>
|
||||
<if test="expireDate != null">expire_date,</if>
|
||||
<if test="packageId != null">package_id,</if>
|
||||
<if test="createBy != null">create_by,</if>
|
||||
<if test="remark != null">remark,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="tenantCode != null and tenantCode != ''">#{tenantCode},</if>
|
||||
<if test="tenantName != null and tenantName != ''">#{tenantName},</if>
|
||||
<if test="tenantType != null">#{tenantType},</if>
|
||||
<if test="companyType != null">#{companyType},</if>
|
||||
<if test="contactPerson != null">#{contactPerson},</if>
|
||||
<if test="contactPhone != null">#{contactPhone},</if>
|
||||
<if test="contactEmail != null">#{contactEmail},</if>
|
||||
<if test="domain != null">#{domain},</if>
|
||||
<if test="logoUrl != null">#{logoUrl},</if>
|
||||
<if test="status != null">#{status},</if>
|
||||
<if test="maxUsers != null">#{maxUsers},</if>
|
||||
<if test="maxCompanies != null">#{maxCompanies},</if>
|
||||
<if test="expireDate != null">#{expireDate},</if>
|
||||
<if test="packageId != null">#{packageId},</if>
|
||||
<if test="createBy != null">#{createBy},</if>
|
||||
<if test="remark != null">#{remark},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
<update id="updateTenant" parameterType="SysTenant">
|
||||
update sys_tenant
|
||||
<trim prefix="SET" suffixOverrides=",">
|
||||
<if test="tenantCode != null and tenantCode != ''">tenant_code = #{tenantCode},</if>
|
||||
<if test="tenantName != null and tenantName != ''">tenant_name = #{tenantName},</if>
|
||||
<if test="tenantType != null">tenant_type = #{tenantType},</if>
|
||||
<if test="companyType != null">company_type = #{companyType},</if>
|
||||
<if test="contactPerson != null">contact_person = #{contactPerson},</if>
|
||||
<if test="contactPhone != null">contact_phone = #{contactPhone},</if>
|
||||
<if test="contactEmail != null">contact_email = #{contactEmail},</if>
|
||||
<if test="domain != null">domain = #{domain},</if>
|
||||
<if test="logoUrl != null">logo_url = #{logoUrl},</if>
|
||||
<if test="status != null">status = #{status},</if>
|
||||
<if test="maxUsers != null">max_users = #{maxUsers},</if>
|
||||
<if test="maxCompanies != null">max_companies = #{maxCompanies},</if>
|
||||
<if test="expireDate != null">expire_date = #{expireDate},</if>
|
||||
<if test="packageId != null">package_id = #{packageId},</if>
|
||||
<if test="updateBy != null">update_by = #{updateBy},</if>
|
||||
<if test="remark != null">remark = #{remark},</if>
|
||||
</trim>
|
||||
where tenant_id = #{tenantId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteTenantById" parameterType="Long">
|
||||
update sys_tenant set status = 'DELETED' where tenant_id = #{tenantId}
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
@ -68,6 +68,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<!-- 仅查询认证状态(轻量级查询,用于权限检查) -->
|
||||
<select id="selectVerificationStatusByUserId" parameterType="Long" resultType="String">
|
||||
select verification_status
|
||||
from sys_user_enterprise_verification
|
||||
where user_id = #{userId}
|
||||
order by create_time desc
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<select id="selectApprovedVerifications" resultMap="UserEnterpriseVerificationResult">
|
||||
<include refid="selectVerificationVo"/>
|
||||
where verification_status = 'APPROVED'
|
||||
|
||||
@ -7,6 +7,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<resultMap type="UserIdentityVerification" id="UserIdentityVerificationResult">
|
||||
<id property="verificationId" column="verification_id" />
|
||||
<result property="userId" column="user_id" />
|
||||
<result property="userName" column="user_name" />
|
||||
<result property="realName" column="real_name" />
|
||||
<result property="idCardNumber" column="id_card_number" />
|
||||
<result property="verificationStatus" column="verification_status" />
|
||||
@ -20,44 +21,54 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectVerificationVo">
|
||||
select verification_id, user_id, real_name, id_card_number, verification_status,
|
||||
ca_verification_id, reject_reason, verification_time,
|
||||
create_by, create_time, update_by, update_time
|
||||
from sys_user_identity_verification
|
||||
select v.verification_id, v.user_id, u.user_name, v.real_name, v.id_card_number,
|
||||
v.verification_status, v.ca_verification_id, v.reject_reason, v.verification_time,
|
||||
v.create_by, v.create_time, v.update_by, v.update_time
|
||||
from sys_user_identity_verification v
|
||||
left join sys_user u on u.user_id = v.user_id
|
||||
</sql>
|
||||
|
||||
<select id="selectVerificationList" parameterType="UserIdentityVerification" resultMap="UserIdentityVerificationResult">
|
||||
<include refid="selectVerificationVo"/>
|
||||
<where>
|
||||
<if test="userId != null">
|
||||
AND user_id = #{userId}
|
||||
AND v.user_id = #{userId}
|
||||
</if>
|
||||
<if test="realName != null and realName != ''">
|
||||
AND real_name like concat('%', #{realName}, '%')
|
||||
AND v.real_name like concat('%', #{realName}, '%')
|
||||
</if>
|
||||
<if test="verificationStatus != null and verificationStatus != ''">
|
||||
AND verification_status = #{verificationStatus}
|
||||
AND v.verification_status = #{verificationStatus}
|
||||
</if>
|
||||
<if test="caVerificationId != null and caVerificationId != ''">
|
||||
AND ca_verification_id = #{caVerificationId}
|
||||
AND v.ca_verification_id = #{caVerificationId}
|
||||
</if>
|
||||
<if test="params.beginTime != null and params.beginTime != ''">
|
||||
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')
|
||||
</if>
|
||||
<if test="params.endTime != null and params.endTime != ''">
|
||||
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')
|
||||
</if>
|
||||
</where>
|
||||
order by create_time desc
|
||||
order by v.create_time desc
|
||||
</select>
|
||||
|
||||
<select id="selectVerificationById" parameterType="Long" resultMap="UserIdentityVerificationResult">
|
||||
<include refid="selectVerificationVo"/>
|
||||
where verification_id = #{verificationId}
|
||||
where v.verification_id = #{verificationId}
|
||||
</select>
|
||||
|
||||
<select id="selectVerificationByUserId" parameterType="Long" resultMap="UserIdentityVerificationResult">
|
||||
<include refid="selectVerificationVo"/>
|
||||
where v.user_id = #{userId}
|
||||
order by v.create_time desc
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<!-- 仅查询认证状态(轻量级查询,用于权限检查) -->
|
||||
<select id="selectVerificationStatusByUserId" parameterType="Long" resultType="String">
|
||||
select verification_status
|
||||
from sys_user_identity_verification
|
||||
where user_id = #{userId}
|
||||
order by create_time desc
|
||||
limit 1
|
||||
|
||||
@ -3850,6 +3850,11 @@ INSERT INTO `sys_menu` VALUES (6134, '关系删除', 6130, 4, '', '', '', '', 1,
|
||||
INSERT INTO `sys_menu` VALUES (6135, '关系导出', 6130, 5, '', '', '', '', 1, 0, 'F', '0', '0', 'credit:relationship:export', '#', 'admin', '2026-03-16 14:59:18', '', NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (6155, '认证管理', 0, 5, 'verification', NULL, NULL, '', 1, 0, 'M', '0', '0', '', 'documentation', 'admin', '2026-03-05 18:23:25', 'admin', '2026-03-05 18:24:19', '认证管理目录');
|
||||
INSERT INTO `sys_menu` VALUES (6156, '认证信息管理', 6155, 1, 'manage', 'system/verification/manage/index', NULL, '', 1, 0, 'C', '0', '0', 'system:verification:view:all', 'list', 'admin', '2026-03-05 18:23:25', '', NULL, '认证信息管理菜单');
|
||||
INSERT INTO `sys_menu` VALUES (6157, '企业认证管理', 6155, 2, 'enterprise', 'system/verification/enterprise', NULL, '', 1, 0, 'C', '0', '0', 'system:verification:view:all', 'office', 'admin', '2026-03-05 18:23:25', '', NULL, '企业认证管理菜单');
|
||||
INSERT INTO `sys_menu` VALUES (6158, '企业认证查询', 6157, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'system:verification:view:all', '#', 'admin', '2026-03-05 18:23:25', '', NULL, '企业认证列表查询权限');
|
||||
INSERT INTO `sys_menu` VALUES (6159, '企业认证详情', 6157, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'system:verification:view:all', '#', 'admin', '2026-03-05 18:23:25', '', NULL, '企业认证详情查看权限');
|
||||
INSERT INTO `sys_menu` VALUES (6160, '企业认证导出', 6157, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'system:verification:export', '#', 'admin', '2026-03-05 18:23:25', '', NULL, '企业认证数据导出权限');
|
||||
INSERT INTO `sys_menu` VALUES (6161, '认证管理权限', 6157, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'system:verification:manage', '#', 'admin', '2026-03-05 18:23:25', '', NULL, '企业认证管理操作权限');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sys_notice
|
||||
|
||||
34
sql/tenant-menu.sql
Normal file
34
sql/tenant-menu.sql
Normal file
@ -0,0 +1,34 @@
|
||||
-- =============================================
|
||||
-- 租户管理菜单权限SQL
|
||||
-- =============================================
|
||||
|
||||
-- ----------------------------
|
||||
-- 1. 插入租户管理菜单
|
||||
-- ----------------------------
|
||||
-- 菜单 SQL
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES (2000, '租户管理', 0, 6, 'tenant', 'system/tenant/index', 1, 0, 'C', '0', '0', 'system:tenant:list', 'peoples', 'admin', sysdate(), '', '', '租户管理菜单');
|
||||
|
||||
-- 按钮 SQL
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, perms, menu_type, visible, status, create_by, create_time)
|
||||
VALUES ('租户查询', 2000, 1, 'system:tenant:query', 'F', '0', '0', 'admin', sysdate());
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, perms, menu_type, visible, status, create_by, create_time)
|
||||
VALUES ('租户新增', 2000, 2, 'system:tenant:add', 'F', '0', '0', 'admin', sysdate());
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, perms, menu_type, visible, status, create_by, create_time)
|
||||
VALUES ('租户修改', 2000, 3, 'system:tenant:edit', 'F', '0', '0', 'admin', sysdate());
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, perms, menu_type, visible, status, create_by, create_time)
|
||||
VALUES ('租户删除', 2000, 4, 'system:tenant:remove', 'F', '0', '0', 'admin', sysdate());
|
||||
|
||||
-- ----------------------------
|
||||
-- 2. 为超级管理员角色分配租户管理权限
|
||||
-- ----------------------------
|
||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||
SELECT 1, menu_id FROM sys_menu WHERE perms IN ('system:tenant:list', 'system:tenant:query', 'system:tenant:add', 'system:tenant:edit', 'system:tenant:remove');
|
||||
|
||||
-- ----------------------------
|
||||
-- 3. 查看菜单是否插入成功
|
||||
-- ----------------------------
|
||||
-- SELECT * FROM sys_menu WHERE menu_id = 2000 OR parent_id = 2000;
|
||||
125
sql/tenant-migration.sql
Normal file
125
sql/tenant-migration.sql
Normal file
@ -0,0 +1,125 @@
|
||||
-- =============================================
|
||||
-- 租户相关数据库变更脚本
|
||||
-- 用于若依框架 SAAS 化迁移
|
||||
-- =============================================
|
||||
|
||||
-- ----------------------------
|
||||
-- 1. 创建租户表
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `sys_tenant`;
|
||||
CREATE TABLE `sys_tenant` (
|
||||
`tenant_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '租户ID',
|
||||
`tenant_code` varchar(50) NOT NULL COMMENT '租户编码(唯一)',
|
||||
`tenant_name` varchar(100) NOT NULL COMMENT '租户名称',
|
||||
`tenant_type` varchar(20) NOT NULL DEFAULT 'COMPANY' COMMENT '租户类型: COMPANY-企业',
|
||||
`company_type` varchar(20) NULL COMMENT '公司类型: BANK-银行, CLIENT-甲方, LABOR-劳务公司',
|
||||
`contact_person` varchar(50) NULL COMMENT '联系人',
|
||||
`contact_phone` varchar(20) NULL COMMENT '联系电话',
|
||||
`contact_email` varchar(100) NULL COMMENT '联系邮箱',
|
||||
`domain` varchar(100) NULL COMMENT '租户域名',
|
||||
`logo_url` varchar(500) NULL COMMENT 'Logo URL',
|
||||
`status` varchar(10) NOT NULL DEFAULT 'ACTIVE' COMMENT '状态: ACTIVE-正常, FROZEN-冻结, DELETED-已删除',
|
||||
`max_users` int(11) NULL DEFAULT 10 COMMENT '最大用户数',
|
||||
`max_companies` int(11) NULL DEFAULT 5 COMMENT '最大公司数',
|
||||
`expire_date` date NULL COMMENT '过期日期',
|
||||
`package_id` bigint(20) NULL COMMENT '套餐ID',
|
||||
`create_by` varchar(64) NULL DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_by` varchar(64) NULL DEFAULT '' COMMENT '更新者',
|
||||
`update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`remark` varchar(500) NULL COMMENT '备注',
|
||||
PRIMARY KEY (`tenant_id`),
|
||||
UNIQUE INDEX `uk_tenant_code`(`tenant_code` ASC),
|
||||
INDEX `idx_status`(`status` ASC),
|
||||
INDEX `idx_company_type`(`company_type` ASC)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='租户表';
|
||||
|
||||
-- ----------------------------
|
||||
-- 2. 初始化默认租户 (兼容现有数据)
|
||||
-- ----------------------------
|
||||
INSERT INTO `sys_tenant` (`tenant_id`, `tenant_code`, `tenant_name`, `tenant_type`, `company_type`, `status`, `max_users`, `max_companies`, `remark`)
|
||||
VALUES (1, 'DEFAULT', '默认租户', 'COMPANY', NULL, 'ACTIVE', 1000, 100, '系统默认租户,兼容历史数据');
|
||||
|
||||
-- ----------------------------
|
||||
-- 3. 为 sys_user 表添加 tenant_id 字段
|
||||
-- ----------------------------
|
||||
ALTER TABLE `sys_user` ADD COLUMN `tenant_id` bigint(20) NULL DEFAULT 1 COMMENT '租户ID' AFTER `del_flag`;
|
||||
ALTER TABLE `sys_user` ADD INDEX `idx_user_tenant_id`(`tenant_id` ASC);
|
||||
|
||||
-- ----------------------------
|
||||
-- 4. 为 sys_dept 表添加 tenant_id 字段
|
||||
-- ----------------------------
|
||||
ALTER TABLE `sys_dept` ADD COLUMN `tenant_id` bigint(20) NULL DEFAULT 1 COMMENT '租户ID' AFTER `dept_id`;
|
||||
ALTER TABLE `sys_dept` ADD INDEX `idx_dept_tenant_id`(`tenant_id` ASC);
|
||||
|
||||
-- ----------------------------
|
||||
-- 5. 为 dc_contract 表添加 tenant_id 字段
|
||||
-- ----------------------------
|
||||
ALTER TABLE `dc_contract` ADD COLUMN `tenant_id` bigint(20) NULL DEFAULT 1 COMMENT '租户ID' AFTER `contract_id`;
|
||||
ALTER TABLE `dc_contract` ADD INDEX `idx_contract_tenant_id`(`tenant_id` ASC);
|
||||
|
||||
-- ----------------------------
|
||||
-- 6. 为 dc_service_contract 表添加 tenant_id 字段
|
||||
-- ----------------------------
|
||||
ALTER TABLE `dc_service_contract` ADD COLUMN `tenant_id` bigint(20) NULL DEFAULT 1 COMMENT '租户ID' AFTER `service_contract_id`;
|
||||
ALTER TABLE `dc_service_contract` ADD INDEX `idx_service_contract_tenant_id`(`tenant_id` ASC);
|
||||
|
||||
-- ----------------------------
|
||||
-- 7. 为 dc_employee_info 表添加 tenant_id 字段
|
||||
-- ----------------------------
|
||||
ALTER TABLE `dc_employee_info` ADD COLUMN `tenant_id` bigint(20) NULL DEFAULT 1 COMMENT '租户ID' AFTER `employee_id`;
|
||||
ALTER TABLE `dc_employee_info` ADD INDEX `idx_employee_tenant_id`(`tenant_id` ASC);
|
||||
|
||||
-- ----------------------------
|
||||
-- 8. 为 dc_employee_library 表添加 tenant_id 字段
|
||||
-- ----------------------------
|
||||
ALTER TABLE `dc_employee_library` ADD COLUMN `tenant_id` bigint(20) NULL DEFAULT 1 COMMENT '租户ID' AFTER `library_id`;
|
||||
ALTER TABLE `dc_employee_library` ADD INDEX `idx_library_tenant_id`(`tenant_id` ASC);
|
||||
|
||||
-- ----------------------------
|
||||
-- 9. 为 dc_credit 表添加 tenant_id 字段
|
||||
-- ----------------------------
|
||||
ALTER TABLE `dc_credit` ADD COLUMN `tenant_id` bigint(20) NULL DEFAULT 1 COMMENT '租户ID' AFTER `credit_id`;
|
||||
ALTER TABLE `dc_credit` ADD INDEX `idx_credit_tenant_id`(`tenant_id` ASC);
|
||||
|
||||
-- ----------------------------
|
||||
-- 10. 为 dc_financing 表添加 tenant_id 字段
|
||||
-- ----------------------------
|
||||
ALTER TABLE `dc_financing` ADD COLUMN `tenant_id` bigint(20) NULL DEFAULT 1 COMMENT '租户ID' AFTER `financing_id`;
|
||||
ALTER TABLE `dc_financing` ADD INDEX `idx_financing_tenant_id`(`tenant_id` ASC);
|
||||
|
||||
-- ----------------------------
|
||||
-- 11. 为 dc_company_relationship 表添加 tenant_id 字段
|
||||
-- ----------------------------
|
||||
ALTER TABLE `dc_company_relationship` ADD COLUMN `tenant_id` bigint(20) NULL DEFAULT 1 COMMENT '租户ID' AFTER `relationship_id`;
|
||||
ALTER TABLE `dc_company_relationship` ADD INDEX `idx_relationship_tenant_id`(`tenant_id` ASC);
|
||||
|
||||
-- ----------------------------
|
||||
-- 12. 为 dc_bank_institution 表添加 tenant_id 字段
|
||||
-- ----------------------------
|
||||
ALTER TABLE `dc_bank_institution` ADD COLUMN `tenant_id` bigint(20) NULL DEFAULT 1 COMMENT '租户ID' AFTER `bank_id`;
|
||||
ALTER TABLE `dc_bank_institution` ADD INDEX `idx_bank_tenant_id`(`tenant_id` ASC);
|
||||
|
||||
-- ----------------------------
|
||||
-- 13. 为 dc_service_period 表添加 tenant_id 字段
|
||||
-- ----------------------------
|
||||
ALTER TABLE `dc_service_period` ADD COLUMN `tenant_id` bigint(20) NULL DEFAULT 1 COMMENT '租户ID' AFTER `service_period_id`;
|
||||
ALTER TABLE `dc_service_period` ADD INDEX `idx_service_period_tenant_id`(`tenant_id` ASC);
|
||||
|
||||
-- ----------------------------
|
||||
-- 14. 为 dc_service_period_loan 表添加 tenant_id 字段
|
||||
-- ----------------------------
|
||||
ALTER TABLE `dc_service_period_loan` ADD COLUMN `tenant_id` bigint(20) NULL DEFAULT 1 COMMENT '租户ID' AFTER `link_id`;
|
||||
ALTER TABLE `dc_service_period_loan` ADD INDEX `idx_loan_tenant_id`(`tenant_id` ASC);
|
||||
|
||||
-- ----------------------------
|
||||
-- 15. 更新现有数据的 tenant_id (根据 company_id 关联)
|
||||
-- ----------------------------
|
||||
-- 注意:此脚本需要在确认现有公司数据后再执行
|
||||
-- 以下为示例逻辑,实际执行时需要根据具体业务逻辑调整
|
||||
|
||||
-- 更新用户的 tenant_id 为 1 (默认租户)
|
||||
UPDATE sys_user SET tenant_id = 1 WHERE tenant_id IS NULL;
|
||||
|
||||
-- 更新部门的 tenant_id 为 1 (默认租户)
|
||||
UPDATE sys_dept SET tenant_id = 1 WHERE tenant_id IS NULL;
|
||||
Loading…
Reference in New Issue
Block a user