-- Phase 1 authentication and authorization baseline. -- Internal BIGINT identifiers are never exposed through public APIs. CREATE TABLE `users` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `public_id` CHAR(36) NOT NULL, `status` ENUM('ACTIVE', 'SUSPENDED', 'DISABLED') NOT NULL DEFAULT 'ACTIVE', `user_type` ENUM('PUBLIC', 'STAFF') NOT NULL, `mobile_encrypted` VARCHAR(512) NULL, `mobile_hash` CHAR(64) NULL, `nickname` VARCHAR(100) NULL, `avatar_url` VARCHAR(512) NULL, `last_login_at` DATETIME(3) NULL, `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `updated_at` DATETIME(3) NOT NULL, UNIQUE INDEX `users_public_id_key`(`public_id`), UNIQUE INDEX `users_mobile_hash_key`(`mobile_hash`), PRIMARY KEY (`id`) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE TABLE `wechat_identities` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `user_id` BIGINT UNSIGNED NOT NULL, `appid` VARCHAR(64) NOT NULL, `openid_ciphertext` VARCHAR(512) NOT NULL, `openid_hash` CHAR(64) NOT NULL, `unionid_ciphertext` VARCHAR(512) NULL, `unionid_hash` CHAR(64) NULL, `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `updated_at` DATETIME(3) NOT NULL, INDEX `wechat_identities_user_id_idx`(`user_id`), UNIQUE INDEX `wechat_identities_appid_openid_hash_key`(`appid`, `openid_hash`), PRIMARY KEY (`id`) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE TABLE `staff_profiles` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `user_id` BIGINT UNSIGNED NOT NULL, `employee_no` VARCHAR(50) NULL, `display_name` VARCHAR(100) NOT NULL, `department` VARCHAR(100) NULL, `status` ENUM('ACTIVE', 'INACTIVE') NOT NULL DEFAULT 'ACTIVE', `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `updated_at` DATETIME(3) NOT NULL, UNIQUE INDEX `staff_profiles_user_id_key`(`user_id`), UNIQUE INDEX `staff_profiles_employee_no_key`(`employee_no`), PRIMARY KEY (`id`) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE TABLE `admin_credentials` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `user_id` BIGINT UNSIGNED NOT NULL, `username` VARCHAR(64) NOT NULL, `password_hash` VARCHAR(255) NOT NULL, `failed_login_count` INTEGER UNSIGNED NOT NULL DEFAULT 0, `locked_until` DATETIME(3) NULL, `password_changed_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `updated_at` DATETIME(3) NOT NULL, UNIQUE INDEX `admin_credentials_user_id_key`(`user_id`), UNIQUE INDEX `admin_credentials_username_key`(`username`), PRIMARY KEY (`id`) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE TABLE `auth_sessions` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `public_id` CHAR(36) NOT NULL, `user_id` BIGINT UNSIGNED NOT NULL, `channel` ENUM('WECHAT', 'ADMIN') NOT NULL, `token_hash` CHAR(64) NOT NULL, `expires_at` DATETIME(3) NOT NULL, `last_used_at` DATETIME(3) NULL, `revoked_at` DATETIME(3) NULL, `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), UNIQUE INDEX `auth_sessions_public_id_key`(`public_id`), UNIQUE INDEX `auth_sessions_token_hash_key`(`token_hash`), INDEX `auth_sessions_user_channel_status_idx`(`user_id`, `channel`, `revoked_at`, `expires_at`), INDEX `auth_sessions_expires_at_idx`(`expires_at`), PRIMARY KEY (`id`) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE TABLE `roles` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `public_id` CHAR(36) NOT NULL, `code` VARCHAR(64) NOT NULL, `name` VARCHAR(100) NOT NULL, `description` VARCHAR(255) NULL, `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `updated_at` DATETIME(3) NOT NULL, UNIQUE INDEX `roles_public_id_key`(`public_id`), UNIQUE INDEX `roles_code_key`(`code`), PRIMARY KEY (`id`) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE TABLE `permissions` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `code` VARCHAR(100) NOT NULL, `name` VARCHAR(100) NOT NULL, `description` VARCHAR(255) NULL, `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), UNIQUE INDEX `permissions_code_key`(`code`), PRIMARY KEY (`id`) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE TABLE `user_roles` ( `user_id` BIGINT UNSIGNED NOT NULL, `role_id` BIGINT UNSIGNED NOT NULL, `assigned_by` BIGINT UNSIGNED NULL, `assigned_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), INDEX `user_roles_role_id_idx`(`role_id`), INDEX `user_roles_assigned_by_idx`(`assigned_by`), PRIMARY KEY (`user_id`, `role_id`) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE TABLE `role_permissions` ( `role_id` BIGINT UNSIGNED NOT NULL, `permission_id` BIGINT UNSIGNED NOT NULL, INDEX `role_permissions_permission_id_idx`(`permission_id`), PRIMARY KEY (`role_id`, `permission_id`) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE TABLE `audit_logs` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `request_id` CHAR(36) NOT NULL, `actor_user_id` BIGINT UNSIGNED NULL, `actor_role` VARCHAR(64) NULL, `action` VARCHAR(100) NOT NULL, `resource_type` VARCHAR(100) NULL, `resource_public_id` VARCHAR(100) NULL, `result` ENUM('SUCCESS', 'FAILURE') NOT NULL, `metadata_json` JSON NULL, `ip_hash` CHAR(64) NULL, `user_agent_summary` VARCHAR(255) NULL, `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), INDEX `audit_logs_actor_created_at_idx`(`actor_user_id`, `created_at`), INDEX `audit_logs_resource_created_at_idx`(`resource_type`, `resource_public_id`, `created_at`), INDEX `audit_logs_action_created_at_idx`(`action`, `created_at`), PRIMARY KEY (`id`) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE `wechat_identities` ADD CONSTRAINT `wechat_identities_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; ALTER TABLE `staff_profiles` ADD CONSTRAINT `staff_profiles_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; ALTER TABLE `admin_credentials` ADD CONSTRAINT `admin_credentials_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; ALTER TABLE `auth_sessions` ADD CONSTRAINT `auth_sessions_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; ALTER TABLE `user_roles` ADD CONSTRAINT `user_roles_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; ALTER TABLE `user_roles` ADD CONSTRAINT `user_roles_role_id_fkey` FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; ALTER TABLE `user_roles` ADD CONSTRAINT `user_roles_assigned_by_fkey` FOREIGN KEY (`assigned_by`) REFERENCES `users`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; ALTER TABLE `role_permissions` ADD CONSTRAINT `role_permissions_role_id_fkey` FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; ALTER TABLE `role_permissions` ADD CONSTRAINT `role_permissions_permission_id_fkey` FOREIGN KEY (`permission_id`) REFERENCES `permissions`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; ALTER TABLE `audit_logs` ADD CONSTRAINT `audit_logs_actor_user_id_fkey` FOREIGN KEY (`actor_user_id`) REFERENCES `users`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; INSERT INTO `roles` (`public_id`, `code`, `name`, `description`, `updated_at`) VALUES ('9fc73dca-5e75-4ce6-b8ad-d270bd020001', 'VOLUNTEER', '志愿者', '仅访问本人被分配的最小必要任务信息', CURRENT_TIMESTAMP(3)), ('9fc73dca-5e75-4ce6-b8ad-d270bd020002', 'STAFF', '工作人员', '按负责范围处理公益服务', CURRENT_TIMESTAMP(3)), ('9fc73dca-5e75-4ce6-b8ad-d270bd020003', 'PROJECT_MANAGER', '项目负责人', '按项目范围管理服务', CURRENT_TIMESTAMP(3)), ('9fc73dca-5e75-4ce6-b8ad-d270bd020004', 'ADMIN', '管理员', '跨项目业务管理', CURRENT_TIMESTAMP(3)), ('9fc73dca-5e75-4ce6-b8ad-d270bd020005', 'SUPER_ADMIN', '超级管理员', '系统级配置维护,日常不使用', CURRENT_TIMESTAMP(3)); INSERT INTO `permissions` (`code`, `name`, `description`) VALUES ('admin:access', '访问管理后台', '允许进入管理后台'), ('users:read', '查看用户', '按授权范围查看用户基础信息'), ('users:manage', '管理用户', '管理用户状态与基础信息'), ('roles:manage', '管理角色权限', '分配角色和维护权限'), ('audit:read', '查看审计日志', '按权限查看审计记录'), ('system:manage', '管理系统配置', '维护系统级配置'), ('sensitive:read', '查看敏感信息', '经审计查看必要敏感信息'), ('sensitive:export', '导出敏感信息', '受控导出敏感信息'), ('resources:access_all', '跨范围访问资源', '按角色范围访问非本人资源'); INSERT INTO `role_permissions` (`role_id`, `permission_id`) SELECT r.id, p.id FROM roles r CROSS JOIN permissions p WHERE r.code = 'STAFF' AND p.code IN ('admin:access', 'users:read'); INSERT INTO `role_permissions` (`role_id`, `permission_id`) SELECT r.id, p.id FROM roles r CROSS JOIN permissions p WHERE r.code = 'PROJECT_MANAGER' AND p.code IN ('admin:access', 'users:read', 'resources:access_all'); INSERT INTO `role_permissions` (`role_id`, `permission_id`) SELECT r.id, p.id FROM roles r CROSS JOIN permissions p WHERE r.code = 'ADMIN' AND p.code IN ('admin:access', 'users:read', 'users:manage', 'roles:manage', 'audit:read', 'sensitive:read', 'resources:access_all'); INSERT INTO `role_permissions` (`role_id`, `permission_id`) SELECT r.id, p.id FROM roles r CROSS JOIN permissions p WHERE r.code = 'SUPER_ADMIN';