Compare commits
No commits in common. "70e739818a06ac25c4cbee67b7b3639b8fd68edf" and "21b3ab3afc5858952b1e6b0e993cf174bd580b1a" have entirely different histories.
70e739818a
...
21b3ab3afc
|
|
@ -69,8 +69,8 @@
|
||||||
| 字段 | 类型 | 约束 | 说明 |
|
| 字段 | 类型 | 约束 | 说明 |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| role_id | BIGSERIAL | PK | 角色ID |
|
| role_id | BIGSERIAL | PK | 角色ID |
|
||||||
| tenant_id | BIGINT | NOT NULL | 租户ID |
|
| tenant_id | BIGINT | | 租户ID |
|
||||||
| role_code | VARCHAR(50) | NOT NULL | 角色编码(租户内唯一) |
|
| role_code | VARCHAR(50) | NOT NULL, UNIQUE | 角色编码 |
|
||||||
| role_name | VARCHAR(50) | NOT NULL | 角色名称 |
|
| role_name | VARCHAR(50) | NOT NULL | 角色名称 |
|
||||||
| status | SMALLINT | NOT NULL, DEFAULT 1 | 状态 |
|
| status | SMALLINT | NOT NULL, DEFAULT 1 | 状态 |
|
||||||
| remark | TEXT | | 备注 |
|
| remark | TEXT | | 备注 |
|
||||||
|
|
@ -82,11 +82,11 @@
|
||||||
- `idx_sys_role_tenant`:`(tenant_id)`
|
- `idx_sys_role_tenant`:`(tenant_id)`
|
||||||
- `uk_role_code`:`UNIQUE (tenant_id, role_code) WHERE is_deleted = FALSE`
|
- `uk_role_code`:`UNIQUE (tenant_id, role_code) WHERE is_deleted = FALSE`
|
||||||
|
|
||||||
### 1.3 `sys_user_role`(用户-角色关联表,租户强约束)
|
### 1.3 `sys_user_role`(用户-角色关联表)
|
||||||
| 字段 | 类型 | 约束 | 说明 |
|
| 字段 | 类型 | 约束 | 说明 |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| id | BIGSERIAL | PK | 关联ID |
|
| id | BIGSERIAL | PK | 关联ID |
|
||||||
| tenant_id | BIGINT | NOT NULL | 租户ID |
|
| tenant_id | BIGINT | | 租户ID |
|
||||||
| user_id | BIGINT | NOT NULL | 用户ID |
|
| user_id | BIGINT | NOT NULL | 用户ID |
|
||||||
| role_id | BIGINT | NOT NULL | 角色ID |
|
| role_id | BIGINT | NOT NULL | 角色ID |
|
||||||
| is_deleted | SMALLINT | NOT NULL, DEFAULT 0 | 逻辑删除标记 |
|
| is_deleted | SMALLINT | NOT NULL, DEFAULT 0 | 逻辑删除标记 |
|
||||||
|
|
@ -94,7 +94,7 @@
|
||||||
| updated_at | TIMESTAMP(6) | NOT NULL, DEFAULT now() | 更新时间 |
|
| updated_at | TIMESTAMP(6) | NOT NULL, DEFAULT now() | 更新时间 |
|
||||||
|
|
||||||
唯一约束:
|
唯一约束:
|
||||||
- `UNIQUE (tenant_id, user_id, role_id) WHERE is_deleted = 0`
|
- `UNIQUE (user_id, role_id)`
|
||||||
|
|
||||||
### 1.4 `sys_tenant_user`(租户成员关联表)
|
### 1.4 `sys_tenant_user`(租户成员关联表)
|
||||||
| 字段 | 类型 | 约束 | 说明 |
|
| 字段 | 类型 | 约束 | 说明 |
|
||||||
|
|
|
||||||
|
|
@ -72,8 +72,8 @@ DROP TABLE IF EXISTS sys_role CASCADE;
|
||||||
|
|
||||||
CREATE TABLE sys_role (
|
CREATE TABLE sys_role (
|
||||||
role_id BIGSERIAL PRIMARY KEY,
|
role_id BIGSERIAL PRIMARY KEY,
|
||||||
tenant_id BIGINT NOT NULL,
|
tenant_id BIGINT,
|
||||||
role_code VARCHAR(50) NOT NULL,
|
role_code VARCHAR(50) NOT NULL UNIQUE,
|
||||||
role_name VARCHAR(50) NOT NULL,
|
role_name VARCHAR(50) NOT NULL,
|
||||||
status SMALLINT NOT NULL DEFAULT 1,
|
status SMALLINT NOT NULL DEFAULT 1,
|
||||||
remark TEXT,
|
remark TEXT,
|
||||||
|
|
@ -85,18 +85,18 @@ CREATE TABLE sys_role (
|
||||||
CREATE INDEX idx_sys_role_tenant ON sys_role (tenant_id);
|
CREATE INDEX idx_sys_role_tenant ON sys_role (tenant_id);
|
||||||
CREATE UNIQUE INDEX uk_role_code ON sys_role (tenant_id, role_code) WHERE is_deleted = FALSE;
|
CREATE UNIQUE INDEX uk_role_code ON sys_role (tenant_id, role_code) WHERE is_deleted = FALSE;
|
||||||
|
|
||||||
-- 用户-角色关联表 (按 tenant_id 强约束,避免跨租户角色污染)
|
-- 用户-角色关联表 (无 tenant_id, 随 User/Role 隔离)
|
||||||
DROP TABLE IF EXISTS sys_user_role CASCADE;
|
DROP TABLE IF EXISTS sys_user_role CASCADE;
|
||||||
|
|
||||||
CREATE TABLE sys_user_role (
|
CREATE TABLE sys_user_role (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
tenant_id BIGINT NOT NULL,
|
tenant_id BIGINT,
|
||||||
user_id BIGINT NOT NULL,
|
user_id BIGINT NOT NULL,
|
||||||
role_id BIGINT NOT NULL,
|
role_id BIGINT NOT NULL,
|
||||||
is_deleted SMALLINT NOT NULL DEFAULT 0,
|
is_deleted SMALLINT NOT NULL DEFAULT 0,
|
||||||
created_at TIMESTAMP(6) NOT NULL DEFAULT now(),
|
created_at TIMESTAMP(6) NOT NULL DEFAULT now(),
|
||||||
updated_at TIMESTAMP(6) NOT NULL DEFAULT now(),
|
updated_at TIMESTAMP(6) NOT NULL DEFAULT now(),
|
||||||
UNIQUE (tenant_id, user_id, role_id)
|
UNIQUE (user_id, role_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
package com.imeeting.auth;
|
package com.imeeting.auth;
|
||||||
|
|
||||||
import com.imeeting.common.RedisKeys;
|
|
||||||
import com.imeeting.entity.SysTenant;
|
import com.imeeting.entity.SysTenant;
|
||||||
import com.imeeting.entity.SysUser;
|
import com.imeeting.entity.SysUser;
|
||||||
import com.imeeting.security.LoginUser;
|
import com.imeeting.security.LoginUser;
|
||||||
import com.imeeting.service.AuthScopeService;
|
|
||||||
import com.imeeting.service.AuthVersionService;
|
|
||||||
import com.imeeting.service.SysParamService;
|
import com.imeeting.service.SysParamService;
|
||||||
import com.imeeting.service.SysPermissionService;
|
import com.imeeting.service.SysPermissionService;
|
||||||
import com.imeeting.mapper.SysTenantMapper;
|
import com.imeeting.mapper.SysTenantMapper;
|
||||||
|
|
@ -25,7 +22,6 @@ import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
|
|
@ -36,25 +32,19 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||||
private final SysUserMapper sysUserMapper;
|
private final SysUserMapper sysUserMapper;
|
||||||
private final SysParamService sysParamService;
|
private final SysParamService sysParamService;
|
||||||
private final StringRedisTemplate redisTemplate;
|
private final StringRedisTemplate redisTemplate;
|
||||||
private final AuthScopeService authScopeService;
|
|
||||||
private final AuthVersionService authVersionService;
|
|
||||||
|
|
||||||
public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider,
|
public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider,
|
||||||
@Lazy SysPermissionService sysPermissionService,
|
@Lazy SysPermissionService sysPermissionService,
|
||||||
SysTenantMapper sysTenantMapper,
|
SysTenantMapper sysTenantMapper,
|
||||||
SysUserMapper sysUserMapper,
|
SysUserMapper sysUserMapper,
|
||||||
@Lazy SysParamService sysParamService,
|
@Lazy SysParamService sysParamService,
|
||||||
StringRedisTemplate redisTemplate,
|
StringRedisTemplate redisTemplate) {
|
||||||
AuthScopeService authScopeService,
|
|
||||||
AuthVersionService authVersionService) {
|
|
||||||
this.jwtTokenProvider = jwtTokenProvider;
|
this.jwtTokenProvider = jwtTokenProvider;
|
||||||
this.sysPermissionService = sysPermissionService;
|
this.sysPermissionService = sysPermissionService;
|
||||||
this.sysTenantMapper = sysTenantMapper;
|
this.sysTenantMapper = sysTenantMapper;
|
||||||
this.sysUserMapper = sysUserMapper;
|
this.sysUserMapper = sysUserMapper;
|
||||||
this.sysParamService = sysParamService;
|
this.sysParamService = sysParamService;
|
||||||
this.redisTemplate = redisTemplate;
|
this.redisTemplate = redisTemplate;
|
||||||
this.authScopeService = authScopeService;
|
|
||||||
this.authVersionService = authVersionService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -76,8 +66,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||||
String displayName = claims.get("displayName", String.class);
|
String displayName = claims.get("displayName", String.class);
|
||||||
Long userId = claims.get("userId", Long.class);
|
Long userId = claims.get("userId", Long.class);
|
||||||
Long tenantId = claims.get("tenantId", Long.class);
|
Long tenantId = claims.get("tenantId", Long.class);
|
||||||
Number tokenAuthVersionNum = claims.get("authVersion", Number.class);
|
|
||||||
|
|
||||||
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||||
// 1. Validate User Status (Ignore Tenant isolation here)
|
// 1. Validate User Status (Ignore Tenant isolation here)
|
||||||
SysUser user = sysUserMapper.selectByIdIgnoreTenant(userId);
|
SysUser user = sysUserMapper.selectByIdIgnoreTenant(userId);
|
||||||
|
|
@ -115,32 +104,20 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
long currentAuthVersion = authVersionService.getVersion(userId, activeTenantId);
|
|
||||||
long requestAuthVersion = tokenAuthVersionNum == null ? 0L : tokenAuthVersionNum.longValue();
|
|
||||||
if (currentAuthVersion != requestAuthVersion) {
|
|
||||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
|
||||||
response.setContentType("application/json;charset=UTF-8");
|
|
||||||
response.getWriter().write("{\"code\":\"401\",\"msg\":\"Token revoked\"}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Get Permissions (With Redis Cache, Key must include tenantId)
|
// 3. Get Permissions (With Redis Cache, Key must include tenantId)
|
||||||
String permKey = RedisKeys.authPermKey(userId, activeTenantId, currentAuthVersion);
|
String permKey = "sys:auth:perm:" + userId + ":" + activeTenantId;
|
||||||
Set<String> permissions;
|
Set<String> permissions;
|
||||||
String cachedPerms = redisTemplate.opsForValue().get(permKey);
|
String cachedPerms = redisTemplate.opsForValue().get(permKey);
|
||||||
if (cachedPerms != null && !cachedPerms.trim().isEmpty()) {
|
if (cachedPerms != null) {
|
||||||
permissions = Set.of(cachedPerms.split(","));
|
permissions = Set.of(cachedPerms.split(","));
|
||||||
} else {
|
} else {
|
||||||
permissions = sysPermissionService.listPermissionCodesByUserId(userId, activeTenantId);
|
permissions = sysPermissionService.listPermissionCodesByUserId(userId, activeTenantId);
|
||||||
if (permissions != null && !permissions.isEmpty()) {
|
if (permissions != null && !permissions.isEmpty()) {
|
||||||
redisTemplate.opsForValue().set(permKey, String.join(",", permissions), java.time.Duration.ofHours(2));
|
redisTemplate.opsForValue().set(permKey, String.join(",", permissions), java.time.Duration.ofHours(2));
|
||||||
} else {
|
|
||||||
permissions = Collections.emptySet();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isTenantAdmin = authScopeService.isTenantAdmin(userId, activeTenantId);
|
LoginUser loginUser = new LoginUser(userId, activeTenantId, username, displayName, user.getIsPlatformAdmin(), permissions);
|
||||||
LoginUser loginUser = new LoginUser(userId, activeTenantId, username, displayName,user.getIsPlatformAdmin(), isTenantAdmin, permissions);
|
|
||||||
|
|
||||||
UsernamePasswordAuthenticationToken authentication =
|
UsernamePasswordAuthenticationToken authentication =
|
||||||
new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
|
new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
|
||||||
|
|
|
||||||
|
|
@ -15,14 +15,6 @@ public final class RedisKeys {
|
||||||
return "refresh:" + userId + ":" + deviceCode;
|
return "refresh:" + userId + ":" + deviceCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String authVersionKey(Long userId, Long tenantId) {
|
|
||||||
return "sys:auth:ver:" + userId + ":" + tenantId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String authPermKey(Long userId, Long tenantId, long authVersion) {
|
|
||||||
return "sys:auth:perm:" + userId + ":" + tenantId + ":" + authVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String sysParamKey(String paramKey) {
|
public static String sysParamKey(String paramKey) {
|
||||||
return "sys:param:" + paramKey;
|
return "sys:param:" + paramKey;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,6 @@ public class DictItemController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/type/{typeCode}")
|
@GetMapping("/type/{typeCode}")
|
||||||
// @PreAuthorize("@ss.hasPermi('sys_dict:query')")
|
|
||||||
public ApiResponse<List<SysDictItem>> getByType(@PathVariable String typeCode) {
|
public ApiResponse<List<SysDictItem>> getByType(@PathVariable String typeCode) {
|
||||||
return ApiResponse.ok(sysDictItemService.getItemsByTypeCode(typeCode));
|
return ApiResponse.ok(sysDictItemService.getItemsByTypeCode(typeCode));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,11 @@
|
||||||
package com.imeeting.controller;
|
package com.imeeting.controller;
|
||||||
|
|
||||||
|
import com.imeeting.auth.JwtTokenProvider;
|
||||||
import com.imeeting.common.ApiResponse;
|
import com.imeeting.common.ApiResponse;
|
||||||
import com.imeeting.dto.PermissionNode;
|
import com.imeeting.dto.PermissionNode;
|
||||||
import com.imeeting.entity.SysPermission;
|
import com.imeeting.entity.SysPermission;
|
||||||
import com.imeeting.entity.SysRole;
|
|
||||||
import com.imeeting.mapper.SysRolePermissionMapper;
|
|
||||||
import com.imeeting.mapper.SysUserRoleMapper;
|
|
||||||
import com.imeeting.service.AuthVersionService;
|
|
||||||
import com.imeeting.service.SysPermissionService;
|
import com.imeeting.service.SysPermissionService;
|
||||||
import com.imeeting.service.SysRoleService;
|
import io.jsonwebtoken.Claims;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
|
@ -22,19 +19,11 @@ import java.util.Map;
|
||||||
@RequestMapping("/api/permissions")
|
@RequestMapping("/api/permissions")
|
||||||
public class PermissionController {
|
public class PermissionController {
|
||||||
private final SysPermissionService sysPermissionService;
|
private final SysPermissionService sysPermissionService;
|
||||||
private final SysRolePermissionMapper sysRolePermissionMapper;
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
private final SysUserRoleMapper sysUserRoleMapper;
|
|
||||||
private final SysRoleService sysRoleService;
|
|
||||||
private final AuthVersionService authVersionService;
|
|
||||||
|
|
||||||
public PermissionController(SysPermissionService sysPermissionService,
|
public PermissionController(SysPermissionService sysPermissionService, JwtTokenProvider jwtTokenProvider) {
|
||||||
SysRolePermissionMapper sysRolePermissionMapper, SysUserRoleMapper sysUserRoleMapper,
|
|
||||||
SysRoleService sysRoleService, AuthVersionService authVersionService) {
|
|
||||||
this.sysPermissionService = sysPermissionService;
|
this.sysPermissionService = sysPermissionService;
|
||||||
this.sysRolePermissionMapper = sysRolePermissionMapper;
|
this.jwtTokenProvider = jwtTokenProvider;
|
||||||
this.sysUserRoleMapper = sysUserRoleMapper;
|
|
||||||
this.sysRoleService = sysRoleService;
|
|
||||||
this.authVersionService = authVersionService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
|
|
@ -91,7 +80,6 @@ public class PermissionController {
|
||||||
@PutMapping("/{id}")
|
@PutMapping("/{id}")
|
||||||
@PreAuthorize("@ss.hasPermi('sys:permission:update')")
|
@PreAuthorize("@ss.hasPermi('sys:permission:update')")
|
||||||
public ApiResponse<Boolean> update(@PathVariable Long id, @RequestBody SysPermission perm) {
|
public ApiResponse<Boolean> update(@PathVariable Long id, @RequestBody SysPermission perm) {
|
||||||
List<Long> roleIds = sysRolePermissionMapper.selectRoleIdsByPermId(id);
|
|
||||||
perm.setPermId(id);
|
perm.setPermId(id);
|
||||||
String error = validateParent(perm);
|
String error = validateParent(perm);
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
|
|
@ -104,21 +92,13 @@ public class PermissionController {
|
||||||
.eq(SysPermission::getPermId, id)
|
.eq(SysPermission::getPermId, id)
|
||||||
.update();
|
.update();
|
||||||
}
|
}
|
||||||
if (updated) {
|
|
||||||
invalidateRoleUsers(roleIds);
|
|
||||||
}
|
|
||||||
return ApiResponse.ok(updated);
|
return ApiResponse.ok(updated);
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
@PreAuthorize("@ss.hasPermi('sys:permission:delete')")
|
@PreAuthorize("@ss.hasPermi('sys:permission:delete')")
|
||||||
public ApiResponse<Boolean> delete(@PathVariable Long id) {
|
public ApiResponse<Boolean> delete(@PathVariable Long id) {
|
||||||
List<Long> roleIds = sysRolePermissionMapper.selectRoleIdsByPermId(id);
|
return ApiResponse.ok(sysPermissionService.removeById(id));
|
||||||
boolean removed = sysPermissionService.removeById(id);
|
|
||||||
if (removed) {
|
|
||||||
invalidateRoleUsers(roleIds);
|
|
||||||
}
|
|
||||||
return ApiResponse.ok(removed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Long getCurrentUserId() {
|
private Long getCurrentUserId() {
|
||||||
|
|
@ -211,21 +191,4 @@ public class PermissionController {
|
||||||
node.setMeta(p.getMeta());
|
node.setMeta(p.getMeta());
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void invalidateRoleUsers(List<Long> roleIds) {
|
|
||||||
if (roleIds == null || roleIds.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (Long roleId : roleIds) {
|
|
||||||
if (roleId == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
SysRole role = sysRoleService.getById(roleId);
|
|
||||||
if (role == null || role.getTenantId() == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
List<Long> userIds = sysUserRoleMapper.selectUserIdsByRoleId(roleId);
|
|
||||||
authVersionService.invalidateUsersTenantAuth(userIds, role.getTenantId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,10 @@ import com.imeeting.entity.SysUser;
|
||||||
import com.imeeting.entity.SysUserRole;
|
import com.imeeting.entity.SysUserRole;
|
||||||
import com.imeeting.mapper.SysRolePermissionMapper;
|
import com.imeeting.mapper.SysRolePermissionMapper;
|
||||||
import com.imeeting.mapper.SysUserRoleMapper;
|
import com.imeeting.mapper.SysUserRoleMapper;
|
||||||
import com.imeeting.service.AuthScopeService;
|
|
||||||
import com.imeeting.service.AuthVersionService;
|
|
||||||
import com.imeeting.service.SysRoleService;
|
import com.imeeting.service.SysRoleService;
|
||||||
import com.imeeting.service.SysUserService;
|
import com.imeeting.service.SysUserService;
|
||||||
import com.imeeting.service.SysPermissionService;
|
import com.imeeting.service.SysPermissionService;
|
||||||
import com.imeeting.service.SysTenantUserService;
|
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -32,24 +28,15 @@ public class RoleController {
|
||||||
private final SysRolePermissionMapper sysRolePermissionMapper;
|
private final SysRolePermissionMapper sysRolePermissionMapper;
|
||||||
private final SysUserRoleMapper sysUserRoleMapper;
|
private final SysUserRoleMapper sysUserRoleMapper;
|
||||||
private final SysPermissionService sysPermissionService;
|
private final SysPermissionService sysPermissionService;
|
||||||
private final AuthScopeService authScopeService;
|
|
||||||
private final AuthVersionService authVersionService;
|
|
||||||
private final SysTenantUserService sysTenantUserService;
|
|
||||||
|
|
||||||
public RoleController(SysRoleService sysRoleService, SysUserService sysUserService,
|
public RoleController(SysRoleService sysRoleService, SysUserService sysUserService,
|
||||||
SysRolePermissionMapper sysRolePermissionMapper, SysUserRoleMapper sysUserRoleMapper,
|
SysRolePermissionMapper sysRolePermissionMapper, SysUserRoleMapper sysUserRoleMapper,
|
||||||
SysPermissionService sysPermissionService,
|
SysPermissionService sysPermissionService) {
|
||||||
AuthScopeService authScopeService,
|
|
||||||
AuthVersionService authVersionService,
|
|
||||||
SysTenantUserService sysTenantUserService) {
|
|
||||||
this.sysRoleService = sysRoleService;
|
this.sysRoleService = sysRoleService;
|
||||||
this.sysUserService = sysUserService;
|
this.sysUserService = sysUserService;
|
||||||
this.sysRolePermissionMapper = sysRolePermissionMapper;
|
this.sysRolePermissionMapper = sysRolePermissionMapper;
|
||||||
this.sysUserRoleMapper = sysUserRoleMapper;
|
this.sysUserRoleMapper = sysUserRoleMapper;
|
||||||
this.sysPermissionService = sysPermissionService;
|
this.sysPermissionService = sysPermissionService;
|
||||||
this.authScopeService = authScopeService;
|
|
||||||
this.authVersionService = authVersionService;
|
|
||||||
this.sysTenantUserService = sysTenantUserService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
|
|
@ -61,42 +48,19 @@ public class RoleController {
|
||||||
@GetMapping("/{id}/users")
|
@GetMapping("/{id}/users")
|
||||||
@PreAuthorize("@ss.hasPermi('sys:role:query')")
|
@PreAuthorize("@ss.hasPermi('sys:role:query')")
|
||||||
public ApiResponse<List<SysUser>> listUsers(@PathVariable Long id) {
|
public ApiResponse<List<SysUser>> listUsers(@PathVariable Long id) {
|
||||||
SysRole role = sysRoleService.getById(id);
|
|
||||||
if (role == null) {
|
|
||||||
return ApiResponse.error("角色不存在");
|
|
||||||
}
|
|
||||||
if (!canAccessTenant(role.getTenantId())) {
|
|
||||||
return ApiResponse.error("禁止跨租户查看角色用户");
|
|
||||||
}
|
|
||||||
return ApiResponse.ok(sysUserService.listUsersByRoleId(id));
|
return ApiResponse.ok(sysUserService.listUsersByRoleId(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
@PreAuthorize("@ss.hasPermi('sys:role:query')")
|
@PreAuthorize("@ss.hasPermi('sys:role:query')")
|
||||||
public ApiResponse<SysRole> get(@PathVariable Long id) {
|
public ApiResponse<SysRole> get(@PathVariable Long id) {
|
||||||
SysRole role = sysRoleService.getById(id);
|
return ApiResponse.ok(sysRoleService.getById(id));
|
||||||
if (role == null) {
|
|
||||||
return ApiResponse.error("角色不存在");
|
|
||||||
}
|
|
||||||
if (!canAccessTenant(role.getTenantId())) {
|
|
||||||
return ApiResponse.error("禁止跨租户查看角色");
|
|
||||||
}
|
|
||||||
return ApiResponse.ok(role);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@PreAuthorize("@ss.hasPermi('sys:role:create')")
|
@PreAuthorize("@ss.hasPermi('sys:role:create')")
|
||||||
@Log(value = "新增角色", type = "角色管理")
|
@Log(value = "新增角色", type = "角色管理")
|
||||||
public ApiResponse<Boolean> create(@RequestBody SysRole role) {
|
public ApiResponse<Boolean> create(@RequestBody SysRole role) {
|
||||||
Long currentTenantId = getCurrentTenantId();
|
|
||||||
if (currentTenantId == null) {
|
|
||||||
return ApiResponse.error("Tenant ID required");
|
|
||||||
}
|
|
||||||
if (!authScopeService.isCurrentPlatformAdmin()) {
|
|
||||||
role.setTenantId(currentTenantId);
|
|
||||||
} else if (role.getTenantId() == null) {
|
|
||||||
return ApiResponse.error("tenantId required for platform role creation");
|
|
||||||
}
|
|
||||||
return ApiResponse.ok(sysRoleService.save(role));
|
return ApiResponse.ok(sysRoleService.save(role));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,15 +68,7 @@ public class RoleController {
|
||||||
@PreAuthorize("@ss.hasPermi('sys:role:update')")
|
@PreAuthorize("@ss.hasPermi('sys:role:update')")
|
||||||
@Log(value = "修改角色", type = "角色管理")
|
@Log(value = "修改角色", type = "角色管理")
|
||||||
public ApiResponse<Boolean> update(@PathVariable Long id, @RequestBody SysRole role) {
|
public ApiResponse<Boolean> update(@PathVariable Long id, @RequestBody SysRole role) {
|
||||||
SysRole existing = sysRoleService.getById(id);
|
|
||||||
if (existing == null) {
|
|
||||||
return ApiResponse.error("角色不存在");
|
|
||||||
}
|
|
||||||
if (!canAccessTenant(existing.getTenantId())) {
|
|
||||||
return ApiResponse.error("禁止跨租户修改角色");
|
|
||||||
}
|
|
||||||
role.setRoleId(id);
|
role.setRoleId(id);
|
||||||
role.setTenantId(existing.getTenantId());
|
|
||||||
return ApiResponse.ok(sysRoleService.updateById(role));
|
return ApiResponse.ok(sysRoleService.updateById(role));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,34 +76,12 @@ public class RoleController {
|
||||||
@PreAuthorize("@ss.hasPermi('sys:role:delete')")
|
@PreAuthorize("@ss.hasPermi('sys:role:delete')")
|
||||||
@Log(value = "删除角色", type = "角色管理")
|
@Log(value = "删除角色", type = "角色管理")
|
||||||
public ApiResponse<Boolean> delete(@PathVariable Long id) {
|
public ApiResponse<Boolean> delete(@PathVariable Long id) {
|
||||||
SysRole existing = sysRoleService.getById(id);
|
return ApiResponse.ok(sysRoleService.removeById(id));
|
||||||
if (existing == null) {
|
|
||||||
return ApiResponse.error("角色不存在");
|
|
||||||
}
|
|
||||||
if (!canAccessTenant(existing.getTenantId())) {
|
|
||||||
return ApiResponse.error("禁止跨租户删除角色");
|
|
||||||
}
|
|
||||||
if ("TENANT_ADMIN".equalsIgnoreCase(existing.getRoleCode()) && !authScopeService.isCurrentPlatformAdmin()) {
|
|
||||||
return ApiResponse.error("租户管理员角色只能由平台管理员删除");
|
|
||||||
}
|
|
||||||
List<Long> userIds = sysUserRoleMapper.selectUserIdsByRoleId(id);
|
|
||||||
boolean removed = sysRoleService.removeById(id);
|
|
||||||
if (removed) {
|
|
||||||
authVersionService.invalidateUsersTenantAuth(userIds, existing.getTenantId());
|
|
||||||
}
|
|
||||||
return ApiResponse.ok(removed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}/permissions")
|
@GetMapping("/{id}/permissions")
|
||||||
@PreAuthorize("@ss.hasPermi('sys:role:permission:list')")
|
@PreAuthorize("@ss.hasPermi('sys:role:permission:list')")
|
||||||
public ApiResponse<List<Long>> listRolePermissions(@PathVariable Long id) {
|
public ApiResponse<List<Long>> listRolePermissions(@PathVariable Long id) {
|
||||||
SysRole targetRole = sysRoleService.getById(id);
|
|
||||||
if (targetRole == null) {
|
|
||||||
return ApiResponse.error("角色不存在");
|
|
||||||
}
|
|
||||||
if (!canAccessTenant(targetRole.getTenantId())) {
|
|
||||||
return ApiResponse.error("禁止跨租户查看角色权限");
|
|
||||||
}
|
|
||||||
List<SysRolePermission> rows = sysRolePermissionMapper.selectList(
|
List<SysRolePermission> rows = sysRolePermissionMapper.selectList(
|
||||||
new QueryWrapper<SysRolePermission>().eq("role_id", id)
|
new QueryWrapper<SysRolePermission>().eq("role_id", id)
|
||||||
);
|
);
|
||||||
|
|
@ -162,15 +96,11 @@ public class RoleController {
|
||||||
|
|
||||||
@PostMapping("/{id}/permissions")
|
@PostMapping("/{id}/permissions")
|
||||||
@PreAuthorize("@ss.hasPermi('sys:role:permission:save')")
|
@PreAuthorize("@ss.hasPermi('sys:role:permission:save')")
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
public ApiResponse<Boolean> saveRolePermissions(@PathVariable Long id, @RequestBody PermissionBindingPayload payload) {
|
public ApiResponse<Boolean> saveRolePermissions(@PathVariable Long id, @RequestBody PermissionBindingPayload payload) {
|
||||||
List<Long> permIds = payload == null ? null : payload.getPermIds();
|
List<Long> permIds = payload == null ? null : payload.getPermIds();
|
||||||
|
|
||||||
// 权限越权校验
|
// 权限越权校验
|
||||||
Long currentTenantId = getCurrentTenantId();
|
Long currentTenantId = getCurrentTenantId();
|
||||||
if (currentTenantId == null) {
|
|
||||||
return ApiResponse.error("Tenant ID required");
|
|
||||||
}
|
|
||||||
SysRole targetRole = sysRoleService.getById(id);
|
SysRole targetRole = sysRoleService.getById(id);
|
||||||
if (targetRole == null) {
|
if (targetRole == null) {
|
||||||
return ApiResponse.error("角色不存在");
|
return ApiResponse.error("角色不存在");
|
||||||
|
|
@ -178,15 +108,12 @@ public class RoleController {
|
||||||
|
|
||||||
// 关键校验:只有平台管理员可以修改 TENANT_ADMIN 角色的权限
|
// 关键校验:只有平台管理员可以修改 TENANT_ADMIN 角色的权限
|
||||||
if ("TENANT_ADMIN".equalsIgnoreCase(targetRole.getRoleCode())) {
|
if ("TENANT_ADMIN".equalsIgnoreCase(targetRole.getRoleCode())) {
|
||||||
if (!authScopeService.isCurrentPlatformAdmin()) {
|
if (!Long.valueOf(0).equals(currentTenantId)) {
|
||||||
return ApiResponse.error("租户管理员角色的权限只能由平台管理员修改");
|
return ApiResponse.error("租户管理员角色的权限只能由平台管理员修改");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!canAccessTenant(targetRole.getTenantId())) {
|
|
||||||
return ApiResponse.error("禁止跨租户修改角色权限");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!authScopeService.isCurrentPlatformAdmin()) {
|
if (!Long.valueOf(0).equals(currentTenantId)) {
|
||||||
List<com.imeeting.entity.SysPermission> myPerms = sysPermissionService.listByUserId(getCurrentUserId(), currentTenantId);
|
List<com.imeeting.entity.SysPermission> myPerms = sysPermissionService.listByUserId(getCurrentUserId(), currentTenantId);
|
||||||
|
|
||||||
Set<Long> myPermIds = myPerms.stream()
|
Set<Long> myPermIds = myPerms.stream()
|
||||||
|
|
@ -204,7 +131,6 @@ public class RoleController {
|
||||||
|
|
||||||
sysRolePermissionMapper.delete(new QueryWrapper<SysRolePermission>().eq("role_id", id));
|
sysRolePermissionMapper.delete(new QueryWrapper<SysRolePermission>().eq("role_id", id));
|
||||||
if (permIds == null || permIds.isEmpty()) {
|
if (permIds == null || permIds.isEmpty()) {
|
||||||
authVersionService.invalidateUsersTenantAuth(sysUserRoleMapper.selectUserIdsByRoleId(id), targetRole.getTenantId());
|
|
||||||
return ApiResponse.ok(true);
|
return ApiResponse.ok(true);
|
||||||
}
|
}
|
||||||
for (Long permId : permIds) {
|
for (Long permId : permIds) {
|
||||||
|
|
@ -216,73 +142,36 @@ public class RoleController {
|
||||||
item.setPermId(permId);
|
item.setPermId(permId);
|
||||||
sysRolePermissionMapper.insert(item);
|
sysRolePermissionMapper.insert(item);
|
||||||
}
|
}
|
||||||
authVersionService.invalidateUsersTenantAuth(sysUserRoleMapper.selectUserIdsByRoleId(id), targetRole.getTenantId());
|
|
||||||
return ApiResponse.ok(true);
|
return ApiResponse.ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/{id}/users")
|
@PostMapping("/{id}/users")
|
||||||
@PreAuthorize("@ss.hasPermi('sys:role:update')")
|
@PreAuthorize("@ss.hasPermi('sys:role:update')")
|
||||||
@Log(value = "角色关联用户", type = "角色管理")
|
@Log(value = "角色关联用户", type = "角色管理")
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
public ApiResponse<Boolean> bindUsers(@PathVariable Long id, @RequestBody UserBindingPayload payload) {
|
public ApiResponse<Boolean> bindUsers(@PathVariable Long id, @RequestBody UserBindingPayload payload) {
|
||||||
if (payload == null || payload.getUserIds() == null) {
|
if (payload == null || payload.getUserIds() == null) {
|
||||||
return ApiResponse.ok(true);
|
return ApiResponse.ok(true);
|
||||||
}
|
}
|
||||||
SysRole role = sysRoleService.getById(id);
|
|
||||||
if (role == null || role.getRoleId() == null || role.getTenantId() == null) {
|
|
||||||
return ApiResponse.error("角色不存在");
|
|
||||||
}
|
|
||||||
if (!canAccessTenant(role.getTenantId())) {
|
|
||||||
return ApiResponse.error("禁止跨租户绑定用户");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Long> toInsertUserIds = new ArrayList<>();
|
|
||||||
for (Long userId : payload.getUserIds()) {
|
for (Long userId : payload.getUserIds()) {
|
||||||
if (userId == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
QueryWrapper<SysUserRole> qw = new QueryWrapper<>();
|
QueryWrapper<SysUserRole> qw = new QueryWrapper<>();
|
||||||
qw.eq("role_id", id).eq("user_id", userId).eq("tenant_id", role.getTenantId());
|
qw.eq("role_id", id).eq("user_id", userId);
|
||||||
if (sysUserRoleMapper.selectCount(qw) == 0) {
|
if (sysUserRoleMapper.selectCount(qw) == 0) {
|
||||||
boolean hasMembership = sysTenantUserService.count(
|
SysUserRole ur = new SysUserRole();
|
||||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.imeeting.entity.SysTenantUser>()
|
ur.setRoleId(id);
|
||||||
.eq(com.imeeting.entity.SysTenantUser::getUserId, userId)
|
ur.setUserId(userId);
|
||||||
.eq(com.imeeting.entity.SysTenantUser::getTenantId, role.getTenantId())
|
sysUserRoleMapper.insert(ur);
|
||||||
) > 0;
|
|
||||||
if (!hasMembership) {
|
|
||||||
return ApiResponse.error("用户不属于角色所在租户:" + role.getTenantId());
|
|
||||||
}
|
|
||||||
toInsertUserIds.add(userId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Long userId : toInsertUserIds) {
|
|
||||||
SysUserRole ur = new SysUserRole();
|
|
||||||
ur.setTenantId(role.getTenantId());
|
|
||||||
ur.setRoleId(id);
|
|
||||||
ur.setUserId(userId);
|
|
||||||
sysUserRoleMapper.insert(ur);
|
|
||||||
authVersionService.invalidateUserTenantAuth(userId, role.getTenantId());
|
|
||||||
}
|
|
||||||
return ApiResponse.ok(true);
|
return ApiResponse.ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/{id}/users/{userId}")
|
@DeleteMapping("/{id}/users/{userId}")
|
||||||
@PreAuthorize("@ss.hasPermi('sys:role:update')")
|
@PreAuthorize("@ss.hasPermi('sys:role:update')")
|
||||||
@Log(value = "角色取消关联用户", type = "角色管理")
|
@Log(value = "角色取消关联用户", type = "角色管理")
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
public ApiResponse<Boolean> unbindUser(@PathVariable Long id, @PathVariable Long userId) {
|
public ApiResponse<Boolean> unbindUser(@PathVariable Long id, @PathVariable Long userId) {
|
||||||
SysRole role = sysRoleService.getById(id);
|
|
||||||
if (role == null || role.getRoleId() == null || role.getTenantId() == null) {
|
|
||||||
return ApiResponse.error("角色不存在");
|
|
||||||
}
|
|
||||||
if (!canAccessTenant(role.getTenantId())) {
|
|
||||||
return ApiResponse.error("禁止跨租户解绑用户");
|
|
||||||
}
|
|
||||||
QueryWrapper<SysUserRole> qw = new QueryWrapper<>();
|
QueryWrapper<SysUserRole> qw = new QueryWrapper<>();
|
||||||
qw.eq("role_id", id).eq("user_id", userId).eq("tenant_id", role.getTenantId());
|
qw.eq("role_id", id).eq("user_id", userId);
|
||||||
sysUserRoleMapper.delete(qw);
|
sysUserRoleMapper.delete(qw);
|
||||||
authVersionService.invalidateUserTenantAuth(userId, role.getTenantId());
|
|
||||||
return ApiResponse.ok(true);
|
return ApiResponse.ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -302,17 +191,6 @@ public class RoleController {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean canAccessTenant(Long targetTenantId) {
|
|
||||||
if (targetTenantId == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (authScopeService.isCurrentPlatformAdmin()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
Long currentTenantId = getCurrentTenantId();
|
|
||||||
return currentTenantId != null && currentTenantId.equals(targetTenantId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class UserBindingPayload {
|
public static class UserBindingPayload {
|
||||||
private List<Long> userIds;
|
private List<Long> userIds;
|
||||||
public List<Long> getUserIds() { return userIds; }
|
public List<Long> getUserIds() { return userIds; }
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.imeeting.controller;
|
package com.imeeting.controller;
|
||||||
|
|
||||||
|
import com.imeeting.auth.JwtTokenProvider;
|
||||||
import com.imeeting.common.ApiResponse;
|
import com.imeeting.common.ApiResponse;
|
||||||
import com.imeeting.dto.PasswordUpdateDTO;
|
import com.imeeting.dto.PasswordUpdateDTO;
|
||||||
import com.imeeting.dto.UserProfile;
|
import com.imeeting.dto.UserProfile;
|
||||||
|
|
@ -9,15 +10,13 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.imeeting.entity.SysUser;
|
import com.imeeting.entity.SysUser;
|
||||||
import com.imeeting.entity.SysUserRole;
|
import com.imeeting.entity.SysUserRole;
|
||||||
import com.imeeting.mapper.SysUserRoleMapper;
|
import com.imeeting.mapper.SysUserRoleMapper;
|
||||||
import com.imeeting.service.AuthScopeService;
|
|
||||||
import com.imeeting.service.AuthVersionService;
|
|
||||||
import com.imeeting.service.SysUserService;
|
import com.imeeting.service.SysUserService;
|
||||||
import com.imeeting.common.annotation.Log;
|
import com.imeeting.common.annotation.Log;
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -28,25 +27,21 @@ import java.util.List;
|
||||||
public class UserController {
|
public class UserController {
|
||||||
private final SysUserService sysUserService;
|
private final SysUserService sysUserService;
|
||||||
private final PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
private final SysUserRoleMapper sysUserRoleMapper;
|
private final SysUserRoleMapper sysUserRoleMapper;
|
||||||
private final com.imeeting.service.SysTenantUserService sysTenantUserService;
|
private final com.imeeting.service.SysTenantUserService sysTenantUserService;
|
||||||
private final com.imeeting.service.SysRoleService sysRoleService;
|
private final com.imeeting.service.SysRoleService sysRoleService;
|
||||||
private final AuthScopeService authScopeService;
|
|
||||||
private final AuthVersionService authVersionService;
|
|
||||||
|
|
||||||
public UserController(SysUserService sysUserService, PasswordEncoder passwordEncoder,
|
public UserController(SysUserService sysUserService, PasswordEncoder passwordEncoder,
|
||||||
SysUserRoleMapper sysUserRoleMapper,
|
JwtTokenProvider jwtTokenProvider, SysUserRoleMapper sysUserRoleMapper,
|
||||||
com.imeeting.service.SysTenantUserService sysTenantUserService,
|
com.imeeting.service.SysTenantUserService sysTenantUserService,
|
||||||
com.imeeting.service.SysRoleService sysRoleService,
|
com.imeeting.service.SysRoleService sysRoleService) {
|
||||||
AuthScopeService authScopeService,
|
|
||||||
AuthVersionService authVersionService) {
|
|
||||||
this.sysUserService = sysUserService;
|
this.sysUserService = sysUserService;
|
||||||
this.passwordEncoder = passwordEncoder;
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
this.jwtTokenProvider = jwtTokenProvider;
|
||||||
this.sysUserRoleMapper = sysUserRoleMapper;
|
this.sysUserRoleMapper = sysUserRoleMapper;
|
||||||
this.sysTenantUserService = sysTenantUserService;
|
this.sysTenantUserService = sysTenantUserService;
|
||||||
this.sysRoleService = sysRoleService;
|
this.sysRoleService = sysRoleService;
|
||||||
this.authScopeService = authScopeService;
|
|
||||||
this.authVersionService = authVersionService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
|
|
@ -54,12 +49,11 @@ public class UserController {
|
||||||
public ApiResponse<List<SysUser>> list(@RequestParam(required = false) Long tenantId, @RequestParam(required = false) Long orgId) {
|
public ApiResponse<List<SysUser>> list(@RequestParam(required = false) Long tenantId, @RequestParam(required = false) Long orgId) {
|
||||||
Long currentTenantId = getCurrentTenantId();
|
Long currentTenantId = getCurrentTenantId();
|
||||||
List<SysUser> users;
|
List<SysUser> users;
|
||||||
Long targetTenantId = null;
|
|
||||||
|
|
||||||
if (Long.valueOf(0).equals(currentTenantId) && tenantId == null) {
|
if (Long.valueOf(0).equals(currentTenantId) && tenantId == null) {
|
||||||
users = sysUserService.list();
|
users = sysUserService.list();
|
||||||
} else {
|
} else {
|
||||||
targetTenantId = tenantId != null ? tenantId : currentTenantId;
|
Long targetTenantId = tenantId != null ? tenantId : currentTenantId;
|
||||||
if (targetTenantId == null) {
|
if (targetTenantId == null) {
|
||||||
return ApiResponse.error("Tenant ID required");
|
return ApiResponse.error("Tenant ID required");
|
||||||
}
|
}
|
||||||
|
|
@ -72,11 +66,9 @@ public class UserController {
|
||||||
user.setMemberships(sysTenantUserService.listByUserId(user.getUserId()));
|
user.setMemberships(sysTenantUserService.listByUserId(user.getUserId()));
|
||||||
|
|
||||||
// 加载角色信息
|
// 加载角色信息
|
||||||
QueryWrapper<SysUserRole> roleQuery = new QueryWrapper<SysUserRole>().eq("user_id", user.getUserId());
|
List<SysUserRole> userRoles = sysUserRoleMapper.selectList(
|
||||||
if (targetTenantId != null) {
|
new QueryWrapper<SysUserRole>().eq("user_id", user.getUserId())
|
||||||
roleQuery.eq("tenant_id", targetTenantId);
|
);
|
||||||
}
|
|
||||||
List<SysUserRole> userRoles = sysUserRoleMapper.selectList(roleQuery);
|
|
||||||
if (userRoles != null && !userRoles.isEmpty()) {
|
if (userRoles != null && !userRoles.isEmpty()) {
|
||||||
List<Long> roleIds = userRoles.stream()
|
List<Long> roleIds = userRoles.stream()
|
||||||
.map(SysUserRole::getRoleId)
|
.map(SysUserRole::getRoleId)
|
||||||
|
|
@ -110,7 +102,6 @@ public class UserController {
|
||||||
profile.setStatus(user.getStatus());
|
profile.setStatus(user.getStatus());
|
||||||
profile.setAdmin(userId == 1L);
|
profile.setAdmin(userId == 1L);
|
||||||
profile.setIsPlatformAdmin(user.getIsPlatformAdmin());
|
profile.setIsPlatformAdmin(user.getIsPlatformAdmin());
|
||||||
profile.setIsTenantAdmin(loginUser.getIsTenantAdmin());
|
|
||||||
profile.setPwdResetRequired(user.getPwdResetRequired());
|
profile.setPwdResetRequired(user.getPwdResetRequired());
|
||||||
return ApiResponse.ok(profile);
|
return ApiResponse.ok(profile);
|
||||||
}
|
}
|
||||||
|
|
@ -118,13 +109,6 @@ public class UserController {
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
@PreAuthorize("@ss.hasPermi('sys:user:query')")
|
@PreAuthorize("@ss.hasPermi('sys:user:query')")
|
||||||
public ApiResponse<SysUser> get(@PathVariable Long id) {
|
public ApiResponse<SysUser> get(@PathVariable Long id) {
|
||||||
Long currentTenantId = getCurrentTenantId();
|
|
||||||
if (currentTenantId == null) {
|
|
||||||
return ApiResponse.error("Tenant ID required");
|
|
||||||
}
|
|
||||||
if (!authScopeService.isCurrentPlatformAdmin() && !isUserInTenant(id, currentTenantId)) {
|
|
||||||
return ApiResponse.error("禁止跨租户查看用户");
|
|
||||||
}
|
|
||||||
SysUser user = sysUserService.getByIdIgnoreTenant(id);
|
SysUser user = sysUserService.getByIdIgnoreTenant(id);
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
user.setMemberships(sysTenantUserService.listByUserId(id));
|
user.setMemberships(sysTenantUserService.listByUserId(id));
|
||||||
|
|
@ -145,9 +129,6 @@ public class UserController {
|
||||||
@Log(value = "新增用户", type = "用户管理")
|
@Log(value = "新增用户", type = "用户管理")
|
||||||
public ApiResponse<Boolean> create(@RequestBody SysUser user) {
|
public ApiResponse<Boolean> create(@RequestBody SysUser user) {
|
||||||
Long currentTenantId = getCurrentTenantId();
|
Long currentTenantId = getCurrentTenantId();
|
||||||
if (currentTenantId == null) {
|
|
||||||
return ApiResponse.error("Tenant ID required");
|
|
||||||
}
|
|
||||||
// 非平台管理员强制设置为当前租户
|
// 非平台管理员强制设置为当前租户
|
||||||
if (!Long.valueOf(0).equals(currentTenantId)) {
|
if (!Long.valueOf(0).equals(currentTenantId)) {
|
||||||
if (user.getMemberships() != null && !user.getMemberships().isEmpty()) {
|
if (user.getMemberships() != null && !user.getMemberships().isEmpty()) {
|
||||||
|
|
@ -177,13 +158,7 @@ public class UserController {
|
||||||
@Log(value = "修改用户", type = "用户管理")
|
@Log(value = "修改用户", type = "用户管理")
|
||||||
public ApiResponse<Boolean> update(@PathVariable Long id, @RequestBody SysUser user) {
|
public ApiResponse<Boolean> update(@PathVariable Long id, @RequestBody SysUser user) {
|
||||||
Long currentTenantId = getCurrentTenantId();
|
Long currentTenantId = getCurrentTenantId();
|
||||||
if (currentTenantId == null) {
|
|
||||||
return ApiResponse.error("Tenant ID required");
|
|
||||||
}
|
|
||||||
user.setUserId(id);
|
user.setUserId(id);
|
||||||
if (!authScopeService.isCurrentPlatformAdmin() && !isUserInTenant(id, currentTenantId)) {
|
|
||||||
return ApiResponse.error("禁止跨租户修改用户");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 非平台管理员强制约束租户身份
|
// 非平台管理员强制约束租户身份
|
||||||
if (!Long.valueOf(0).equals(currentTenantId)) {
|
if (!Long.valueOf(0).equals(currentTenantId)) {
|
||||||
|
|
@ -206,13 +181,6 @@ public class UserController {
|
||||||
@PreAuthorize("@ss.hasPermi('sys:user:delete')")
|
@PreAuthorize("@ss.hasPermi('sys:user:delete')")
|
||||||
@Log(value = "删除用户", type = "用户管理")
|
@Log(value = "删除用户", type = "用户管理")
|
||||||
public ApiResponse<Boolean> delete(@PathVariable Long id) {
|
public ApiResponse<Boolean> delete(@PathVariable Long id) {
|
||||||
Long currentTenantId = getCurrentTenantId();
|
|
||||||
if (currentTenantId == null) {
|
|
||||||
return ApiResponse.error("Tenant ID required");
|
|
||||||
}
|
|
||||||
if (!authScopeService.isCurrentPlatformAdmin() && !isUserInTenant(id, currentTenantId)) {
|
|
||||||
return ApiResponse.error("禁止跨租户删除用户");
|
|
||||||
}
|
|
||||||
return ApiResponse.ok(sysUserService.removeById(id));
|
return ApiResponse.ok(sysUserService.removeById(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -254,18 +222,9 @@ public class UserController {
|
||||||
@GetMapping("/{id}/roles")
|
@GetMapping("/{id}/roles")
|
||||||
@PreAuthorize("@ss.hasPermi('sys:user:role:list')")
|
@PreAuthorize("@ss.hasPermi('sys:user:role:list')")
|
||||||
public ApiResponse<List<Long>> listUserRoles(@PathVariable Long id) {
|
public ApiResponse<List<Long>> listUserRoles(@PathVariable Long id) {
|
||||||
Long currentTenantId = getCurrentTenantId();
|
List<SysUserRole> rows = sysUserRoleMapper.selectList(
|
||||||
if (currentTenantId == null) {
|
new QueryWrapper<SysUserRole>().eq("user_id", id)
|
||||||
return ApiResponse.error("Tenant ID required");
|
);
|
||||||
}
|
|
||||||
if (!authScopeService.isCurrentPlatformAdmin() && !isUserInTenant(id, currentTenantId)) {
|
|
||||||
return ApiResponse.error("禁止跨租户查看用户角色");
|
|
||||||
}
|
|
||||||
QueryWrapper<SysUserRole> query = new QueryWrapper<SysUserRole>().eq("user_id", id);
|
|
||||||
if (!authScopeService.isCurrentPlatformAdmin()) {
|
|
||||||
query.eq("tenant_id", currentTenantId);
|
|
||||||
}
|
|
||||||
List<SysUserRole> rows = sysUserRoleMapper.selectList(query);
|
|
||||||
List<Long> roleIds = new ArrayList<>();
|
List<Long> roleIds = new ArrayList<>();
|
||||||
for (SysUserRole row : rows) {
|
for (SysUserRole row : rows) {
|
||||||
if (row.getRoleId() != null) {
|
if (row.getRoleId() != null) {
|
||||||
|
|
@ -277,84 +236,31 @@ public class UserController {
|
||||||
|
|
||||||
@PostMapping("/{id}/roles")
|
@PostMapping("/{id}/roles")
|
||||||
@PreAuthorize("@ss.hasPermi('sys:user:role:save')")
|
@PreAuthorize("@ss.hasPermi('sys:user:role:save')")
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
public ApiResponse<Boolean> saveUserRoles(@PathVariable Long id, @RequestBody RoleBindingPayload payload) {
|
public ApiResponse<Boolean> saveUserRoles(@PathVariable Long id, @RequestBody RoleBindingPayload payload) {
|
||||||
Long currentTenantId = getCurrentTenantId();
|
|
||||||
if (currentTenantId == null) {
|
|
||||||
return ApiResponse.error("Tenant ID required");
|
|
||||||
}
|
|
||||||
if (!authScopeService.isCurrentPlatformAdmin() && !isUserInTenant(id, currentTenantId)) {
|
|
||||||
return ApiResponse.error("禁止跨租户分配角色");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Long> roleIds = payload == null ? null : payload.getRoleIds();
|
List<Long> roleIds = payload == null ? null : payload.getRoleIds();
|
||||||
|
sysUserRoleMapper.delete(new QueryWrapper<SysUserRole>().eq("user_id", id));
|
||||||
List<com.imeeting.entity.SysRole> rolesToBind = new ArrayList<>();
|
if (roleIds == null || roleIds.isEmpty()) {
|
||||||
if (roleIds != null) {
|
return ApiResponse.ok(true);
|
||||||
for (Long roleId : roleIds) {
|
}
|
||||||
if (roleId == null) {
|
for (Long roleId : roleIds) {
|
||||||
continue;
|
if (roleId == null) {
|
||||||
}
|
continue;
|
||||||
com.imeeting.entity.SysRole role = sysRoleService.getById(roleId);
|
|
||||||
if (role == null || role.getRoleId() == null || role.getTenantId() == null) {
|
|
||||||
return ApiResponse.error("角色不存在:" + roleId);
|
|
||||||
}
|
|
||||||
Long roleTenantId = role.getTenantId();
|
|
||||||
if (!authScopeService.isCurrentPlatformAdmin() && !currentTenantId.equals(roleTenantId)) {
|
|
||||||
return ApiResponse.error("禁止跨租户分配角色:" + roleId);
|
|
||||||
}
|
|
||||||
boolean hasMembership = sysTenantUserService.count(
|
|
||||||
new LambdaQueryWrapper<com.imeeting.entity.SysTenantUser>()
|
|
||||||
.eq(com.imeeting.entity.SysTenantUser::getUserId, id)
|
|
||||||
.eq(com.imeeting.entity.SysTenantUser::getTenantId, roleTenantId)
|
|
||||||
) > 0;
|
|
||||||
if (!hasMembership) {
|
|
||||||
return ApiResponse.error("用户不属于角色所在租户:" + roleTenantId);
|
|
||||||
}
|
|
||||||
rolesToBind.add(role);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
QueryWrapper<SysUserRole> scopeQuery = new QueryWrapper<SysUserRole>().eq("user_id", id);
|
|
||||||
if (!authScopeService.isCurrentPlatformAdmin()) {
|
|
||||||
scopeQuery.eq("tenant_id", currentTenantId);
|
|
||||||
}
|
|
||||||
List<SysUserRole> existingRows = sysUserRoleMapper.selectList(scopeQuery);
|
|
||||||
java.util.Set<Long> affectedTenantIds = new java.util.HashSet<>();
|
|
||||||
for (SysUserRole row : existingRows) {
|
|
||||||
if (row.getTenantId() != null) {
|
|
||||||
affectedTenantIds.add(row.getTenantId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (com.imeeting.entity.SysRole role : rolesToBind) {
|
|
||||||
if (role.getTenantId() != null) {
|
|
||||||
affectedTenantIds.add(role.getTenantId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sysUserRoleMapper.delete(scopeQuery);
|
|
||||||
for (com.imeeting.entity.SysRole role : rolesToBind) {
|
|
||||||
SysUserRole item = new SysUserRole();
|
SysUserRole item = new SysUserRole();
|
||||||
item.setTenantId(role.getTenantId());
|
|
||||||
item.setUserId(id);
|
item.setUserId(id);
|
||||||
item.setRoleId(role.getRoleId());
|
item.setRoleId(roleId);
|
||||||
sysUserRoleMapper.insert(item);
|
sysUserRoleMapper.insert(item);
|
||||||
}
|
}
|
||||||
for (Long tenantId : affectedTenantIds) {
|
|
||||||
authVersionService.invalidateUserTenantAuth(id, tenantId);
|
|
||||||
}
|
|
||||||
return ApiResponse.ok(true);
|
return ApiResponse.ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isUserInTenant(Long userId, Long tenantId) {
|
private Long resolveUserId(String authorization) {
|
||||||
if (userId == null || tenantId == null) {
|
if (authorization == null || !authorization.startsWith("Bearer ")) {
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
return sysTenantUserService.count(
|
String token = authorization.substring(7);
|
||||||
new LambdaQueryWrapper<com.imeeting.entity.SysTenantUser>()
|
Claims claims = jwtTokenProvider.parseToken(token);
|
||||||
.eq(com.imeeting.entity.SysTenantUser::getUserId, userId)
|
return claims.get("userId", Long.class);
|
||||||
.eq(com.imeeting.entity.SysTenantUser::getTenantId, tenantId)
|
|
||||||
) > 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class RoleBindingPayload {
|
public static class RoleBindingPayload {
|
||||||
|
|
|
||||||
|
|
@ -26,23 +26,17 @@ public class HotWordController {
|
||||||
this.hotWordService = hotWordService;
|
this.hotWordService = hotWordService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断当前用户是否具备管理员权限 (平台管理员或租户管理员)
|
|
||||||
*/
|
|
||||||
private boolean isCurrentUserAdmin(LoginUser user) {
|
|
||||||
return Boolean.TRUE.equals(user.getIsPlatformAdmin()) || Boolean.TRUE.equals(user.getIsTenantAdmin());
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@PreAuthorize("isAuthenticated()")
|
@PreAuthorize("isAuthenticated()")
|
||||||
public ApiResponse<HotWordVO> save(@RequestBody HotWordDTO hotWordDTO) {
|
public ApiResponse<HotWordVO> save(@RequestBody HotWordDTO hotWordDTO) {
|
||||||
LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||||
|
// 只有管理员可以创建公开热词,普通用户强制为私有
|
||||||
// 核心校验:只有管理员可以创建公开热词
|
if (Integer.valueOf(1).equals(hotWordDTO.getIsPublic()) && !Boolean.TRUE.equals(loginUser.getIsPlatformAdmin())) {
|
||||||
if (Integer.valueOf(1).equals(hotWordDTO.getIsPublic()) && !isCurrentUserAdmin(loginUser)) {
|
// 这里根据需求:租户内管理员可编辑。由于目前权限模型暂未细化到租户管理员字段,
|
||||||
return ApiResponse.error("无权创建租户公开热词,请设为个人私有");
|
// 我们暂定具有 ADMIN 角色或 PlatformAdmin 的为管理员。
|
||||||
|
// 简单处理:普通用户强制设为 0。
|
||||||
|
hotWordDTO.setIsPublic(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ApiResponse.ok(hotWordService.saveHotWord(hotWordDTO, loginUser.getUserId()));
|
return ApiResponse.ok(hotWordService.saveHotWord(hotWordDTO, loginUser.getUserId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,20 +47,13 @@ public class HotWordController {
|
||||||
HotWord existing = hotWordService.getById(hotWordDTO.getId());
|
HotWord existing = hotWordService.getById(hotWordDTO.getId());
|
||||||
if (existing == null) return ApiResponse.error("热词不存在");
|
if (existing == null) return ApiResponse.error("热词不存在");
|
||||||
|
|
||||||
boolean isAdmin = isCurrentUserAdmin(loginUser);
|
// 权限校验:公开热词仅管理员可改,私有热词仅本人可改
|
||||||
|
|
||||||
// 核心校验逻辑:
|
|
||||||
// 1. 如果用户尝试将热词设为公开,必须具备管理员权限
|
|
||||||
if (Integer.valueOf(1).equals(hotWordDTO.getIsPublic()) && !isAdmin) {
|
|
||||||
return ApiResponse.error("无权将热词设为公开");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 如果是公开热词,只有管理员能改
|
|
||||||
if (Integer.valueOf(1).equals(existing.getIsPublic())) {
|
if (Integer.valueOf(1).equals(existing.getIsPublic())) {
|
||||||
if (!isAdmin) return ApiResponse.error("无权修改公开热词");
|
if (!Boolean.TRUE.equals(loginUser.getIsPlatformAdmin())) {
|
||||||
|
return ApiResponse.error("无权修改公开热词");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 3. 如果是私有热词,本人或管理员能改
|
if (!existing.getCreatorId().equals(loginUser.getUserId())) {
|
||||||
if (!existing.getCreatorId().equals(loginUser.getUserId()) && !isAdmin) {
|
|
||||||
return ApiResponse.error("无权修改他人私有热词");
|
return ApiResponse.error("无权修改他人私有热词");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -81,13 +68,12 @@ public class HotWordController {
|
||||||
HotWord existing = hotWordService.getById(id);
|
HotWord existing = hotWordService.getById(id);
|
||||||
if (existing == null) return ApiResponse.ok(true);
|
if (existing == null) return ApiResponse.ok(true);
|
||||||
|
|
||||||
boolean isAdmin = isCurrentUserAdmin(loginUser);
|
|
||||||
|
|
||||||
// 权限校验:公开热词管理员可删,私有热词本人或管理员可删
|
|
||||||
if (Integer.valueOf(1).equals(existing.getIsPublic())) {
|
if (Integer.valueOf(1).equals(existing.getIsPublic())) {
|
||||||
if (!isAdmin) return ApiResponse.error("无权删除公开热词");
|
if (!Boolean.TRUE.equals(loginUser.getIsPlatformAdmin())) {
|
||||||
|
return ApiResponse.error("无权删除公开热词");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!existing.getCreatorId().equals(loginUser.getUserId()) && !isAdmin) {
|
if (!existing.getCreatorId().equals(loginUser.getUserId())) {
|
||||||
return ApiResponse.error("无权删除他人私有热词");
|
return ApiResponse.error("无权删除他人私有热词");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -101,33 +87,20 @@ public class HotWordController {
|
||||||
@RequestParam(defaultValue = "1") Integer current,
|
@RequestParam(defaultValue = "1") Integer current,
|
||||||
@RequestParam(defaultValue = "10") Integer size,
|
@RequestParam(defaultValue = "10") Integer size,
|
||||||
@RequestParam(required = false) String word,
|
@RequestParam(required = false) String word,
|
||||||
@RequestParam(required = false) String category,
|
@RequestParam(required = false) String category) {
|
||||||
@RequestParam(required = false) Integer isPublic) {
|
|
||||||
|
|
||||||
LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||||
boolean isAdmin = isCurrentUserAdmin(loginUser);
|
|
||||||
|
|
||||||
LambdaQueryWrapper<HotWord> wrapper = new LambdaQueryWrapper<HotWord>()
|
|
||||||
.eq(HotWord::getTenantId, loginUser.getTenantId());
|
|
||||||
|
|
||||||
if (!isAdmin) {
|
|
||||||
// 普通用户:只能看到“已公开”的,或者“自己创建”的
|
|
||||||
wrapper.and(w -> w.eq(HotWord::getIsPublic, 1).or().eq(HotWord::getCreatorId, loginUser.getUserId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 增加类型过滤
|
Page<HotWord> page = hotWordService.page(new Page<>(current, size),
|
||||||
if (isPublic != null) {
|
new LambdaQueryWrapper<HotWord>()
|
||||||
wrapper.eq(HotWord::getIsPublic, isPublic);
|
.eq(HotWord::getTenantId, loginUser.getTenantId())
|
||||||
}
|
.and(w -> w.eq(HotWord::getCreatorId, loginUser.getUserId()).or().eq(HotWord::getIsPublic, 1))
|
||||||
|
.like(word != null && !word.isEmpty(), HotWord::getWord, word)
|
||||||
wrapper.like(word != null && !word.isEmpty(), HotWord::getWord, word)
|
.eq(category != null && !category.isEmpty(), HotWord::getCategory, category)
|
||||||
.eq(category != null && !category.isEmpty(), HotWord::getCategory, category)
|
.orderByDesc(HotWord::getIsPublic) // 公开的排在前面
|
||||||
.orderByDesc(HotWord::getIsPublic)
|
.orderByDesc(HotWord::getCreatedAt));
|
||||||
.orderByDesc(HotWord::getCreatedAt);
|
|
||||||
|
|
||||||
Page<HotWord> page = hotWordService.page(new Page<>(current, size), wrapper);
|
|
||||||
List<HotWordVO> vos = page.getRecords().stream().map(this::toVO).collect(Collectors.toList());
|
List<HotWordVO> vos = page.getRecords().stream().map(this::toVO).collect(Collectors.toList());
|
||||||
|
|
||||||
PageResult<List<HotWordVO>> result = new PageResult<>();
|
PageResult<List<HotWordVO>> result = new PageResult<>();
|
||||||
result.setTotal(page.getTotal());
|
result.setTotal(page.getTotal());
|
||||||
result.setRecords(vos);
|
result.setRecords(vos);
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,5 @@ public class UserProfile {
|
||||||
@JsonProperty("isAdmin")
|
@JsonProperty("isAdmin")
|
||||||
private boolean isAdmin;
|
private boolean isAdmin;
|
||||||
private Boolean isPlatformAdmin;
|
private Boolean isPlatformAdmin;
|
||||||
private Boolean isTenantAdmin;
|
|
||||||
private Integer pwdResetRequired;
|
private Integer pwdResetRequired;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
|
@ -15,11 +14,8 @@ import java.time.LocalDateTime;
|
||||||
public class SysUserRole {
|
public class SysUserRole {
|
||||||
@TableId(value = "id", type = IdType.AUTO)
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
private Long id;
|
private Long id;
|
||||||
private Long tenantId;
|
|
||||||
private Long userId;
|
private Long userId;
|
||||||
private Long roleId;
|
private Long roleId;
|
||||||
@TableLogic(value = "0", delval = "1")
|
|
||||||
private Integer isDeleted;
|
|
||||||
|
|
||||||
@TableField(fill = FieldFill.INSERT)
|
@TableField(fill = FieldFill.INSERT)
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
|
|
|
||||||
|
|
@ -14,15 +14,10 @@ public interface SysPermissionMapper extends BaseMapper<SysPermission> {
|
||||||
@Select("""
|
@Select("""
|
||||||
SELECT DISTINCT p.*
|
SELECT DISTINCT p.*
|
||||||
FROM sys_permission p
|
FROM sys_permission p
|
||||||
JOIN sys_role_permission rp ON rp.perm_id = p.perm_id
|
JOIN sys_role_permission rp ON rp.perm_id = p.perm_id and p.is_deleted=0
|
||||||
JOIN sys_role r ON r.role_id = rp.role_id
|
JOIN sys_role r ON r.role_id = rp.role_id
|
||||||
JOIN sys_user_role ur ON ur.role_id = r.role_id
|
JOIN sys_user_role ur ON ur.role_id = r.role_id
|
||||||
WHERE p.is_deleted = 0
|
WHERE ur.user_id = #{userId} AND r.tenant_id = #{tenantId}
|
||||||
AND r.is_deleted = 0
|
|
||||||
AND ur.is_deleted = 0
|
|
||||||
AND ur.user_id = #{userId}
|
|
||||||
AND r.tenant_id = #{tenantId}
|
|
||||||
AND (ur.tenant_id = #{tenantId} OR ur.tenant_id IS NULL)
|
|
||||||
""")
|
""")
|
||||||
List<SysPermission> selectByUserId(@Param("userId") Long userId, @Param("tenantId") Long tenantId);
|
List<SysPermission> selectByUserId(@Param("userId") Long userId, @Param("tenantId") Long tenantId);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,7 @@ package com.imeeting.mapper;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import com.imeeting.entity.SysRolePermission;
|
import com.imeeting.entity.SysRolePermission;
|
||||||
import org.apache.ibatis.annotations.Param;
|
|
||||||
import org.apache.ibatis.annotations.Select;
|
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface SysRolePermissionMapper extends BaseMapper<SysRolePermission> {
|
public interface SysRolePermissionMapper extends BaseMapper<SysRolePermission> {}
|
||||||
@Select("""
|
|
||||||
SELECT DISTINCT role_id
|
|
||||||
FROM sys_role_permission
|
|
||||||
WHERE perm_id = #{permId}
|
|
||||||
""")
|
|
||||||
List<Long> selectRoleIdsByPermId(@Param("permId") Long permId);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,7 @@ import java.util.List;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface SysUserMapper extends BaseMapper<SysUser> {
|
public interface SysUserMapper extends BaseMapper<SysUser> {
|
||||||
@Select("""
|
@Select("SELECT u.* FROM sys_user u JOIN sys_user_role ur ON u.user_id = ur.user_id WHERE ur.role_id = #{roleId}")
|
||||||
SELECT u.*
|
|
||||||
FROM sys_user u
|
|
||||||
JOIN sys_user_role ur ON u.user_id = ur.user_id
|
|
||||||
WHERE ur.role_id = #{roleId}
|
|
||||||
AND ur.is_deleted = 0
|
|
||||||
AND u.is_deleted = 0
|
|
||||||
""")
|
|
||||||
List<SysUser> selectUsersByRoleId(@Param("roleId") Long roleId);
|
List<SysUser> selectUsersByRoleId(@Param("roleId") Long roleId);
|
||||||
|
|
||||||
@InterceptorIgnore(tenantLine = "true")
|
@InterceptorIgnore(tenantLine = "true")
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,8 @@
|
||||||
package com.imeeting.mapper;
|
package com.imeeting.mapper;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
|
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import com.imeeting.entity.SysUserRole;
|
import com.imeeting.entity.SysUserRole;
|
||||||
import org.apache.ibatis.annotations.Param;
|
|
||||||
import org.apache.ibatis.annotations.Select;
|
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface SysUserRoleMapper extends BaseMapper<SysUserRole> {
|
public interface SysUserRoleMapper extends BaseMapper<SysUserRole> {}
|
||||||
@InterceptorIgnore(tenantLine = "true")
|
|
||||||
@Select("""
|
|
||||||
SELECT COUNT(1)
|
|
||||||
FROM sys_user_role ur
|
|
||||||
JOIN sys_role r ON r.role_id = ur.role_id
|
|
||||||
WHERE ur.user_id = #{userId}
|
|
||||||
AND (ur.tenant_id = #{tenantId} OR ur.tenant_id IS NULL)
|
|
||||||
AND ur.is_deleted = 0
|
|
||||||
AND r.is_deleted = 0
|
|
||||||
AND r.tenant_id = #{tenantId}
|
|
||||||
AND r.role_code = 'TENANT_ADMIN'
|
|
||||||
""")
|
|
||||||
Long countTenantAdminRole(@Param("userId") Long userId, @Param("tenantId") Long tenantId);
|
|
||||||
|
|
||||||
@Select("""
|
|
||||||
SELECT DISTINCT ur.user_id
|
|
||||||
FROM sys_user_role ur
|
|
||||||
WHERE ur.role_id = #{roleId}
|
|
||||||
AND ur.is_deleted = 0
|
|
||||||
""")
|
|
||||||
List<Long> selectUserIdsByRoleId(@Param("roleId") Long roleId);
|
|
||||||
|
|
||||||
@Select("""
|
|
||||||
SELECT ur.role_id
|
|
||||||
FROM sys_user_role ur
|
|
||||||
WHERE ur.user_id = #{userId}
|
|
||||||
AND ur.tenant_id = #{tenantId}
|
|
||||||
AND ur.is_deleted = 0
|
|
||||||
""")
|
|
||||||
List<Long> selectRoleIdsByUserIdAndTenantId(@Param("userId") Long userId, @Param("tenantId") Long tenantId);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ public class LoginUser implements UserDetails {
|
||||||
private String username;
|
private String username;
|
||||||
private String displayName;
|
private String displayName;
|
||||||
private Boolean isPlatformAdmin;
|
private Boolean isPlatformAdmin;
|
||||||
private Boolean isTenantAdmin;
|
|
||||||
private Set<String> permissions;
|
private Set<String> permissions;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,11 @@ public class PermissionService {
|
||||||
if (authentication == null || !(authentication.getPrincipal() instanceof LoginUser)) {
|
if (authentication == null || !(authentication.getPrincipal() instanceof LoginUser)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
|
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
|
||||||
// 平台管理员在系统租户(0)下放行全部权限点
|
|
||||||
if (Boolean.TRUE.equals(loginUser.getIsPlatformAdmin())
|
// 超级管理员(ID=1) 拥有所有权限
|
||||||
&& Long.valueOf(0L).equals(loginUser.getTenantId())) {
|
if (loginUser.getUserId() != null && loginUser.getUserId() == 1L) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package com.imeeting.service;
|
|
||||||
|
|
||||||
public interface AuthScopeService {
|
|
||||||
boolean isCurrentPlatformAdmin();
|
|
||||||
|
|
||||||
boolean isCurrentTenantAdmin();
|
|
||||||
|
|
||||||
boolean isTenantAdmin(Long userId, Long tenantId);
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
package com.imeeting.service;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
public interface AuthVersionService {
|
|
||||||
long getVersion(Long userId, Long tenantId);
|
|
||||||
|
|
||||||
void invalidateUserTenantAuth(Long userId, Long tenantId);
|
|
||||||
|
|
||||||
void invalidateUsersTenantAuth(Collection<Long> userIds, Long tenantId);
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
package com.imeeting.service.impl;
|
|
||||||
|
|
||||||
import com.imeeting.mapper.SysUserRoleMapper;
|
|
||||||
import com.imeeting.security.LoginUser;
|
|
||||||
import com.imeeting.service.AuthScopeService;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class AuthScopeServiceImpl implements AuthScopeService {
|
|
||||||
private final SysUserRoleMapper sysUserRoleMapper;
|
|
||||||
|
|
||||||
public AuthScopeServiceImpl(SysUserRoleMapper sysUserRoleMapper) {
|
|
||||||
this.sysUserRoleMapper = sysUserRoleMapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCurrentPlatformAdmin() {
|
|
||||||
LoginUser loginUser = getCurrentLoginUser();
|
|
||||||
if (loginUser == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return Boolean.TRUE.equals(loginUser.getIsPlatformAdmin())
|
|
||||||
&& Long.valueOf(0L).equals(loginUser.getTenantId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCurrentTenantAdmin() {
|
|
||||||
LoginUser loginUser = getCurrentLoginUser();
|
|
||||||
if (loginUser == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return isTenantAdmin(loginUser.getUserId(), loginUser.getTenantId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isTenantAdmin(Long userId, Long tenantId) {
|
|
||||||
if (userId == null || tenantId == null || tenantId <= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Long count = sysUserRoleMapper.countTenantAdminRole(userId, tenantId);
|
|
||||||
return count != null && count > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private LoginUser getCurrentLoginUser() {
|
|
||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
|
||||||
if (authentication == null || !(authentication.getPrincipal() instanceof LoginUser)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (LoginUser) authentication.getPrincipal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -33,7 +33,6 @@ public class AuthServiceImpl implements AuthService {
|
||||||
private final StringRedisTemplate stringRedisTemplate;
|
private final StringRedisTemplate stringRedisTemplate;
|
||||||
private final PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
private final JwtTokenProvider jwtTokenProvider;
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
private final AuthVersionService authVersionService;
|
|
||||||
private final SysLogService sysLogService;
|
private final SysLogService sysLogService;
|
||||||
private final HttpServletRequest httpServletRequest;
|
private final HttpServletRequest httpServletRequest;
|
||||||
|
|
||||||
|
|
@ -51,8 +50,7 @@ public class AuthServiceImpl implements AuthService {
|
||||||
StringRedisTemplate stringRedisTemplate,
|
StringRedisTemplate stringRedisTemplate,
|
||||||
PasswordEncoder passwordEncoder,
|
PasswordEncoder passwordEncoder,
|
||||||
JwtTokenProvider jwtTokenProvider,
|
JwtTokenProvider jwtTokenProvider,
|
||||||
AuthVersionService authVersionService,
|
SysLogService sysLogService,
|
||||||
SysLogService sysLogService,
|
|
||||||
HttpServletRequest httpServletRequest) {
|
HttpServletRequest httpServletRequest) {
|
||||||
this.sysUserService = sysUserService;
|
this.sysUserService = sysUserService;
|
||||||
this.sysUserMapper = sysUserMapper;
|
this.sysUserMapper = sysUserMapper;
|
||||||
|
|
@ -61,7 +59,6 @@ public class AuthServiceImpl implements AuthService {
|
||||||
this.stringRedisTemplate = stringRedisTemplate;
|
this.stringRedisTemplate = stringRedisTemplate;
|
||||||
this.passwordEncoder = passwordEncoder;
|
this.passwordEncoder = passwordEncoder;
|
||||||
this.jwtTokenProvider = jwtTokenProvider;
|
this.jwtTokenProvider = jwtTokenProvider;
|
||||||
this.authVersionService = authVersionService;
|
|
||||||
this.sysLogService = sysLogService;
|
this.sysLogService = sysLogService;
|
||||||
this.httpServletRequest = httpServletRequest;
|
this.httpServletRequest = httpServletRequest;
|
||||||
}
|
}
|
||||||
|
|
@ -178,11 +175,6 @@ public class AuthServiceImpl implements AuthService {
|
||||||
Long userId = claims.get("userId", Long.class);
|
Long userId = claims.get("userId", Long.class);
|
||||||
Long tenantId = claims.get("tenantId", Long.class);
|
Long tenantId = claims.get("tenantId", Long.class);
|
||||||
String deviceCode = claims.get("deviceCode", String.class);
|
String deviceCode = claims.get("deviceCode", String.class);
|
||||||
Number tokenAuthVersionNum = claims.get("authVersion", Number.class);
|
|
||||||
long currentAuthVersion = authVersionService.getVersion(userId, tenantId);
|
|
||||||
if (currentAuthVersion != (tokenAuthVersionNum == null ? 0L : tokenAuthVersionNum.longValue())) {
|
|
||||||
throw new IllegalArgumentException("刷新令牌已失效");
|
|
||||||
}
|
|
||||||
String cached = stringRedisTemplate.opsForValue().get(RedisKeys.refreshTokenKey(userId, deviceCode));
|
String cached = stringRedisTemplate.opsForValue().get(RedisKeys.refreshTokenKey(userId, deviceCode));
|
||||||
if (cached == null || !cached.equals(refreshToken)) {
|
if (cached == null || !cached.equals(refreshToken)) {
|
||||||
throw new IllegalArgumentException("刷新令牌已失效");
|
throw new IllegalArgumentException("刷新令牌已失效");
|
||||||
|
|
@ -306,7 +298,6 @@ public class AuthServiceImpl implements AuthService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private TokenResponse issueTokens(SysUser user, Long tenantId, String deviceCode, long accessMinutes, long refreshDays) {
|
private TokenResponse issueTokens(SysUser user, Long tenantId, String deviceCode, long accessMinutes, long refreshDays) {
|
||||||
long authVersion = authVersionService.getVersion(user.getUserId(), tenantId);
|
|
||||||
Map<String, Object> accessClaims = new HashMap<>();
|
Map<String, Object> accessClaims = new HashMap<>();
|
||||||
accessClaims.put("tokenType", "access");
|
accessClaims.put("tokenType", "access");
|
||||||
accessClaims.put("userId", user.getUserId());
|
accessClaims.put("userId", user.getUserId());
|
||||||
|
|
@ -314,14 +305,13 @@ public class AuthServiceImpl implements AuthService {
|
||||||
accessClaims.put("username", user.getUsername());
|
accessClaims.put("username", user.getUsername());
|
||||||
accessClaims.put("displayName", user.getDisplayName());
|
accessClaims.put("displayName", user.getDisplayName());
|
||||||
accessClaims.put("deviceCode", deviceCode);
|
accessClaims.put("deviceCode", deviceCode);
|
||||||
accessClaims.put("authVersion", authVersion);
|
accessClaims.put("pwdResetRequired", user.getPwdResetRequired());
|
||||||
|
|
||||||
Map<String, Object> refreshClaims = new HashMap<>();
|
Map<String, Object> refreshClaims = new HashMap<>();
|
||||||
refreshClaims.put("tokenType", "refresh");
|
refreshClaims.put("tokenType", "refresh");
|
||||||
refreshClaims.put("userId", user.getUserId());
|
refreshClaims.put("userId", user.getUserId());
|
||||||
refreshClaims.put("tenantId", tenantId);
|
refreshClaims.put("tenantId", tenantId);
|
||||||
refreshClaims.put("deviceCode", deviceCode);
|
refreshClaims.put("deviceCode", deviceCode);
|
||||||
refreshClaims.put("authVersion", authVersion);
|
|
||||||
|
|
||||||
String access = jwtTokenProvider.createToken(accessClaims, Duration.ofMinutes(accessMinutes).toMillis());
|
String access = jwtTokenProvider.createToken(accessClaims, Duration.ofMinutes(accessMinutes).toMillis());
|
||||||
String refresh = jwtTokenProvider.createToken(refreshClaims, Duration.ofDays(refreshDays).toMillis());
|
String refresh = jwtTokenProvider.createToken(refreshClaims, Duration.ofDays(refreshDays).toMillis());
|
||||||
|
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
package com.imeeting.service.impl;
|
|
||||||
|
|
||||||
import com.imeeting.common.RedisKeys;
|
|
||||||
import com.imeeting.service.AuthVersionService;
|
|
||||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class AuthVersionServiceImpl implements AuthVersionService {
|
|
||||||
private static final Duration VERSION_TTL = Duration.ofDays(30);
|
|
||||||
private final StringRedisTemplate stringRedisTemplate;
|
|
||||||
|
|
||||||
public AuthVersionServiceImpl(StringRedisTemplate stringRedisTemplate) {
|
|
||||||
this.stringRedisTemplate = stringRedisTemplate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getVersion(Long userId, Long tenantId) {
|
|
||||||
if (userId == null || tenantId == null) {
|
|
||||||
return 0L;
|
|
||||||
}
|
|
||||||
String value = stringRedisTemplate.opsForValue().get(RedisKeys.authVersionKey(userId, tenantId));
|
|
||||||
if (value == null || value.trim().isEmpty()) {
|
|
||||||
return 0L;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return Long.parseLong(value);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
return 0L;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void invalidateUserTenantAuth(Long userId, Long tenantId) {
|
|
||||||
if (userId == null || tenantId == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String versionKey = RedisKeys.authVersionKey(userId, tenantId);
|
|
||||||
Long newVersion = stringRedisTemplate.opsForValue().increment(versionKey);
|
|
||||||
if (newVersion == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
stringRedisTemplate.expire(versionKey, VERSION_TTL);
|
|
||||||
long previousVersion = Math.max(newVersion - 1, 0);
|
|
||||||
stringRedisTemplate.delete(RedisKeys.authPermKey(userId, tenantId, previousVersion));
|
|
||||||
stringRedisTemplate.delete(RedisKeys.authPermKey(userId, tenantId, newVersion));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void invalidateUsersTenantAuth(Collection<Long> userIds, Long tenantId) {
|
|
||||||
if (userIds == null || userIds.isEmpty() || tenantId == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (Long userId : userIds) {
|
|
||||||
invalidateUserTenantAuth(userId, tenantId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -175,7 +175,6 @@ public class SysTenantServiceImpl extends ServiceImpl<SysTenantMapper, SysTenant
|
||||||
|
|
||||||
// 7. 绑定用户与角色 (sys_user_role)
|
// 7. 绑定用户与角色 (sys_user_role)
|
||||||
SysUserRole ur = new SysUserRole();
|
SysUserRole ur = new SysUserRole();
|
||||||
ur.setTenantId(tenantId);
|
|
||||||
ur.setUserId(userId);
|
ur.setUserId(userId);
|
||||||
ur.setRoleId(roleId);
|
ur.setRoleId(roleId);
|
||||||
sysUserRoleMapper.insert(ur);
|
sysUserRoleMapper.insert(ur);
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,8 @@ export const getHotWordPage = (params: {
|
||||||
current: number;
|
current: number;
|
||||||
size: number;
|
size: number;
|
||||||
word?: string;
|
word?: string;
|
||||||
category?: string;
|
category?: string;
|
||||||
isPublic?: number;
|
matchStrategy?: number
|
||||||
}) => {
|
}) => {
|
||||||
return http.get<any, { code: string; data: { records: HotWordVO[]; total: number }; msg: string }>(
|
return http.get<any, { code: string; data: { records: HotWordVO[]; total: number }; msg: string }>(
|
||||||
"/api/biz/hotword/page",
|
"/api/biz/hotword/page",
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ const { Text } = Typography;
|
||||||
|
|
||||||
const HotWords: React.FC = () => {
|
const HotWords: React.FC = () => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
const [searchForm] = Form.useForm();
|
||||||
const { items: categories, loading: dictLoading } = useDict('biz_hotword_category');
|
const { items: categories, loading: dictLoading } = useDict('biz_hotword_category');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [data, setData] = useState<HotWordVO[]>([]);
|
const [data, setData] = useState<HotWordVO[]>([]);
|
||||||
|
|
@ -32,7 +33,6 @@ const HotWords: React.FC = () => {
|
||||||
const [size, setSize] = useState(10);
|
const [size, setSize] = useState(10);
|
||||||
const [searchWord, setSearchWord] = useState('');
|
const [searchWord, setSearchWord] = useState('');
|
||||||
const [searchCategory, setSearchCategory] = useState<string | undefined>(undefined);
|
const [searchCategory, setSearchCategory] = useState<string | undefined>(undefined);
|
||||||
const [searchType, setSearchType] = useState<number | undefined>(undefined);
|
|
||||||
|
|
||||||
const [modalVisible, setModalVisible] = useState(false);
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
const [editingId, setEditingId] = useState<number | null>(null);
|
const [editingId, setEditingId] = useState<number | null>(null);
|
||||||
|
|
@ -44,14 +44,11 @@ const HotWords: React.FC = () => {
|
||||||
return profileStr ? JSON.parse(profileStr) : {};
|
return profileStr ? JSON.parse(profileStr) : {};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 判定是否具有管理员权限 (平台管理员或租户管理员)
|
const isPlatformAdmin = userProfile.isPlatformAdmin === true;
|
||||||
const isAdmin = React.useMemo(() => {
|
|
||||||
return userProfile.isPlatformAdmin === true || userProfile.isTenantAdmin === true;
|
|
||||||
}, [userProfile]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [current, size, searchWord, searchCategory, searchType]);
|
}, [current, size, searchWord, searchCategory]);
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
@ -60,8 +57,7 @@ const HotWords: React.FC = () => {
|
||||||
current,
|
current,
|
||||||
size,
|
size,
|
||||||
word: searchWord,
|
word: searchWord,
|
||||||
category: searchCategory,
|
category: searchCategory
|
||||||
isPublic: searchType
|
|
||||||
});
|
});
|
||||||
if (res.data && res.data.data) {
|
if (res.data && res.data.data) {
|
||||||
setData(res.data.data.records);
|
setData(res.data.data.records);
|
||||||
|
|
@ -76,7 +72,8 @@ const HotWords: React.FC = () => {
|
||||||
|
|
||||||
const handleOpenModal = (record?: HotWordVO) => {
|
const handleOpenModal = (record?: HotWordVO) => {
|
||||||
if (record) {
|
if (record) {
|
||||||
if (record.isPublic === 1 && !isAdmin) {
|
// 权限校验
|
||||||
|
if (record.isPublic === 1 && !isPlatformAdmin) {
|
||||||
message.error('公开热词仅限管理员修改');
|
message.error('公开热词仅限管理员修改');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -145,9 +142,9 @@ const HotWords: React.FC = () => {
|
||||||
<Space>
|
<Space>
|
||||||
<Text strong>{text}</Text>
|
<Text strong>{text}</Text>
|
||||||
{record.isPublic === 1 ? (
|
{record.isPublic === 1 ? (
|
||||||
<Tooltip title="租户公开"><GlobalOutlined style={{ color: '#52c41a' }} /></Tooltip>
|
<Tooltip title="全租户公开"><GlobalOutlined style={{ color: '#52c41a' }} /></Tooltip>
|
||||||
) : (
|
) : (
|
||||||
<Tooltip title="个人私有"><UserOutlined style={{ color: '#1890ff' }} /></Tooltip>
|
<Tooltip title="仅个人私有"><UserOutlined style={{ color: '#1890ff' }} /></Tooltip>
|
||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
)
|
)
|
||||||
|
|
@ -169,7 +166,7 @@ const HotWords: React.FC = () => {
|
||||||
render: (val: string) => categories.find(i => i.itemValue === val)?.itemLabel || val
|
render: (val: string) => categories.find(i => i.itemValue === val)?.itemLabel || val
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '范围',
|
title: '类型',
|
||||||
dataIndex: 'isPublic',
|
dataIndex: 'isPublic',
|
||||||
key: 'isPublic',
|
key: 'isPublic',
|
||||||
render: (val: number) => val === 1 ? <Tag color="green">公开</Tag> : <Tag color="blue">私有</Tag>
|
render: (val: number) => val === 1 ? <Tag color="green">公开</Tag> : <Tag color="blue">私有</Tag>
|
||||||
|
|
@ -184,8 +181,7 @@ const HotWords: React.FC = () => {
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
render: (_: any, record: HotWordVO) => {
|
render: (_: any, record: HotWordVO) => {
|
||||||
const isMine = record.creatorId === userProfile.userId;
|
const canEdit = record.isPublic === 1 ? isPlatformAdmin : record.creatorId === userProfile.userId;
|
||||||
const canEdit = record.isPublic === 1 ? isAdmin : (isMine || isAdmin);
|
|
||||||
return (
|
return (
|
||||||
<Space size="middle">
|
<Space size="middle">
|
||||||
{canEdit ? (
|
{canEdit ? (
|
||||||
|
|
@ -209,21 +205,12 @@ const HotWords: React.FC = () => {
|
||||||
<Card
|
<Card
|
||||||
title="热词库管理"
|
title="热词库管理"
|
||||||
extra={
|
extra={
|
||||||
<Space wrap>
|
<Space>
|
||||||
<Select
|
|
||||||
placeholder="热词类型"
|
|
||||||
style={{ width: 110 }}
|
|
||||||
allowClear
|
|
||||||
onChange={v => {setSearchType(v); setCurrent(1);}}
|
|
||||||
>
|
|
||||||
<Option value={1}>租户公开</Option>
|
|
||||||
<Option value={0}>个人私有</Option>
|
|
||||||
</Select>
|
|
||||||
<Select
|
<Select
|
||||||
placeholder="按类别筛选"
|
placeholder="按类别筛选"
|
||||||
style={{ width: 130 }}
|
style={{ width: 140 }}
|
||||||
allowClear
|
allowClear
|
||||||
onChange={v => {setSearchCategory(v); setCurrent(1);}}
|
onChange={setSearchCategory}
|
||||||
>
|
>
|
||||||
{categories.map(c => <Option key={c.itemValue} value={c.itemValue}>{c.itemLabel}</Option>)}
|
{categories.map(c => <Option key={c.itemValue} value={c.itemValue}>{c.itemLabel}</Option>)}
|
||||||
</Select>
|
</Select>
|
||||||
|
|
@ -232,7 +219,7 @@ const HotWords: React.FC = () => {
|
||||||
prefix={<SearchOutlined />}
|
prefix={<SearchOutlined />}
|
||||||
allowClear
|
allowClear
|
||||||
onPressEnter={(e) => {setSearchWord((e.target as any).value); setCurrent(1);}}
|
onPressEnter={(e) => {setSearchWord((e.target as any).value); setCurrent(1);}}
|
||||||
style={{ width: 180 }}
|
style={{ width: 200 }}
|
||||||
/>
|
/>
|
||||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => handleOpenModal()}>
|
<Button type="primary" icon={<PlusOutlined />} onClick={() => handleOpenModal()}>
|
||||||
新增热词
|
新增热词
|
||||||
|
|
@ -265,10 +252,10 @@ const HotWords: React.FC = () => {
|
||||||
>
|
>
|
||||||
<Form form={form} layout="vertical" style={{ marginTop: '16px' }}>
|
<Form form={form} layout="vertical" style={{ marginTop: '16px' }}>
|
||||||
<Form.Item name="word" label="热词原文" rules={[{ required: true, message: '请输入热词原文' }]}>
|
<Form.Item name="word" label="热词原文" rules={[{ required: true, message: '请输入热词原文' }]}>
|
||||||
<Input placeholder="输入识别关键词" onBlur={handleWordBlur} />
|
<Input placeholder="输入中文或英文关键词" onBlur={handleWordBlur} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item name="pinyinList" label="拼音 (多音字用逗号分隔)" tooltip="留空自动生成">
|
<Form.Item name="pinyinList" label="拼音 (多音字用逗号分隔)" tooltip="留空将根据原文自动生成">
|
||||||
<Input.TextArea placeholder="例如: chong qing, zhong qing" rows={2} />
|
<Input.TextArea placeholder="例如: chong qing, zhong qing" rows={2} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
|
@ -296,7 +283,7 @@ const HotWords: React.FC = () => {
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
{isAdmin && (
|
{isPlatformAdmin && (
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Form.Item name="isPublic" label="租户公开" tooltip="开启后,租户内所有成员均可共享此热词">
|
<Form.Item name="isPublic" label="租户公开" tooltip="开启后,租户内所有成员均可共享此热词">
|
||||||
<Radio.Group>
|
<Radio.Group>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue