|
|
@@ -0,0 +1,310 @@
|
|
|
+package com.nylc.touyan.rbac;
|
|
|
+
|
|
|
+import com.nylc.touyan.auth.AuthException;
|
|
|
+import com.nylc.touyan.datasource.DataSourceClient;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+/**
|
|
|
+ * RBAC(基于角色的访问控制)服务:
|
|
|
+ * - 数据模型:sys_role / sys_permission / sys_user_role / sys_role_permission;
|
|
|
+ * - 启动时建表并灌入种子(内置 ADMIN/USER 角色、初始权限集、默认管理员);
|
|
|
+ * - 提供角色/权限管理与用户角色授权能力;登录后据此解析用户角色与权限。
|
|
|
+ */
|
|
|
+@Service
|
|
|
+public class RbacService {
|
|
|
+
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(RbacService.class);
|
|
|
+
|
|
|
+ private final DataSourceClient dataSource;
|
|
|
+ private final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
|
|
|
+ private final String adminUsername;
|
|
|
+ private final String adminPassword;
|
|
|
+
|
|
|
+ public RbacService(DataSourceClient dataSource,
|
|
|
+ com.nylc.touyan.auth.AuthProperties properties) {
|
|
|
+ this.dataSource = dataSource;
|
|
|
+ this.adminUsername = properties.getAdmin().getUsername();
|
|
|
+ this.adminPassword = properties.getAdmin().getPassword();
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 初始化 RBAC 表并灌入种子数据(幂等)。 */
|
|
|
+ public void init() {
|
|
|
+ dataSource.execute(
|
|
|
+ "CREATE TABLE IF NOT EXISTS sys_role (" +
|
|
|
+ "id INTEGER PRIMARY KEY AUTOINCREMENT," +
|
|
|
+ "code TEXT NOT NULL UNIQUE," +
|
|
|
+ "name TEXT NOT NULL," +
|
|
|
+ "description TEXT," +
|
|
|
+ "created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)");
|
|
|
+ dataSource.execute(
|
|
|
+ "CREATE TABLE IF NOT EXISTS sys_permission (" +
|
|
|
+ "id INTEGER PRIMARY KEY AUTOINCREMENT," +
|
|
|
+ "code TEXT NOT NULL UNIQUE," +
|
|
|
+ "name TEXT NOT NULL," +
|
|
|
+ "description TEXT," +
|
|
|
+ "created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)");
|
|
|
+ dataSource.execute(
|
|
|
+ "CREATE TABLE IF NOT EXISTS sys_user_role (" +
|
|
|
+ "user_id INTEGER NOT NULL," +
|
|
|
+ "role_id INTEGER NOT NULL," +
|
|
|
+ "PRIMARY KEY (user_id, role_id))");
|
|
|
+ dataSource.execute(
|
|
|
+ "CREATE TABLE IF NOT EXISTS sys_role_permission (" +
|
|
|
+ "role_id INTEGER NOT NULL," +
|
|
|
+ "permission_id INTEGER NOT NULL," +
|
|
|
+ "PRIMARY KEY (role_id, permission_id))");
|
|
|
+
|
|
|
+ ensureRole("ADMIN", "超级管理员", "系统内置,拥有全部权限");
|
|
|
+ ensureRole("USER", "普通用户", "系统内置,默认用户角色");
|
|
|
+
|
|
|
+ List<String> seedPerms = Arrays.asList(
|
|
|
+ "user:list", "user:create", "user:update", "user:delete",
|
|
|
+ "role:list", "role:create", "role:update", "role:delete",
|
|
|
+ "role:assign", "permission:list", "permission:create", "permission:delete",
|
|
|
+ "data:query", "data:write");
|
|
|
+ for (String code : seedPerms) {
|
|
|
+ ensurePermission(code, code, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ Long adminRoleId = roleIdByCode("ADMIN");
|
|
|
+ Long userRoleId = roleIdByCode("USER");
|
|
|
+ List<Long> allPermIds = allPermissionIds();
|
|
|
+ assignRolePermissions(adminRoleId, allPermIds);
|
|
|
+
|
|
|
+ ensureAdminUser();
|
|
|
+
|
|
|
+ log.info("[rbac] 初始化完成:角色 ADMIN/USER,管理员 [{}]", adminUsername);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 角色 ====================
|
|
|
+
|
|
|
+ public List<Map<String, Object>> listRoles() {
|
|
|
+ return dataSource.queryForList(
|
|
|
+ "SELECT r.id, r.code, r.name, r.description, r.created_at, " +
|
|
|
+ "(SELECT COUNT(*) FROM sys_user_role ur WHERE ur.role_id = r.id) AS user_count " +
|
|
|
+ "FROM sys_role r ORDER BY r.id");
|
|
|
+ }
|
|
|
+
|
|
|
+ public Map<String, Object> createRole(String code, String name, String description) {
|
|
|
+ if (code == null || code.trim().isEmpty() || roleIdByCode(code) != null) {
|
|
|
+ throw new AuthException("ROLE_EXISTS", "角色编码为空或已存在: " + code);
|
|
|
+ }
|
|
|
+ int rows = dataSource.update(
|
|
|
+ "INSERT INTO sys_role(code, name, description) VALUES(?, ?, ?)",
|
|
|
+ code, name, description);
|
|
|
+ if (rows <= 0) throw new AuthException("INTERNAL", "创建角色失败");
|
|
|
+ return dataSource.queryForMap(
|
|
|
+ "SELECT id, code, name, description, created_at FROM sys_role WHERE code = ?", code);
|
|
|
+ }
|
|
|
+
|
|
|
+ public Map<String, Object> updateRole(Long roleId, String name, String description) {
|
|
|
+ int rows = dataSource.update(
|
|
|
+ "UPDATE sys_role SET name = ?, description = ? WHERE id = ?",
|
|
|
+ name, description, roleId);
|
|
|
+ if (rows <= 0) throw new AuthException("NOT_FOUND", "角色不存在: " + roleId);
|
|
|
+ return dataSource.queryForMap(
|
|
|
+ "SELECT id, code, name, description, created_at FROM sys_role WHERE id = ?", roleId);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void deleteRole(Long roleId) {
|
|
|
+ dataSource.update("DELETE FROM sys_user_role WHERE role_id = ?", roleId);
|
|
|
+ dataSource.update("DELETE FROM sys_role_permission WHERE role_id = ?", roleId);
|
|
|
+ dataSource.update("DELETE FROM sys_role_menu WHERE role_id = ?", roleId);
|
|
|
+ dataSource.update("DELETE FROM sys_role WHERE id = ?", roleId);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 权限 ====================
|
|
|
+
|
|
|
+ public List<Map<String, Object>> listPermissions() {
|
|
|
+ return dataSource.queryForList("SELECT id, code, name, description, created_at FROM sys_permission ORDER BY id");
|
|
|
+ }
|
|
|
+
|
|
|
+ public Map<String, Object> createPermission(String code, String name, String description) {
|
|
|
+ if (code == null || code.trim().isEmpty() || permissionIdByCode(code) != null) {
|
|
|
+ throw new AuthException("PERMISSION_EXISTS", "权限编码为空或已存在: " + code);
|
|
|
+ }
|
|
|
+ dataSource.update(
|
|
|
+ "INSERT INTO sys_permission(code, name, description) VALUES(?, ?, ?)",
|
|
|
+ code, name, description);
|
|
|
+ return dataSource.queryForMap(
|
|
|
+ "SELECT id, code, name, description, created_at FROM sys_permission WHERE code = ?", code);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void deletePermission(Long permissionId) {
|
|
|
+ dataSource.update("DELETE FROM sys_role_permission WHERE permission_id = ?", permissionId);
|
|
|
+ dataSource.update("DELETE FROM sys_permission WHERE id = ?", permissionId);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 授权关系 ====================
|
|
|
+
|
|
|
+ /** 覆盖式设置角色拥有的权限。 */
|
|
|
+ public void assignRolePermissions(Long roleId, List<Long> permissionIds) {
|
|
|
+ dataSource.update("DELETE FROM sys_role_permission WHERE role_id = ?", roleId);
|
|
|
+ if (permissionIds != null) {
|
|
|
+ List<Object[]> batch = new ArrayList<>();
|
|
|
+ for (Long pid : permissionIds) {
|
|
|
+ if (pid != null) batch.add(new Object[]{roleId, pid});
|
|
|
+ }
|
|
|
+ dataSource.batchUpdate("INSERT INTO sys_role_permission(role_id, permission_id) VALUES(?, ?)", batch);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 查询某角色拥有的权限。 */
|
|
|
+ public Map<String, Object> getRolePermissions(Long roleId) {
|
|
|
+ List<Map<String, Object>> perms = dataSource.queryForList(
|
|
|
+ "SELECT p.id, p.code, p.name FROM sys_permission p " +
|
|
|
+ "JOIN sys_role_permission rp ON rp.permission_id = p.id " +
|
|
|
+ "WHERE rp.role_id = ? ORDER BY p.id", roleId);
|
|
|
+ Map<String, Object> role = dataSource.queryForMap(
|
|
|
+ "SELECT id, code, name FROM sys_role WHERE id = ?", roleId);
|
|
|
+ Map<String, Object> r = new LinkedHashMap<>();
|
|
|
+ r.put("role", role);
|
|
|
+ r.put("permissions", perms);
|
|
|
+ return r;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 覆盖式设置用户拥有的角色。 */
|
|
|
+ public void assignUserRoles(Long userId, List<Long> roleIds) {
|
|
|
+ dataSource.update("DELETE FROM sys_user_role WHERE user_id = ?", userId);
|
|
|
+ if (roleIds != null) {
|
|
|
+ List<Object[]> batch = new ArrayList<>();
|
|
|
+ for (Long rid : roleIds) {
|
|
|
+ if (rid != null) batch.add(new Object[]{userId, rid});
|
|
|
+ }
|
|
|
+ dataSource.batchUpdate("INSERT INTO sys_user_role(user_id, role_id) VALUES(?, ?)", batch);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 查询用户角色(含权限)。 */
|
|
|
+ public Map<String, Object> getUserAuth(Long userId) {
|
|
|
+ List<Map<String, Object>> roles = dataSource.queryForList(
|
|
|
+ "SELECT r.id, r.code, r.name FROM sys_role r " +
|
|
|
+ "JOIN sys_user_role ur ON ur.role_id = r.id WHERE ur.user_id = ? ORDER BY r.id", userId);
|
|
|
+ List<Map<String, Object>> perms = dataSource.queryForList(
|
|
|
+ "SELECT DISTINCT p.id, p.code, p.name FROM sys_permission p " +
|
|
|
+ "JOIN sys_role_permission rp ON rp.permission_id = p.id " +
|
|
|
+ "JOIN sys_user_role ur ON ur.role_id = rp.role_id " +
|
|
|
+ "WHERE ur.user_id = ? ORDER BY p.id", userId);
|
|
|
+ Map<String, Object> r = new LinkedHashMap<>();
|
|
|
+ r.put("userId", userId);
|
|
|
+ r.put("roles", roles);
|
|
|
+ r.put("permissions", perms);
|
|
|
+ return r;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 解析某用户去重后的角色编码列表。 */
|
|
|
+ public List<String> roleCodesOf(Long userId) {
|
|
|
+ List<Map<String, Object>> rows = dataSource.queryForList(
|
|
|
+ "SELECT r.code FROM sys_role r " +
|
|
|
+ "JOIN sys_user_role ur ON ur.role_id = r.id WHERE ur.user_id = ?", userId);
|
|
|
+ List<String> codes = new ArrayList<>();
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
+ codes.add(String.valueOf(row.get("code")));
|
|
|
+ }
|
|
|
+ return codes;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 解析某用户去重后的权限编码列表。 */
|
|
|
+ public List<String> permissionCodesOf(Long userId) {
|
|
|
+ List<Map<String, Object>> rows = dataSource.queryForList(
|
|
|
+ "SELECT DISTINCT p.code FROM sys_permission p " +
|
|
|
+ "JOIN sys_role_permission rp ON rp.permission_id = p.id " +
|
|
|
+ "JOIN sys_user_role ur ON ur.role_id = rp.role_id " +
|
|
|
+ "WHERE ur.user_id = ?", userId);
|
|
|
+ List<String> codes = new ArrayList<>();
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
+ codes.add(String.valueOf(row.get("code")));
|
|
|
+ }
|
|
|
+ return codes;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 内部 ====================
|
|
|
+
|
|
|
+ private void ensureRole(String code, String name, String description) {
|
|
|
+ if (roleIdByCode(code) == null) {
|
|
|
+ dataSource.update("INSERT INTO sys_role(code, name, description) VALUES(?, ?, ?)",
|
|
|
+ code, name, description);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ensurePermission(String code, String name, String description) {
|
|
|
+ if (permissionIdByCode(code) == null) {
|
|
|
+ dataSource.update("INSERT INTO sys_permission(code, name, description) VALUES(?, ?, ?)",
|
|
|
+ code, name, description);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ensureAdminUser() {
|
|
|
+ Map<String, Object> exists = dataSource.queryForMap(
|
|
|
+ "SELECT id FROM sys_user WHERE username = ?", adminUsername);
|
|
|
+ Long adminId;
|
|
|
+ if (exists == null) {
|
|
|
+ dataSource.update("INSERT INTO sys_user(username, password, nickname, enabled) VALUES(?, ?, ?, 1)",
|
|
|
+ adminUsername, encoder.encode(adminPassword), "管理员");
|
|
|
+ adminId = ((Number) dataSource.queryForObject(
|
|
|
+ "SELECT id FROM sys_user WHERE username = ?", adminUsername)).longValue();
|
|
|
+ } else {
|
|
|
+ adminId = ((Number) exists.get("id")).longValue();
|
|
|
+ }
|
|
|
+ Long adminRoleId = roleIdByCode("ADMIN");
|
|
|
+ if (adminRoleId != null && userHasRole(adminId, adminRoleId)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ assignUserRoles(adminId, java.util.Collections.singletonList(adminRoleId));
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean userHasRole(Long userId, Long roleId) {
|
|
|
+ return dataSource.queryForMap(
|
|
|
+ "SELECT 1 FROM sys_user_role WHERE user_id = ? AND role_id = ?",
|
|
|
+ userId, roleId) != null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Long roleIdByCode(String code) {
|
|
|
+ Map<String, Object> row = dataSource.queryForMap(
|
|
|
+ "SELECT id FROM sys_role WHERE code = ?", code);
|
|
|
+ return row == null ? null : ((Number) row.get("id")).longValue();
|
|
|
+ }
|
|
|
+
|
|
|
+ private Long permissionIdByCode(String code) {
|
|
|
+ Map<String, Object> row = dataSource.queryForMap(
|
|
|
+ "SELECT id FROM sys_permission WHERE code = ?", code);
|
|
|
+ return row == null ? null : ((Number) row.get("id")).longValue();
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<Long> allPermissionIds() {
|
|
|
+ List<Map<String, Object>> rows = dataSource.queryForList("SELECT id FROM sys_permission");
|
|
|
+ List<Long> ids = new ArrayList<>();
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
+ ids.add(((Number) row.get("id")).longValue());
|
|
|
+ }
|
|
|
+ return ids;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 当前用户权限(供登录时嵌入 token)。 */
|
|
|
+ public AuthSnapshot resolve(Long userId) {
|
|
|
+ return new AuthSnapshot(roleCodesOf(userId), permissionCodesOf(userId));
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 角色 + 权限编码集合快照。 */
|
|
|
+ public static class AuthSnapshot {
|
|
|
+ private final List<String> roles;
|
|
|
+ private final List<String> permissions;
|
|
|
+
|
|
|
+ public AuthSnapshot(List<String> roles, List<String> permissions) {
|
|
|
+ this.roles = roles == null ? new ArrayList<>() : roles;
|
|
|
+ this.permissions = permissions == null ? new ArrayList<>() : permissions;
|
|
|
+ }
|
|
|
+
|
|
|
+ public List<String> getRoles() { return roles; }
|
|
|
+ public List<String> getPermissions() { return permissions; }
|
|
|
+ }
|
|
|
+}
|