批量更新

This commit is contained in:
李志强 2025-11-06 16:40:53 +08:00
parent 6a9b5d413a
commit 873a2e297a
43 changed files with 229 additions and 3321 deletions

View File

@ -108,7 +108,7 @@ async function refreshCache() {
// CommonAside
window.dispatchEvent(new CustomEvent('menu-cache-refreshed'));
ElMessage.success('菜单缓存和路由更新成功');
ElMessage.success('更新成功');
} catch (error) {
console.error('Failed to refresh cache', error);
ElMessage.error('更新缓存失败,请检查网络连接');

View File

@ -1,138 +0,0 @@
# OA 模块优化总结
## 优化完成时间
2024年
## 优化内容
### 1. 创建 OA 基础数据 Store (`pc/src/stores/oa.js`)
**功能特性:**
- ✅ 智能缓存机制5分钟缓存
- ✅ 并发请求控制
- ✅ 批量数据获取
- ✅ 统一数据管理
### 2. 重构的页面
#### ✅ 员工管理 (`employees/index.vue`)
- 使用 OA Store 获取部门、职位、角色数据
- 利用缓存机制,减少重复请求
- 数据更新后自动刷新缓存
#### ✅ 部门管理 (`departments/index.vue`)
- 使用 OA Store 管理部门数据
- 添加/编辑/删除后自动刷新缓存
- 使用响应式数据绑定
#### ✅ 职位管理 (`positions/index.vue`)
- 使用 OA Store 获取部门和职位数据
- 利用缓存机制优化性能
- 数据更新后自动刷新缓存
#### ✅ 组织架构 (`organization/index.vue`)
- 使用 OA Store 获取部门和职位数据
- 部门树和职位列表共享同一份数据
- 数据更新后自动刷新缓存
## 性能提升
### 优化前
- 每次进入页面都发起 3-4 个独立请求
- 无缓存机制,频繁切换页面产生大量重复请求
- 服务器压力大
### 优化后
- **首次访问**:并行请求所有基础数据(更快)
- **缓存有效期内**:只请求业务数据(员工列表等)
- **请求次数减少**75% 减少
- **响应速度提升**:缓存命中时,几乎瞬时响应
- **服务器压力降低**:减少 75% 的基础数据请求
## 缓存策略
### 缓存时间
- 默认5 分钟
- 可配置:在 `oa.js` 中修改 `cacheTime` 常量
### 缓存管理
- 自动失效:缓存过期后自动刷新
- 手动刷新:调用 `refresh` 方法
- 数据更新后:自动刷新相关缓存
## 使用方式
### 在页面中使用
```javascript
import { useOAStore } from '@/stores/oa';
const oaStore = useOAStore();
// 页面初始化
onMounted(async () => {
// 使用批量获取,自动利用缓存
await oaStore.fetchAllBaseData();
// 然后获取业务数据
await fetchEmployees();
});
```
### 数据更新后刷新缓存
```javascript
// 添加/编辑/删除后
await addDepartment(data);
await oaStore.refreshDepartments(); // 刷新部门缓存
```
## 优化效果对比
| 指标 | 优化前 | 优化后 | 提升 |
|------|--------|--------|------|
| 请求次数(缓存命中) | 4 次 | 1 次 | **减少 75%** |
| 响应时间(缓存命中) | ~500ms | ~50ms | **提升 90%** |
| 服务器压力 | 高 | 低 | **降低 75%** |
| 代码复用性 | 低 | 高 | **提升** |
| 数据一致性 | 一般 | 优秀 | **提升** |
## 后续建议
1. **其他模块**:可以将类似的优化应用到其他模块(如用户管理、权限管理等)
2. **缓存时间**:根据业务需求调整缓存时间
3. **后端优化**:考虑提供批量接口,一次返回所有基础数据
4. **监控**:添加性能监控,跟踪缓存命中率
## 注意事项
1. **缓存时间**:默认 5 分钟,可根据业务需求调整
2. **数据一致性**:更新数据后记得调用 `refresh` 方法
3. **租户隔离**:缓存是基于当前租户的,不同租户数据不会混淆
4. **内存占用**:缓存数据存储在内存中,页面刷新后会清空
## 文件清单
### 新增文件
- `pc/src/stores/oa.js` - OA 基础数据 Store
- `pc/src/stores/README_OA.md` - Store 使用文档
- `pc/src/views/apps/oa/OPTIMIZATION_SUMMARY.md` - 优化总结(本文件)
### 重构文件
- `pc/src/views/apps/oa/employees/index.vue` - 员工管理页面
- `pc/src/views/apps/oa/departments/index.vue` - 部门管理页面
- `pc/src/views/apps/oa/positions/index.vue` - 职位管理页面
- `pc/src/views/apps/oa/organization/index.vue` - 组织架构页面
## 测试建议
1. **功能测试**:确保所有 CRUD 操作正常工作
2. **缓存测试**:验证缓存机制是否正常工作
3. **性能测试**:对比优化前后的性能指标
4. **并发测试**:验证并发请求控制是否正常
---
**优化完成!** 🎉
所有 OA 模块页面已成功使用统一的 Store 进行数据管理,大幅提升了性能和代码质量。

View File

@ -69,6 +69,7 @@
style="width: 100%"
:loading="loadingRoles"
clearable
:key="`role-${filteredRoleList.length}-${form.tenant_id}-${form.role}`"
>
<el-option
v-for="role in filteredRoleList"
@ -83,7 +84,7 @@
</el-option>
</el-select>
<div v-if="filteredRoleList.length === 0 && !loadingRoles" style="color: #999; font-size: 12px; margin-top: 4px;">
暂无可用角色
暂无可用角色当前租户ID: {{ form.tenant_id }}
</div>
<div v-if="form.role && !hasValidRole" style="color: #f56c6c; font-size: 12px; margin-top: 4px;">
警告当前选择的角色ID ({{ form.role }}) 在角色列表中不存在
@ -139,32 +140,79 @@ const props = defineProps<{
loadingRoles: boolean;
}>();
// tenant_id
// tenant_id default
const filteredRoleList = computed(() => {
if (!props.roleList || props.roleList.length === 0) {
return [];
}
// tenant_id
const currentTenantId = props.formData?.tenant_id;
// tenant_id使 form.value.tenant_id
const currentTenantId = form.value.tenant_id !== null && form.value.tenant_id !== undefined
? form.value.tenant_id
: (props.formData?.tenant_id !== null && props.formData?.tenant_id !== undefined
? props.formData.tenant_id
: null);
//
console.log('filteredRoleList - currentTenantId:', currentTenantId);
console.log('filteredRoleList - roleList length:', props.roleList.length);
if (currentTenantId === null || currentTenantId === undefined) {
// tenant_id
console.log('filteredRoleList - 没有 tenant_id返回所有角色');
return props.roleList;
}
// tenant_id tenant_id 0
return props.roleList.filter((role: any) => {
const currentTenantIdNum = Number(currentTenantId);
// default tenant_id
// default = 1: tenant_id = 1
// default = 2: 使
// default = 3: tenant_id
const filtered = props.roleList.filter((role: any) => {
// tenantId tenant_id
const roleTenantId = role.tenantId !== undefined ? role.tenantId : (role.tenant_id !== undefined ? role.tenant_id : null);
const roleDefault = role.default !== undefined ? role.default : (role.Default !== undefined ? role.Default : 2); // 2
//
const currentTenantIdNum = Number(currentTenantId);
const roleTenantIdNum = roleTenantId !== null && roleTenantId !== undefined ? Number(roleTenantId) : null;
const roleDefaultNum = Number(roleDefault);
// tenant_id 0
return roleTenantIdNum === currentTenantIdNum || roleTenantIdNum === 0;
let match = false;
// 1: default = 11使
if (roleDefaultNum === 1) {
match = currentTenantIdNum === 1;
}
// 2: default = 2
else if (roleDefaultNum === 2) {
match = true;
}
// 3: default = 3tenant_id
else if (roleDefaultNum === 3) {
match = roleTenantIdNum === currentTenantIdNum;
}
// default 使
else {
// tenant_id = 0
// tenant_id
match = roleTenantIdNum === 0 || roleTenantIdNum === currentTenantIdNum;
}
if (match) {
console.log('filteredRoleList - 匹配角色:', {
id: role.roleId || role.id || role.role_id,
name: role.roleName || role.name || role.role_name,
roleTenantId: roleTenantIdNum,
roleDefault: roleDefaultNum,
currentTenantId: currentTenantIdNum
});
}
return match;
});
console.log('filteredRoleList - 过滤后数量:', filtered.length);
return filtered;
});
//
@ -267,11 +315,19 @@ watch(() => props.visible, async (val) => {
department_id: props.formData.department_id ? Number(props.formData.department_id) : null,
position_id: props.formData.position_id ? Number(props.formData.position_id) : null,
role: props.formData.role ? Number(props.formData.role) : null,
tenant_id: props.formData.tenant_id ? Number(props.formData.tenant_id) : null,
};
console.log('EmployeeEdit - 编辑模式,设置表单数据:', {
tenant_id: form.value.tenant_id,
role: form.value.role,
formData_tenant_id: props.formData.tenant_id
});
// tick
await nextTick();
} else if (val && !props.isEdit) {
//
// props.formData tenant_id
const tenantId = props.formData?.tenant_id || null;
form.value = {
id: null,
employeeNo: "",
@ -284,8 +340,11 @@ watch(() => props.visible, async (val) => {
bank_name: "",
bank_account: "",
status: 1,
tenant_id: null,
tenant_id: tenantId ? Number(tenantId) : null,
};
console.log('EmployeeEdit - 新增模式,设置表单数据:', {
tenant_id: form.value.tenant_id
});
}
});

View File

@ -63,7 +63,7 @@
width="180"
align="center"
/>
<el-table-column label="操作" width="280" align="center" fixed="right">
<el-table-column label="操作" width="340" align="center" fixed="right">
<template #default="scope">
<el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
<el-button
@ -79,6 +79,7 @@
<el-button
size="small"
type="danger"
:disabled="scope.row.role === 5"
@click="handleDelete(scope.row)"
>删除</el-button>
</template>

View File

@ -164,15 +164,15 @@ const handleCollapseAll = () => {
gap: 8px;
margin-top: 8px;
.el-button {
padding: 0;
font-size: 13px;
color: var(--el-color-primary);
// .el-button {
// padding: 0;
// font-size: 13px;
// color: var(--el-color-primary);
&:hover {
color: var(--el-color-primary-light-3);
}
}
// &:hover {
// color: var(--el-color-primary-light-3);
// }
// }
}
}

View File

@ -458,6 +458,12 @@ const submitForm = async (formData: any) => {
};
const handleDelete = async (employee: Employee) => {
// ID5
if (employee.role === 5) {
ElMessage.warning("该员工的角色不允许删除");
return;
}
ElMessageBox.confirm("确认删除该员工?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",

View File

@ -273,7 +273,13 @@ const handleTaskChange = (task: any) => {
//
onMounted(() => {
// 线
new Chart(document.getElementById("lineChart") as HTMLCanvasElement, {
const lineChartEl = document.getElementById("lineChart") as HTMLCanvasElement | null;
if (!lineChartEl) {
console.error("Line chart element not found");
return;
}
new Chart(lineChartEl, {
type: "line",
data: {
labels: ["1月", "2月", "3月", "4月", "5月", "6月", "7月"],
@ -332,7 +338,13 @@ onMounted(() => {
});
//
new Chart(document.getElementById("barChart") as HTMLCanvasElement, {
const barChartEl = document.getElementById("barChart") as HTMLCanvasElement | null;
if (!barChartEl) {
console.error("Bar chart element not found");
return;
}
new Chart(barChartEl, {
type: "bar",
data: {
labels: ["周一", "周二", "周三", "周四", "周五", "周六", "周日"],

View File

@ -85,6 +85,7 @@ func (c *OAController) GetOABaseData() {
roleList = append(roleList, map[string]interface{}{
"roleId": role.RoleId,
"tenantId": role.TenantId,
"default": role.Default,
"roleCode": role.RoleCode,
"roleName": role.RoleName,
"description": role.Description,

View File

@ -29,6 +29,8 @@ func (c *RoleController) GetAllRoles() {
for _, role := range roles {
roleList = append(roleList, map[string]interface{}{
"roleId": role.RoleId,
"tenantId": role.TenantId,
"default": role.Default,
"roleCode": role.RoleCode,
"roleName": role.RoleName,
"description": role.Description,
@ -124,10 +126,30 @@ func (c *RoleController) GetRoleByTenantId() {
c.ServeJSON()
return
}
// 转换为前端需要的格式,确保包含 tenantId 和 default 字段
roleList := make([]map[string]interface{}, 0)
for _, role := range roles {
roleList = append(roleList, map[string]interface{}{
"roleId": role.RoleId,
"tenantId": role.TenantId,
"default": role.Default,
"roleCode": role.RoleCode,
"roleName": role.RoleName,
"description": role.Description,
"status": role.Status,
"sortOrder": role.SortOrder,
"createTime": role.CreateTime,
"updateTime": role.UpdateTime,
"createBy": role.CreateBy,
"updateBy": role.UpdateBy,
})
}
c.Data["json"] = map[string]interface{}{
"code": 0,
"message": "获取角色列表成功",
"data": roles,
"data": roleList,
}
c.ServeJSON()
}

View File

@ -1,108 +0,0 @@
-- 为员工表添加工资卡信息和密码字段
-- 创建时间: 2025
SET @dbname = DATABASE();
SET @tablename = 'yz_tenant_employees';
-- 添加工资卡开户行字段
SET @columnname = 'bank_name';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE
(TABLE_SCHEMA = @dbname)
AND (TABLE_NAME = @tablename)
AND (COLUMN_NAME = @columnname)
) > 0,
'SELECT "Column bank_name already exists in yz_tenant_employees" AS "";',
'ALTER TABLE yz_tenant_employees ADD COLUMN bank_name VARCHAR(100) DEFAULT NULL COMMENT ''工资卡开户行'' AFTER position_id;'
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;
-- 添加工资卡卡号字段
SET @columnname = 'bank_account';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE
(TABLE_SCHEMA = @dbname)
AND (TABLE_NAME = @tablename)
AND (COLUMN_NAME = @columnname)
) > 0,
'SELECT "Column bank_account already exists in yz_tenant_employees" AS "";',
'ALTER TABLE yz_tenant_employees ADD COLUMN bank_account VARCHAR(50) DEFAULT NULL COMMENT ''工资卡卡号'' AFTER bank_name;'
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;
-- 添加登录密码字段
SET @columnname = 'password';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE
(TABLE_SCHEMA = @dbname)
AND (TABLE_NAME = @tablename)
AND (COLUMN_NAME = @columnname)
) > 0,
'SELECT "Column password already exists in yz_tenant_employees" AS "";',
'ALTER TABLE yz_tenant_employees ADD COLUMN password VARCHAR(255) DEFAULT NULL COMMENT ''登录密码(加密后)'' AFTER bank_account;'
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;
-- 添加盐值字段
SET @columnname = 'salt';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE
(TABLE_SCHEMA = @dbname)
AND (TABLE_NAME = @tablename)
AND (COLUMN_NAME = @columnname)
) > 0,
'SELECT "Column salt already exists in yz_tenant_employees" AS "";',
'ALTER TABLE yz_tenant_employees ADD COLUMN salt VARCHAR(100) DEFAULT NULL COMMENT ''密码盐值'' AFTER password;'
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;
-- 添加最后登录时间字段
SET @columnname = 'last_login_time';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE
(TABLE_SCHEMA = @dbname)
AND (TABLE_NAME = @tablename)
AND (COLUMN_NAME = @columnname)
) > 0,
'SELECT "Column last_login_time already exists in yz_tenant_employees" AS "";',
'ALTER TABLE yz_tenant_employees ADD COLUMN last_login_time DATETIME DEFAULT NULL COMMENT ''最后登录时间'' AFTER salt;'
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;
-- 添加最后登录IP字段
SET @columnname = 'last_login_ip';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE
(TABLE_SCHEMA = @dbname)
AND (TABLE_NAME = @tablename)
AND (COLUMN_NAME = @columnname)
) > 0,
'SELECT "Column last_login_ip already exists in yz_tenant_employees" AS "";',
'ALTER TABLE yz_tenant_employees ADD COLUMN last_login_ip VARCHAR(50) DEFAULT NULL COMMENT ''最后登录IP'' AFTER last_login_time;'
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;

View File

@ -1,65 +0,0 @@
-- 添加知识库管理相关的菜单项
-- 注意需要先查询知识库菜单的IDparent_id假设为 11
-- 如果知识库菜单ID为11添加分类管理和标签管理菜单
-- 请根据实际数据库中的知识库菜单ID修改下面的 parent_id 值
-- 查询知识库菜单ID如果需要
-- SELECT id FROM yz_menus WHERE path = '/apps/knowledge';
-- 添加分类管理菜单(如果不存在)
INSERT INTO yz_menus (
name,
path,
parent_id,
icon,
`order`,
status,
component_path,
menu_type,
description
)
SELECT
'分类管理',
'/apps/knowledge/category',
id,
'fa-solid fa-folder',
2,
1,
'@/views/apps/knowledge/category/index.vue',
1,
'知识库分类管理'
FROM yz_menus
WHERE path = '/apps/knowledge'
AND NOT EXISTS (
SELECT 1 FROM yz_menus WHERE path = '/apps/knowledge/category'
);
-- 添加标签管理菜单(如果不存在)
INSERT INTO yz_menus (
name,
path,
parent_id,
icon,
`order`,
status,
component_path,
menu_type,
description
)
SELECT
'标签管理',
'/apps/knowledge/tag',
id,
'fa-solid fa-tags',
3,
1,
'@/views/apps/knowledge/tag/index.vue',
1,
'知识库标签管理'
FROM yz_menus
WHERE path = '/apps/knowledge'
AND NOT EXISTS (
SELECT 1 FROM yz_menus WHERE path = '/apps/knowledge/tag'
);

View File

@ -1,38 +0,0 @@
-- 为菜单表添加 delete_time 字段(软删除)
-- 如果字段已存在,则不会重复添加
SET @dbname = DATABASE();
SET @tablename = 'yz_menus';
SET @columnname = 'delete_time';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE
(TABLE_SCHEMA = @dbname)
AND (TABLE_NAME = @tablename)
AND (COLUMN_NAME = @columnname)
) > 0,
'SELECT "Column delete_time already exists in yz_menus" AS "";',
'ALTER TABLE yz_menus ADD COLUMN delete_time DATETIME DEFAULT NULL COMMENT ''删除时间(软删除)'' AFTER update_time;'
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;
-- 添加索引以优化查询性能
SET @indexname = 'idx_delete_time';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
WHERE
(TABLE_SCHEMA = @dbname)
AND (TABLE_NAME = @tablename)
AND (INDEX_NAME = @indexname)
) > 0,
'SELECT "Index idx_delete_time already exists in yz_menus" AS "";',
'ALTER TABLE yz_menus ADD INDEX idx_delete_time (delete_time);'
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;

View File

@ -1,15 +0,0 @@
-- 添加组织架构管理菜单
-- 注意:请根据实际情况修改 parent_id92 是 OA 模块的 ID请确认
INSERT INTO yz_menus (name, path, parent_id, icon, `order`, status, component_path, menu_type, description)
VALUES ('组织架构', '/apps/oa/organization', 92, 'fa-solid fa-sitemap', 1, 1, '@/views/apps/oa/organization/index.vue', 1, '组织架构管理(部门与职位)')
ON DUPLICATE KEY UPDATE
name = VALUES(name),
path = VALUES(path),
icon = VALUES(icon),
`order` = VALUES(`order`),
status = VALUES(status),
component_path = VALUES(component_path),
menu_type = VALUES(menu_type),
description = VALUES(description);

View File

@ -1,57 +0,0 @@
-- 检查角色权限数据
-- 查询角色ID为1的权限信息
-- 1. 查看角色基本信息
SELECT
role_id,
role_name,
menu_ids,
JSON_LENGTH(COALESCE(menu_ids, CAST('[]' AS JSON))) as menu_count,
tenant_id,
status
FROM yz_roles
WHERE role_id = 1;
-- 2. 查看所有角色的 menu_ids 字段
SELECT
role_id,
role_name,
menu_ids,
JSON_LENGTH(COALESCE(menu_ids, CAST('[]' AS JSON))) as menu_count
FROM yz_roles
WHERE delete_time IS NULL
ORDER BY role_id;
-- 3. 查看菜单表中有权限标识的菜单
SELECT
id,
name,
path,
permission,
menu_type,
parent_id
FROM yz_menus
WHERE delete_time IS NULL
AND permission IS NOT NULL
AND permission != ''
ORDER BY id
LIMIT 20;
-- 4. 如果 role_id=1 的 menu_ids 不为空,查看这些菜单的权限标识
-- 假设 menu_ids 是 [1,2,3],可以这样查询:
-- SELECT DISTINCT permission
-- FROM yz_menus
-- WHERE id IN (1,2,3)
-- AND delete_time IS NULL
-- AND permission IS NOT NULL
-- AND permission != '';
-- 5. 查看 menu_ids 字段的原始JSON值用于调试
SELECT
role_id,
role_name,
menu_ids,
CAST(menu_ids AS CHAR) as menu_ids_str
FROM yz_roles
WHERE role_id = 1;

View File

@ -1,178 +0,0 @@
-- 只创建缺失的表,不重复创建已存在的表
-- 适用于部分表已存在的情况
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- 检查并创建用户表(如果不存在)
CREATE TABLE IF NOT EXISTS yz_users (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
username VARCHAR(50) NOT NULL COMMENT '用户名',
password VARCHAR(255) NOT NULL COMMENT '加密后的密码',
salt VARCHAR(100) NOT NULL COMMENT '密码盐值',
email VARCHAR(100) DEFAULT NULL COMMENT '邮箱地址',
avatar VARCHAR(500) DEFAULT NULL COMMENT '头像URL',
nickname VARCHAR(50) DEFAULT NULL COMMENT '昵称',
role VARCHAR(20) DEFAULT 'user' COMMENT '用户角色admin-管理员user-普通用户',
status TINYINT DEFAULT 1 COMMENT '用户状态0-禁用1-启用',
last_login_time DATETIME DEFAULT NULL COMMENT '最后登录时间',
last_login_ip VARCHAR(45) DEFAULT NULL COMMENT '最后登录IP',
login_count INT DEFAULT 0 COMMENT '登录次数',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
create_by VARCHAR(50) DEFAULT NULL COMMENT '创建人',
update_by VARCHAR(50) DEFAULT NULL COMMENT '更新人',
-- 索引
UNIQUE KEY uk_username (username),
INDEX idx_email (email),
INDEX idx_role (role),
INDEX idx_status (status),
INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
-- 检查并创建菜单表(如果不存在)
CREATE TABLE IF NOT EXISTS yz_menus (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '菜单ID',
name VARCHAR(100) NOT NULL COMMENT '菜单名称',
path VARCHAR(255) NOT NULL COMMENT '菜单路径',
parent_id INT DEFAULT 0 COMMENT '父菜单ID0表示顶级菜单',
level TINYINT DEFAULT 1 COMMENT '菜单层级1-一级菜单2-二级菜单3-三级菜单',
full_path VARCHAR(500) DEFAULT NULL COMMENT '完整路径(包含所有父级路径)',
is_leaf TINYINT DEFAULT 0 COMMENT '是否叶子节点0-非叶子节点1-叶子节点',
has_children TINYINT DEFAULT 0 COMMENT '是否有子菜单0-无子菜单1-有子菜单',
children_count INT DEFAULT 0 COMMENT '子菜单数量',
icon VARCHAR(100) DEFAULT NULL COMMENT '菜单图标',
order_num INT DEFAULT 0 COMMENT '排序序号',
status TINYINT DEFAULT 1 COMMENT '状态0-禁用1-启用',
component_path VARCHAR(500) DEFAULT NULL COMMENT '组件路径',
is_external TINYINT DEFAULT 0 COMMENT '是否外部链接0-内部路由1-外部链接',
external_url VARCHAR(1000) DEFAULT NULL COMMENT '外部链接地址',
menu_type TINYINT DEFAULT 1 COMMENT '菜单类型1-普通菜单2-分组菜单3-按钮菜单',
permission VARCHAR(200) DEFAULT NULL COMMENT '权限标识',
description VARCHAR(500) DEFAULT NULL COMMENT '菜单描述',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
create_by VARCHAR(100) DEFAULT NULL COMMENT '创建人',
update_by VARCHAR(100) DEFAULT NULL COMMENT '更新人',
-- 索引
INDEX idx_parent_id (parent_id),
INDEX idx_level (level),
INDEX idx_status (status),
INDEX idx_order (order_num),
INDEX idx_menu_type (menu_type),
INDEX idx_full_path (full_path(255))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='菜单表(增强版)';
-- 检查并创建程序分类表(如果不存在)
CREATE TABLE IF NOT EXISTS yz_program_category (
category_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '分类ID',
category_name VARCHAR(100) NOT NULL COMMENT '分类名称',
category_desc VARCHAR(500) DEFAULT NULL COMMENT '分类描述',
parent_id INT DEFAULT 0 COMMENT '父分类ID0表示顶级分类',
sort_order INT DEFAULT 0 COMMENT '排序序号,用于展示顺序',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-- 索引
INDEX idx_parent_id (parent_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='程序分类表';
-- 检查并创建程序信息表(如果不存在)
CREATE TABLE IF NOT EXISTS yz_program_info (
program_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '程序ID',
category_id INT NOT NULL COMMENT '所属分类ID',
program_name VARCHAR(200) NOT NULL COMMENT '程序名称',
program_desc TEXT COMMENT '程序描述',
jump_url VARCHAR(1000) NOT NULL COMMENT '跳转地址',
icon_url VARCHAR(1000) DEFAULT NULL COMMENT '程序图标地址',
version VARCHAR(50) DEFAULT NULL COMMENT '程序版本',
status TINYINT DEFAULT 1 COMMENT '状态0-禁用1-启用',
sort_order INT DEFAULT 0 COMMENT '排序序号,用于展示顺序',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-- 索引
INDEX idx_category_id (category_id),
INDEX idx_status (status),
CONSTRAINT yz_fk_program_category FOREIGN KEY (category_id) REFERENCES yz_program_category (category_id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='程序信息表';
-- 检查并创建租户表(如果不存在)
CREATE TABLE IF NOT EXISTS yz_tenants (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '租户ID',
-- 基本信息
name VARCHAR(100) NOT NULL COMMENT '租户名称',
code VARCHAR(50) NOT NULL COMMENT '租户编码(唯一)',
owner VARCHAR(50) NOT NULL COMMENT '负责人',
phone VARCHAR(20) DEFAULT NULL COMMENT '联系电话',
email VARCHAR(100) DEFAULT NULL COMMENT '邮箱地址',
-- 状态信息
status VARCHAR(20) DEFAULT 'enabled' COMMENT '状态enabled-启用disabled-禁用',
audit_status VARCHAR(20) DEFAULT 'pending' COMMENT '审核状态pending-待审核approved-已通过rejected-已拒绝',
-- 审核信息
audit_comment TEXT DEFAULT NULL COMMENT '审核意见',
audit_by VARCHAR(50) DEFAULT NULL COMMENT '审核人',
audit_time DATETIME DEFAULT NULL COMMENT '审核时间',
-- 其他信息
remark TEXT DEFAULT NULL COMMENT '备注',
-- 时间戳
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
create_by VARCHAR(50) DEFAULT NULL COMMENT '创建人',
update_by VARCHAR(50) DEFAULT NULL COMMENT '更新人',
-- 索引
UNIQUE KEY uk_code (code),
INDEX idx_name (name),
INDEX idx_owner (owner),
INDEX idx_status (status),
INDEX idx_audit_status (audit_status),
INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='租户表';
-- 检查并创建文件表(如果不存在)
CREATE TABLE IF NOT EXISTS yz_files (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '文件ID',
tenant_id VARCHAR(64) NOT NULL COMMENT '租户ID',
user_id INT NOT NULL DEFAULT 0 COMMENT '用户ID',
-- 文件基础信息
file_name VARCHAR(255) NOT NULL COMMENT '文件名称',
original_name VARCHAR(255) NOT NULL COMMENT '原始文件名',
file_path VARCHAR(500) NOT NULL COMMENT '文件存储路径',
file_url VARCHAR(500) COMMENT '文件访问URL',
file_size BIGINT NOT NULL DEFAULT 0 COMMENT '文件大小(字节)',
file_type VARCHAR(50) NOT NULL COMMENT '文件类型',
file_ext VARCHAR(20) NOT NULL COMMENT '文件扩展名',
-- 分类信息
category VARCHAR(100) NOT NULL COMMENT '文件分类',
sub_category VARCHAR(100) COMMENT '子分类',
-- 状态信息
status TINYINT DEFAULT 1 COMMENT '状态(1:正常, 0:删除)',
is_public TINYINT DEFAULT 0 COMMENT '是否公开(1:是, 0:否)',
-- 上传信息
upload_by VARCHAR(100) NOT NULL COMMENT '上传人',
upload_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间',
-- 索引
INDEX idx_tenant (tenant_id),
INDEX idx_user (user_id),
INDEX idx_category (category),
INDEX idx_upload_time (upload_time),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='文件表';
SET FOREIGN_KEY_CHECKS = 1;
-- 完成创建
SELECT 'Missing tables created successfully!' as message;

View File

@ -1,163 +0,0 @@
-- 创建OA模块相关表租户应用
-- 创建时间: 2025
-- 描述: 创建租户部门表、租户职位表和租户员工表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- =============================================
-- 1. 创建租户部门表
-- =============================================
CREATE TABLE IF NOT EXISTS yz_tenant_departments (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '部门ID',
tenant_id INT NOT NULL DEFAULT 0 COMMENT '租户ID',
name VARCHAR(100) NOT NULL COMMENT '部门名称',
code VARCHAR(50) DEFAULT NULL COMMENT '部门编码',
parent_id INT DEFAULT 0 COMMENT '父部门ID0表示顶级部门',
description TEXT DEFAULT NULL COMMENT '部门描述',
manager_id INT DEFAULT NULL COMMENT '部门经理ID',
sort_order INT DEFAULT 0 COMMENT '排序序号',
status TINYINT DEFAULT 1 COMMENT '状态1-启用0-禁用',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
delete_time DATETIME DEFAULT NULL COMMENT '删除时间(软删除)',
-- 索引
INDEX idx_tenant_id (tenant_id),
INDEX idx_code (code),
INDEX idx_parent_id (parent_id),
INDEX idx_status (status),
INDEX idx_sort_order (sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='租户部门表';
-- =============================================
-- 2. 创建租户职位表
-- =============================================
CREATE TABLE IF NOT EXISTS yz_tenant_positions (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '职位ID',
tenant_id INT NOT NULL DEFAULT 0 COMMENT '租户ID',
name VARCHAR(100) NOT NULL COMMENT '职位名称',
code VARCHAR(50) DEFAULT NULL COMMENT '职位编码',
department_id INT DEFAULT NULL COMMENT '所属部门ID',
level INT DEFAULT 0 COMMENT '职位级别',
description TEXT DEFAULT NULL COMMENT '职位描述',
sort_order INT DEFAULT 0 COMMENT '排序序号',
status TINYINT DEFAULT 1 COMMENT '状态1-启用0-禁用',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
delete_time DATETIME DEFAULT NULL COMMENT '删除时间(软删除)',
-- 索引
INDEX idx_tenant_id (tenant_id),
INDEX idx_code (code),
INDEX idx_department_id (department_id),
INDEX idx_status (status),
INDEX idx_sort_order (sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='租户职位表';
-- =============================================
-- 3. 创建租户员工表(如果不存在)
-- =============================================
CREATE TABLE IF NOT EXISTS yz_tenant_employees (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '员工ID',
tenant_id INT NOT NULL DEFAULT 0 COMMENT '租户ID',
employee_no VARCHAR(50) NOT NULL COMMENT '工号',
name VARCHAR(50) NOT NULL COMMENT '姓名',
phone VARCHAR(20) DEFAULT NULL COMMENT '手机号',
email VARCHAR(100) DEFAULT NULL COMMENT '邮箱',
department_id INT DEFAULT NULL COMMENT '部门ID',
position_id INT DEFAULT NULL COMMENT '职位ID',
status TINYINT DEFAULT 1 COMMENT '状态1-在职0-离职',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
delete_time DATETIME DEFAULT NULL COMMENT '删除时间(软删除)',
-- 索引
INDEX idx_tenant_id (tenant_id),
INDEX idx_employee_no (employee_no),
INDEX idx_name (name),
INDEX idx_department_id (department_id),
INDEX idx_position_id (position_id),
INDEX idx_status (status),
INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='租户员工表';
-- =============================================
-- 4. 更新用户表,添加部门和职位字段(如果不存在)
-- =============================================
-- 检查并添加 department_id 字段
SET @dbname = DATABASE();
SET @tablename = 'yz_users';
SET @columnname = 'department_id';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE
(TABLE_SCHEMA = @dbname)
AND (TABLE_NAME = @tablename)
AND (COLUMN_NAME = @columnname)
) > 0,
'SELECT "Column department_id already exists in yz_users" AS "";',
'ALTER TABLE yz_users ADD COLUMN department_id INT DEFAULT NULL COMMENT ''部门ID'' AFTER role;'
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;
-- 检查并添加 position_id 字段
SET @columnname = 'position_id';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE
(TABLE_SCHEMA = @dbname)
AND (TABLE_NAME = @tablename)
AND (COLUMN_NAME = @columnname)
) > 0,
'SELECT "Column position_id already exists in yz_users" AS "";',
'ALTER TABLE yz_users ADD COLUMN position_id INT DEFAULT NULL COMMENT ''职位ID'' AFTER department_id;'
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;
-- =============================================
-- 5. 更新租户员工表,添加部门和职位字段(如果不存在)
-- =============================================
-- 检查并添加 department_id 字段
SET @tablename = 'yz_tenant_employees';
SET @columnname = 'department_id';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE
(TABLE_SCHEMA = @dbname)
AND (TABLE_NAME = @tablename)
AND (COLUMN_NAME = @columnname)
) > 0,
'SELECT "Column department_id already exists in yz_employees" AS "";',
'ALTER TABLE yz_tenant_employees ADD COLUMN department_id INT DEFAULT NULL COMMENT ''部门ID'' AFTER email;'
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;
-- 检查并添加 position_id 字段
SET @columnname = 'position_id';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE
(TABLE_SCHEMA = @dbname)
AND (TABLE_NAME = @tablename)
AND (COLUMN_NAME = @columnname)
) > 0,
'SELECT "Column position_id already exists in yz_tenant_employees" AS "";',
'ALTER TABLE yz_tenant_employees ADD COLUMN position_id INT DEFAULT NULL COMMENT ''职位ID'' AFTER department_id;'
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;
SET FOREIGN_KEY_CHECKS = 1;

View File

@ -1,18 +0,0 @@
-- 删除现有表并重新创建
-- 注意:这会删除所有数据,请谨慎使用
SET FOREIGN_KEY_CHECKS = 0;
-- 删除现有表(按依赖关系顺序)
DROP TABLE IF EXISTS yz_user_role_relations;
DROP TABLE IF EXISTS yz_user_roles;
DROP TABLE IF EXISTS yz_files;
DROP TABLE IF EXISTS yz_program_info;
DROP TABLE IF EXISTS yz_program_category;
DROP TABLE IF EXISTS yz_menus;
DROP TABLE IF EXISTS yz_users;
SET FOREIGN_KEY_CHECKS = 1;
-- 现在可以重新运行 init_database.sql
SELECT 'Tables dropped successfully. You can now run init_database.sql' as message;

View File

@ -1,336 +0,0 @@
-- 云泽系统数据库初始化脚本
-- 创建时间: 2024
-- 描述: 包含所有系统表的创建和初始化数据
-- 设置字符集
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- =============================================
-- 1. 用户相关表
-- =============================================
-- 创建用户表
CREATE TABLE yz_users (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
username VARCHAR(50) NOT NULL COMMENT '用户名',
password VARCHAR(255) NOT NULL COMMENT '加密后的密码',
salt VARCHAR(100) NOT NULL COMMENT '密码盐值',
email VARCHAR(100) DEFAULT NULL COMMENT '邮箱地址',
avatar VARCHAR(500) DEFAULT NULL COMMENT '头像URL',
nickname VARCHAR(50) DEFAULT NULL COMMENT '昵称',
role VARCHAR(20) DEFAULT 'user' COMMENT '用户角色admin-管理员user-普通用户',
status TINYINT DEFAULT 1 COMMENT '用户状态0-禁用1-启用',
last_login_time DATETIME DEFAULT NULL COMMENT '最后登录时间',
last_login_ip VARCHAR(45) DEFAULT NULL COMMENT '最后登录IP',
login_count INT DEFAULT 0 COMMENT '登录次数',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
create_by VARCHAR(50) DEFAULT NULL COMMENT '创建人',
update_by VARCHAR(50) DEFAULT NULL COMMENT '更新人',
-- 索引
UNIQUE KEY uk_username (username),
INDEX idx_email (email),
INDEX idx_role (role),
INDEX idx_status (status),
INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
-- =============================================
-- 2. 菜单相关表
-- =============================================
-- 创建菜单表(增强版,包含完整父子级别参数)
CREATE TABLE yz_menus (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '菜单ID',
name VARCHAR(100) NOT NULL COMMENT '菜单名称',
path VARCHAR(255) NOT NULL COMMENT '菜单路径',
parent_id INT DEFAULT 0 COMMENT '父菜单ID0表示顶级菜单',
icon VARCHAR(100) DEFAULT NULL COMMENT '菜单图标',
`order` INT DEFAULT 0 COMMENT '排序序号',
`status` TINYINT DEFAULT 1 COMMENT '状态0-禁用1-启用',
component_path VARCHAR(500) DEFAULT NULL COMMENT '组件路径',
is_external TINYINT DEFAULT 0 COMMENT '是否外部链接0-内部路由1-外部链接',
external_url VARCHAR(1000) DEFAULT NULL COMMENT '外部链接地址',
menu_type TINYINT DEFAULT 1 COMMENT '菜单类型1-普通菜单2-分组菜单3-按钮菜单',
permission VARCHAR(200) DEFAULT NULL COMMENT '权限标识',
description VARCHAR(500) DEFAULT NULL COMMENT '菜单描述',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
create_by VARCHAR(100) DEFAULT NULL COMMENT '创建人',
update_by VARCHAR(100) DEFAULT NULL COMMENT '更新人',
-- 索引
INDEX idx_parent_id (parent_id),
INDEX idx_status (`status`),
INDEX idx_order (`order`),
INDEX idx_menu_type (menu_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='菜单表(增强版)';
-- =============================================
-- 3. 程序管理相关表
-- =============================================
-- 创建程序分类表
CREATE TABLE yz_program_category (
category_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '分类ID',
category_name VARCHAR(100) NOT NULL COMMENT '分类名称',
category_desc VARCHAR(500) DEFAULT NULL COMMENT '分类描述',
parent_id INT DEFAULT 0 COMMENT '父分类ID0表示顶级分类',
sort_order INT DEFAULT 0 COMMENT '排序序号,用于展示顺序',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-- 索引
INDEX idx_parent_id (parent_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='程序分类表';
-- 创建程序信息表
CREATE TABLE yz_program_info (
program_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '程序ID',
category_id INT NOT NULL COMMENT '所属分类ID',
program_name VARCHAR(200) NOT NULL COMMENT '程序名称',
program_desc TEXT COMMENT '程序描述',
jump_url VARCHAR(1000) NOT NULL COMMENT '跳转地址',
icon_url VARCHAR(1000) DEFAULT NULL COMMENT '程序图标地址',
version VARCHAR(50) DEFAULT NULL COMMENT '程序版本',
status TINYINT DEFAULT 1 COMMENT '状态0-禁用1-启用',
sort_order INT DEFAULT 0 COMMENT '排序序号,用于展示顺序',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-- 索引
INDEX idx_category_id (category_id),
INDEX idx_status (status),
CONSTRAINT yz_fk_program_category FOREIGN KEY (category_id) REFERENCES yz_program_category (category_id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='程序信息表';
-- =============================================
-- 4. 租户管理相关表
-- =============================================
-- 创建租户表
CREATE TABLE yz_tenants (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '租户ID',
-- 基本信息
name VARCHAR(100) NOT NULL COMMENT '租户名称',
code VARCHAR(50) NOT NULL COMMENT '租户编码(唯一)',
owner VARCHAR(50) NOT NULL COMMENT '负责人',
phone VARCHAR(20) DEFAULT NULL COMMENT '联系电话',
email VARCHAR(100) DEFAULT NULL COMMENT '邮箱地址',
-- 状态信息
status VARCHAR(20) DEFAULT 'enabled' COMMENT '状态enabled-启用disabled-禁用',
audit_status VARCHAR(20) DEFAULT 'pending' COMMENT '审核状态pending-待审核approved-已通过rejected-已拒绝',
-- 审核信息
audit_comment TEXT DEFAULT NULL COMMENT '审核意见',
audit_by VARCHAR(50) DEFAULT NULL COMMENT '审核人',
audit_time DATETIME DEFAULT NULL COMMENT '审核时间',
-- 其他信息
remark TEXT DEFAULT NULL COMMENT '备注',
-- 时间戳
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
create_by VARCHAR(50) DEFAULT NULL COMMENT '创建人',
update_by VARCHAR(50) DEFAULT NULL COMMENT '更新人',
-- 索引
UNIQUE KEY uk_code (code),
INDEX idx_name (name),
INDEX idx_owner (owner),
INDEX idx_status (status),
INDEX idx_audit_status (audit_status),
INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='租户表';
-- =============================================
-- 5. 文件管理相关表
-- =============================================
-- 创建文件表
CREATE TABLE yz_files (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '文件ID',
tenant_id VARCHAR(64) NOT NULL COMMENT '租户ID',
user_id INT NOT NULL DEFAULT 0 COMMENT '用户ID',
-- 文件基础信息
file_name VARCHAR(255) NOT NULL COMMENT '文件名称',
original_name VARCHAR(255) NOT NULL COMMENT '原始文件名',
file_path VARCHAR(500) NOT NULL COMMENT '文件存储路径',
file_url VARCHAR(500) COMMENT '文件访问URL',
file_size BIGINT NOT NULL DEFAULT 0 COMMENT '文件大小(字节)',
file_type VARCHAR(50) NOT NULL COMMENT '文件类型',
file_ext VARCHAR(20) NOT NULL COMMENT '文件扩展名',
-- 分类信息
category VARCHAR(100) NOT NULL COMMENT '文件分类',
sub_category VARCHAR(100) COMMENT '子分类',
-- 状态信息
status TINYINT DEFAULT 1 COMMENT '状态(1:正常, 0:删除)',
is_public TINYINT DEFAULT 0 COMMENT '是否公开(1:是, 0:否)',
-- 上传信息
upload_by VARCHAR(100) NOT NULL COMMENT '上传人',
upload_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间',
-- 索引
INDEX idx_tenant (tenant_id),
INDEX idx_user (user_id),
INDEX idx_category (category),
INDEX idx_upload_time (upload_time),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='文件表';
-- =============================================
-- 6. 插入初始数据
-- =============================================
-- 插入默认租户数据
INSERT INTO yz_tenants (name, code, owner, phone, email, status, audit_status, audit_comment, audit_by, audit_time, remark, create_by) VALUES
('默认租户', 'default', 'admin', '13800138000', 'admin@yunzer.com', 'enabled', 'approved', '系统默认租户,自动通过审核', 'system', NOW(), '系统默认租户,用于初始化数据', 'system'),
('示例租户A', 'demo-a', '张三', '13900139000', 'zhangsan@demo.com', 'enabled', 'approved', '资料完整,审核通过', 'admin', DATE_SUB(NOW(), INTERVAL 30 DAY), '演示租户A用于展示功能', 'admin'),
('示例租户B', 'demo-b', '李四', '13700137000', 'lisi@demo.com', 'enabled', 'pending', NULL, NULL, NULL, '待审核租户,资料已提交', 'admin');
-- 插入默认管理员用户
-- 注意:实际使用时需要生成真实的加密密码和盐值
INSERT INTO yz_users (username, password, salt, email, nickname, role, status, create_by) VALUES
('admin', 'encrypted_password_here', 'salt_here', 'admin@yunzer.com', '超级管理员', 'admin', 1, 'system'),
('test', 'encrypted_password_here', 'salt_here', 'test@yunzer.com', '测试用户', 'user', 1, 'system');
-- 插入默认菜单数据
INSERT INTO yz_menus (name, path, parent_id, icon, `order`, status, component_path, menu_type, description) VALUES
('仪表盘', '/dashboard', 0, 'fa-solid fa-gauge', 1, 1, '@/views/dashboard/index.vue', 1, '系统仪表盘,展示关键指标'),
('系统管理', '/system', 0, 'fa-solid fa-screwdriver-wrench', 4, 1, null, 2, '系统管理功能模块'),
('系统设置', '/settings', 0, 'fa-solid fa-gear', 99, 1, '@/views/settings/index.vue', 1, '系统参数设置'),
('应用管理', '/apps', 0, 'fa-solid fa-gear', 4, 1, null, 2, '应用管理功能模块'),
('文件管理', '/system/files', 2, 'fa-solid fa-folder', 3, 1, '@/views/system/files/index.vue', 1, '文件管理系统'),
('租户管理', '/system/tenant', 2, 'fas fa-user-friends', 2, 1, '@/views/system/tenant/index.vue', 1, '租户管理'),
('用户管理', '/system/users', 2, 'fa-solid fa-user', 1, 1, '@/views/system/users/index.vue', 1, '用户信息管理'),
('角色管理', '/system/roles', 2, 'fa-solid fa-user-tag', 2, 1, '@/views/system/roles/index.vue', 1, '角色权限管理'),
('权限管理', '/system/permissions', 2, 'fa-solid fa-key', 2, 1, '@/views/system/permissions/index.vue', 1, '权限管理'),
('菜单管理', '/system/menus', 2, 'fa-solid fa-bars-progress', 2, 1, '@/views/system/menus/manager.vue', 1, '菜单权限管理'),
('程序管理', '/system/programs', 2, 'fa-solid fa-grip', 3, 1, '@/views/system/programs/index.vue', 1, '程序功能管理'),
('知识库', '/apps/knowledge', 4, 'fa-solid fa-book', 1, 1, '@/views/apps/knowledge/index.vue', 1, '知识库管理'),
('编辑', '/apps/knowledge/edit', 11, '', 1, 1, '@/views/apps/knowledge/components/edit.vue', 1, '知识库编辑'),
('详情', '/apps/knowledge/detail', 11, '', 1, 1, '@/views/apps/knowledge/components/detail.vue', 1, '知识库详情');
-- =============================================
-- 4. 知识库相关表
-- =============================================
-- 创建知识库分类表
CREATE TABLE IF NOT EXISTS yz_knowledge_category (
category_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '分类ID',
category_name VARCHAR(100) NOT NULL COMMENT '分类名称',
category_desc VARCHAR(500) DEFAULT NULL COMMENT '分类描述',
parent_id INT DEFAULT 0 COMMENT '父分类ID0表示顶级分类',
sort_order INT DEFAULT 0 COMMENT '排序序号',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_parent_id (parent_id),
INDEX idx_sort_order (sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识库分类表';
-- 创建知识库标签表
CREATE TABLE IF NOT EXISTS yz_knowledge_tags (
tag_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '标签ID',
tag_name VARCHAR(50) NOT NULL COMMENT '标签名称',
tag_color VARCHAR(20) DEFAULT NULL COMMENT '标签颜色',
usage_count INT DEFAULT 0 COMMENT '使用次数',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE KEY uk_tag_name (tag_name),
INDEX idx_usage_count (usage_count)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识库标签表';
-- 创建知识库内容表
CREATE TABLE IF NOT EXISTS yz_knowledge (
knowledge_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '知识ID',
title VARCHAR(200) NOT NULL COMMENT '标题',
category_id INT DEFAULT NULL COMMENT '分类ID',
tags TEXT COMMENT '标签JSON数组存储标签名称',
author VARCHAR(50) NOT NULL COMMENT '作者',
content LONGTEXT COMMENT '正文内容(富文本)',
summary VARCHAR(500) DEFAULT NULL COMMENT '摘要',
cover_url VARCHAR(500) DEFAULT NULL COMMENT '封面图片URL',
status TINYINT DEFAULT 0 COMMENT '状态0-草稿1-已发布2-已归档',
view_count INT DEFAULT 0 COMMENT '查看次数',
like_count INT DEFAULT 0 COMMENT '点赞数',
is_recommend TINYINT DEFAULT 0 COMMENT '是否推荐0-否1-是',
is_top TINYINT DEFAULT 0 COMMENT '是否置顶0-否1-是',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
create_by VARCHAR(50) DEFAULT NULL COMMENT '创建人',
update_by VARCHAR(50) DEFAULT NULL COMMENT '更新人',
INDEX idx_category_id (category_id),
INDEX idx_author (author),
INDEX idx_title (title),
INDEX idx_status (status),
INDEX idx_create_time (create_time),
INDEX idx_view_count (view_count),
INDEX idx_is_recommend (is_recommend),
INDEX idx_is_top (is_top)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识库内容表';
-- 创建知识库收藏表
CREATE TABLE IF NOT EXISTS yz_knowledge_favorites (
favorite_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '收藏ID',
knowledge_id INT NOT NULL COMMENT '知识ID',
user_id INT NOT NULL COMMENT '用户ID',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
UNIQUE KEY uk_knowledge_user (knowledge_id, user_id),
INDEX idx_user_id (user_id),
CONSTRAINT yz_fk_fav_knowledge FOREIGN KEY (knowledge_id) REFERENCES yz_knowledge (knowledge_id) ON DELETE CASCADE,
CONSTRAINT yz_fk_fav_user FOREIGN KEY (user_id) REFERENCES yz_users (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识库收藏表';
-- 插入默认知识库分类
INSERT INTO yz_knowledge_category (category_name, category_desc, parent_id, sort_order) VALUES
('技术文档', '技术相关的文档资料', 0, 1),
('产品手册', '产品使用手册和说明', 0, 2),
('用户指南', '用户操作指南和教程', 0, 3),
('常见问题', '常见问题解答', 0, 4),
('API文档', 'API接口文档', 0, 5),
('Vue', 'Vue框架相关', 1, 101),
('React', 'React框架相关', 1, 102),
('Go', 'Go语言相关', 1, 103);
-- 插入默认知识库标签
INSERT INTO yz_knowledge_tags (tag_name, tag_color, usage_count) VALUES
('Vue', '#4CAF50', 0),
('React', '#42A5F5', 0),
('TypeScript', '#3178C6', 0),
('Element Plus', '#409EFF', 0),
('Vue Router', '#4FC08D', 0),
('Pinia', '#FFD54F', 0),
('Go', '#00ADD8', 0),
('Python', '#3776AB', 0);
-- 插入默认程序分类
INSERT INTO yz_program_category (category_name, category_desc, parent_id, sort_order) VALUES
('办公软件', '办公相关程序', 0, 1),
('开发工具', '开发相关工具', 0, 2),
('系统工具', '系统管理工具', 0, 3);
-- 插入默认程序信息
INSERT INTO yz_program_info (category_id, program_name, program_desc, jump_url, icon_url, version, status, sort_order) VALUES
(1, '文档编辑器', '在线文档编辑工具', '/editor', '/icons/editor.png', '1.0.0', 1, 1),
(2, '代码编辑器', '在线代码编辑工具', '/code', '/icons/code.png', '1.0.0', 1, 1),
(3, '系统监控', '系统状态监控工具', '/monitor', '/icons/monitor.png', '1.0.0', 1, 1);
-- 恢复外键检查
SET FOREIGN_KEY_CHECKS = 1;
-- 完成初始化
SELECT 'Database initialization completed successfully!' as message;

View File

@ -1,124 +0,0 @@
-- =============================================
-- 角色权限迁移脚本(简化版,不使用存储过程)
-- 将 yz_role_menus 表中的权限数据迁移到 yz_roles 表的 menu_ids 字段JSON数组
-- =============================================
SET NAMES utf8mb4;
-- =============================================
-- 步骤1: 在 yz_roles 表中添加 menu_ids 字段JSON类型存储菜单ID数组
-- =============================================
-- 检查字段是否已存在,如果不存在则添加
SET @exist := (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = 'yz_roles'
AND column_name = 'menu_ids');
SET @sqlstmt := IF(@exist = 0,
'ALTER TABLE yz_roles ADD COLUMN menu_ids JSON NULL COMMENT ''菜单权限ID数组JSON格式存储'' AFTER description',
'SELECT ''字段 menu_ids 已存在,跳过添加'' AS message');
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- =============================================
-- 步骤2: 从 yz_role_menus 表迁移数据到 yz_roles.menu_ids
-- =============================================
-- 检查 yz_role_menus 表是否存在
SET @table_exists := (SELECT COUNT(*) FROM information_schema.tables
WHERE table_schema = DATABASE()
AND table_name = 'yz_role_menus');
-- 如果表存在,执行迁移
-- 注意需要分步执行因为PREPARE不能执行多语句
-- 2.1 创建临时表存储每个角色的菜单ID数组
-- 使用 GROUP_CONCAT 和 CONCAT 来构建JSON数组兼容性更好
-- 处理 NULL 情况:如果 GROUP_CONCAT 返回 NULL则使用空数组 '[]'
SET @sqlstmt := IF(@table_exists > 0,
'CREATE TEMPORARY TABLE temp_role_menu_ids AS
SELECT
role_id,
IFNULL(CONCAT(''['', GROUP_CONCAT(menu_id ORDER BY menu_id SEPARATOR '',''), '']''), ''[]'') as menu_ids_json
FROM yz_role_menus
GROUP BY role_id',
'SELECT ''表 yz_role_menus 不存在,跳过数据迁移'' AS message');
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 2.2 更新 yz_roles 表的 menu_ids 字段
-- 将字符串转换为 JSON 类型
SET @sqlstmt := IF(@table_exists > 0,
'UPDATE yz_roles r
INNER JOIN temp_role_menu_ids t ON r.role_id = t.role_id
SET r.menu_ids = CAST(t.menu_ids_json AS JSON)',
'SELECT ''跳过更新'' AS message');
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 2.3 删除临时表
SET @sqlstmt := IF(@table_exists > 0,
'DROP TEMPORARY TABLE IF EXISTS temp_role_menu_ids',
'SELECT ''跳过删除临时表'' AS message');
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 2.4 对于没有权限的角色,确保设置为空数组(如果还没有设置)
-- 使用 CAST 将字符串转换为 JSON 类型
UPDATE yz_roles
SET menu_ids = CAST('[]' AS JSON)
WHERE menu_ids IS NULL;
-- =============================================
-- 步骤3: 验证迁移结果
-- =============================================
SELECT
r.role_id,
r.role_name,
r.menu_ids,
JSON_LENGTH(COALESCE(r.menu_ids, CAST('[]' AS JSON))) as menu_count,
(SELECT COUNT(*) FROM yz_role_menus WHERE role_id = r.role_id) as old_count
FROM yz_roles r
WHERE r.delete_time IS NULL
ORDER BY r.role_id;
-- =============================================
-- 步骤4: 备份旧表(可选,建议先备份)
-- =============================================
-- 检查 yz_role_menus 表是否存在
SET @table_exists := (SELECT COUNT(*) FROM information_schema.tables
WHERE table_schema = DATABASE()
AND table_name = 'yz_role_menus');
-- 检查备份表是否已存在
SET @backup_exists := (SELECT COUNT(*) FROM information_schema.tables
WHERE table_schema = DATABASE()
AND table_name = 'yz_role_menus_backup');
-- 如果原表存在且备份表不存在,则创建备份
SET @sqlstmt := IF(@table_exists > 0 AND @backup_exists = 0,
'CREATE TABLE yz_role_menus_backup AS SELECT * FROM yz_role_menus',
IF(@backup_exists > 0,
'SELECT ''备份表 yz_role_menus_backup 已存在,跳过备份'' AS message',
'SELECT ''表 yz_role_menus 不存在,无需备份'' AS message'));
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- =============================================
-- 注意:迁移完成后,需要确认数据正确,然后可以删除 yz_role_menus 表
-- 删除命令DROP TABLE IF EXISTS yz_role_menus;
-- =============================================

View File

@ -1,109 +0,0 @@
-- =============================================
-- 角色权限迁移脚本
-- 将 yz_role_menus 表中的权限数据迁移到 yz_roles 表的 menu_ids 字段JSON数组
-- 执行时间: 2025
-- =============================================
SET NAMES utf8mb4;
-- =============================================
-- 步骤1: 在 yz_roles 表中添加 menu_ids 字段JSON类型存储菜单ID数组
-- =============================================
-- 检查字段是否已存在
SET @exist := (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = 'yz_roles'
AND column_name = 'menu_ids');
SET @sqlstmt := IF(@exist = 0,
'ALTER TABLE yz_roles ADD COLUMN menu_ids JSON NULL COMMENT "菜单权限ID数组JSON格式存储" AFTER description',
'SELECT "字段 menu_ids 已存在" AS message');
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- =============================================
-- 步骤2: 从 yz_role_menus 表迁移数据到 yz_roles.menu_ids
-- =============================================
-- 使用临时存储过程迁移数据
DELIMITER $$
DROP PROCEDURE IF EXISTS migrate_role_permissions$$
CREATE PROCEDURE migrate_role_permissions()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE v_role_id INT;
DECLARE v_menu_ids JSON;
DECLARE cur CURSOR FOR
SELECT role_id, JSON_ARRAYAGG(menu_id ORDER BY menu_id) as menu_ids
FROM yz_role_menus
GROUP BY role_id;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
read_loop: LOOP
FETCH cur INTO v_role_id, v_menu_ids;
IF done THEN
LEAVE read_loop;
END IF;
-- 更新角色表的 menu_ids 字段
UPDATE yz_roles
SET menu_ids = v_menu_ids
WHERE role_id = v_role_id;
END LOOP;
CLOSE cur;
-- 对于没有权限的角色,设置为空数组
UPDATE yz_roles
SET menu_ids = JSON_ARRAY()
WHERE menu_ids IS NULL;
SELECT '数据迁移完成' AS message;
END$$
DELIMITER ;
-- 执行迁移
CALL migrate_role_permissions();
-- 删除临时存储过程
DROP PROCEDURE IF EXISTS migrate_role_permissions;
-- =============================================
-- 步骤3: 验证迁移结果
-- =============================================
-- 查看迁移后的数据
SELECT
r.role_id,
r.role_name,
r.menu_ids,
JSON_LENGTH(r.menu_ids) as menu_count,
(SELECT COUNT(*) FROM yz_role_menus WHERE role_id = r.role_id) as old_count
FROM yz_roles r
WHERE r.delete_time IS NULL
ORDER BY r.role_id;
-- =============================================
-- 步骤4: 备份旧表(可选,建议先备份)
-- =============================================
-- 创建备份表
CREATE TABLE IF NOT EXISTS yz_role_menus_backup AS
SELECT * FROM yz_role_menus;
SELECT '备份表 yz_role_menus_backup 创建完成' AS message;
-- =============================================
-- 注意:迁移完成后,需要确认数据正确,然后可以删除 yz_role_menus 表
-- 删除命令DROP TABLE IF EXISTS yz_role_menus;
-- =============================================

View File

@ -1,192 +0,0 @@
-- 性能优化索引脚本
-- 创建时间: 2025
-- 描述: 为常用查询字段添加索引,提升查询性能
SET NAMES utf8mb4;
-- =============================================
-- 1. 部门表 (yz_tenant_departments) 索引优化
-- =============================================
-- 检查并添加 tenant_id 索引(如果不存在)
SET @exist := (SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'yz_tenant_departments'
AND index_name = 'idx_tenant_id');
SET @sqlstmt := IF(@exist = 0,
'CREATE INDEX idx_tenant_id ON yz_tenant_departments(tenant_id)',
'SELECT "索引 idx_tenant_id 已存在" AS message');
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 检查并添加 delete_time 索引(如果不存在)
SET @exist := (SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'yz_tenant_departments'
AND index_name = 'idx_delete_time');
SET @sqlstmt := IF(@exist = 0,
'CREATE INDEX idx_delete_time ON yz_tenant_departments(delete_time)',
'SELECT "索引 idx_delete_time 已存在" AS message');
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 检查并添加复合索引 (tenant_id, delete_time) 用于常用查询
SET @exist := (SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'yz_tenant_departments'
AND index_name = 'idx_tenant_delete');
SET @sqlstmt := IF(@exist = 0,
'CREATE INDEX idx_tenant_delete ON yz_tenant_departments(tenant_id, delete_time)',
'SELECT "索引 idx_tenant_delete 已存在" AS message');
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 检查并添加 parent_id 索引(用于树形结构查询)
SET @exist := (SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'yz_tenant_departments'
AND index_name = 'idx_parent_id');
SET @sqlstmt := IF(@exist = 0,
'CREATE INDEX idx_parent_id ON yz_tenant_departments(parent_id)',
'SELECT "索引 idx_parent_id 已存在" AS message');
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- =============================================
-- 2. 职位表 (yz_tenant_positions) 索引优化
-- =============================================
-- 检查并添加 tenant_id 索引(如果不存在)
SET @exist := (SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'yz_tenant_positions'
AND index_name = 'idx_tenant_id');
SET @sqlstmt := IF(@exist = 0,
'CREATE INDEX idx_tenant_id ON yz_tenant_positions(tenant_id)',
'SELECT "索引 idx_tenant_id 已存在" AS message');
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 检查并添加 delete_time 索引(如果不存在)
SET @exist := (SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'yz_tenant_positions'
AND index_name = 'idx_delete_time');
SET @sqlstmt := IF(@exist = 0,
'CREATE INDEX idx_delete_time ON yz_tenant_positions(delete_time)',
'SELECT "索引 idx_delete_time 已存在" AS message');
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 检查并添加 department_id 索引(用于按部门查询职位)
SET @exist := (SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'yz_tenant_positions'
AND index_name = 'idx_department_id');
SET @sqlstmt := IF(@exist = 0,
'CREATE INDEX idx_department_id ON yz_tenant_positions(department_id)',
'SELECT "索引 idx_department_id 已存在" AS message');
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 检查并添加复合索引 (department_id, delete_time, status) 用于常用查询
SET @exist := (SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'yz_tenant_positions'
AND index_name = 'idx_dept_delete_status');
SET @sqlstmt := IF(@exist = 0,
'CREATE INDEX idx_dept_delete_status ON yz_tenant_positions(department_id, delete_time, status)',
'SELECT "索引 idx_dept_delete_status 已存在" AS message');
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- =============================================
-- 3. 角色表 (yz_roles) 索引优化
-- =============================================
-- 检查并添加 tenant_id 索引(如果不存在)
SET @exist := (SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'yz_roles'
AND index_name = 'idx_tenant_id');
SET @sqlstmt := IF(@exist = 0,
'CREATE INDEX idx_tenant_id ON yz_roles(tenant_id)',
'SELECT "索引 idx_tenant_id 已存在" AS message');
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 检查并添加 delete_time 索引(如果不存在)
SET @exist := (SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'yz_roles'
AND index_name = 'idx_delete_time');
SET @sqlstmt := IF(@exist = 0,
'CREATE INDEX idx_delete_time ON yz_roles(delete_time)',
'SELECT "索引 idx_delete_time 已存在" AS message');
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- =============================================
-- 4. 员工表 (yz_employees) 索引优化
-- =============================================
-- 检查并添加 tenant_id 索引(如果不存在)
SET @exist := (SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'yz_employees'
AND index_name = 'idx_tenant_id');
SET @sqlstmt := IF(@exist = 0,
'CREATE INDEX idx_tenant_id ON yz_employees(tenant_id)',
'SELECT "索引 idx_tenant_id 已存在" AS message');
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 检查并添加 delete_time 索引(如果不存在)
SET @exist := (SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'yz_employees'
AND index_name = 'idx_delete_time');
SET @sqlstmt := IF(@exist = 0,
'CREATE INDEX idx_delete_time ON yz_employees(delete_time)',
'SELECT "索引 idx_delete_time 已存在" AS message');
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 检查并添加 department_id 索引
SET @exist := (SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'yz_employees'
AND index_name = 'idx_department_id');
SET @sqlstmt := IF(@exist = 0,
'CREATE INDEX idx_department_id ON yz_employees(department_id)',
'SELECT "索引 idx_department_id 已存在" AS message');
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 检查并添加 position_id 索引
SET @exist := (SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'yz_employees'
AND index_name = 'idx_position_id');
SET @sqlstmt := IF(@exist = 0,
'CREATE INDEX idx_position_id ON yz_employees(position_id)',
'SELECT "索引 idx_position_id 已存在" AS message');
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SELECT '性能优化索引创建完成!' AS message;

View File

@ -1,67 +0,0 @@
-- 性能优化索引脚本(简化版)
-- 创建时间: 2025
-- 描述: 为常用查询字段添加索引,提升查询性能
-- 注意: 如果索引已存在会报错,可以忽略或手动删除重复的索引
SET NAMES utf8mb4;
-- =============================================
-- 1. 部门表 (yz_tenant_departments) 索引优化
-- =============================================
-- 添加 tenant_id 索引
CREATE INDEX IF NOT EXISTS idx_tenant_id ON yz_tenant_departments(tenant_id);
-- 添加 delete_time 索引
CREATE INDEX IF NOT EXISTS idx_delete_time ON yz_tenant_departments(delete_time);
-- 添加复合索引 (tenant_id, delete_time) 用于常用查询
CREATE INDEX IF NOT EXISTS idx_tenant_delete ON yz_tenant_departments(tenant_id, delete_time);
-- 添加 parent_id 索引(用于树形结构查询)
CREATE INDEX IF NOT EXISTS idx_parent_id ON yz_tenant_departments(parent_id);
-- =============================================
-- 2. 职位表 (yz_tenant_positions) 索引优化
-- =============================================
-- 添加 tenant_id 索引
CREATE INDEX IF NOT EXISTS idx_tenant_id ON yz_tenant_positions(tenant_id);
-- 添加 delete_time 索引
CREATE INDEX IF NOT EXISTS idx_delete_time ON yz_tenant_positions(delete_time);
-- 添加 department_id 索引(用于按部门查询职位)
CREATE INDEX IF NOT EXISTS idx_department_id ON yz_tenant_positions(department_id);
-- 添加复合索引 (department_id, delete_time, status) 用于常用查询
CREATE INDEX IF NOT EXISTS idx_dept_delete_status ON yz_tenant_positions(department_id, delete_time, status);
-- =============================================
-- 3. 角色表 (yz_roles) 索引优化
-- =============================================
-- 添加 tenant_id 索引
CREATE INDEX IF NOT EXISTS idx_tenant_id ON yz_roles(tenant_id);
-- 添加 delete_time 索引
CREATE INDEX IF NOT EXISTS idx_delete_time ON yz_roles(delete_time);
-- =============================================
-- 4. 员工表 (yz_employees) 索引优化
-- =============================================
-- 添加 tenant_id 索引
CREATE INDEX IF NOT EXISTS idx_tenant_id ON yz_employees(tenant_id);
-- 添加 delete_time 索引
CREATE INDEX IF NOT EXISTS idx_delete_time ON yz_employees(delete_time);
-- 添加 department_id 索引
CREATE INDEX IF NOT EXISTS idx_department_id ON yz_employees(department_id);
-- 添加 position_id 索引
CREATE INDEX IF NOT EXISTS idx_position_id ON yz_employees(position_id);
SELECT '性能优化索引创建完成!' AS message;

View File

@ -1,41 +0,0 @@
-- =============================================
-- 角色权限回滚脚本
-- 如果迁移出现问题,可以从备份表恢复数据
-- =============================================
SET NAMES utf8mb4;
-- =============================================
-- 步骤1: 从备份表恢复数据到 yz_role_menus
-- =============================================
-- 如果 yz_role_menus 表被删除,先创建它
CREATE TABLE IF NOT EXISTS yz_role_menus (
id INT AUTO_INCREMENT PRIMARY KEY,
role_id INT NOT NULL,
menu_id INT NOT NULL,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
create_by VARCHAR(50) NULL,
UNIQUE KEY uk_role_menu (role_id, menu_id),
INDEX idx_role_id (role_id),
INDEX idx_menu_id (menu_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色菜单关联表(备份恢复)';
-- 从备份表恢复数据
INSERT INTO yz_role_menus (id, role_id, menu_id, create_time, create_by)
SELECT id, role_id, menu_id, create_time, create_by
FROM yz_role_menus_backup
ON DUPLICATE KEY UPDATE
menu_id = VALUES(menu_id),
create_time = VALUES(create_time),
create_by = VALUES(create_by);
SELECT '数据恢复完成' AS message;
-- =============================================
-- 步骤2: 如果需要移除 menu_ids 字段(可选)
-- =============================================
-- 注意:如果确定要移除新字段,可以执行以下命令
-- ALTER TABLE yz_roles DROP COLUMN menu_ids;

View File

@ -1,32 +0,0 @@
-- 创建部门表
-- 创建时间: 2025
-- 描述: OA系统部门管理表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- 检查并创建租户部门表(如果不存在)
CREATE TABLE IF NOT EXISTS yz_tenant_departments (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '部门ID',
tenant_id INT NOT NULL DEFAULT 0 COMMENT '租户ID',
name VARCHAR(100) NOT NULL COMMENT '部门名称',
code VARCHAR(50) DEFAULT NULL COMMENT '部门编码',
parent_id INT DEFAULT 0 COMMENT '父部门ID0表示顶级部门',
description TEXT DEFAULT NULL COMMENT '部门描述',
manager_id INT DEFAULT NULL COMMENT '部门经理ID',
sort_order INT DEFAULT 0 COMMENT '排序序号',
status TINYINT DEFAULT 1 COMMENT '状态1-启用0-禁用',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
delete_time DATETIME DEFAULT NULL COMMENT '删除时间(软删除)',
-- 索引
INDEX idx_tenant_id (tenant_id),
INDEX idx_code (code),
INDEX idx_parent_id (parent_id),
INDEX idx_status (status),
INDEX idx_sort_order (sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='租户部门表';
SET FOREIGN_KEY_CHECKS = 1;

View File

@ -1,34 +0,0 @@
-- 创建员工表
-- 创建时间: 2025
-- 描述: OA系统员工管理表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- 检查并创建租户员工表(如果不存在)
CREATE TABLE IF NOT EXISTS yz_tenant_employees (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '员工ID',
tenant_id INT NOT NULL DEFAULT 0 COMMENT '租户ID',
employee_no VARCHAR(50) NOT NULL COMMENT '工号',
name VARCHAR(50) NOT NULL COMMENT '姓名',
phone VARCHAR(20) DEFAULT NULL COMMENT '手机号',
email VARCHAR(100) DEFAULT NULL COMMENT '邮箱',
department_id INT DEFAULT NULL COMMENT '部门ID',
position_id INT DEFAULT NULL COMMENT '职位ID',
status TINYINT DEFAULT 1 COMMENT '状态1-在职0-离职',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
delete_time DATETIME DEFAULT NULL COMMENT '删除时间(软删除)',
-- 索引
INDEX idx_tenant_id (tenant_id),
INDEX idx_employee_no (employee_no),
INDEX idx_name (name),
INDEX idx_department_id (department_id),
INDEX idx_position_id (position_id),
INDEX idx_status (status),
INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='租户员工表';
SET FOREIGN_KEY_CHECKS = 1;

View File

@ -1,33 +0,0 @@
CREATE TABLE yz_files (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '文件ID',
tenant_id VARCHAR(64) NOT NULL COMMENT '租户ID',
user_id INT NOT NULL DEFAULT 0 COMMENT '用户ID',
-- 文件基础信息
file_name VARCHAR(255) NOT NULL COMMENT '文件名称',
original_name VARCHAR(255) NOT NULL COMMENT '原始文件名',
file_path VARCHAR(500) NOT NULL COMMENT '文件存储路径',
file_url VARCHAR(500) COMMENT '文件访问URL',
file_size BIGINT NOT NULL DEFAULT 0 COMMENT '文件大小(字节)',
file_type VARCHAR(50) NOT NULL COMMENT '文件类型',
file_ext VARCHAR(20) NOT NULL COMMENT '文件扩展名',
-- 分类信息
category VARCHAR(100) NOT NULL COMMENT '文件分类',
sub_category VARCHAR(100) COMMENT '子分类',
-- 状态信息
status TINYINT DEFAULT 1 COMMENT '状态(1:正常, 0:删除)',
is_public TINYINT DEFAULT 0 COMMENT '是否公开(1:是, 0:否)',
-- 上传信息
upload_by VARCHAR(100) NOT NULL COMMENT '上传人',
upload_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间',
-- 索引
INDEX idx_tenant (tenant_id),
INDEX idx_user (user_id),
INDEX idx_category (category),
INDEX idx_upload_time (upload_time),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='文件表';

View File

@ -1,94 +0,0 @@
-- 创建知识库分类表
CREATE TABLE `yz_knowledge_category` (
`category_id` INT NOT NULL AUTO_INCREMENT COMMENT '分类ID',
`category_name` VARCHAR(100) NOT NULL COMMENT '分类名称',
`category_desc` VARCHAR(500) DEFAULT NULL COMMENT '分类描述',
`parent_id` INT DEFAULT 0 COMMENT '父分类ID0表示顶级分类',
`sort_order` INT DEFAULT 0 COMMENT '排序序号',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`category_id`),
KEY `idx_parent_id` (`parent_id`),
KEY `idx_sort_order` (`sort_order`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识库分类表';
-- 创建知识库标签表
CREATE TABLE `yz_knowledge_tags` (
`tag_id` INT NOT NULL AUTO_INCREMENT COMMENT '标签ID',
`tag_name` VARCHAR(50) NOT NULL COMMENT '标签名称',
`tag_color` VARCHAR(20) DEFAULT NULL COMMENT '标签颜色',
`usage_count` INT DEFAULT 0 COMMENT '使用次数',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`tag_id`),
UNIQUE KEY `uk_tag_name` (`tag_name`),
KEY `idx_usage_count` (`usage_count`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识库标签表';
-- 创建知识库内容表
CREATE TABLE `yz_knowledge` (
`knowledge_id` INT NOT NULL AUTO_INCREMENT COMMENT '知识ID',
`title` VARCHAR(200) NOT NULL COMMENT '标题',
`category_id` INT DEFAULT NULL COMMENT '分类ID',
`tags` TEXT COMMENT '标签JSON数组存储标签名称',
`author` VARCHAR(50) NOT NULL COMMENT '作者',
`content` LONGTEXT COMMENT '正文内容(富文本)',
`summary` VARCHAR(500) DEFAULT NULL COMMENT '摘要',
`cover_url` VARCHAR(500) DEFAULT NULL COMMENT '封面图片URL',
`status` TINYINT DEFAULT 0 COMMENT '状态0-草稿1-已发布2-已归档',
`view_count` INT DEFAULT 0 COMMENT '查看次数',
`like_count` INT DEFAULT 0 COMMENT '点赞数',
`is_recommend` TINYINT DEFAULT 0 COMMENT '是否推荐0-否1-是',
`is_top` TINYINT DEFAULT 0 COMMENT '是否置顶0-否1-是',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_by` VARCHAR(50) DEFAULT NULL COMMENT '创建人',
`update_by` VARCHAR(50) DEFAULT NULL COMMENT '更新人',
PRIMARY KEY (`knowledge_id`),
KEY `idx_category_id` (`category_id`),
KEY `idx_author` (`author`),
KEY `idx_status` (`status`),
KEY `idx_create_time` (`create_time`),
KEY `idx_view_count` (`view_count`),
KEY `idx_is_recommend` (`is_recommend`),
KEY `idx_is_top` (`is_top`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识库内容表';
-- 创建知识库收藏表
CREATE TABLE `yz_knowledge_favorites` (
`favorite_id` INT NOT NULL AUTO_INCREMENT COMMENT '收藏ID',
`knowledge_id` INT NOT NULL COMMENT '知识ID',
`user_id` INT NOT NULL COMMENT '用户ID',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`favorite_id`),
UNIQUE KEY `uk_knowledge_user` (`knowledge_id`, `user_id`),
KEY `idx_user_id` (`user_id`),
CONSTRAINT `yz_fk_fav_knowledge` FOREIGN KEY (`knowledge_id`) REFERENCES `yz_knowledge` (`knowledge_id`) ON DELETE CASCADE,
CONSTRAINT `yz_fk_fav_user` FOREIGN KEY (`user_id`) REFERENCES `yz_users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识库收藏表';
-- 插入默认分类
INSERT INTO `yz_knowledge_category` (`category_name`, `category_desc`, `parent_id`, `sort_order`) VALUES
('技术文档', '技术相关的文档资料', 0, 1),
('产品手册', '产品使用手册和说明', 0, 2),
('用户指南', '用户操作指南和教程', 0, 3),
('常见问题', '常见问题解答', 0, 4),
('API文档', 'API接口文档', 0, 5),
('Vue', 'Vue框架相关', 1, 101),
('React', 'React框架相关', 1, 102),
('Go', 'Go语言相关', 1, 103);
-- 插入默认标签
INSERT INTO `yz_knowledge_tags` (`tag_name`, `tag_color`, `usage_count`) VALUES
('Vue', '#4CAF50', 0),
('React', '#42A5F5', 0),
('TypeScript', '#3178C6', 0),
('Element Plus', '#409EFF', 0),
('Vue Router', '#4FC08D', 0),
('Pinia', '#FFD54F', 0),
('Go', '#00ADD8', 0),
('Python', '#3776AB', 0);
--
INSERT INTO `yz_knowledge` (`title`, `category_id`, `tags`, `author`, `content`, `summary`, `cover_url`, `status`, `view_count`, `like_count`, `is_recommend`, `is_top`, `create_time`, `update_time`, `create_by`, `update_by`) VALUES
('Vue', 1, 'Vue', 'admin', 'Vue is a progressive framework for building user interfaces.', 'Vue is a progressive framework for building user interfaces.', 'https://vuejs.org/images/logo.png', 1, 0, 0, 0, 0, '2021-01-01 00:00:00', '2021-01-01 00:00:00', 'admin', 'admin');

View File

@ -1,42 +0,0 @@
-- 创建菜单表(增强版,包含完整父子级别参数)
CREATE TABLE `yz_menus` (
`id` INT NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
`name` VARCHAR(100) NOT NULL COMMENT '菜单名称',
`path` VARCHAR(255) NOT NULL COMMENT '菜单路径',
`parent_id` INT DEFAULT 0 COMMENT '父菜单ID0表示顶级菜单',
`level` TINYINT DEFAULT 1 COMMENT '菜单层级1-一级菜单2-二级菜单3-三级菜单',
`full_path` VARCHAR(500) DEFAULT NULL COMMENT '完整路径(包含所有父级路径)',
`is_leaf` TINYINT DEFAULT 0 COMMENT '是否叶子节点0-非叶子节点1-叶子节点',
`has_children` TINYINT DEFAULT 0 COMMENT '是否有子菜单0-无子菜单1-有子菜单',
`children_count` INT DEFAULT 0 COMMENT '子菜单数量',
`icon` VARCHAR(100) DEFAULT NULL COMMENT '菜单图标',
`order` INT DEFAULT 0 COMMENT '排序序号',
`status` TINYINT DEFAULT 1 COMMENT '状态0-禁用1-启用',
`component_path` VARCHAR(500) DEFAULT NULL COMMENT '组件路径',
`is_external` TINYINT DEFAULT 0 COMMENT '是否外部链接0-内部路由1-外部链接',
`external_url` VARCHAR(1000) DEFAULT NULL COMMENT '外部链接地址',
`menu_type` TINYINT DEFAULT 1 COMMENT '菜单类型1-普通菜单2-分组菜单3-按钮菜单',
`permission` VARCHAR(200) DEFAULT NULL COMMENT '权限标识',
`description` VARCHAR(500) DEFAULT NULL COMMENT '菜单描述',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_by` VARCHAR(100) DEFAULT NULL COMMENT '创建人',
`update_by` VARCHAR(100) DEFAULT NULL COMMENT '更新人',
PRIMARY KEY (`id`),
KEY `idx_parent_id` (`parent_id`),
KEY `idx_level` (`level`),
KEY `idx_status` (`status`),
KEY `idx_order` (`order`),
KEY `idx_menu_type` (`menu_type`),
KEY `idx_full_path` (`full_path`(255))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菜单表(增强版)';
-- 插入默认菜单数据(包含完整父子级别参数)
INSERT INTO `yz_menus` (`name`, `path`, `parent_id`, `level`, `full_path`, `is_leaf`, `has_children`, `children_count`, `icon`, `order`, `status`, `component_path`, `menu_type`, `description`) VALUES
('仪表盘', '/dashboard', 0, 1, '/dashboard', 1, 0, 0, 'el-icon-monitor', 1, 1, '@/views/dashboard/index.vue', 1, '系统仪表盘,展示关键指标'),
('文件管理', '/files', 0, 1, '/files', 1, 0, 0, 'el-icon-folder', 3, 1, '@/views/files/index.vue', 1, '文件管理系统'),
('系统管理', '/system', 0, 1, '/system', 0, 1, 3, 'el-icon-setting', 4, 1, null, 2, '系统管理功能模块'),
('用户管理', '/system/users', 4, 2, '/system/users', 1, 0, 0, 'el-icon-user', 1, 1, '@/views/system/users/index.vue', 1, '用户信息管理'),
('菜单管理', '/system/menus', 4, 2, '/system/menus', 1, 0, 0, 'el-icon-menu', 2, 1, '@/views/system/menumanager.vue', 1, '菜单权限管理'),
('程序管理', '/system/programs', 4, 2, '/system/programs', 1, 0, 0, 'el-icon-cpu', 3, 1, '@/views/system/programs/index.vue', 1, '程序功能管理'),
('系统设置', '/settings', 0, 1, '/settings', 1, 0, 0, 'el-icon-setting', 99, 1, '@/views/settings/index.vue', 1, '系统参数设置');

View File

@ -1,32 +0,0 @@
-- 创建职位表
-- 创建时间: 2025
-- 描述: OA系统职位管理表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- 检查并创建租户职位表(如果不存在)
CREATE TABLE IF NOT EXISTS yz_tenant_positions (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '职位ID',
tenant_id INT NOT NULL DEFAULT 0 COMMENT '租户ID',
name VARCHAR(100) NOT NULL COMMENT '职位名称',
code VARCHAR(50) DEFAULT NULL COMMENT '职位编码',
department_id INT DEFAULT NULL COMMENT '所属部门ID',
level INT DEFAULT 0 COMMENT '职位级别',
description TEXT DEFAULT NULL COMMENT '职位描述',
sort_order INT DEFAULT 0 COMMENT '排序序号',
status TINYINT DEFAULT 1 COMMENT '状态1-启用0-禁用',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
delete_time DATETIME DEFAULT NULL COMMENT '删除时间(软删除)',
-- 索引
INDEX idx_tenant_id (tenant_id),
INDEX idx_code (code),
INDEX idx_department_id (department_id),
INDEX idx_status (status),
INDEX idx_sort_order (sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='租户职位表';
SET FOREIGN_KEY_CHECKS = 1;

View File

@ -1,31 +0,0 @@
-- 创建程序分类表
CREATE TABLE `yz_program_category` (
`category_id` INT NOT NULL AUTO_INCREMENT COMMENT '分类ID',
`category_name` VARCHAR(100) NOT NULL COMMENT '分类名称',
`category_desc` VARCHAR(500) DEFAULT NULL COMMENT '分类描述',
`parent_id` INT DEFAULT 0 COMMENT '父分类ID0表示顶级分类',
`sort_order` INT DEFAULT 0 COMMENT '排序序号,用于展示顺序',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`category_id`),
KEY `idx_parent_id` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='程序分类表';
-- 创建程序信息表
CREATE TABLE `yz_program_info` (
`program_id` INT NOT NULL AUTO_INCREMENT COMMENT '程序ID',
`category_id` INT NOT NULL COMMENT '所属分类ID',
`program_name` VARCHAR(200) NOT NULL COMMENT '程序名称',
`program_desc` TEXT COMMENT '程序描述',
`jump_url` VARCHAR(1000) NOT NULL COMMENT '跳转地址',
`icon_url` VARCHAR(1000) DEFAULT NULL COMMENT '程序图标地址',
`version` VARCHAR(50) DEFAULT NULL COMMENT '程序版本',
`status` TINYINT DEFAULT 1 COMMENT '状态0-禁用1-启用',
`sort_order` INT DEFAULT 0 COMMENT '排序序号,用于展示顺序',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`program_id`),
KEY `idx_category_id` (`category_id`),
KEY `idx_status` (`status`),
CONSTRAINT `yz_fk_program_category` FOREIGN KEY (`category_id`) REFERENCES `yz_program_category` (`category_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='程序信息表';

View File

@ -1,34 +0,0 @@
-- 创建角色表
CREATE TABLE IF NOT EXISTS yz_roles (
role_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '角色ID',
tenant_id INT NOT NULL COMMENT '租户ID',
role_code VARCHAR(50) NOT NULL COMMENT '角色代码',
role_name VARCHAR(50) NOT NULL COMMENT '角色名称',
description VARCHAR(500) DEFAULT NULL COMMENT '角色描述',
status TINYINT DEFAULT 1 COMMENT '角色状态0-禁用1-启用',
sort_order INT DEFAULT 0 COMMENT '排序序号',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
delete_time DATETIME DEFAULT NULL COMMENT '删除时间',
create_by VARCHAR(50) DEFAULT NULL COMMENT '创建人',
update_by VARCHAR(50) DEFAULT NULL COMMENT '更新人',
-- 索引
UNIQUE KEY uk_role_code (role_code),
INDEX idx_status (status),
INDEX idx_sort_order (sort_order),
INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色表';
-- 插入默认角色数据
INSERT INTO yz_roles (tenant_id, role_code, role_name, description, status, sort_order, create_by, delete_time) VALUES
(0, 'system_admin', '系统管理员', '系统超级管理员,拥有所有权限', 1, 1, 'system'),
(0, 'admin', '管理员', '普通管理员,拥有大部分管理权限', 1, 2, 'system'),
(0, 'user', '普通用户', '普通用户,拥有基础使用权限', 1, 3, 'system')
ON DUPLICATE KEY UPDATE
tenant_id = VALUES(tenant_id),
role_name = VALUES(role_name),
description = VALUES(description),
update_time = CURRENT_TIMESTAMP,
delete_time = NULL;

View File

@ -1,111 +0,0 @@
-- 创建租户表
-- 创建时间: 2025
-- 描述: 云泽系统租户管理表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- 检查并创建租户表(如果不存在)
CREATE TABLE IF NOT EXISTS yz_tenants (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '租户ID',
-- 基本信息
name VARCHAR(100) NOT NULL COMMENT '租户名称',
code VARCHAR(50) NOT NULL COMMENT '租户编码(唯一)',
owner VARCHAR(50) NOT NULL COMMENT '负责人',
phone VARCHAR(20) DEFAULT NULL COMMENT '联系电话',
email VARCHAR(100) DEFAULT NULL COMMENT '邮箱地址',
-- 状态信息
status VARCHAR(20) DEFAULT 'enabled' COMMENT '状态enabled-启用disabled-禁用',
audit_status VARCHAR(20) DEFAULT 'pending' COMMENT '审核状态pending-待审核approved-已通过rejected-已拒绝',
-- 审核信息
audit_comment TEXT DEFAULT NULL COMMENT '审核意见',
audit_by VARCHAR(50) DEFAULT NULL COMMENT '审核人',
audit_time DATETIME DEFAULT NULL COMMENT '审核时间',
-- 其他信息
remark TEXT DEFAULT NULL COMMENT '备注',
-- 时间戳
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
create_by VARCHAR(50) DEFAULT NULL COMMENT '创建人',
update_by VARCHAR(50) DEFAULT NULL COMMENT '更新人',
-- 索引
UNIQUE KEY uk_code (code),
INDEX idx_name (name),
INDEX idx_owner (owner),
INDEX idx_status (status),
INDEX idx_audit_status (audit_status),
INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='租户表';
SET FOREIGN_KEY_CHECKS = 1;
-- =============================================
-- 插入测试数据
-- =============================================
-- 清空现有测试数据(可选,注释掉以保留现有数据)
-- DELETE FROM yz_tenants WHERE id > 0;
-- 插入测试租户数据
INSERT INTO yz_tenants (
name,
code,
owner,
phone,
email,
status,
audit_status,
audit_comment,
audit_by,
audit_time,
remark,
create_by
) VALUES
-- 默认租户(已通过审核)
('默认租户', 'default', 'admin', '13800138000', 'admin@yunzer.com', 'enabled', 'approved', '系统默认租户,自动通过审核', 'system', NOW(), '系统默认租户,用于初始化数据', 'system'),
-- 示例租户A已通过审核
('示例租户A', 'demo-a', '张三', '13900139000', 'zhangsan@demo.com', 'enabled', 'approved', '资料完整,审核通过', 'admin', DATE_SUB(NOW(), INTERVAL 30 DAY), '演示租户A用于展示功能', 'admin'),
-- 示例租户B待审核
('示例租户B', 'demo-b', '李四', '13700137000', 'lisi@demo.com', 'enabled', 'pending', NULL, NULL, NULL, '待审核租户,资料已提交', 'admin'),
-- 新申请租户C待审核
('新申请租户C', 'new-tenant-c', '王五', '13600136000', 'wangwu@new.com', 'enabled', 'pending', NULL, NULL, NULL, '新申请的租户,等待审核', 'admin'),
-- 已拒绝租户D
('已拒绝租户D', 'rejected-tenant', '赵六', '13500135000', 'zhaoliu@reject.com', 'disabled', 'rejected', '申请资料不完整,缺少必要信息', 'admin', DATE_SUB(NOW(), INTERVAL 10 DAY), '申请被拒绝的租户示例', 'admin'),
-- 企业租户E已通过
('企业租户E', 'enterprise-e', '陈七', '13400134000', 'chenqi@enterprise.com', 'enabled', 'approved', '企业级用户,认证通过', 'admin', DATE_SUB(NOW(), INTERVAL 15 DAY), '大型企业客户租户', 'admin'),
-- 测试租户F已通过
('测试租户F', 'test-f', '刘八', '13300133000', 'liuba@test.com', 'enabled', 'approved', '测试环境使用,已通过', 'admin', DATE_SUB(NOW(), INTERVAL 5 DAY), '测试环境租户', 'admin'),
-- 禁用租户G
('禁用租户G', 'disabled-g', '周九', '13200132000', 'zhoujiu@disabled.com', 'disabled', 'approved', '已通过审核但被禁用', 'admin', DATE_SUB(NOW(), INTERVAL 20 DAY), '已禁用的租户示例', 'admin'),
-- 小公司租户H待审核
('小公司租户H', 'small-h', '吴十', '13100131000', 'wushi@small.com', 'enabled', 'pending', NULL, NULL, NULL, '小型公司申请,等待审核', 'admin'),
-- 个人开发者租户I已通过
('个人开发者I', 'developer-i', '郑十一', '13000130000', 'zhengshiyi@dev.com', 'enabled', 'approved', '个人开发者账户,审核通过', 'admin', DATE_SUB(NOW(), INTERVAL 8 DAY), '个人开发者租户', 'admin');
-- 查询验证
SELECT 'Tenants table created and test data inserted successfully!' as message;
SELECT COUNT(*) as total_tenants FROM yz_tenants;
SELECT
id,
name,
code,
status,
audit_status,
create_time
FROM yz_tenants
ORDER BY id;

View File

@ -1,32 +0,0 @@
-- 创建用户表
CREATE TABLE yz_users (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
username VARCHAR(50) NOT NULL COMMENT '用户名',
password VARCHAR(255) NOT NULL COMMENT '加密后的密码',
salt VARCHAR(100) NOT NULL COMMENT '密码盐值',
email VARCHAR(100) DEFAULT NULL COMMENT '邮箱地址',
avatar VARCHAR(500) DEFAULT NULL COMMENT '头像URL',
nickname VARCHAR(50) DEFAULT NULL COMMENT '昵称',
role VARCHAR(20) DEFAULT 'user' COMMENT '用户角色admin-管理员user-普通用户',
status TINYINT DEFAULT 1 COMMENT '用户状态0-禁用1-启用',
last_login_time DATETIME DEFAULT NULL COMMENT '最后登录时间',
last_login_ip VARCHAR(45) DEFAULT NULL COMMENT '最后登录IP',
login_count INT DEFAULT 0 COMMENT '登录次数',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
create_by VARCHAR(50) DEFAULT NULL COMMENT '创建人',
update_by VARCHAR(50) DEFAULT NULL COMMENT '更新人',
-- 索引
UNIQUE KEY uk_username (username),
INDEX idx_email (email),
INDEX idx_role (role),
INDEX idx_status (status),
INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
-- 插入默认管理员用户
-- 密码为 yunzer123已使用scrypt算法加密
INSERT INTO yz_users (username, password, salt, email, nickname, role, status, create_by) VALUES
('admin', 'encrypted_password_here', 'salt_here', 'admin@yunzer.com', '超级管理员', 'admin', 1, 'system'),
('test', 'encrypted_password_here', 'salt_here', 'test@yunzer.com', '测试用户', 'user', 1, 'system');

View File

@ -1,99 +0,0 @@
# 执行数据库索引优化说明
## 方法一:使用数据库管理工具(推荐)
### 使用 Navicat、DBeaver、phpMyAdmin 等工具
1. 连接到数据库:
- 主机:`43.133.71.191`
- 端口:`3308`
- 用户名:`gotest`
- 密码:`2nZhRdMPCNZrdzsd`
- 数据库:`gotest`
2. 打开并执行以下文件之一:
- `server/database/performance_indexes_simple.sql` (推荐,简单版本)
- `server/database/performance_indexes.sql` (完整版本,包含存在性检查)
## 方法二:使用 MySQL 命令行(如果已安装)
### Windows PowerShell
```powershell
# 方法 1: 使用 Get-Content 管道
Get-Content server\database\performance_indexes_simple.sql | mysql -u gotest -p2nZhRdMPCNZrdzsd -h 43.133.71.191 -P 3308 gotest
# 方法 2: 使用 source 命令(需要先登录 MySQL
mysql -u gotest -p2nZhRdMPCNZrdzsd -h 43.133.71.191 -P 3308 gotest
# 然后在 MySQL 提示符下执行:
source server/database/performance_indexes_simple.sql
```
### Windows CMD
```cmd
mysql -u gotest -p2nZhRdMPCNZrdzsd -h 43.133.71.191 -P 3308 gotest < server\database\performance_indexes_simple.sql
```
### Linux/Mac
```bash
mysql -u gotest -p2nZhRdMPCNZrdzsd -h 43.133.71.191 -P 3308 gotest < server/database/performance_indexes_simple.sql
```
## 方法三:在 Go 代码中执行(临时方案)
如果无法直接执行 SQL可以在后端代码初始化时执行
```go
// 在 server/models/user.go 的 Init 函数中添加
func Init(version string) {
// ... 现有代码 ...
// 执行索引优化(可选,建议直接执行 SQL 文件)
// 这里可以添加执行索引创建的代码
}
```
## 验证索引是否创建成功
执行以下 SQL 查询验证索引:
```sql
-- 查看部门表索引
SHOW INDEX FROM yz_tenant_departments;
-- 查看职位表索引
SHOW INDEX FROM yz_tenant_positions;
-- 查看角色表索引
SHOW INDEX FROM yz_roles;
-- 查看员工表索引
SHOW INDEX FROM yz_employees;
```
## 注意事项
1. **MySQL 版本要求**
- `CREATE INDEX IF NOT EXISTS` 需要 MySQL 8.0.12+
- 如果使用较低版本,请使用 `performance_indexes.sql`(包含存在性检查)
2. **执行时间**
- 索引创建可能需要几秒到几分钟,取决于数据量
- 创建索引期间,表会锁定(通常很快)
3. **索引已存在**
- 如果索引已存在,`CREATE INDEX IF NOT EXISTS` 会忽略
- 如果使用 `performance_indexes.sql`,会显示"索引已存在"的消息
4. **性能影响**
- 索引创建后,查询性能会显著提升
- 插入/更新操作可能稍慢(通常可忽略)
## 预期效果
- 查询速度提升:**50-90%**(取决于数据量)
- 减少全表扫描
- 优化 WHERE 和 JOIN 查询

View File

@ -1,71 +0,0 @@
# 角色权限迁移说明
## 概述
将角色权限从关系表 `yz_role_menus` 迁移到 `yz_roles` 表的 JSON 数组字段 `menu_ids`
## 迁移步骤
### 1. 执行迁移脚本
```bash
mysql -u gotest -p gotest < server/database/migrate_role_permissions_to_json.sql
```
或者在 MySQL 客户端中执行:
```sql
source server/database/migrate_role_permissions_to_json.sql
```
### 2. 验证迁移结果
迁移脚本会自动:
- 在 `yz_roles` 表中添加 `menu_ids` JSON 字段
- 从 `yz_role_menus` 表迁移数据到 `menu_ids` 字段
- 创建备份表 `yz_role_menus_backup`
- 验证迁移结果
### 3. 确认数据正确性
执行以下查询验证数据:
```sql
SELECT
r.role_id,
r.role_name,
r.menu_ids,
JSON_LENGTH(r.menu_ids) as menu_count,
(SELECT COUNT(*) FROM yz_role_menus_backup WHERE role_id = r.role_id) as old_count
FROM yz_roles r
WHERE r.delete_time IS NULL
ORDER BY r.role_id;
```
### 4. 删除旧表(可选)
确认数据迁移正确后,可以删除旧的关系表:
```sql
DROP TABLE IF EXISTS yz_role_menus;
```
## 回滚方案
如果迁移出现问题,可以使用回滚脚本:
```bash
mysql -u gotest -p gotest < server/database/rollback_role_permissions.sql
```
## 代码变更
- `server/models/role.go`: 添加 `MenuIds` 字段和 JSON 序列化/反序列化方法
- `server/models/permission.go`: 更新 `GetRoleMenus``AssignRolePermissions` 函数
## 注意事项
1. **备份数据**:迁移前请确保已备份数据库
2. **测试环境**:建议先在测试环境执行迁移
3. **数据一致性**:迁移后请验证权限分配功能是否正常
4. **性能影响**JSON 字段查询性能可能略低于关系表,但简化了数据结构
## JSON 字段格式
`menu_ids` 字段存储格式为 JSON 数组,例如:
```json
[1, 2, 3, 4, 5]
```
空数组表示该角色没有任何权限:
```json
[]
```

View File

@ -1,203 +0,0 @@
# 文件上传功能文档
## 概述
文件上传功能已完整实现,支持将文件保存到本地文件系统并记录到数据库中。
## 功能特性
1. **自动目录管理**:按年月日自动创建目录结构(如 `front/uploads/2024/01/15/`
2. **唯一文件名**:使用时间戳生成唯一文件名,避免重名冲突
3. **文件类型识别**:自动识别文件类型(图片、文档、视频、音频、压缩包等)
4. **数据库记录**:所有文件信息保存到 `yz_files`
5. **用户关联**:自动关联当前登录用户
6. **异常处理**:文件保存失败时自动清理已上传的文件
## 文件结构
```
front/
└── uploads/
├── 2024/
│ ├── 01/
│ │ ├── 15/
│ │ │ ├── 20240115143045_example.jpg
│ │ │ └── 20240115143046_document.pdf
```
## API 接口
### 上传文件
**POST** `/api/files`
**请求头**:
```
Authorization: Bearer <token>
Content-Type: multipart/form-data
```
**请求参数**:
- `file` (File, required): 上传的文件
- `category` (String, optional): 文件分类,默认为"未分类"
- `tenant_id` (String, optional): 租户ID默认为"default"
**响应示例**:
```json
{
"success": true,
"message": "文件上传成功",
"data": {
"id": 1,
"tenant_id": "default",
"user_id": 123,
"file_name": "example",
"original_name": "example.jpg",
"file_path": "uploads/2024/01/15/20240115143045_example.jpg",
"file_url": "/uploads/2024/01/15/20240115143045_example.jpg",
"file_size": 102400,
"file_type": "image",
"file_ext": ".jpg",
"category": "未分类",
"upload_by": "username",
"upload_time": "2024-01-15T14:30:45Z"
}
}
```
## 后端实现
### 路由配置
`server/routers/router.go` 中配置:
```go
// 文件管理路由
beego.Router("/api/files", &controllers.FileController{}, "get:GetAllFiles")
beego.Router("/api/files", &controllers.FileController{}, "post:Post")
beego.Router("/api/files/my", &controllers.FileController{}, "get:GetMyFiles")
beego.Router("/api/files/:id", &controllers.FileController{}, "get:GetFileById")
beego.Router("/api/files/:id", &controllers.FileController{}, "put:UpdateFile")
beego.Router("/api/files/:id", &controllers.FileController{}, "delete:DeleteFile")
```
### 控制器实现
`Post()` 方法位于 `server/controllers/file.go`:
1. 验证用户登录状态
2. 接收上传的文件
3. 生成日期路径和唯一文件名
4. 保存文件到本地
5. 记录文件信息到数据库
6. 返回文件信息
## 前端使用
### 基本用法
```typescript
import { fileAPI } from '@/api/file'
// 创建 FormData
const formData = new FormData()
formData.append('file', fileObject) // fileObject 是 File 对象
formData.append('category', '文档')
formData.append('tenant_id', 'tenant-001')
// 上传文件
try {
const response = await fileAPI.uploadFile(formData, {
category: '文档',
tenantId: 'tenant-001'
})
console.log('上传成功:', response.data)
} catch (error) {
console.error('上传失败:', error)
}
```
### Element Plus 上传组件
```vue
<el-upload
ref="uploadRef"
drag
:action="uploadUrl"
:headers="uploadHeaders"
:data="{ category: uploadForm.category }"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
:before-upload="beforeUpload"
multiple
>
<el-icon><upload-filled /></el-icon>
<div class="el-upload__text">
将文件拖到此处,或<em>点击上传</em>
</div>
</el-upload>
<script setup>
const uploadUrl = computed(() => {
const baseUrl = import.meta.env.VITE_API_BASE_URL
return `${baseUrl}/api/files`
})
const uploadHeaders = computed(() => {
const token = localStorage.getItem('token')
return {
'Authorization': `Bearer ${token}`
}
})
const handleUploadSuccess = (response) => {
console.log('上传成功:', response)
}
const beforeUpload = (file) => {
const maxSize = 10 * 1024 * 1024 // 10MB
if (file.size > maxSize) {
ElMessage.error('文件大小不能超过 10MB')
return false
}
return true
}
</script>
```
## 文件访问
上传后的文件可以通过以下URL访问
```
http://localhost:8080/uploads/2024/01/15/20240115143045_example.jpg
```
注意:需要配置 Beego 的静态文件服务来提供上传文件的访问。
`server/conf/app.conf` 中添加:
```conf
# 文件上传目录
StaticDir = /uploads:../front/uploads
```
## 安全注意事项
1. **文件大小限制**:建议在前端和后端都添加文件大小限制
2. **文件类型验证**:根据业务需求限制允许上传的文件类型
3. **文件名安全**:避免用户控制文件名造成安全问题
4. **权限控制**:确保只有授权用户可以上传文件
5. **存储位置**:考虑使用对象存储服务(如 OSS、S3替代本地存储
## 数据库字段说明
`yz_files` 表的主要字段:
- `file_path`: 相对路径,用于存储和访问文件
- `file_url`: 访问URL
- `original_name`: 用户上传时的原始文件名
- `file_name`: 去除扩展名的文件名
- `file_type`: 文件类型image, document, video, audio, archive, other
- `category`: 用户自定义分类

View File

@ -1,145 +0,0 @@
# OA 基础数据合并接口说明
## 概述
为了减少网络请求次数,提升系统性能,新增了一个合并接口,用于一次性获取部门、职位、角色三类基础数据。
## 接口信息
### 接口路径
```
GET /api/oa/base-data/:tenantId
```
### 请求参数
- `tenantId` (路径参数): 租户ID
### 响应格式
```json
{
"code": 0,
"message": "获取基础数据成功",
"data": {
"departments": [
{
"id": 1,
"tenant_id": 1,
"name": "技术部",
"code": "TECH",
"parent_id": 0,
"description": "技术部门",
"manager_id": 0,
"sort_order": 0,
"status": 1,
"create_time": "2024-01-01T00:00:00Z",
"update_time": "2024-01-01T00:00:00Z"
}
],
"positions": [
{
"id": 1,
"tenant_id": 1,
"name": "高级工程师",
"code": "SENIOR",
"department_id": 1,
"level": 3,
"description": "高级工程师职位",
"sort_order": 0,
"status": 1,
"create_time": "2024-01-01T00:00:00Z",
"update_time": "2024-01-01T00:00:00Z"
}
],
"roles": [
{
"roleId": 1,
"tenantId": 1,
"roleCode": "ADMIN",
"roleName": "管理员",
"description": "管理员角色",
"status": 1,
"sortOrder": 0,
"createTime": "2024-01-01T00:00:00Z",
"updateTime": "2024-01-01T00:00:00Z"
}
]
}
}
```
## 实现细节
### 后端实现
#### Services 层 (`server/services/oa.go`)
- 使用 goroutine 并行查询三个数据源
- 使用 channel 安全地传递查询结果
- 任何查询失败都会返回错误
#### Controllers 层 (`server/controllers/oa.go`)
- 接收租户ID参数
- 调用 services 层获取数据
- 格式化返回数据
#### 路由配置 (`server/routers/router.go`)
- 路由:`/api/oa/base-data/:tenantId`
- 方法GET
### 前端实现
#### API 文件 (`pc/src/api/oa.js`)
- 封装了 `getOABaseData` 方法
#### Store 更新 (`pc/src/stores/oa.js`)
- `fetchAllBaseData` 方法优先使用合并接口
- 如果合并接口失败,自动回退到分别请求三个接口
- 保持缓存机制不变
## 性能优势
### 优化前
- 前端需要发起 3 个独立的 HTTP 请求
- 每次请求都有网络延迟
- 总耗时 = 3 × 网络延迟 + 3 × 查询时间
### 优化后
- 前端只需发起 1 个 HTTP 请求
- 后端使用 goroutine 并行查询,总耗时 = 1 × 网络延迟 + max(查询时间)
- **性能提升**:减少 2 个网络请求,总耗时减少约 60-70%
## 使用示例
### 前端使用
```javascript
import { useOAStore } from '@/stores/oa';
const oaStore = useOAStore();
// 页面初始化时,会自动使用合并接口
onMounted(async () => {
await oaStore.fetchAllBaseData();
});
```
### 后端扩展
如果需要添加更多数据到合并接口,只需:
1. 在 `OABaseData` 结构体中添加新字段
2. 在 `GetOABaseData` 方法中添加新的查询逻辑
3. 在 controller 中格式化返回新数据
## 兼容性
- 合并接口与原有的三个独立接口并存
- 前端 Store 有自动回退机制,确保兼容性
- 如果合并接口失败,会自动使用原有接口
## 注意事项
1. **租户隔离**:确保返回的数据属于指定租户
2. **错误处理**:任何查询失败都会返回错误
3. **数据一致性**:确保返回的数据是最新的
4. **性能考虑**:后端使用并行查询,但仍需注意数据库性能

View File

@ -1,164 +0,0 @@
# 文件上传路径说明
## 当前配置
### 文件保存路径
文件保存在项目根目录的 `front/uploads/` 目录下,按照日期自动分类:
```
项目根目录/
├── server/
│ └── controllers/
│ └── file.go (处理上传)
├── front/
│ └── uploads/ ← 文件保存位置
│ ├── 2024/
│ │ └── 01/
│ │ └── 15/
│ │ └── 20240115143045_example.jpg
```
### 代码中的路径
`server/controllers/file.go``Post()` 方法中:
```go
// 构造保存路径:../front/uploads/年/月/日/
uploadDir := path.Join("..", "front", "uploads", datePath)
```
**说明**
- `..` 表示从 server 目录向上一级到项目根目录
- `front/uploads/` 是上传文件的根目录
- `datePath` 是按日期自动生成的子目录(如 `2024/01/15`
### 静态文件访问配置
`server/conf/app.conf` 中:
```conf
StaticDir = /uploads:../front/uploads
```
**说明**
- `/uploads` 是 URL 访问路径
- `../front/uploads` 是实际文件存储路径(相对于 server 目录)
## 目录结构
### 保存到数据库的路径
- `file_path`: `uploads/2024/01/15/20240115143045_example.jpg`(相对路径)
- `file_url`: `/uploads/2024/01/15/20240115143045_example.jpg`URL 路径)
### 实际文件系统路径
```
front/uploads/2024/01/15/20240115143045_example.jpg
```
### 访问 URL
```
http://localhost:8080/uploads/2024/01/15/20240115143045_example.jpg
```
## 为什么使用相对路径 `..`
由于项目结构是:
```
yunzer_go/
├── server/ ← 服务端代码
└── front/ ← 前端代码和上传文件
└── uploads/
```
`server` 目录运行应用时,要访问 `front/uploads`,需要使用 `../front/uploads`
## 如果路径不对怎么办?
### 方案1修改代码中的路径
如果您的项目启动目录不同,可以修改 `file.go` 中的路径:
```go
// 如果从项目根目录运行
uploadDir := path.Join("front", "uploads", datePath)
// 或者使用绝对路径
uploadDir := path.Join("/path/to/project", "front", "uploads", datePath)
```
### 方案2从配置文件读取
`app.conf` 中添加配置:
```conf
# 上传文件目录
uploadDir = ../front/uploads
```
然后在代码中读取:
```go
import "github.com/beego/beego/v2/server/web"
uploadDir := path.Join(
web.AppConfig.String("uploadDir"),
datePath,
)
```
## 验证路径是否正确
### 1. 检查文件保存位置
上传一个文件后,查看文件是否在正确的位置:
```bash
ls front/uploads/
```
应该看到按日期分类的文件夹和文件。
### 2. 检查数据库记录
查看 `yz_files` 表中的 `file_path` 字段:
```sql
SELECT file_path, file_url FROM yz_files ORDER BY upload_time DESC LIMIT 1;
```
应该看到类似:
```
file_path: uploads/2024/01/15/20240115143045_example.jpg
file_url: /uploads/2024/01/15/20240115143045_example.jpg
```
### 3. 检查 URL 访问
直接在浏览器访问:
```
http://localhost:8080/uploads/2024/01/15/文件名
```
如果能看到文件,说明路径配置正确。
## 常见问题
### Q: 文件保存在了 server/front/uploads
A: 修改代码中的路径为 `../front/uploads`(已经修改)
### Q: 文件保存在了 front/front/uploads
A: 检查当前工作目录,确保在 server 目录运行应用
### Q: 访问文件返回 404
A: 检查 `app.conf` 中的 StaticDir 配置是否正确
### Q: 权限问题?
A: 确保应用有创建目录和写入文件的权限:
```bash
chmod 755 front/uploads
```
## 建议的改进
如果需要更可靠的路径处理,可以考虑:
1. **使用绝对路径**:从配置文件或环境变量读取项目根目录
2. **路径验证**:在应用启动时检查上传目录是否存在,不存在则创建
3. **日志记录**:记录文件保存的完整路径,便于调试

View File

@ -1,328 +0,0 @@
# 文件管理 API 文档
## 概述
文件管理模块提供对 `yz_files` 表的完整 CRUD 操作,支持文件信息的管理、搜索和统计功能。
## 数据库表结构
```sql
CREATE TABLE yz_files (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '文件ID',
tenant_id VARCHAR(64) NOT NULL COMMENT '租户ID',
-- 文件基础信息
file_name VARCHAR(255) NOT NULL COMMENT '文件名称',
original_name VARCHAR(255) NOT NULL COMMENT '原始文件名',
file_path VARCHAR(500) NOT NULL COMMENT '文件存储路径',
file_url VARCHAR(500) COMMENT '文件访问URL',
file_size BIGINT NOT NULL DEFAULT 0 COMMENT '文件大小(字节)',
file_type VARCHAR(50) NOT NULL COMMENT '文件类型',
file_ext VARCHAR(20) NOT NULL COMMENT '文件扩展名',
-- 分类信息
category VARCHAR(100) NOT NULL COMMENT '文件分类',
sub_category VARCHAR(100) COMMENT '子分类',
-- 状态信息
status TINYINT DEFAULT 1 COMMENT '状态(1:正常, 0:删除)',
is_public TINYINT DEFAULT 0 COMMENT '是否公开(1:是, 0:否)',
-- 上传信息
upload_by VARCHAR(100) NOT NULL COMMENT '上传人',
upload_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间',
-- 索引
INDEX idx_tenant (tenant_id),
INDEX idx_category (category),
INDEX idx_upload_time (upload_time),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='文件表';
```
## API 接口列表
### 1. 获取所有文件信息
**GET** `/api/files`
**认证方式**: JWT认证
**参数**: 无
**响应**:
```json
{
"success": true,
"message": "获取成功",
"data": [
{
"id": 1,
"tenant_id": "default",
"user_id": 1,
"file_name": "example.pdf",
"original_name": "example.pdf",
"file_path": "/uploads/2024/01/01/example.pdf",
"file_url": "http://example.com/uploads/2024/01/01/example.pdf",
"file_size": 1024,
"file_type": "application/pdf",
"file_ext": "pdf",
"category": "文档",
"sub_category": "PDF",
"status": 1,
"is_public": 0,
"upload_by": "admin",
"upload_time": "2024-01-01T10:00:00Z"
}
]
}
```
### 2. 获取当前用户的文件列表
**GET** `/api/files/my`
**认证方式**: JWT认证
**参数**: 无
**说明**: 通过JWT token自动获取当前登录用户的文件列表
**响应**:
```json
{
"success": true,
"message": "获取成功",
"data": [
{
"id": 1,
"tenant_id": "default",
"user_id": 1,
"file_name": "example.pdf",
"original_name": "example.pdf",
"file_path": "/uploads/2024/01/01/example.pdf",
"file_url": "http://example.com/uploads/2024/01/01/example.pdf",
"file_size": 1024,
"file_type": "application/pdf",
"file_ext": "pdf",
"category": "文档",
"sub_category": "PDF",
"status": 1,
"is_public": 0,
"upload_by": "admin",
"upload_time": "2024-01-01T10:00:00Z"
}
]
}
```
### 2. 根据ID获取文件信息
**GET** `/api/files/:id`
**参数**:
- `id` (路径参数): 文件ID
**响应**: 同获取所有文件接口
### 3. 创建文件信息
**POST** `/api/files`
**请求体**:
```json
{
"tenant_id": "tenant-001",
"file_name": "new-file.txt",
"original_name": "原始文件.txt",
"file_path": "/uploads/tenant-001/new-file.txt",
"file_url": "http://localhost:8080/uploads/tenant-001/new-file.txt",
"file_size": 1024,
"file_type": "text/plain",
"file_ext": "txt",
"category": "文本",
"sub_category": "TXT",
"status": 1,
"is_public": 0,
"upload_by": "admin"
}
```
**响应**:
```json
{
"success": true,
"message": "创建文件成功",
"data": {
"id": 2,
... // 创建的文件信息
}
}
```
### 4. 更新文件信息
**PUT** `/api/files/:id`
**参数**:
- `id` (路径参数): 文件ID
**请求体**: 同创建文件接口
**响应**: 同创建文件接口
### 5. 删除文件(软删除)
**DELETE** `/api/files/:id`
**参数**:
- `id` (路径参数): 文件ID
**响应**:
```json
{
"success": true,
"message": "删除文件成功"
}
```
### 6. 硬删除文件
**DELETE** `/api/files/:id/hard`
**参数**:
- `id` (路径参数): 文件ID
**响应**: 同软删除接口
### 7. 根据租户ID获取文件
**GET** `/api/files/tenant?tenant_id=tenant-001`
**参数**:
- `tenant_id` (查询参数): 租户ID
**响应**: 同获取所有文件接口
### 8. 根据分类获取文件
**GET** `/api/files/category?category=文档`
**参数**:
- `category` (查询参数): 文件分类
**响应**: 同获取所有文件接口
### 9. 根据状态获取文件
**GET** `/api/files/status?status=1`
**参数**:
- `status` (查询参数): 文件状态 (1:正常, 0:删除)
**响应**: 同获取所有文件接口
### 10. 获取文件统计信息
**GET** `/api/files/statistics?tenant_id=tenant-001`
**参数**:
- `tenant_id` (查询参数): 租户ID
**响应**:
```json
{
"success": true,
"message": "获取统计信息成功",
"data": {
"total_count": 10,
"total_size": 10485760,
"category_stats": [
{
"category": "文档",
"count": 5,
"size": 5242880
},
{
"category": "图片",
"count": 3,
"size": 3145728
}
]
}
}
```
### 11. 搜索文件
**GET** `/api/files/search?keyword=文档&tenant_id=tenant-001`
**参数**:
- `keyword` (查询参数): 搜索关键词
- `tenant_id` (查询参数): 租户ID
**响应**: 同获取所有文件接口
### 12. 公开接口(无需认证)
**GET** `/api/files/public`
**参数**: 无
**响应**: 同获取所有文件接口
## 错误响应
所有接口在发生错误时返回统一的错误格式:
```json
{
"success": false,
"message": "错误描述",
"error": "详细错误信息"
}
```
## 状态码说明
- `200`: 请求成功
- `400`: 参数错误
- `404`: 资源不存在
- `500`: 服务器内部错误
## 使用示例
### 前端调用示例JavaScript
```javascript
// 获取所有文件
const response = await fetch('/api/files');
const result = await response.json();
// 创建文件
const newFile = {
tenant_id: "tenant-001",
file_name: "example.txt",
original_name: "示例文件.txt",
file_path: "/uploads/example.txt",
file_type: "text/plain",
file_ext: "txt",
category: "文档",
upload_by: "user123"
};
const createResponse = await fetch('/api/files', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(newFile)
});
```
## 注意事项
1. 所有需要认证的接口都需要在请求头中包含有效的 JWT Token
2. 文件上传功能需要配合文件存储服务实现
3. 软删除只是标记文件状态为删除,实际数据仍然保留
4. 硬删除会永久删除文件记录,请谨慎使用

View File

@ -1,135 +0,0 @@
# 后端接口性能优化说明
## 问题描述
后端接口请求响应慢,主要原因是:
1. **数据库连接池未配置** - 每次请求都创建新的数据库连接
2. **缺少数据库索引** - 常用查询字段tenant_id, delete_time没有索引
3. **内存分配未优化** - Controller 层数据格式化时未预分配容量
4. **网络延迟** - 使用远程数据库,网络延迟较高
## 优化措施
### 1. 数据库连接池配置 ✅
**位置**: `server/models/user.go`
**优化内容**:
- 设置最大空闲连接数:`MaxIdleConns = 10`
- 设置最大打开连接数:`MaxOpenConns = 100`
- 设置连接最大生存时间:`ConnMaxLifetime = 1小时`
- 添加连接超时参数:`timeout=10s&readTimeout=30s&writeTimeout=30s`
**效果**:
- 减少连接创建和销毁的开销
- 复用数据库连接,提升响应速度
- 避免连接泄漏
### 2. 数据库索引优化 ✅
**位置**: `server/database/performance_indexes.sql`
**优化内容**:
- 为 `yz_tenant_departments` 表添加索引:
- `idx_tenant_id` - 租户ID索引
- `idx_delete_time` - 删除时间索引
- `idx_tenant_delete` - 复合索引 (tenant_id, delete_time)
- `idx_parent_id` - 父级ID索引树形结构查询
- 为 `yz_tenant_positions` 表添加索引:
- `idx_tenant_id` - 租户ID索引
- `idx_delete_time` - 删除时间索引
- `idx_department_id` - 部门ID索引
- `idx_dept_delete_status` - 复合索引 (department_id, delete_time, status)
- 为 `yz_roles` 表添加索引:
- `idx_tenant_id` - 租户ID索引
- `idx_delete_time` - 删除时间索引
- 为 `yz_employees` 表添加索引:
- `idx_tenant_id` - 租户ID索引
- `idx_delete_time` - 删除时间索引
- `idx_department_id` - 部门ID索引
- `idx_position_id` - 职位ID索引
**执行方法**:
```bash
mysql -u gotest -p -h 43.133.71.191 -P 3308 gotest < server/database/performance_indexes.sql
```
**效果**:
- 查询速度提升 10-100 倍(取决于数据量)
- 减少全表扫描
- 优化 WHERE 和 JOIN 查询
### 3. 内存分配优化 ✅
**位置**: `server/controllers/oa.go`
**优化内容**:
- 预分配切片容量,避免多次扩容
- 使用 `make([]map[string]interface{}, 0, count)` 替代 `make([]map[string]interface{}, 0)`
**效果**:
- 减少内存分配次数
- 降低 GC 压力
- 提升响应速度约 5-10%
### 4. 查询优化建议
**已实现**:
- 使用 `services.GetOABaseData()` 并行查询部门、职位、角色数据
- 使用 goroutine 并发执行多个查询
**建议**:
- 对于大数据量查询,考虑实现分页
- 对于频繁查询的数据,考虑添加 Redis 缓存层
- 监控慢查询日志,持续优化
## 性能提升预期
- **连接池配置**: 提升 20-30%
- **数据库索引**: 提升 50-90%(取决于数据量)
- **内存优化**: 提升 5-10%
- **总体提升**: 预期提升 50-80%
## 注意事项
1. **索引维护成本**:
- 索引会占用额外存储空间
- 插入/更新操作会稍慢(通常可忽略)
- 建议定期检查索引使用情况
2. **连接池配置**:
- `MaxOpenConns` 应根据实际并发量调整
- 过大的连接池可能导致数据库连接耗尽
- 建议监控连接池使用情况
3. **远程数据库**:
- 网络延迟是主要瓶颈之一
- 考虑使用 CDN 或数据库代理
- 对于高并发场景,建议使用本地数据库或缓存
## 下一步优化建议
1. **添加 Redis 缓存层**:
- 缓存常用的基础数据(部门、职位、角色)
- 设置合理的过期时间(如 5 分钟)
- 减少数据库查询压力
2. **实现查询日志**:
- 记录慢查询(> 100ms
- 分析查询模式
- 持续优化
3. **数据库查询优化**:
- 使用 `SELECT` 只查询需要的字段
- 避免 `SELECT *`
- 使用 `LIMIT` 限制结果集
4. **监控和告警**:
- 监控接口响应时间
- 监控数据库连接池使用情况
- 设置性能告警阈值

View File

@ -50,33 +50,46 @@ func init() {
func GetRoleMenus(roleId int) ([]int, error) {
o := orm.NewOrm()
var menuIdsJson sql.NullString
var menuIdsStr string
// 方法1: 尝试使用 JSON_UNQUOTE 读取 JSON 字段
err := o.Raw("SELECT IFNULL(JSON_UNQUOTE(JSON_EXTRACT(menu_ids, '$')), '[]') FROM yz_roles WHERE role_id = ? AND delete_time IS NULL", roleId).QueryRow(&menuIdsJson)
// 如果方法1失败或结果为空尝试方法2: 直接 CAST
if err != nil {
fmt.Printf("方法1失败尝试方法2: %v\n", err)
err = nil // 重置错误尝试方法2
}
if err != nil || !menuIdsJson.Valid || menuIdsJson.String == "" || menuIdsJson.String == "[]" {
fmt.Printf("方法1结果无效尝试方法2\n")
err2 := o.Raw("SELECT CAST(IFNULL(menu_ids, '[]') AS CHAR) FROM yz_roles WHERE role_id = ? AND delete_time IS NULL", roleId).QueryRow(&menuIdsJson)
err := o.Raw("SELECT IFNULL(JSON_UNQUOTE(JSON_EXTRACT(menu_ids, '$')), '[]') FROM yz_roles WHERE role_id = ? AND delete_time IS NULL", roleId).QueryRow(&menuIdsStr)
if err == nil && menuIdsStr != "" && menuIdsStr != "[]" {
menuIdsJson = sql.NullString{String: menuIdsStr, Valid: true}
fmt.Printf("GetRoleMenus: 方法1成功角色 %d 的 menu_ids: %s\n", roleId, menuIdsStr[:min(100, len(menuIdsStr))])
} else {
// 方法1失败尝试方法2: 直接 CAST
if err != nil {
fmt.Printf("GetRoleMenus: 方法1失败 (%v)尝试方法2\n", err)
} else {
fmt.Printf("GetRoleMenus: 方法1结果为空尝试方法2\n")
}
// 重置变量
menuIdsStr = ""
err2 := o.Raw("SELECT CAST(IFNULL(menu_ids, '[]') AS CHAR) FROM yz_roles WHERE role_id = ? AND delete_time IS NULL", roleId).QueryRow(&menuIdsStr)
if err2 != nil {
// 如果角色不存在,返回空数组而不是错误(兼容性处理)
if err2 == orm.ErrNoRows {
fmt.Printf("角色 %d 不存在\n", roleId)
fmt.Printf("GetRoleMenus: 角色 %d 不存在\n", roleId)
return []int{}, nil
}
fmt.Printf("读取角色 %d 的 menu_ids 失败: %v\n", roleId, err2)
return nil, fmt.Errorf("获取角色菜单失败: %v", err2)
fmt.Printf("GetRoleMenus: 方法2也失败角色 %d 的 menu_ids 读取失败: %v\n", roleId, err2)
return []int{}, nil // 返回空数组而不是错误,保持兼容性
}
if menuIdsStr != "" && menuIdsStr != "[]" && menuIdsStr != "null" {
menuIdsJson = sql.NullString{String: menuIdsStr, Valid: true}
fmt.Printf("GetRoleMenus: 方法2成功角色 %d 的 menu_ids: %s\n", roleId, menuIdsStr[:min(100, len(menuIdsStr))])
} else {
fmt.Printf("GetRoleMenus: 方法2结果也为空角色 %d 的 menu_ids 为空或 null\n", roleId)
return []int{}, nil
}
}
// 如果 menuIdsJson 无效或为空,返回空数组
if !menuIdsJson.Valid || menuIdsJson.String == "" {
fmt.Printf("角色 %d 的 menu_ids 为空或无效\n", roleId)
fmt.Printf("GetRoleMenus: 角色 %d 的 menu_ids 最终为空或无效\n", roleId)
return []int{}, nil
}

View File

@ -14,6 +14,7 @@ import (
type Role struct {
RoleId int `orm:"pk;auto;column(role_id)" json:"roleId"`
TenantId int `orm:"column(tenant_id)" json:"tenantId"`
Default int8 `orm:"column(default);default(2)" json:"default"` // 角色默认分配1-只给租户12-所有租户可用3-租户专属
RoleCode string `orm:"size(50);unique" json:"roleCode"`
RoleName string `orm:"size(100)" json:"roleName"`
Description string `orm:"type(text);null" json:"description"`
@ -90,6 +91,7 @@ func GetRoleById(roleId int) (*Role, error) {
type roleResult struct {
RoleId int
TenantId int
Default int8
RoleCode string
RoleName string
Description string
@ -105,8 +107,8 @@ func GetRoleById(roleId int) (*Role, error) {
var result roleResult
// 先读取其他字段(不包括 menu_ids因为 Beego ORM 可能无法直接读取 JSON 类型
err := o.Raw("SELECT role_id, tenant_id, role_code, role_name, description, status, sort_order, create_time, update_time, delete_time, create_by, update_by FROM yz_roles WHERE role_id = ? AND delete_time IS NULL", roleId).QueryRow(
&result.RoleId, &result.TenantId, &result.RoleCode, &result.RoleName, &result.Description,
err := o.Raw("SELECT role_id, tenant_id, `default`, role_code, role_name, description, status, sort_order, create_time, update_time, delete_time, create_by, update_by FROM yz_roles WHERE role_id = ? AND delete_time IS NULL", roleId).QueryRow(
&result.RoleId, &result.TenantId, &result.Default, &result.RoleCode, &result.RoleName, &result.Description,
&result.Status, &result.SortOrder, &result.CreateTime, &result.UpdateTime,
&result.DeleteTime, &result.CreateBy, &result.UpdateBy,
)
@ -150,6 +152,7 @@ func GetRoleById(roleId int) (*Role, error) {
role := &Role{
RoleId: result.RoleId,
TenantId: result.TenantId,
Default: result.Default,
RoleCode: result.RoleCode,
RoleName: result.RoleName,
Description: result.Description,
@ -178,6 +181,7 @@ func GetAllRoles() ([]*Role, error) {
var results []struct {
RoleId int
TenantId int
Default int8
RoleCode string
RoleName string
Description string
@ -191,7 +195,7 @@ func GetAllRoles() ([]*Role, error) {
UpdateBy string
}
_, err := o.Raw("SELECT role_id, tenant_id, role_code, role_name, description, CAST(IFNULL(menu_ids, '[]') AS CHAR) as menu_ids, status, sort_order, create_time, update_time, delete_time, create_by, update_by FROM yz_roles WHERE delete_time IS NULL ORDER BY sort_order ASC, role_id ASC").QueryRows(&results)
_, err := o.Raw("SELECT role_id, tenant_id, `default`, role_code, role_name, description, CAST(IFNULL(menu_ids, '[]') AS CHAR) as menu_ids, status, sort_order, create_time, update_time, delete_time, create_by, update_by FROM yz_roles WHERE delete_time IS NULL ORDER BY sort_order ASC, role_id ASC").QueryRows(&results)
if err != nil {
return nil, err
}
@ -200,6 +204,7 @@ func GetAllRoles() ([]*Role, error) {
role := &Role{
RoleId: r.RoleId,
TenantId: r.TenantId,
Default: r.Default,
RoleCode: r.RoleCode,
RoleName: r.RoleName,
Description: r.Description,
@ -228,6 +233,7 @@ func GetRoleByTenantId(tenantId int) ([]*Role, error) {
var results []struct {
RoleId int
TenantId int
Default int8
RoleCode string
RoleName string
Description string
@ -241,7 +247,7 @@ func GetRoleByTenantId(tenantId int) ([]*Role, error) {
UpdateBy string
}
_, err := o.Raw("SELECT role_id, tenant_id, role_code, role_name, description, CAST(IFNULL(menu_ids, '[]') AS CHAR) as menu_ids, status, sort_order, create_time, update_time, delete_time, create_by, update_by FROM yz_roles WHERE (tenant_id = ? OR tenant_id = 0) AND delete_time IS NULL ORDER BY sort_order ASC, role_id ASC", tenantId).QueryRows(&results)
_, err := o.Raw("SELECT role_id, tenant_id, `default`, role_code, role_name, description, CAST(IFNULL(menu_ids, '[]') AS CHAR) as menu_ids, status, sort_order, create_time, update_time, delete_time, create_by, update_by FROM yz_roles WHERE (tenant_id = ? OR tenant_id = 0) AND delete_time IS NULL ORDER BY sort_order ASC, role_id ASC", tenantId).QueryRows(&results)
if err != nil {
return nil, err
}
@ -250,6 +256,7 @@ func GetRoleByTenantId(tenantId int) ([]*Role, error) {
role := &Role{
RoleId: r.RoleId,
TenantId: r.TenantId,
Default: r.Default,
RoleCode: r.RoleCode,
RoleName: r.RoleName,
Description: r.Description,
@ -272,9 +279,31 @@ func GetRoleByTenantId(tenantId int) ([]*Role, error) {
// GetRoleByCode 根据角色代码获取角色
func GetRoleByCode(roleCode string) (*Role, error) {
o := orm.NewOrm()
var role Role
err := o.QueryTable("yz_roles").Filter("role_code", roleCode).Filter("delete_time__isnull", true).One(&role)
// 使用Raw查询以正确读取所有字段包括 default 和 menu_ids
type roleResult struct {
RoleId int
TenantId int
Default int8
RoleCode string
RoleName string
Description string
MenuIdsJson sql.NullString
Status int8
SortOrder int
CreateTime time.Time
UpdateTime time.Time
DeleteTime *time.Time
CreateBy string
UpdateBy string
}
var result roleResult
err := o.Raw("SELECT role_id, tenant_id, `default`, role_code, role_name, description, status, sort_order, create_time, update_time, delete_time, create_by, update_by FROM yz_roles WHERE role_code = ? AND delete_time IS NULL", roleCode).QueryRow(
&result.RoleId, &result.TenantId, &result.Default, &result.RoleCode, &result.RoleName, &result.Description,
&result.Status, &result.SortOrder, &result.CreateTime, &result.UpdateTime,
&result.DeleteTime, &result.CreateBy, &result.UpdateBy,
)
if err != nil {
return nil, err
}
@ -283,11 +312,28 @@ func GetRoleByCode(roleCode string) (*Role, error) {
var menuIdsStr string
err2 := o.Raw("SELECT IFNULL(JSON_UNQUOTE(JSON_EXTRACT(menu_ids, '$')), '[]') FROM yz_roles WHERE role_code = ? AND delete_time IS NULL", roleCode).QueryRow(&menuIdsStr)
if err2 == nil && menuIdsStr != "" && menuIdsStr != "[]" {
role.MenuIdsJson = sql.NullString{String: menuIdsStr, Valid: true}
result.MenuIdsJson = sql.NullString{String: menuIdsStr, Valid: true}
}
role := &Role{
RoleId: result.RoleId,
TenantId: result.TenantId,
Default: result.Default,
RoleCode: result.RoleCode,
RoleName: result.RoleName,
Description: result.Description,
MenuIdsJson: result.MenuIdsJson,
Status: result.Status,
SortOrder: result.SortOrder,
CreateTime: result.CreateTime,
UpdateTime: result.UpdateTime,
DeleteTime: result.DeleteTime,
CreateBy: result.CreateBy,
UpdateBy: result.UpdateBy,
}
role.AfterRead()
return &role, nil
return role, nil
}
// CreateRole 创建角色
@ -296,8 +342,17 @@ func CreateRole(role *Role) error {
role.BeforeInsert()
// 使用Raw插入以正确处理JSON字段并获取插入后的ID
res, err := o.Raw("INSERT INTO yz_roles (tenant_id, role_code, role_name, description, menu_ids, status, sort_order, create_time, update_time, create_by, update_by) VALUES (?, ?, ?, ?, CAST(? AS JSON), ?, ?, NOW(), NOW(), ?, ?)",
role.TenantId, role.RoleCode, role.RoleName, role.Description, role.MenuIdsJson.String, role.Status, role.SortOrder, role.CreateBy, role.UpdateBy).Exec()
// 如果没有设置 default 值,根据 tenant_id 自动设置tenant_id=0 时 default=2否则 default=3
defaultValue := role.Default
if defaultValue == 0 {
if role.TenantId == 0 {
defaultValue = 2 // 所有租户可用
} else {
defaultValue = 3 // 租户专属
}
}
res, err := o.Raw("INSERT INTO yz_roles (tenant_id, `default`, role_code, role_name, description, menu_ids, status, sort_order, create_time, update_time, create_by, update_by) VALUES (?, ?, ?, ?, ?, CAST(? AS JSON), ?, ?, NOW(), NOW(), ?, ?)",
role.TenantId, defaultValue, role.RoleCode, role.RoleName, role.Description, role.MenuIdsJson.String, role.Status, role.SortOrder, role.CreateBy, role.UpdateBy).Exec()
if err != nil {
return err
}
@ -324,8 +379,17 @@ func UpdateRole(role *Role) error {
role.BeforeUpdate()
// 使用Raw更新以正确处理JSON字段
_, err := o.Raw("UPDATE yz_roles SET tenant_id = ?, role_code = ?, role_name = ?, description = ?, menu_ids = CAST(? AS JSON), status = ?, sort_order = ?, update_time = NOW(), update_by = ? WHERE role_id = ? AND delete_time IS NULL",
role.TenantId, role.RoleCode, role.RoleName, role.Description, role.MenuIdsJson.String, role.Status, role.SortOrder, role.UpdateBy, role.RoleId).Exec()
// 如果没有设置 default 值,根据 tenant_id 自动设置tenant_id=0 时 default=2否则 default=3
defaultValue := role.Default
if defaultValue == 0 {
if role.TenantId == 0 {
defaultValue = 2 // 所有租户可用
} else {
defaultValue = 3 // 租户专属
}
}
_, err := o.Raw("UPDATE yz_roles SET tenant_id = ?, `default` = ?, role_code = ?, role_name = ?, description = ?, menu_ids = CAST(? AS JSON), status = ?, sort_order = ?, update_time = NOW(), update_by = ? WHERE role_id = ? AND delete_time IS NULL",
role.TenantId, defaultValue, role.RoleCode, role.RoleName, role.Description, role.MenuIdsJson.String, role.Status, role.SortOrder, role.UpdateBy, role.RoleId).Exec()
return err
}