feat(app): resolve bean conflicts and complete role/department migrations
This commit is contained in:
+21
-48
@@ -1,29 +1,15 @@
|
||||
package com.ljwx.platform.app.controller;
|
||||
|
||||
import com.ljwx.platform.app.facade.MenuFacade;
|
||||
import com.ljwx.platform.app.facade.dto.menu.CreateMenuRequest;
|
||||
import com.ljwx.platform.app.facade.dto.menu.MenuResponse;
|
||||
import com.ljwx.platform.app.facade.dto.menu.UpdateMenuRequest;
|
||||
import com.ljwx.platform.app.facade.dto.menu.*;
|
||||
import com.ljwx.platform.core.result.Result;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Tag(name = "data-plane", description = "菜单管理")
|
||||
@RestController @RequestMapping("/api/v1/menus")
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/menus")
|
||||
public class MenuController {
|
||||
|
||||
private final MenuFacade menuFacade;
|
||||
@@ -32,49 +18,36 @@ public class MenuController {
|
||||
this.menuFacade = menuFacade;
|
||||
}
|
||||
|
||||
@Operation(summary = "查询所有菜单(扁平列表,含 parentId)")
|
||||
@PreAuthorize("hasAuthority('menu:read')")
|
||||
@GetMapping
|
||||
public Result<List<MenuResponse>> listAll() {
|
||||
return Result.success(menuFacade.listAll());
|
||||
public Result<List<MenuResponse>> list() {
|
||||
return Result.success(menuFacade.list());
|
||||
}
|
||||
|
||||
@Operation(summary = "查询直接子菜单")
|
||||
@PreAuthorize("hasAuthority('menu:read')")
|
||||
@GetMapping("/children")
|
||||
public Result<List<MenuResponse>> listChildren(
|
||||
@Parameter(description = "父菜单ID(0=顶级)") @RequestParam Long parentId) {
|
||||
return Result.success(menuFacade.listChildren(parentId));
|
||||
}
|
||||
|
||||
@Operation(summary = "根据ID查询菜单")
|
||||
@PreAuthorize("hasAuthority('menu:read')")
|
||||
@GetMapping("/{id}")
|
||||
public Result<MenuResponse> getById(
|
||||
@Parameter(description = "菜单ID") @PathVariable Long id) {
|
||||
return Result.success(menuFacade.getById(id));
|
||||
public Result<MenuResponse> get(@PathVariable Long id) {
|
||||
return Result.success(menuFacade.get(id));
|
||||
}
|
||||
|
||||
@Operation(summary = "创建菜单")
|
||||
@PreAuthorize("hasAuthority('menu:write')")
|
||||
@PostMapping
|
||||
public Result<MenuResponse> create(@Valid @RequestBody CreateMenuRequest request) {
|
||||
return Result.success(menuFacade.create(request));
|
||||
}
|
||||
|
||||
@Operation(summary = "更新菜单")
|
||||
@PreAuthorize("hasAuthority('menu:write')")
|
||||
@PutMapping
|
||||
public Result<MenuResponse> update(@Valid @RequestBody UpdateMenuRequest request) {
|
||||
return Result.success(menuFacade.update(request));
|
||||
@PutMapping("/{id}")
|
||||
public Result<MenuResponse> update(@PathVariable Long id,
|
||||
@Valid @RequestBody UpdateMenuRequest request) {
|
||||
return Result.success(menuFacade.update(id, request));
|
||||
}
|
||||
|
||||
@Operation(summary = "删除菜单(级联软删所有子菜单)")
|
||||
@PreAuthorize("hasAuthority('menu:write')")
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Void> delete(
|
||||
@Parameter(description = "菜单ID") @PathVariable Long id) {
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
menuFacade.delete(id);
|
||||
return Result.success();
|
||||
return Result.success(null);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a tree of menus filtered by current user's authorities. */
|
||||
@GetMapping("/me")
|
||||
public Result<List<MenuResponse>> myMenus() {
|
||||
return Result.success(menuFacade.getMyMenuTree());
|
||||
}
|
||||
}
|
||||
|
||||
+33
-36
@@ -1,26 +1,13 @@
|
||||
package com.ljwx.platform.app.controller;
|
||||
|
||||
import com.ljwx.platform.app.facade.RoleFacade;
|
||||
import com.ljwx.platform.app.facade.dto.CreateRoleRequest;
|
||||
import com.ljwx.platform.app.facade.dto.RoleDTO;
|
||||
import com.ljwx.platform.app.facade.dto.UpdateRoleRequest;
|
||||
import com.ljwx.platform.core.result.PageResult;
|
||||
import com.ljwx.platform.app.facade.dto.role.*;
|
||||
import com.ljwx.platform.core.result.Result;
|
||||
import com.ljwx.platform.core.util.PageRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Role CRUD endpoints (tenant-scoped).
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/roles")
|
||||
public class RoleController {
|
||||
@@ -32,37 +19,47 @@ public class RoleController {
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('role:read')")
|
||||
public Result<PageResult<RoleDTO>> list(PageRequest pageRequest) {
|
||||
PageResult<RoleDTO> page = roleFacade.list(pageRequest);
|
||||
return Result.success(page);
|
||||
public Result<List<RoleResponse>> list() {
|
||||
return Result.success(roleFacade.list());
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('role:read')")
|
||||
public Result<RoleDTO> getById(@PathVariable Long id) {
|
||||
RoleDTO role = roleFacade.getById(id);
|
||||
return Result.success(role);
|
||||
public Result<RoleResponse> get(@PathVariable Long id) {
|
||||
return Result.success(roleFacade.get(id));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('role:write')")
|
||||
public Result<RoleDTO> create(@Valid @RequestBody CreateRoleRequest request) {
|
||||
RoleDTO role = roleFacade.create(request);
|
||||
return Result.success("Role created", role);
|
||||
public Result<RoleResponse> create(@Valid @RequestBody CreateRoleRequest request) {
|
||||
return Result.success(roleFacade.create(request));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('role:write')")
|
||||
public Result<RoleDTO> update(@PathVariable Long id, @Valid @RequestBody UpdateRoleRequest request) {
|
||||
RoleDTO role = roleFacade.update(id, request);
|
||||
return Result.success("Role updated", role);
|
||||
public Result<RoleResponse> update(@PathVariable Long id,
|
||||
@Valid @RequestBody UpdateRoleRequest request) {
|
||||
return Result.success(roleFacade.update(id, request));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('role:write')")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
roleFacade.delete(id);
|
||||
return Result.success("Role deleted", null);
|
||||
return Result.success(null);
|
||||
}
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/permissions")
|
||||
public Result<Void> assignPermissions(@PathVariable Long id,
|
||||
@Valid @RequestBody AssignPermissionsRequest request) {
|
||||
roleFacade.assignPermissions(id, request);
|
||||
return Result.success(null);
|
||||
}
|
||||
|
||||
@PutMapping("/user-roles")
|
||||
public Result<Void> assignUserRoles(@Valid @RequestBody AssignUserRolesRequest request) {
|
||||
roleFacade.assignUserRoles(request);
|
||||
return Result.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/user-roles/{userId}")
|
||||
public Result<List<Long>> getUserRoleIds(@PathVariable Long userId) {
|
||||
return Result.success(roleFacade.getUserRoleIds(userId));
|
||||
}
|
||||
}
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.ljwx.platform.app.domain.repository;
|
||||
|
||||
import com.ljwx.platform.app.domain.model.UserRole;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface UserRoleRepository {
|
||||
|
||||
List<Long> findRoleIdsByUserId(Long userId, Long tenantId);
|
||||
|
||||
/** Soft-delete all active bindings for a user in a tenant. */
|
||||
void softDeleteByUserId(Long userId, Long tenantId, String updateUser, Long updateUserId);
|
||||
|
||||
/** Soft-delete a specific user-role binding. */
|
||||
void softDeleteByUserIdAndRoleId(Long userId, Long roleId, Long tenantId,
|
||||
String updateUser, Long updateUserId);
|
||||
|
||||
void batchInsert(List<UserRole> userRoles);
|
||||
|
||||
boolean existsActiveBinding(Long userId, Long roleId, Long tenantId);
|
||||
|
||||
/** Soft-delete all bindings for a role (used when deleting a role). */
|
||||
void softDeleteByRoleId(Long roleId, Long tenantId, String updateUser, Long updateUserId);
|
||||
}
|
||||
@@ -1,52 +1,69 @@
|
||||
package com.ljwx.platform.app.facade;
|
||||
|
||||
import com.ljwx.platform.app.appservice.RoleAppService;
|
||||
import com.ljwx.platform.app.domain.model.Role;
|
||||
import com.ljwx.platform.app.facade.converter.RoleConverter;
|
||||
import com.ljwx.platform.app.facade.dto.CreateRoleRequest;
|
||||
import com.ljwx.platform.app.facade.dto.RoleDTO;
|
||||
import com.ljwx.platform.app.facade.dto.UpdateRoleRequest;
|
||||
import com.ljwx.platform.core.result.PageResult;
|
||||
import com.ljwx.platform.core.util.PageRequest;
|
||||
import com.ljwx.platform.app.facade.dto.role.*;
|
||||
import com.ljwx.platform.app.service.RoleAppService;
|
||||
import com.ljwx.platform.app.service.UserRoleAppService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Role facade: DTO conversion + delegates to RoleAppService.
|
||||
*/
|
||||
@Component
|
||||
public class RoleFacade {
|
||||
|
||||
private final RoleAppService roleAppService;
|
||||
private final UserRoleAppService userRoleAppService;
|
||||
|
||||
public RoleFacade(RoleAppService roleAppService) {
|
||||
public RoleFacade(RoleAppService roleAppService,
|
||||
UserRoleAppService userRoleAppService) {
|
||||
this.roleAppService = roleAppService;
|
||||
this.userRoleAppService = userRoleAppService;
|
||||
}
|
||||
|
||||
public RoleDTO getById(Long id) {
|
||||
public List<RoleResponse> list() {
|
||||
return roleAppService.list().stream()
|
||||
.map(role -> {
|
||||
role.setPermissionIds(
|
||||
roleAppService.getById(role.getId()).getPermissionIds());
|
||||
return RoleConverter.toResponse(role);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public RoleResponse get(Long id) {
|
||||
Role role = roleAppService.getById(id);
|
||||
return RoleConverter.toDTO(role);
|
||||
role.setPermissionIds(
|
||||
roleAppService.getById(id).getPermissionIds());
|
||||
return RoleConverter.toResponse(role);
|
||||
}
|
||||
|
||||
public PageResult<RoleDTO> list(PageRequest pageRequest) {
|
||||
List<Role> roles = roleAppService.listByTenant(pageRequest.getOffset(), pageRequest.getSize());
|
||||
long total = roleAppService.countByTenant();
|
||||
List<RoleDTO> dtos = roles.stream().map(RoleConverter::toDTO).toList();
|
||||
return PageResult.of(dtos, total, pageRequest.getPage(), pageRequest.getSize());
|
||||
public RoleResponse create(CreateRoleRequest request) {
|
||||
Role role = RoleConverter.toModel(request);
|
||||
Role created = roleAppService.create(role);
|
||||
return RoleConverter.toResponse(created);
|
||||
}
|
||||
|
||||
public RoleDTO create(CreateRoleRequest request) {
|
||||
Role role = roleAppService.create(request.getCode(), request.getName(), request.getDescription());
|
||||
return RoleConverter.toDTO(role);
|
||||
}
|
||||
|
||||
public RoleDTO update(Long id, UpdateRoleRequest request) {
|
||||
Role role = roleAppService.update(id, request.getName(), request.getDescription());
|
||||
return RoleConverter.toDTO(role);
|
||||
public RoleResponse update(Long id, UpdateRoleRequest request) {
|
||||
Role updates = RoleConverter.toModel(request);
|
||||
Role updated = roleAppService.update(id, updates);
|
||||
return RoleConverter.toResponse(updated);
|
||||
}
|
||||
|
||||
public void delete(Long id) {
|
||||
roleAppService.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
public void assignPermissions(Long roleId, AssignPermissionsRequest request) {
|
||||
roleAppService.assignPermissions(roleId, request.getPermissionIds());
|
||||
}
|
||||
|
||||
public void assignUserRoles(AssignUserRolesRequest request) {
|
||||
userRoleAppService.assignRoles(request.getUserId(), request.getRoleIds());
|
||||
}
|
||||
|
||||
public List<Long> getUserRoleIds(Long userId) {
|
||||
return userRoleAppService.getRoleIds(userId);
|
||||
}
|
||||
}
|
||||
|
||||
+36
-25
@@ -1,35 +1,46 @@
|
||||
package com.ljwx.platform.app.facade.converter;
|
||||
|
||||
import com.ljwx.platform.app.domain.model.Role;
|
||||
import com.ljwx.platform.app.facade.dto.CreateRoleRequest;
|
||||
import com.ljwx.platform.app.facade.dto.RoleDTO;
|
||||
import com.ljwx.platform.app.facade.dto.role.CreateRoleRequest;
|
||||
import com.ljwx.platform.app.facade.dto.role.RoleResponse;
|
||||
import com.ljwx.platform.app.facade.dto.role.UpdateRoleRequest;
|
||||
|
||||
/**
|
||||
* Role entity ↔ DTO converter (hand-written, MVP).
|
||||
*/
|
||||
public final class RoleConverter {
|
||||
public class RoleConverter {
|
||||
|
||||
private RoleConverter() {
|
||||
private RoleConverter() {}
|
||||
|
||||
public static Role toModel(CreateRoleRequest req) {
|
||||
Role r = new Role();
|
||||
r.setCode(req.getCode());
|
||||
r.setName(req.getName());
|
||||
r.setDescription(req.getDescription());
|
||||
r.setSortOrder(req.getSortOrder());
|
||||
r.setStatus(req.getStatus());
|
||||
return r;
|
||||
}
|
||||
|
||||
public static RoleDTO toDTO(Role role) {
|
||||
if (role == null) return null;
|
||||
RoleDTO dto = new RoleDTO();
|
||||
dto.setId(role.getId());
|
||||
dto.setCode(role.getCode());
|
||||
dto.setName(role.getName());
|
||||
dto.setDescription(role.getDescription());
|
||||
dto.setCreatedAt(role.getCreatedAt());
|
||||
dto.setUpdatedAt(role.getUpdatedAt());
|
||||
return dto;
|
||||
public static Role toModel(UpdateRoleRequest req) {
|
||||
Role r = new Role();
|
||||
r.setName(req.getName());
|
||||
r.setDescription(req.getDescription());
|
||||
r.setSortOrder(req.getSortOrder());
|
||||
r.setStatus(req.getStatus());
|
||||
return r;
|
||||
}
|
||||
|
||||
public static Role fromCreateRequest(CreateRoleRequest request, Long tenantId) {
|
||||
Role role = new Role();
|
||||
role.setTenantId(tenantId);
|
||||
role.setCode(request.getCode());
|
||||
role.setName(request.getName());
|
||||
role.setDescription(request.getDescription());
|
||||
return role;
|
||||
public static RoleResponse toResponse(Role role) {
|
||||
RoleResponse resp = new RoleResponse();
|
||||
resp.setId(role.getId());
|
||||
resp.setCode(role.getCode());
|
||||
resp.setName(role.getName());
|
||||
resp.setDescription(role.getDescription());
|
||||
resp.setSortOrder(role.getSortOrder());
|
||||
resp.setStatus(role.getStatus());
|
||||
resp.setPermissionIds(role.getPermissionIds());
|
||||
resp.setCreateUser(role.getCreateUser());
|
||||
resp.setCreateTime(role.getCreateTime());
|
||||
resp.setUpdateUser(role.getUpdateUser());
|
||||
resp.setUpdateTime(role.getUpdateTime());
|
||||
return resp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.ljwx.platform.app.facade.dto.role;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
public class AssignPermissionsRequest {
|
||||
|
||||
@NotNull(message = "权限ID列表不能为空")
|
||||
private List<Long> permissionIds;
|
||||
|
||||
public List<Long> getPermissionIds() { return permissionIds; }
|
||||
public void setPermissionIds(List<Long> permissionIds) { this.permissionIds = permissionIds; }
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.ljwx.platform.app.facade.dto.role;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
public class AssignUserRolesRequest {
|
||||
|
||||
@NotNull(message = "用户ID不能为空")
|
||||
private Long userId;
|
||||
|
||||
@NotNull(message = "角色ID列表不能为空")
|
||||
private List<Long> roleIds;
|
||||
|
||||
public Long getUserId() { return userId; }
|
||||
public void setUserId(Long userId) { this.userId = userId; }
|
||||
|
||||
public List<Long> getRoleIds() { return roleIds; }
|
||||
public void setRoleIds(List<Long> roleIds) { this.roleIds = roleIds; }
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.ljwx.platform.app.facade.dto.role;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
public class CreateRoleRequest {
|
||||
|
||||
@NotBlank(message = "角色编码不能为空")
|
||||
@Size(max = 50, message = "角色编码最长50字符")
|
||||
private String code;
|
||||
|
||||
@NotBlank(message = "角色名称不能为空")
|
||||
@Size(max = 100, message = "角色名称最长100字符")
|
||||
private String name;
|
||||
|
||||
@Size(max = 200, message = "描述最长200字符")
|
||||
private String description;
|
||||
|
||||
private Integer sortOrder;
|
||||
private Integer status;
|
||||
|
||||
public String getCode() { return code; }
|
||||
public void setCode(String code) { this.code = code; }
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
|
||||
public String getDescription() { return description; }
|
||||
public void setDescription(String description) { this.description = description; }
|
||||
|
||||
public Integer getSortOrder() { return sortOrder; }
|
||||
public void setSortOrder(Integer sortOrder) { this.sortOrder = sortOrder; }
|
||||
|
||||
public Integer getStatus() { return status; }
|
||||
public void setStatus(Integer status) { this.status = status; }
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.ljwx.platform.app.facade.dto.role;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
public class RoleResponse {
|
||||
|
||||
private Long id;
|
||||
private String code;
|
||||
private String name;
|
||||
private String description;
|
||||
private Integer sortOrder;
|
||||
private Integer status;
|
||||
private List<Long> permissionIds;
|
||||
|
||||
private String createUser;
|
||||
private LocalDateTime createTime;
|
||||
private String updateUser;
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
// ── getters & setters ──
|
||||
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
|
||||
public String getCode() { return code; }
|
||||
public void setCode(String code) { this.code = code; }
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
|
||||
public String getDescription() { return description; }
|
||||
public void setDescription(String description) { this.description = description; }
|
||||
|
||||
public Integer getSortOrder() { return sortOrder; }
|
||||
public void setSortOrder(Integer sortOrder) { this.sortOrder = sortOrder; }
|
||||
|
||||
public Integer getStatus() { return status; }
|
||||
public void setStatus(Integer status) { this.status = status; }
|
||||
|
||||
public List<Long> getPermissionIds() { return permissionIds; }
|
||||
public void setPermissionIds(List<Long> permissionIds) { this.permissionIds = permissionIds; }
|
||||
|
||||
public String getCreateUser() { return createUser; }
|
||||
public void setCreateUser(String createUser) { this.createUser = createUser; }
|
||||
|
||||
public LocalDateTime getCreateTime() { return createTime; }
|
||||
public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }
|
||||
|
||||
public String getUpdateUser() { return updateUser; }
|
||||
public void setUpdateUser(String updateUser) { this.updateUser = updateUser; }
|
||||
|
||||
public LocalDateTime getUpdateTime() { return updateTime; }
|
||||
public void setUpdateTime(LocalDateTime updateTime) { this.updateTime = updateTime; }
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.ljwx.platform.app.facade.dto.role;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
public class UpdateRoleRequest {
|
||||
|
||||
@NotBlank(message = "角色名称不能为空")
|
||||
@Size(max = 100, message = "角色名称最长100字符")
|
||||
private String name;
|
||||
|
||||
@Size(max = 200, message = "描述最长200字符")
|
||||
private String description;
|
||||
|
||||
private Integer sortOrder;
|
||||
private Integer status;
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
|
||||
public String getDescription() { return description; }
|
||||
public void setDescription(String description) { this.description = description; }
|
||||
|
||||
public Integer getSortOrder() { return sortOrder; }
|
||||
public void setSortOrder(Integer sortOrder) { this.sortOrder = sortOrder; }
|
||||
|
||||
public Integer getStatus() { return status; }
|
||||
public void setStatus(Integer status) { this.status = status; }
|
||||
}
|
||||
+22
-2
@@ -8,7 +8,7 @@ import org.springframework.stereotype.Repository;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
@Repository("legacyRoleRepository")
|
||||
public class RoleRepositoryImpl implements RoleRepository {
|
||||
|
||||
private final RoleMapper roleMapper;
|
||||
@@ -46,4 +46,24 @@ public class RoleRepositoryImpl implements RoleRepository {
|
||||
public void deleteById(Long id, Long tenantId) {
|
||||
roleMapper.deleteById(id, tenantId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> findPermissionIdsByRoleId(Long roleId) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePermissionsByRoleId(Long roleId) {
|
||||
// no-op in legacy implementation
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertRolePermission(Long roleId, Long permissionId) {
|
||||
// no-op in legacy implementation
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsPermission(Long permissionId) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.ljwx.platform.app.infrastructure.mybatis.mapper;
|
||||
|
||||
import com.ljwx.platform.app.domain.model.Role;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface NewRoleMapper {
|
||||
|
||||
List<Role> selectAllByTenantId(@Param("tenantId") Long tenantId);
|
||||
|
||||
Role selectById(@Param("id") Long id, @Param("tenantId") Long tenantId);
|
||||
|
||||
Role selectByCode(@Param("code") String code, @Param("tenantId") Long tenantId);
|
||||
|
||||
void insert(Role role);
|
||||
|
||||
void update(Role role);
|
||||
|
||||
void softDelete(@Param("id") Long id,
|
||||
@Param("tenantId") Long tenantId,
|
||||
@Param("updateUser") String updateUser,
|
||||
@Param("updateUserId") Long updateUserId);
|
||||
|
||||
// ── permission binding ──
|
||||
|
||||
List<Long> selectPermissionIdsByRoleId(@Param("roleId") Long roleId);
|
||||
|
||||
void deletePermissionsByRoleId(@Param("roleId") Long roleId);
|
||||
|
||||
void insertRolePermission(@Param("roleId") Long roleId,
|
||||
@Param("permissionId") Long permissionId);
|
||||
|
||||
int countPermissionById(@Param("permissionId") Long permissionId);
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.ljwx.platform.app.infrastructure.mybatis.mapper;
|
||||
|
||||
import com.ljwx.platform.app.domain.model.UserRole;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface NewUserRoleMapper {
|
||||
|
||||
List<Long> selectRoleIdsByUserId(@Param("userId") Long userId,
|
||||
@Param("tenantId") Long tenantId);
|
||||
|
||||
void softDeleteByUserId(@Param("userId") Long userId,
|
||||
@Param("tenantId") Long tenantId,
|
||||
@Param("updateUser") String updateUser,
|
||||
@Param("updateUserId") Long updateUserId);
|
||||
|
||||
void softDeleteByUserIdAndRoleId(@Param("userId") Long userId,
|
||||
@Param("roleId") Long roleId,
|
||||
@Param("tenantId") Long tenantId,
|
||||
@Param("updateUser") String updateUser,
|
||||
@Param("updateUserId") Long updateUserId);
|
||||
|
||||
void batchInsert(@Param("list") List<UserRole> userRoles);
|
||||
|
||||
int countActiveBinding(@Param("userId") Long userId,
|
||||
@Param("roleId") Long roleId,
|
||||
@Param("tenantId") Long tenantId);
|
||||
|
||||
void softDeleteByRoleId(@Param("roleId") Long roleId,
|
||||
@Param("tenantId") Long tenantId,
|
||||
@Param("updateUser") String updateUser,
|
||||
@Param("updateUserId") Long updateUserId);
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package com.ljwx.platform.app.infrastructure.mybatis.repository;
|
||||
|
||||
import com.ljwx.platform.app.domain.model.Role;
|
||||
import com.ljwx.platform.app.domain.repository.RoleRepository;
|
||||
import com.ljwx.platform.app.infrastructure.mybatis.mapper.NewRoleMapper;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository("mybatisRoleRepository")
|
||||
@Primary
|
||||
public class RoleRepositoryImpl implements RoleRepository {
|
||||
|
||||
private final NewRoleMapper mapper;
|
||||
|
||||
public RoleRepositoryImpl(NewRoleMapper mapper) {
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Role> findAllByTenantId(Long tenantId) {
|
||||
return mapper.selectAllByTenantId(tenantId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Role> findById(Long id, Long tenantId) {
|
||||
return Optional.ofNullable(mapper.selectById(id, tenantId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Role> findByCode(String code, Long tenantId) {
|
||||
return Optional.ofNullable(mapper.selectByCode(code, tenantId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insert(Role role) {
|
||||
mapper.insert(role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Role role) {
|
||||
mapper.update(role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void softDelete(Long id, Long tenantId, String updateUser, Long updateUserId) {
|
||||
mapper.softDelete(id, tenantId, updateUser, updateUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> findPermissionIdsByRoleId(Long roleId) {
|
||||
return mapper.selectPermissionIdsByRoleId(roleId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePermissionsByRoleId(Long roleId) {
|
||||
mapper.deletePermissionsByRoleId(roleId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertRolePermission(Long roleId, Long permissionId) {
|
||||
mapper.insertRolePermission(roleId, permissionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsPermission(Long permissionId) {
|
||||
return mapper.countPermissionById(permissionId) > 0;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.ljwx.platform.app.infrastructure.mybatis.repository;
|
||||
|
||||
import com.ljwx.platform.app.domain.model.UserRole;
|
||||
import com.ljwx.platform.app.domain.repository.UserRoleRepository;
|
||||
import com.ljwx.platform.app.infrastructure.mybatis.mapper.NewUserRoleMapper;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository("mybatisUserRoleRepository")
|
||||
@Primary
|
||||
public class UserRoleRepositoryImpl implements UserRoleRepository {
|
||||
|
||||
private final NewUserRoleMapper mapper;
|
||||
|
||||
public UserRoleRepositoryImpl(NewUserRoleMapper mapper) {
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> findRoleIdsByUserId(Long userId, Long tenantId) {
|
||||
return mapper.selectRoleIdsByUserId(userId, tenantId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void softDeleteByUserId(Long userId, Long tenantId,
|
||||
String updateUser, Long updateUserId) {
|
||||
mapper.softDeleteByUserId(userId, tenantId, updateUser, updateUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void softDeleteByUserIdAndRoleId(Long userId, Long roleId, Long tenantId,
|
||||
String updateUser, Long updateUserId) {
|
||||
mapper.softDeleteByUserIdAndRoleId(userId, roleId, tenantId, updateUser, updateUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void batchInsert(List<UserRole> userRoles) {
|
||||
if (userRoles != null && !userRoles.isEmpty()) {
|
||||
mapper.batchInsert(userRoles);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsActiveBinding(Long userId, Long roleId, Long tenantId) {
|
||||
return mapper.countActiveBinding(userId, roleId, tenantId) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void softDeleteByRoleId(Long roleId, Long tenantId,
|
||||
String updateUser, Long updateUserId) {
|
||||
mapper.softDeleteByRoleId(roleId, tenantId, updateUser, updateUserId);
|
||||
}
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
package com.ljwx.platform.app.service;
|
||||
|
||||
import com.ljwx.platform.app.domain.model.UserRole;
|
||||
import com.ljwx.platform.app.domain.repository.UserRoleRepository;
|
||||
import com.ljwx.platform.web.security.SecurityUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class UserRoleAppService {
|
||||
|
||||
private final UserRoleRepository userRoleRepository;
|
||||
|
||||
public UserRoleAppService(UserRoleRepository userRoleRepository) {
|
||||
this.userRoleRepository = userRoleRepository;
|
||||
}
|
||||
|
||||
public List<Long> getRoleIds(Long userId) {
|
||||
Long tenantId = SecurityUtil.currentTenantId();
|
||||
return userRoleRepository.findRoleIdsByUserId(userId, tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Full replacement: soft-delete existing bindings, insert new ones.
|
||||
*/
|
||||
@Transactional
|
||||
public void assignRoles(Long userId, List<Long> roleIds) {
|
||||
List<Long> uniqueIds = dedup(roleIds);
|
||||
Long tenantId = SecurityUtil.currentTenantId();
|
||||
String username = SecurityUtil.currentUsername();
|
||||
Long currentUserId = SecurityUtil.currentUserId();
|
||||
|
||||
// soft-delete all existing
|
||||
userRoleRepository.softDeleteByUserId(userId, tenantId, username, currentUserId);
|
||||
|
||||
// insert new bindings
|
||||
if (!uniqueIds.isEmpty()) {
|
||||
List<UserRole> records = buildRecords(userId, uniqueIds, tenantId, username, currentUserId);
|
||||
userRoleRepository.batchInsert(records);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Incremental add: only insert roles not already bound.
|
||||
*/
|
||||
@Transactional
|
||||
public void addRoles(Long userId, List<Long> roleIds) {
|
||||
List<Long> uniqueIds = dedup(roleIds);
|
||||
Long tenantId = SecurityUtil.currentTenantId();
|
||||
String username = SecurityUtil.currentUsername();
|
||||
Long currentUserId = SecurityUtil.currentUserId();
|
||||
|
||||
List<UserRole> toInsert = new ArrayList<>();
|
||||
for (Long roleId : uniqueIds) {
|
||||
if (!userRoleRepository.existsActiveBinding(userId, roleId, tenantId)) {
|
||||
UserRole ur = new UserRole();
|
||||
ur.setTenantId(tenantId);
|
||||
ur.setUserId(userId);
|
||||
ur.setRoleId(roleId);
|
||||
ur.setCreateUser(username);
|
||||
ur.setCreateUserId(currentUserId);
|
||||
ur.setCreateTime(LocalDateTime.now());
|
||||
toInsert.add(ur);
|
||||
}
|
||||
}
|
||||
if (!toInsert.isEmpty()) {
|
||||
userRoleRepository.batchInsert(toInsert);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove specific role bindings.
|
||||
*/
|
||||
@Transactional
|
||||
public void removeRoles(Long userId, List<Long> roleIds) {
|
||||
Long tenantId = SecurityUtil.currentTenantId();
|
||||
String username = SecurityUtil.currentUsername();
|
||||
Long currentUserId = SecurityUtil.currentUserId();
|
||||
for (Long roleId : roleIds) {
|
||||
userRoleRepository.softDeleteByUserIdAndRoleId(userId, roleId, tenantId, username, currentUserId);
|
||||
}
|
||||
}
|
||||
|
||||
// ── private helpers ──
|
||||
|
||||
private static List<Long> dedup(List<Long> ids) {
|
||||
if (ids == null || ids.isEmpty()) return List.of();
|
||||
return new ArrayList<>(new LinkedHashSet<>(ids));
|
||||
}
|
||||
|
||||
private List<UserRole> buildRecords(Long userId, List<Long> roleIds,
|
||||
Long tenantId, String username, Long currentUserId) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
List<UserRole> list = new ArrayList<>(roleIds.size());
|
||||
for (Long roleId : roleIds) {
|
||||
UserRole ur = new UserRole();
|
||||
ur.setTenantId(tenantId);
|
||||
ur.setUserId(userId);
|
||||
ur.setRoleId(roleId);
|
||||
ur.setCreateUser(username);
|
||||
ur.setCreateUserId(currentUserId);
|
||||
ur.setCreateTime(now);
|
||||
list.add(ur);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
-- V110: Enhance users table, add tenant_id to roles, create user_roles table.
|
||||
|
||||
-- ============================================================
|
||||
-- 1. users 表:新增业务字段
|
||||
-- ============================================================
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'users' AND column_name = 'nickname') THEN
|
||||
ALTER TABLE users ADD COLUMN nickname VARCHAR(50) NULL;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'users' AND column_name = 'phone') THEN
|
||||
ALTER TABLE users ADD COLUMN phone VARCHAR(20) NULL;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'users' AND column_name = 'email') THEN
|
||||
ALTER TABLE users ADD COLUMN email VARCHAR(100) NULL;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'users' AND column_name = 'avatar') THEN
|
||||
ALTER TABLE users ADD COLUMN avatar VARCHAR(255) NULL;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'users' AND column_name = 'status') THEN
|
||||
ALTER TABLE users ADD COLUMN status SMALLINT NOT NULL DEFAULT 1;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'users' AND column_name = 'remark') THEN
|
||||
ALTER TABLE users ADD COLUMN remark VARCHAR(200) NULL;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'users' AND column_name = 'department_id') THEN
|
||||
ALTER TABLE users ADD COLUMN department_id BIGINT NULL;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
-- ============================================================
|
||||
-- 2. roles 表:追加 tenant_id、审计字段、软删除
|
||||
-- ============================================================
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'roles' AND column_name = 'tenant_id') THEN
|
||||
ALTER TABLE roles ADD COLUMN tenant_id BIGINT NOT NULL DEFAULT 1;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'roles' AND column_name = 'description') THEN
|
||||
ALTER TABLE roles ADD COLUMN description VARCHAR(200) NULL;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'roles' AND column_name = 'sort_order') THEN
|
||||
ALTER TABLE roles ADD COLUMN sort_order INT NOT NULL DEFAULT 0;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'roles' AND column_name = 'status') THEN
|
||||
ALTER TABLE roles ADD COLUMN status SMALLINT NOT NULL DEFAULT 1;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'roles' AND column_name = 'is_deleted') THEN
|
||||
ALTER TABLE roles ADD COLUMN is_deleted SMALLINT NOT NULL DEFAULT 0;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'roles' AND column_name = 'create_user') THEN
|
||||
ALTER TABLE roles ADD COLUMN create_user VARCHAR(40) NOT NULL DEFAULT 'system';
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'roles' AND column_name = 'create_user_id') THEN
|
||||
ALTER TABLE roles ADD COLUMN create_user_id BIGINT NOT NULL DEFAULT 0;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'roles' AND column_name = 'create_time') THEN
|
||||
ALTER TABLE roles ADD COLUMN create_time TIMESTAMP NOT NULL DEFAULT NOW();
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'roles' AND column_name = 'update_user') THEN
|
||||
ALTER TABLE roles ADD COLUMN update_user VARCHAR(40) NULL;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'roles' AND column_name = 'update_user_id') THEN
|
||||
ALTER TABLE roles ADD COLUMN update_user_id BIGINT NULL;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'roles' AND column_name = 'update_time') THEN
|
||||
ALTER TABLE roles ADD COLUMN update_time TIMESTAMP NULL;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_roles_tenant_id ON roles (tenant_id);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_indexes
|
||||
WHERE tablename = 'roles' AND indexname = 'uk_roles_tenant_code') THEN
|
||||
CREATE UNIQUE INDEX uk_roles_tenant_code ON roles (tenant_id, code)
|
||||
WHERE is_deleted = 0;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
-- ============================================================
|
||||
-- 3. user_roles 表:建表(如不存在)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS user_roles (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL DEFAULT 1,
|
||||
user_id BIGINT NOT NULL,
|
||||
role_id BIGINT NOT NULL,
|
||||
is_deleted SMALLINT NOT NULL DEFAULT 0,
|
||||
create_user VARCHAR(40) NOT NULL DEFAULT 'system',
|
||||
create_user_id BIGINT NOT NULL DEFAULT 0,
|
||||
create_time TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
update_user VARCHAR(40) NULL,
|
||||
update_user_id BIGINT NULL,
|
||||
update_time TIMESTAMP NULL
|
||||
);
|
||||
|
||||
-- ============================================================
|
||||
-- 3b. user_roles 表:如果表已存在但缺少列,逐个补齐
|
||||
-- ============================================================
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'user_roles' AND column_name = 'tenant_id') THEN
|
||||
ALTER TABLE user_roles ADD COLUMN tenant_id BIGINT NOT NULL DEFAULT 1;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'user_roles' AND column_name = 'is_deleted') THEN
|
||||
ALTER TABLE user_roles ADD COLUMN is_deleted SMALLINT NOT NULL DEFAULT 0;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'user_roles' AND column_name = 'create_user') THEN
|
||||
ALTER TABLE user_roles ADD COLUMN create_user VARCHAR(40) NOT NULL DEFAULT 'system';
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'user_roles' AND column_name = 'create_user_id') THEN
|
||||
ALTER TABLE user_roles ADD COLUMN create_user_id BIGINT NOT NULL DEFAULT 0;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'user_roles' AND column_name = 'create_time') THEN
|
||||
ALTER TABLE user_roles ADD COLUMN create_time TIMESTAMP NOT NULL DEFAULT NOW();
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'user_roles' AND column_name = 'update_user') THEN
|
||||
ALTER TABLE user_roles ADD COLUMN update_user VARCHAR(40) NULL;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'user_roles' AND column_name = 'update_user_id') THEN
|
||||
ALTER TABLE user_roles ADD COLUMN update_user_id BIGINT NULL;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'user_roles' AND column_name = 'update_time') THEN
|
||||
ALTER TABLE user_roles ADD COLUMN update_time TIMESTAMP NULL;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
-- ============================================================
|
||||
-- 3c. user_roles 索引(列已确保存在后再创建)
|
||||
-- ============================================================
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_indexes
|
||||
WHERE tablename = 'user_roles'
|
||||
AND indexname = 'uk_user_roles_tenant_user_role') THEN
|
||||
CREATE UNIQUE INDEX uk_user_roles_tenant_user_role
|
||||
ON user_roles (tenant_id, user_id, role_id)
|
||||
WHERE is_deleted = 0;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_roles_user_id ON user_roles (user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_roles_role_id ON user_roles (role_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_roles_tenant_id ON user_roles (tenant_id);
|
||||
|
||||
-- ============================================================
|
||||
-- 4. role_permissions 表:确保存在
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS role_permissions (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
role_id BIGINT NOT NULL,
|
||||
permission_id BIGINT NOT NULL,
|
||||
UNIQUE (role_id, permission_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_role_permissions_role_id ON role_permissions (role_id);
|
||||
|
||||
-- ============================================================
|
||||
-- 5. permissions 表:确保存在
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS permissions (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
key VARCHAR(100) NOT NULL UNIQUE,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
description VARCHAR(200) NULL,
|
||||
create_time TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
-- V111: Seed permissions, bind to ALL tenant ADMIN roles, fill menu permission_key.
|
||||
|
||||
-- ============================================================
|
||||
-- 1. Upsert 9 permissions (列名 code,不是 key)
|
||||
-- ============================================================
|
||||
INSERT INTO permissions (code, name) VALUES ('user:read', '用户查看') ON CONFLICT (code) DO NOTHING;
|
||||
INSERT INTO permissions (code, name) VALUES ('user:write', '用户管理') ON CONFLICT (code) DO NOTHING;
|
||||
INSERT INTO permissions (code, name) VALUES ('role:read', '角色查看') ON CONFLICT (code) DO NOTHING;
|
||||
INSERT INTO permissions (code, name) VALUES ('role:write', '角色管理') ON CONFLICT (code) DO NOTHING;
|
||||
INSERT INTO permissions (code, name) VALUES ('dept:read', '部门查看') ON CONFLICT (code) DO NOTHING;
|
||||
INSERT INTO permissions (code, name) VALUES ('dict:read', '字典查看') ON CONFLICT (code) DO NOTHING;
|
||||
INSERT INTO permissions (code, name) VALUES ('config:read', '配置查看') ON CONFLICT (code) DO NOTHING;
|
||||
INSERT INTO permissions (code, name) VALUES ('menu:read', '菜单查看') ON CONFLICT (code) DO NOTHING;
|
||||
INSERT INTO permissions (code, name) VALUES ('menu:write', '菜单管理') ON CONFLICT (code) DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 2. Bind all 9 permissions to EVERY tenant's ADMIN role
|
||||
-- ============================================================
|
||||
DO $$
|
||||
DECLARE
|
||||
admin_role RECORD;
|
||||
perm RECORD;
|
||||
BEGIN
|
||||
FOR admin_role IN
|
||||
SELECT id FROM roles
|
||||
WHERE code = 'ADMIN' AND is_deleted = 0
|
||||
LOOP
|
||||
FOR perm IN
|
||||
SELECT id FROM permissions
|
||||
WHERE code IN (
|
||||
'user:read', 'user:write',
|
||||
'role:read', 'role:write',
|
||||
'dept:read', 'dict:read', 'config:read',
|
||||
'menu:read', 'menu:write'
|
||||
)
|
||||
LOOP
|
||||
INSERT INTO role_permissions (role_id, permission_id)
|
||||
VALUES (admin_role.id, perm.id)
|
||||
ON CONFLICT (role_id, permission_id) DO NOTHING;
|
||||
END LOOP;
|
||||
END LOOP;
|
||||
END
|
||||
$$;
|
||||
|
||||
-- ============================================================
|
||||
-- 3. Fill permission_key for all 6 second-level menus
|
||||
-- ============================================================
|
||||
UPDATE menus SET permission_key = 'user:read' WHERE menu_code = 'MENU_USER' AND permission_key IS NULL;
|
||||
UPDATE menus SET permission_key = 'role:read' WHERE menu_code = 'MENU_ROLE' AND permission_key IS NULL;
|
||||
UPDATE menus SET permission_key = 'dept:read' WHERE menu_code = 'MENU_DEPT' AND permission_key IS NULL;
|
||||
UPDATE menus SET permission_key = 'dict:read' WHERE menu_code = 'MENU_DICT' AND permission_key IS NULL;
|
||||
UPDATE menus SET permission_key = 'config:read' WHERE menu_code = 'MENU_CONFIG' AND permission_key IS NULL;
|
||||
UPDATE menus SET permission_key = 'menu:read' WHERE menu_code = 'MENU_MENU' AND permission_key IS NULL;
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
-- ============================================================
|
||||
-- V112 全局审计字段对齐
|
||||
--
|
||||
-- 规范:
|
||||
-- create_user VARCHAR(40) NOT NULL
|
||||
-- create_user_id BIGINT NOT NULL
|
||||
-- create_time TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
-- update_user VARCHAR(40) NULL (no default)
|
||||
-- update_user_id BIGINT NULL (no default)
|
||||
-- update_time TIMESTAMP NULL (no default)
|
||||
-- is_deleted SMALLINT NOT NULL DEFAULT 0
|
||||
--
|
||||
-- 实际存在的表:users, dict_types, dict_data, sys_configs,
|
||||
-- menus, roles, user_roles
|
||||
-- 注意:departments 表不存在,已排除
|
||||
-- ============================================================
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
t TEXT;
|
||||
BEGIN
|
||||
FOREACH t IN ARRAY ARRAY[
|
||||
'users',
|
||||
'dict_types',
|
||||
'dict_data',
|
||||
'sys_configs',
|
||||
'menus',
|
||||
'roles',
|
||||
'user_roles'
|
||||
]
|
||||
LOOP
|
||||
-- ── create_user: VARCHAR(40) NOT NULL ──
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = t AND column_name = 'create_user'
|
||||
) THEN
|
||||
EXECUTE format('ALTER TABLE %I ALTER COLUMN create_user TYPE VARCHAR(40)', t);
|
||||
EXECUTE format('ALTER TABLE %I ALTER COLUMN create_user SET NOT NULL', t);
|
||||
END IF;
|
||||
|
||||
-- ── create_user_id: BIGINT NOT NULL ──
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = t AND column_name = 'create_user_id'
|
||||
) THEN
|
||||
EXECUTE format('ALTER TABLE %I ALTER COLUMN create_user_id TYPE BIGINT', t);
|
||||
EXECUTE format('ALTER TABLE %I ALTER COLUMN create_user_id SET NOT NULL', t);
|
||||
END IF;
|
||||
|
||||
-- ── create_time: TIMESTAMP NOT NULL DEFAULT NOW() ──
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = t AND column_name = 'create_time'
|
||||
) THEN
|
||||
EXECUTE format('ALTER TABLE %I ALTER COLUMN create_time TYPE TIMESTAMP', t);
|
||||
EXECUTE format('ALTER TABLE %I ALTER COLUMN create_time SET NOT NULL', t);
|
||||
EXECUTE format('ALTER TABLE %I ALTER COLUMN create_time SET DEFAULT NOW()', t);
|
||||
END IF;
|
||||
|
||||
-- ── update_user: VARCHAR(40) NULL, no default ──
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = t AND column_name = 'update_user'
|
||||
) THEN
|
||||
EXECUTE format('ALTER TABLE %I ALTER COLUMN update_user TYPE VARCHAR(40)', t);
|
||||
EXECUTE format('ALTER TABLE %I ALTER COLUMN update_user DROP NOT NULL', t);
|
||||
EXECUTE format('ALTER TABLE %I ALTER COLUMN update_user DROP DEFAULT', t);
|
||||
END IF;
|
||||
|
||||
-- ── update_user_id: BIGINT NULL, no default ──
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = t AND column_name = 'update_user_id'
|
||||
) THEN
|
||||
EXECUTE format('ALTER TABLE %I ALTER COLUMN update_user_id TYPE BIGINT', t);
|
||||
EXECUTE format('ALTER TABLE %I ALTER COLUMN update_user_id DROP NOT NULL', t);
|
||||
EXECUTE format('ALTER TABLE %I ALTER COLUMN update_user_id DROP DEFAULT', t);
|
||||
END IF;
|
||||
|
||||
-- ── update_time: TIMESTAMP NULL, no default ──
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = t AND column_name = 'update_time'
|
||||
) THEN
|
||||
EXECUTE format('ALTER TABLE %I ALTER COLUMN update_time TYPE TIMESTAMP', t);
|
||||
EXECUTE format('ALTER TABLE %I ALTER COLUMN update_time DROP NOT NULL', t);
|
||||
EXECUTE format('ALTER TABLE %I ALTER COLUMN update_time DROP DEFAULT', t);
|
||||
END IF;
|
||||
|
||||
-- ── is_deleted: SMALLINT NOT NULL DEFAULT 0 ──
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = t AND column_name = 'is_deleted'
|
||||
) THEN
|
||||
EXECUTE format('ALTER TABLE %I ALTER COLUMN is_deleted TYPE SMALLINT USING is_deleted::SMALLINT', t);
|
||||
EXECUTE format('ALTER TABLE %I ALTER COLUMN is_deleted SET NOT NULL', t);
|
||||
EXECUTE format('ALTER TABLE %I ALTER COLUMN is_deleted SET DEFAULT 0', t);
|
||||
END IF;
|
||||
|
||||
END LOOP;
|
||||
END $$;
|
||||
@@ -0,0 +1,40 @@
|
||||
-- V113: Create departments table.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS departments (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL DEFAULT 1,
|
||||
parent_id BIGINT NULL,
|
||||
dept_code VARCHAR(50) NOT NULL,
|
||||
dept_name VARCHAR(100) NOT NULL,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
leader_user_id BIGINT NULL,
|
||||
phone VARCHAR(20) NULL,
|
||||
email VARCHAR(100) NULL,
|
||||
status SMALLINT NOT NULL DEFAULT 1,
|
||||
remark VARCHAR(200) NULL,
|
||||
|
||||
-- audit fields (aligned with V112 convention)
|
||||
create_user VARCHAR(40) NOT NULL DEFAULT 'system',
|
||||
create_user_id BIGINT NOT NULL DEFAULT 0,
|
||||
create_time TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
update_user VARCHAR(40) NULL,
|
||||
update_user_id BIGINT NULL,
|
||||
update_time TIMESTAMP NULL,
|
||||
is_deleted SMALLINT NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX IF NOT EXISTS idx_departments_tenant_id ON departments (tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_departments_parent_id ON departments (parent_id);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_indexes
|
||||
WHERE tablename = 'departments'
|
||||
AND indexname = 'uk_departments_tenant_code') THEN
|
||||
CREATE UNIQUE INDEX uk_departments_tenant_code
|
||||
ON departments (tenant_id, dept_code)
|
||||
WHERE is_deleted = 0;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
@@ -0,0 +1,61 @@
|
||||
-- V114: Align audit fields on departments table (supplement to V112).
|
||||
-- This is a no-op if V113 already created the table with correct types,
|
||||
-- but ensures safety if the table existed before V113.
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
-- create_user
|
||||
IF EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = 'departments' AND column_name = 'create_user') THEN
|
||||
EXECUTE 'ALTER TABLE departments ALTER COLUMN create_user TYPE VARCHAR(40)';
|
||||
EXECUTE 'ALTER TABLE departments ALTER COLUMN create_user SET NOT NULL';
|
||||
END IF;
|
||||
|
||||
-- create_user_id
|
||||
IF EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = 'departments' AND column_name = 'create_user_id') THEN
|
||||
EXECUTE 'ALTER TABLE departments ALTER COLUMN create_user_id TYPE BIGINT';
|
||||
EXECUTE 'ALTER TABLE departments ALTER COLUMN create_user_id SET NOT NULL';
|
||||
END IF;
|
||||
|
||||
-- create_time
|
||||
IF EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = 'departments' AND column_name = 'create_time') THEN
|
||||
EXECUTE 'ALTER TABLE departments ALTER COLUMN create_time TYPE TIMESTAMP';
|
||||
EXECUTE 'ALTER TABLE departments ALTER COLUMN create_time SET NOT NULL';
|
||||
EXECUTE 'ALTER TABLE departments ALTER COLUMN create_time SET DEFAULT NOW()';
|
||||
END IF;
|
||||
|
||||
-- update_user
|
||||
IF EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = 'departments' AND column_name = 'update_user') THEN
|
||||
EXECUTE 'ALTER TABLE departments ALTER COLUMN update_user TYPE VARCHAR(40)';
|
||||
EXECUTE 'ALTER TABLE departments ALTER COLUMN update_user DROP NOT NULL';
|
||||
EXECUTE 'ALTER TABLE departments ALTER COLUMN update_user DROP DEFAULT';
|
||||
END IF;
|
||||
|
||||
-- update_user_id
|
||||
IF EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = 'departments' AND column_name = 'update_user_id') THEN
|
||||
EXECUTE 'ALTER TABLE departments ALTER COLUMN update_user_id TYPE BIGINT';
|
||||
EXECUTE 'ALTER TABLE departments ALTER COLUMN update_user_id DROP NOT NULL';
|
||||
EXECUTE 'ALTER TABLE departments ALTER COLUMN update_user_id DROP DEFAULT';
|
||||
END IF;
|
||||
|
||||
-- update_time
|
||||
IF EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = 'departments' AND column_name = 'update_time') THEN
|
||||
EXECUTE 'ALTER TABLE departments ALTER COLUMN update_time TYPE TIMESTAMP';
|
||||
EXECUTE 'ALTER TABLE departments ALTER COLUMN update_time DROP NOT NULL';
|
||||
EXECUTE 'ALTER TABLE departments ALTER COLUMN update_time DROP DEFAULT';
|
||||
END IF;
|
||||
|
||||
-- is_deleted
|
||||
IF EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = 'departments' AND column_name = 'is_deleted') THEN
|
||||
EXECUTE 'ALTER TABLE departments ALTER COLUMN is_deleted TYPE SMALLINT USING is_deleted::SMALLINT';
|
||||
EXECUTE 'ALTER TABLE departments ALTER COLUMN is_deleted SET NOT NULL';
|
||||
EXECUTE 'ALTER TABLE departments ALTER COLUMN is_deleted SET DEFAULT 0';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ljwx.platform.app.infrastructure.mybatis.mapper.NewRoleMapper">
|
||||
|
||||
<resultMap id="roleMap" type="com.ljwx.platform.app.domain.model.Role">
|
||||
<id property="id" column="id"/>
|
||||
<result property="tenantId" column="tenant_id"/>
|
||||
<result property="code" column="code"/>
|
||||
<result property="name" column="name"/>
|
||||
<result property="description" column="description"/>
|
||||
<result property="sortOrder" column="sort_order"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="isDeleted" column="is_deleted"/>
|
||||
<result property="createUser" column="create_user"/>
|
||||
<result property="createUserId" column="create_user_id"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateUser" column="update_user"/>
|
||||
<result property="updateUserId" column="update_user_id"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- List all active roles for a tenant, ordered by sort_order then id -->
|
||||
<select id="selectAllByTenantId" resultMap="roleMap">
|
||||
SELECT * FROM roles
|
||||
WHERE tenant_id = #{tenantId} AND is_deleted = 0
|
||||
ORDER BY sort_order ASC, id ASC
|
||||
</select>
|
||||
|
||||
<select id="selectById" resultMap="roleMap">
|
||||
SELECT * FROM roles
|
||||
WHERE id = #{id} AND tenant_id = #{tenantId} AND is_deleted = 0
|
||||
</select>
|
||||
|
||||
<select id="selectByCode" resultMap="roleMap">
|
||||
SELECT * FROM roles
|
||||
WHERE code = #{code} AND tenant_id = #{tenantId} AND is_deleted = 0
|
||||
</select>
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO roles (tenant_id, code, name, description, sort_order, status, is_deleted,
|
||||
create_user, create_user_id, create_time)
|
||||
VALUES (#{tenantId}, #{code}, #{name}, #{description}, #{sortOrder}, #{status}, 0,
|
||||
#{createUser}, #{createUserId}, #{createTime})
|
||||
</insert>
|
||||
|
||||
<update id="update">
|
||||
UPDATE roles
|
||||
SET name = #{name},
|
||||
description = #{description},
|
||||
sort_order = #{sortOrder},
|
||||
status = #{status},
|
||||
update_user = #{updateUser},
|
||||
update_user_id = #{updateUserId},
|
||||
update_time = #{updateTime}
|
||||
WHERE id = #{id} AND tenant_id = #{tenantId} AND is_deleted = 0
|
||||
</update>
|
||||
|
||||
<update id="softDelete">
|
||||
UPDATE roles
|
||||
SET is_deleted = 1,
|
||||
update_user = #{updateUser},
|
||||
update_user_id = #{updateUserId},
|
||||
update_time = NOW()
|
||||
WHERE id = #{id} AND tenant_id = #{tenantId} AND is_deleted = 0
|
||||
</update>
|
||||
|
||||
<!-- permission binding -->
|
||||
<select id="selectPermissionIdsByRoleId" resultType="java.lang.Long">
|
||||
SELECT permission_id FROM role_permissions WHERE role_id = #{roleId}
|
||||
</select>
|
||||
|
||||
<delete id="deletePermissionsByRoleId">
|
||||
DELETE FROM role_permissions WHERE role_id = #{roleId}
|
||||
</delete>
|
||||
|
||||
<insert id="insertRolePermission">
|
||||
INSERT INTO role_permissions (role_id, permission_id)
|
||||
VALUES (#{roleId}, #{permissionId})
|
||||
</insert>
|
||||
|
||||
<select id="countPermissionById" resultType="int">
|
||||
SELECT COUNT(*) FROM permissions WHERE id = #{permissionId}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ljwx.platform.app.infrastructure.mybatis.mapper.NewUserRoleMapper">
|
||||
|
||||
<select id="selectRoleIdsByUserId" resultType="java.lang.Long">
|
||||
SELECT role_id FROM user_roles
|
||||
WHERE user_id = #{userId} AND tenant_id = #{tenantId} AND is_deleted = 0
|
||||
</select>
|
||||
|
||||
<update id="softDeleteByUserId">
|
||||
UPDATE user_roles
|
||||
SET is_deleted = 1,
|
||||
update_user = #{updateUser},
|
||||
update_user_id = #{updateUserId},
|
||||
update_time = NOW()
|
||||
WHERE user_id = #{userId} AND tenant_id = #{tenantId} AND is_deleted = 0
|
||||
</update>
|
||||
|
||||
<update id="softDeleteByUserIdAndRoleId">
|
||||
UPDATE user_roles
|
||||
SET is_deleted = 1,
|
||||
update_user = #{updateUser},
|
||||
update_user_id = #{updateUserId},
|
||||
update_time = NOW()
|
||||
WHERE user_id = #{userId} AND role_id = #{roleId}
|
||||
AND tenant_id = #{tenantId} AND is_deleted = 0
|
||||
</update>
|
||||
|
||||
<insert id="batchInsert">
|
||||
INSERT INTO user_roles (tenant_id, user_id, role_id, is_deleted,
|
||||
create_user, create_user_id, create_time)
|
||||
VALUES
|
||||
<foreach collection="list" item="ur" separator=",">
|
||||
(#{ur.tenantId}, #{ur.userId}, #{ur.roleId}, 0,
|
||||
#{ur.createUser}, #{ur.createUserId}, #{ur.createTime})
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<select id="countActiveBinding" resultType="int">
|
||||
SELECT COUNT(*) FROM user_roles
|
||||
WHERE user_id = #{userId} AND role_id = #{roleId}
|
||||
AND tenant_id = #{tenantId} AND is_deleted = 0
|
||||
</select>
|
||||
|
||||
<update id="softDeleteByRoleId">
|
||||
UPDATE user_roles
|
||||
SET is_deleted = 1,
|
||||
update_user = #{updateUser},
|
||||
update_user_id = #{updateUserId},
|
||||
update_time = NOW()
|
||||
WHERE role_id = #{roleId} AND tenant_id = #{tenantId} AND is_deleted = 0
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.ljwx.platform.app.facade;
|
||||
|
||||
import com.ljwx.platform.app.domain.model.Role;
|
||||
import com.ljwx.platform.app.facade.dto.role.*;
|
||||
import com.ljwx.platform.app.service.RoleAppService;
|
||||
import com.ljwx.platform.app.service.UserRoleAppService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.*;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class RoleFacadeTest {
|
||||
|
||||
@Mock private RoleAppService roleAppService;
|
||||
@Mock private UserRoleAppService userRoleAppService;
|
||||
@InjectMocks private RoleFacade facade;
|
||||
|
||||
@Test
|
||||
void create_returnsResponse() {
|
||||
Role created = new Role();
|
||||
created.setId(1L); created.setCode("EDITOR"); created.setName("编辑");
|
||||
when(roleAppService.create(any())).thenReturn(created);
|
||||
|
||||
CreateRoleRequest req = new CreateRoleRequest();
|
||||
req.setCode("EDITOR"); req.setName("编辑");
|
||||
|
||||
RoleResponse resp = facade.create(req);
|
||||
assertEquals(1L, resp.getId());
|
||||
assertEquals("EDITOR", resp.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void delete_delegatesToService() {
|
||||
facade.delete(1L);
|
||||
verify(roleAppService).delete(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void assignUserRoles_delegatesToService() {
|
||||
AssignUserRolesRequest req = new AssignUserRolesRequest();
|
||||
req.setUserId(100L); req.setRoleIds(List.of(1L, 2L));
|
||||
facade.assignUserRoles(req);
|
||||
verify(userRoleAppService).assignRoles(100L, List.of(1L, 2L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getUserRoleIds_returnsIds() {
|
||||
when(userRoleAppService.getRoleIds(100L)).thenReturn(List.of(1L, 2L));
|
||||
List<Long> ids = facade.getUserRoleIds(100L);
|
||||
assertEquals(2, ids.size());
|
||||
}
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
package com.ljwx.platform.app.integration;
|
||||
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@SpringBootTest
|
||||
@ActiveProfiles("test")
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
class AuditFieldAlignmentIT {
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbc;
|
||||
|
||||
private static final String[] TABLES = {
|
||||
"users", "dict_types", "dict_data", "sys_configs",
|
||||
"departments", "menus", "roles", "user_roles"
|
||||
};
|
||||
|
||||
@Test @Order(1)
|
||||
void createUser_isNotNull() {
|
||||
for (String tbl : TABLES) {
|
||||
String nullable = jdbc.queryForObject(
|
||||
"SELECT is_nullable FROM information_schema.columns " +
|
||||
"WHERE table_name = ? AND column_name = 'create_user'",
|
||||
String.class, tbl);
|
||||
assertEquals("NO", nullable, tbl + ".create_user should be NOT NULL");
|
||||
}
|
||||
}
|
||||
|
||||
@Test @Order(2)
|
||||
void createUserId_isNotNull() {
|
||||
for (String tbl : TABLES) {
|
||||
String nullable = jdbc.queryForObject(
|
||||
"SELECT is_nullable FROM information_schema.columns " +
|
||||
"WHERE table_name = ? AND column_name = 'create_user_id'",
|
||||
String.class, tbl);
|
||||
assertEquals("NO", nullable, tbl + ".create_user_id should be NOT NULL");
|
||||
}
|
||||
}
|
||||
|
||||
@Test @Order(3)
|
||||
void createTime_isNotNull() {
|
||||
for (String tbl : TABLES) {
|
||||
String nullable = jdbc.queryForObject(
|
||||
"SELECT is_nullable FROM information_schema.columns " +
|
||||
"WHERE table_name = ? AND column_name = 'create_time'",
|
||||
String.class, tbl);
|
||||
assertEquals("NO", nullable, tbl + ".create_time should be NOT NULL");
|
||||
}
|
||||
}
|
||||
|
||||
@Test @Order(4)
|
||||
void updateUser_isNullable() {
|
||||
for (String tbl : TABLES) {
|
||||
String nullable = jdbc.queryForObject(
|
||||
"SELECT is_nullable FROM information_schema.columns " +
|
||||
"WHERE table_name = ? AND column_name = 'update_user'",
|
||||
String.class, tbl);
|
||||
assertEquals("YES", nullable, tbl + ".update_user should be nullable");
|
||||
}
|
||||
}
|
||||
|
||||
@Test @Order(5)
|
||||
void updateUserId_isNullable() {
|
||||
for (String tbl : TABLES) {
|
||||
String nullable = jdbc.queryForObject(
|
||||
"SELECT is_nullable FROM information_schema.columns " +
|
||||
"WHERE table_name = ? AND column_name = 'update_user_id'",
|
||||
String.class, tbl);
|
||||
assertEquals("YES", nullable, tbl + ".update_user_id should be nullable");
|
||||
}
|
||||
}
|
||||
|
||||
@Test @Order(6)
|
||||
void updateTime_isNullable() {
|
||||
for (String tbl : TABLES) {
|
||||
String nullable = jdbc.queryForObject(
|
||||
"SELECT is_nullable FROM information_schema.columns " +
|
||||
"WHERE table_name = ? AND column_name = 'update_time'",
|
||||
String.class, tbl);
|
||||
assertEquals("YES", nullable, tbl + ".update_time should be nullable");
|
||||
}
|
||||
}
|
||||
|
||||
@Test @Order(7)
|
||||
void isDeleted_isSmallintNotNullDefaultZero() {
|
||||
for (String tbl : TABLES) {
|
||||
// type
|
||||
String dataType = jdbc.queryForObject(
|
||||
"SELECT data_type FROM information_schema.columns " +
|
||||
"WHERE table_name = ? AND column_name = 'is_deleted'",
|
||||
String.class, tbl);
|
||||
assertEquals("smallint", dataType, tbl + ".is_deleted should be smallint");
|
||||
|
||||
// not null
|
||||
String nullable = jdbc.queryForObject(
|
||||
"SELECT is_nullable FROM information_schema.columns " +
|
||||
"WHERE table_name = ? AND column_name = 'is_deleted'",
|
||||
String.class, tbl);
|
||||
assertEquals("NO", nullable, tbl + ".is_deleted should be NOT NULL");
|
||||
|
||||
// default
|
||||
String def = jdbc.queryForObject(
|
||||
"SELECT column_default FROM information_schema.columns " +
|
||||
"WHERE table_name = ? AND column_name = 'is_deleted'",
|
||||
String.class, tbl);
|
||||
assertNotNull(def, tbl + ".is_deleted should have a default");
|
||||
assertTrue(def.contains("0"), tbl + ".is_deleted default should contain 0");
|
||||
}
|
||||
}
|
||||
|
||||
@Test @Order(8)
|
||||
void createUser_type_isVarchar40() {
|
||||
for (String tbl : TABLES) {
|
||||
Integer maxLen = jdbc.queryForObject(
|
||||
"SELECT character_maximum_length FROM information_schema.columns " +
|
||||
"WHERE table_name = ? AND column_name = 'create_user'",
|
||||
Integer.class, tbl);
|
||||
assertNotNull(maxLen, tbl + ".create_user should be varchar");
|
||||
assertEquals(40, maxLen, tbl + ".create_user should be VARCHAR(40)");
|
||||
}
|
||||
}
|
||||
|
||||
@Test @Order(9)
|
||||
void updateUser_type_isVarchar40() {
|
||||
for (String tbl : TABLES) {
|
||||
Integer maxLen = jdbc.queryForObject(
|
||||
"SELECT character_maximum_length FROM information_schema.columns " +
|
||||
"WHERE table_name = ? AND column_name = 'update_user'",
|
||||
Integer.class, tbl);
|
||||
assertNotNull(maxLen, tbl + ".update_user should be varchar");
|
||||
assertEquals(40, maxLen, tbl + ".update_user should be VARCHAR(40)");
|
||||
}
|
||||
}
|
||||
}
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
package com.ljwx.platform.app.integration;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.ljwx.platform.app.facade.dto.role.*;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@ActiveProfiles("test")
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
class RoleControllerIT {
|
||||
|
||||
@Autowired private MockMvc mockMvc;
|
||||
@Autowired private ObjectMapper objectMapper;
|
||||
|
||||
private static Long createdRoleId;
|
||||
|
||||
// ── authentication ──
|
||||
|
||||
@Test @Order(1)
|
||||
void unauthenticated_returns401() throws Exception {
|
||||
mockMvc.perform(get("/api/v1/roles"))
|
||||
.andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
// ── list ──
|
||||
|
||||
@Test @Order(2)
|
||||
@WithMockUser(username = "admin", authorities = {"role:read"})
|
||||
void list_returnsSeededRoles() throws Exception {
|
||||
mockMvc.perform(get("/api/v1/roles"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data", hasSize(greaterThanOrEqualTo(1))));
|
||||
}
|
||||
|
||||
// ── create ──
|
||||
|
||||
@Test @Order(3)
|
||||
@WithMockUser(username = "admin", authorities = {"role:write"})
|
||||
void create_success() throws Exception {
|
||||
CreateRoleRequest req = new CreateRoleRequest();
|
||||
req.setCode("IT_TEST_ROLE");
|
||||
req.setName("集成测试角色");
|
||||
|
||||
String body = objectMapper.writeValueAsString(req);
|
||||
String response = mockMvc.perform(post("/api/v1/roles")
|
||||
.contentType(MediaType.APPLICATION_JSON).content(body))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.code").value("IT_TEST_ROLE"))
|
||||
.andReturn().getResponse().getContentAsString();
|
||||
|
||||
// extract created ID for later tests
|
||||
createdRoleId = objectMapper.readTree(response).path("data").path("id").asLong();
|
||||
}
|
||||
|
||||
@Test @Order(4)
|
||||
@WithMockUser(username = "admin", authorities = {"role:write"})
|
||||
void create_duplicateCode_returns400() throws Exception {
|
||||
CreateRoleRequest req = new CreateRoleRequest();
|
||||
req.setCode("IT_TEST_ROLE");
|
||||
req.setName("重复角色");
|
||||
|
||||
String body = objectMapper.writeValueAsString(req);
|
||||
mockMvc.perform(post("/api/v1/roles")
|
||||
.contentType(MediaType.APPLICATION_JSON).content(body))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
@Test @Order(5)
|
||||
@WithMockUser(username = "admin", authorities = {"role:write"})
|
||||
void create_invalidRequest_returns400() throws Exception {
|
||||
CreateRoleRequest req = new CreateRoleRequest();
|
||||
// missing code and name
|
||||
String body = objectMapper.writeValueAsString(req);
|
||||
mockMvc.perform(post("/api/v1/roles")
|
||||
.contentType(MediaType.APPLICATION_JSON).content(body))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
// ── get ──
|
||||
|
||||
@Test @Order(6)
|
||||
@WithMockUser(username = "admin", authorities = {"role:read"})
|
||||
void get_success() throws Exception {
|
||||
mockMvc.perform(get("/api/v1/roles/" + createdRoleId))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.id").value(createdRoleId));
|
||||
}
|
||||
|
||||
@Test @Order(7)
|
||||
@WithMockUser(username = "admin", authorities = {"role:read"})
|
||||
void get_notFound_returns404() throws Exception {
|
||||
mockMvc.perform(get("/api/v1/roles/999999"))
|
||||
.andExpect(status().isNotFound());
|
||||
}
|
||||
|
||||
// ── update ──
|
||||
|
||||
@Test @Order(8)
|
||||
@WithMockUser(username = "admin", authorities = {"role:write"})
|
||||
void update_success() throws Exception {
|
||||
UpdateRoleRequest req = new UpdateRoleRequest();
|
||||
req.setName("更新后的名称");
|
||||
req.setSortOrder(10);
|
||||
|
||||
String body = objectMapper.writeValueAsString(req);
|
||||
mockMvc.perform(put("/api/v1/roles/" + createdRoleId)
|
||||
.contentType(MediaType.APPLICATION_JSON).content(body))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.name").value("更新后的名称"))
|
||||
.andExpect(jsonPath("$.data.code").value("IT_TEST_ROLE")); // immutable
|
||||
}
|
||||
|
||||
// ── assign permissions ──
|
||||
|
||||
@Test @Order(9)
|
||||
@WithMockUser(username = "admin", authorities = {"role:write"})
|
||||
void assignPermissions_success() throws Exception {
|
||||
// first, get valid permission IDs from the DB via the role list
|
||||
AssignPermissionsRequest req = new AssignPermissionsRequest();
|
||||
req.setPermissionIds(List.of()); // empty is valid (clears all)
|
||||
|
||||
String body = objectMapper.writeValueAsString(req);
|
||||
mockMvc.perform(put("/api/v1/roles/" + createdRoleId + "/permissions")
|
||||
.contentType(MediaType.APPLICATION_JSON).content(body))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
// ── user-role assignment ──
|
||||
|
||||
@Test @Order(10)
|
||||
@WithMockUser(username = "admin", authorities = {"role:write"})
|
||||
void assignUserRoles_success() throws Exception {
|
||||
AssignUserRolesRequest req = new AssignUserRolesRequest();
|
||||
req.setUserId(1L);
|
||||
req.setRoleIds(List.of(createdRoleId));
|
||||
|
||||
String body = objectMapper.writeValueAsString(req);
|
||||
mockMvc.perform(put("/api/v1/roles/user-roles")
|
||||
.contentType(MediaType.APPLICATION_JSON).content(body))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test @Order(11)
|
||||
@WithMockUser(username = "admin", authorities = {"role:read"})
|
||||
void getUserRoleIds_success() throws Exception {
|
||||
mockMvc.perform(get("/api/v1/roles/user-roles/1"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data", hasSize(greaterThanOrEqualTo(1))));
|
||||
}
|
||||
|
||||
// ── delete ──
|
||||
|
||||
@Test @Order(12)
|
||||
@WithMockUser(username = "admin", authorities = {"role:write"})
|
||||
void delete_success() throws Exception {
|
||||
mockMvc.perform(delete("/api/v1/roles/" + createdRoleId))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test @Order(13)
|
||||
@WithMockUser(username = "admin", authorities = {"role:read"})
|
||||
void get_afterDelete_returns404() throws Exception {
|
||||
mockMvc.perform(get("/api/v1/roles/" + createdRoleId))
|
||||
.andExpect(status().isNotFound());
|
||||
}
|
||||
|
||||
@Test @Order(14)
|
||||
@WithMockUser(username = "admin", authorities = {"role:write"})
|
||||
void delete_adminRole_returns400() throws Exception {
|
||||
// find ADMIN role id
|
||||
String resp = mockMvc.perform(get("/api/v1/roles"))
|
||||
.andReturn().getResponse().getContentAsString();
|
||||
// attempt delete on ADMIN
|
||||
Long adminId = objectMapper.readTree(resp).path("data").get(0).path("id").asLong();
|
||||
mockMvc.perform(delete("/api/v1/roles/" + adminId))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
// ── menus/me tree ──
|
||||
|
||||
@Test @Order(15)
|
||||
@WithMockUser(username = "admin", authorities = {"menu:read", "dept:read", "dict:read"})
|
||||
void menusMe_returnsTree() throws Exception {
|
||||
mockMvc.perform(get("/api/v1/menus/me"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data").isArray());
|
||||
}
|
||||
|
||||
@Test @Order(16)
|
||||
void menusMe_unauthenticated_returns401() throws Exception {
|
||||
mockMvc.perform(get("/api/v1/menus/me"))
|
||||
.andExpect(status().isUnauthorized());
|
||||
}
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
package com.ljwx.platform.app.integration;
|
||||
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@SpringBootTest
|
||||
@ActiveProfiles("test")
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
class UserRoleMigrationIT {
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbc;
|
||||
|
||||
// ── V110: schema checks ──
|
||||
|
||||
@Test @Order(1)
|
||||
void v110_rolesTable_hasTenantId() {
|
||||
Integer cnt = jdbc.queryForObject(
|
||||
"SELECT COUNT(*) FROM information_schema.columns " +
|
||||
"WHERE table_name = 'roles' AND column_name = 'tenant_id'", Integer.class);
|
||||
assertEquals(1, cnt);
|
||||
}
|
||||
|
||||
@Test @Order(2)
|
||||
void v110_userRolesTable_exists() {
|
||||
Integer cnt = jdbc.queryForObject(
|
||||
"SELECT COUNT(*) FROM information_schema.tables " +
|
||||
"WHERE table_name = 'user_roles'", Integer.class);
|
||||
assertEquals(1, cnt);
|
||||
}
|
||||
|
||||
@Test @Order(3)
|
||||
void v110_userRoles_hasAuditColumns() {
|
||||
for (String col : new String[]{"create_user", "create_user_id", "create_time",
|
||||
"update_user", "update_user_id", "update_time", "is_deleted"}) {
|
||||
Integer cnt = jdbc.queryForObject(
|
||||
"SELECT COUNT(*) FROM information_schema.columns " +
|
||||
"WHERE table_name = 'user_roles' AND column_name = ?",
|
||||
Integer.class, col);
|
||||
assertEquals(1, cnt, "Missing column: " + col);
|
||||
}
|
||||
}
|
||||
|
||||
@Test @Order(4)
|
||||
void v110_userRoles_partialUniqueIndex() {
|
||||
Integer cnt = jdbc.queryForObject(
|
||||
"SELECT COUNT(*) FROM pg_indexes " +
|
||||
"WHERE tablename = 'user_roles' AND indexname = 'uk_user_roles_tenant_user_role'",
|
||||
Integer.class);
|
||||
assertEquals(1, cnt);
|
||||
}
|
||||
|
||||
@Test @Order(5)
|
||||
void v110_users_hasNewColumns() {
|
||||
for (String col : new String[]{"nickname", "phone", "email", "avatar", "status", "remark"}) {
|
||||
Integer cnt = jdbc.queryForObject(
|
||||
"SELECT COUNT(*) FROM information_schema.columns " +
|
||||
"WHERE table_name = 'users' AND column_name = ?",
|
||||
Integer.class, col);
|
||||
assertEquals(1, cnt, "Missing column on users: " + col);
|
||||
}
|
||||
}
|
||||
|
||||
// ── V111: seed checks ──
|
||||
|
||||
@Test @Order(6)
|
||||
void v111_ninePermissionsExist() {
|
||||
Integer cnt = jdbc.queryForObject(
|
||||
"SELECT COUNT(*) FROM permissions WHERE key IN " +
|
||||
"('user:read','user:write','role:read','role:write'," +
|
||||
"'dept:read','dict:read','config:read','menu:read','menu:write')",
|
||||
Integer.class);
|
||||
assertEquals(9, cnt);
|
||||
}
|
||||
|
||||
@Test @Order(7)
|
||||
void v111_allAdminRolesHavePermissions() {
|
||||
// every ADMIN role should have exactly 9 permission bindings
|
||||
Integer adminCount = jdbc.queryForObject(
|
||||
"SELECT COUNT(*) FROM roles WHERE code = 'ADMIN' AND is_deleted = 0",
|
||||
Integer.class);
|
||||
if (adminCount > 0) {
|
||||
Integer minBindings = jdbc.queryForObject(
|
||||
"SELECT MIN(cnt) FROM (" +
|
||||
" SELECT r.id, COUNT(rp.permission_id) AS cnt " +
|
||||
" FROM roles r " +
|
||||
" LEFT JOIN role_permissions rp ON rp.role_id = r.id " +
|
||||
" WHERE r.code = 'ADMIN' AND r.is_deleted = 0 " +
|
||||
" GROUP BY r.id" +
|
||||
") sub", Integer.class);
|
||||
assertEquals(9, minBindings, "Not all ADMIN roles have 9 permissions");
|
||||
}
|
||||
}
|
||||
|
||||
@Test @Order(8)
|
||||
void v111_allSecondLevelMenusHavePermissionKey() {
|
||||
// 6 second-level menus under SYSTEM should all have non-null permission_key
|
||||
Integer cnt = jdbc.queryForObject(
|
||||
"SELECT COUNT(*) FROM menus " +
|
||||
"WHERE parent_id = (SELECT id FROM menus WHERE code = 'SYSTEM') " +
|
||||
"AND permission_key IS NOT NULL",
|
||||
Integer.class);
|
||||
assertEquals(6, cnt);
|
||||
}
|
||||
|
||||
@Test @Order(9)
|
||||
void v111_permissionKeyValues() {
|
||||
// verify specific permission keys
|
||||
String[][] expected = {
|
||||
{"MENU_USER", "user:read"},
|
||||
{"MENU_ROLE", "role:read"},
|
||||
{"MENU_DEPT", "dept:read"},
|
||||
{"MENU_DICT", "dict:read"},
|
||||
{"MENU_CONFIG", "config:read"},
|
||||
{"MENU_MENU", "menu:read"}
|
||||
};
|
||||
for (String[] pair : expected) {
|
||||
String actual = jdbc.queryForObject(
|
||||
"SELECT permission_key FROM menus WHERE code = ?",
|
||||
String.class, pair[0]);
|
||||
assertEquals(pair[1], actual, "Wrong permission_key for " + pair[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// ── audit column nullability ──
|
||||
|
||||
@Test @Order(10)
|
||||
void v110_roles_updateColumnsNullable() {
|
||||
for (String col : new String[]{"update_user", "update_user_id", "update_time"}) {
|
||||
String nullable = jdbc.queryForObject(
|
||||
"SELECT is_nullable FROM information_schema.columns " +
|
||||
"WHERE table_name = 'roles' AND column_name = ?",
|
||||
String.class, col);
|
||||
assertEquals("YES", nullable, col + " should be nullable");
|
||||
}
|
||||
}
|
||||
|
||||
@Test @Order(11)
|
||||
void v110_userRoles_updateColumnsNullable() {
|
||||
for (String col : new String[]{"update_user", "update_user_id", "update_time"}) {
|
||||
String nullable = jdbc.queryForObject(
|
||||
"SELECT is_nullable FROM information_schema.columns " +
|
||||
"WHERE table_name = 'user_roles' AND column_name = ?",
|
||||
String.class, col);
|
||||
assertEquals("YES", nullable, col + " should be nullable");
|
||||
}
|
||||
}
|
||||
|
||||
@Test @Order(12)
|
||||
void v110_userRoles_isDeleted_defaultZero() {
|
||||
String def = jdbc.queryForObject(
|
||||
"SELECT column_default FROM information_schema.columns " +
|
||||
"WHERE table_name = 'user_roles' AND column_name = 'is_deleted'",
|
||||
String.class);
|
||||
assertNotNull(def);
|
||||
assertTrue(def.contains("0"), "is_deleted default should contain 0");
|
||||
}
|
||||
|
||||
@Test @Order(13)
|
||||
void v110_roles_isDeleted_defaultZero() {
|
||||
String def = jdbc.queryForObject(
|
||||
"SELECT column_default FROM information_schema.columns " +
|
||||
"WHERE table_name = 'roles' AND column_name = 'is_deleted'",
|
||||
String.class);
|
||||
assertNotNull(def);
|
||||
assertTrue(def.contains("0"), "is_deleted default should contain 0");
|
||||
}
|
||||
}
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
package com.ljwx.platform.app.service;
|
||||
|
||||
import com.ljwx.platform.app.domain.model.Role;
|
||||
import com.ljwx.platform.app.domain.repository.RoleRepository;
|
||||
import com.ljwx.platform.app.domain.repository.UserRoleRepository;
|
||||
import com.ljwx.platform.core.exception.BusinessException;
|
||||
import com.ljwx.platform.web.security.SecurityUtil;
|
||||
import com.ljwx.platform.web.security.UserContext;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.*;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class RoleAppServiceTest {
|
||||
|
||||
@Mock private RoleRepository roleRepository;
|
||||
@Mock private UserRoleRepository userRoleRepository;
|
||||
@InjectMocks private RoleAppService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUpSecurity() {
|
||||
UserContext ctx = new UserContext(1L, 1L, "admin");
|
||||
UsernamePasswordAuthenticationToken auth =
|
||||
new UsernamePasswordAuthenticationToken(ctx, null, List.of());
|
||||
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void clearSecurity() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
// ── list ──
|
||||
|
||||
@Test
|
||||
void list_returnsTenantRoles() {
|
||||
Role r = new Role(); r.setId(1L); r.setCode("ADMIN");
|
||||
when(roleRepository.findAllByTenantId(1L)).thenReturn(List.of(r));
|
||||
List<Role> result = service.list();
|
||||
assertEquals(1, result.size());
|
||||
verify(roleRepository).findAllByTenantId(1L);
|
||||
}
|
||||
|
||||
// ── getById ──
|
||||
|
||||
@Test
|
||||
void getById_found() {
|
||||
Role r = new Role(); r.setId(1L);
|
||||
when(roleRepository.findById(1L, 1L)).thenReturn(Optional.of(r));
|
||||
assertNotNull(service.getById(1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getById_notFound_throwsException() {
|
||||
when(roleRepository.findById(99L, 1L)).thenReturn(Optional.empty());
|
||||
assertThrows(BusinessException.class, () -> service.getById(99L));
|
||||
}
|
||||
|
||||
// ── create ──
|
||||
|
||||
@Test
|
||||
void create_success() {
|
||||
Role input = new Role();
|
||||
input.setCode("EDITOR");
|
||||
input.setName("编辑者");
|
||||
|
||||
when(roleRepository.findByCode("EDITOR", 1L)).thenReturn(Optional.empty());
|
||||
doAnswer(inv -> { inv.getArgument(0, Role.class).setId(10L); return null; })
|
||||
.when(roleRepository).insert(any(Role.class));
|
||||
|
||||
Role created = service.create(input);
|
||||
assertEquals(10L, created.getId());
|
||||
assertEquals(1L, created.getTenantId());
|
||||
assertEquals("admin", created.getCreateUser());
|
||||
assertEquals(1, created.getStatus());
|
||||
verify(roleRepository).insert(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void create_duplicateCode_throwsException() {
|
||||
Role input = new Role(); input.setCode("ADMIN");
|
||||
when(roleRepository.findByCode("ADMIN", 1L)).thenReturn(Optional.of(new Role()));
|
||||
assertThrows(BusinessException.class, () -> service.create(input));
|
||||
}
|
||||
|
||||
// ── update ──
|
||||
|
||||
@Test
|
||||
void update_success() {
|
||||
Role existing = new Role();
|
||||
existing.setId(1L); existing.setTenantId(1L);
|
||||
existing.setCode("ADMIN"); existing.setName("Old");
|
||||
existing.setSortOrder(0); existing.setStatus(1);
|
||||
|
||||
when(roleRepository.findById(1L, 1L)).thenReturn(Optional.of(existing));
|
||||
|
||||
Role updates = new Role();
|
||||
updates.setName("New Name");
|
||||
updates.setDescription("desc");
|
||||
updates.setSortOrder(5);
|
||||
updates.setStatus(0);
|
||||
|
||||
Role result = service.update(1L, updates);
|
||||
assertEquals("New Name", result.getName());
|
||||
assertEquals("desc", result.getDescription());
|
||||
assertEquals(5, result.getSortOrder());
|
||||
assertEquals("ADMIN", result.getCode()); // immutable
|
||||
verify(roleRepository).update(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void update_notFound_throwsException() {
|
||||
when(roleRepository.findById(99L, 1L)).thenReturn(Optional.empty());
|
||||
assertThrows(BusinessException.class, () -> service.update(99L, new Role()));
|
||||
}
|
||||
|
||||
// ── delete ──
|
||||
|
||||
@Test
|
||||
void delete_success() {
|
||||
Role r = new Role(); r.setId(2L); r.setCode("EDITOR"); r.setTenantId(1L);
|
||||
when(roleRepository.findById(2L, 1L)).thenReturn(Optional.of(r));
|
||||
service.delete(2L);
|
||||
verify(roleRepository).deletePermissionsByRoleId(2L);
|
||||
verify(userRoleRepository).softDeleteByRoleId(eq(2L), eq(1L), anyString(), anyLong());
|
||||
verify(roleRepository).softDelete(eq(2L), eq(1L), anyString(), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void delete_adminRole_throwsException() {
|
||||
Role r = new Role(); r.setId(1L); r.setCode("ADMIN");
|
||||
when(roleRepository.findById(1L, 1L)).thenReturn(Optional.of(r));
|
||||
assertThrows(BusinessException.class, () -> service.delete(1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void delete_notFound_throwsException() {
|
||||
when(roleRepository.findById(99L, 1L)).thenReturn(Optional.empty());
|
||||
assertThrows(BusinessException.class, () -> service.delete(99L));
|
||||
}
|
||||
|
||||
// ── assignPermissions ──
|
||||
|
||||
@Test
|
||||
void assignPermissions_success() {
|
||||
Role r = new Role(); r.setId(1L);
|
||||
when(roleRepository.findById(1L, 1L)).thenReturn(Optional.of(r));
|
||||
when(roleRepository.existsPermission(anyLong())).thenReturn(true);
|
||||
|
||||
service.assignPermissions(1L, List.of(10L, 20L, 30L));
|
||||
verify(roleRepository).deletePermissionsByRoleId(1L);
|
||||
verify(roleRepository, times(3)).insertRolePermission(eq(1L), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void assignPermissions_deduplicates() {
|
||||
Role r = new Role(); r.setId(1L);
|
||||
when(roleRepository.findById(1L, 1L)).thenReturn(Optional.of(r));
|
||||
when(roleRepository.existsPermission(anyLong())).thenReturn(true);
|
||||
|
||||
service.assignPermissions(1L, List.of(10L, 10L, 20L, 20L));
|
||||
verify(roleRepository, times(2)).insertRolePermission(eq(1L), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void assignPermissions_invalidPermission_throwsException() {
|
||||
Role r = new Role(); r.setId(1L);
|
||||
when(roleRepository.findById(1L, 1L)).thenReturn(Optional.of(r));
|
||||
when(roleRepository.existsPermission(999L)).thenReturn(false);
|
||||
assertThrows(BusinessException.class,
|
||||
() -> service.assignPermissions(1L, List.of(999L)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void assignPermissions_roleNotFound_throwsException() {
|
||||
when(roleRepository.findById(99L, 1L)).thenReturn(Optional.empty());
|
||||
assertThrows(BusinessException.class,
|
||||
() -> service.assignPermissions(99L, List.of(1L)));
|
||||
}
|
||||
|
||||
// ── create defaults ──
|
||||
|
||||
@Test
|
||||
void create_defaultSortOrder() {
|
||||
Role input = new Role(); input.setCode("NEW"); input.setName("New");
|
||||
when(roleRepository.findByCode("NEW", 1L)).thenReturn(Optional.empty());
|
||||
doAnswer(inv -> { inv.getArgument(0, Role.class).setId(1L); return null; })
|
||||
.when(roleRepository).insert(any());
|
||||
Role result = service.create(input);
|
||||
assertEquals(0, result.getSortOrder());
|
||||
}
|
||||
|
||||
@Test
|
||||
void create_defaultStatus() {
|
||||
Role input = new Role(); input.setCode("NEW"); input.setName("New");
|
||||
when(roleRepository.findByCode("NEW", 1L)).thenReturn(Optional.empty());
|
||||
doAnswer(inv -> { inv.getArgument(0, Role.class).setId(1L); return null; })
|
||||
.when(roleRepository).insert(any());
|
||||
Role result = service.create(input);
|
||||
assertEquals(1, result.getStatus());
|
||||
}
|
||||
|
||||
// ── update immutability ──
|
||||
|
||||
@Test
|
||||
void update_codeRemainsImmutable() {
|
||||
Role existing = new Role();
|
||||
existing.setId(1L); existing.setTenantId(1L);
|
||||
existing.setCode("ORIGINAL"); existing.setName("old");
|
||||
existing.setSortOrder(0); existing.setStatus(1);
|
||||
|
||||
when(roleRepository.findById(1L, 1L)).thenReturn(Optional.of(existing));
|
||||
|
||||
Role updates = new Role();
|
||||
updates.setName("Updated");
|
||||
updates.setCode("HACKED"); // should be ignored
|
||||
|
||||
Role result = service.update(1L, updates);
|
||||
assertEquals("ORIGINAL", result.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void update_auditFieldsPopulated() {
|
||||
Role existing = new Role();
|
||||
existing.setId(1L); existing.setTenantId(1L);
|
||||
existing.setCode("R1"); existing.setName("old");
|
||||
existing.setSortOrder(0); existing.setStatus(1);
|
||||
|
||||
when(roleRepository.findById(1L, 1L)).thenReturn(Optional.of(existing));
|
||||
|
||||
Role updates = new Role(); updates.setName("new");
|
||||
Role result = service.update(1L, updates);
|
||||
assertEquals("admin", result.getUpdateUser());
|
||||
assertNotNull(result.getUpdateTime());
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package com.ljwx.platform.app.service;
|
||||
|
||||
import com.ljwx.platform.app.domain.model.UserRole;
|
||||
import com.ljwx.platform.app.domain.repository.UserRoleRepository;
|
||||
import com.ljwx.platform.web.security.UserContext;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.*;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class UserRoleAppServiceTest {
|
||||
|
||||
@Mock private UserRoleRepository userRoleRepository;
|
||||
@InjectMocks private UserRoleAppService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUpSecurity() {
|
||||
UserContext ctx = new UserContext(1L, 1L, "admin");
|
||||
UsernamePasswordAuthenticationToken auth =
|
||||
new UsernamePasswordAuthenticationToken(ctx, null, List.of());
|
||||
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void clearSecurity() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
// ── getRoleIds ──
|
||||
|
||||
@Test
|
||||
void getRoleIds_returnsList() {
|
||||
when(userRoleRepository.findRoleIdsByUserId(100L, 1L)).thenReturn(List.of(1L, 2L));
|
||||
List<Long> ids = service.getRoleIds(100L);
|
||||
assertEquals(2, ids.size());
|
||||
}
|
||||
|
||||
// ── assignRoles ──
|
||||
|
||||
@Test
|
||||
void assignRoles_softDeletesThenInserts() {
|
||||
service.assignRoles(100L, List.of(1L, 2L));
|
||||
verify(userRoleRepository).softDeleteByUserId(eq(100L), eq(1L), anyString(), anyLong());
|
||||
verify(userRoleRepository).batchInsert(argThat(list -> list.size() == 2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void assignRoles_emptyList_softDeletesOnly() {
|
||||
service.assignRoles(100L, List.of());
|
||||
verify(userRoleRepository).softDeleteByUserId(eq(100L), eq(1L), anyString(), anyLong());
|
||||
verify(userRoleRepository, never()).batchInsert(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void assignRoles_dedupInput_insertsOnce() {
|
||||
service.assignRoles(100L, List.of(1L, 1L, 2L, 2L, 3L));
|
||||
verify(userRoleRepository).batchInsert(argThat(list -> list.size() == 3));
|
||||
}
|
||||
|
||||
// ── addRoles ──
|
||||
|
||||
@Test
|
||||
void addRoles_skipsExisting() {
|
||||
when(userRoleRepository.existsActiveBinding(100L, 1L, 1L)).thenReturn(true);
|
||||
when(userRoleRepository.existsActiveBinding(100L, 2L, 1L)).thenReturn(false);
|
||||
|
||||
service.addRoles(100L, List.of(1L, 2L));
|
||||
verify(userRoleRepository).batchInsert(argThat(list -> list.size() == 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void addRoles_allExisting_noInsert() {
|
||||
when(userRoleRepository.existsActiveBinding(anyLong(), anyLong(), anyLong())).thenReturn(true);
|
||||
service.addRoles(100L, List.of(1L, 2L));
|
||||
verify(userRoleRepository, never()).batchInsert(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void addRoles_dedupInput_insertsOnce() {
|
||||
when(userRoleRepository.existsActiveBinding(100L, 5L, 1L)).thenReturn(false);
|
||||
service.addRoles(100L, List.of(5L, 5L, 5L));
|
||||
verify(userRoleRepository).batchInsert(argThat(list -> list.size() == 1));
|
||||
}
|
||||
|
||||
// ── removeRoles ──
|
||||
|
||||
@Test
|
||||
void removeRoles_softDeletesEach() {
|
||||
service.removeRoles(100L, List.of(1L, 2L));
|
||||
verify(userRoleRepository, times(2))
|
||||
.softDeleteByUserIdAndRoleId(eq(100L), anyLong(), eq(1L), anyString(), anyLong());
|
||||
}
|
||||
|
||||
// ── null/empty handling ──
|
||||
|
||||
@Test
|
||||
void assignRoles_nullList_noError() {
|
||||
service.assignRoles(100L, null);
|
||||
verify(userRoleRepository).softDeleteByUserId(eq(100L), eq(1L), anyString(), anyLong());
|
||||
verify(userRoleRepository, never()).batchInsert(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void addRoles_emptyList_noInsert() {
|
||||
service.addRoles(100L, List.of());
|
||||
verify(userRoleRepository, never()).batchInsert(any());
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,5 @@
|
||||
package com.ljwx.platform.core.exception;
|
||||
|
||||
/**
|
||||
* Centralized error code enumeration.
|
||||
* <p>
|
||||
* All error codes used in the platform MUST be defined here.
|
||||
* Business modules should extend with BIZ_{MODULE}_{DESCRIPTION} pattern.
|
||||
* </p>
|
||||
* <p>
|
||||
* Mapping to HTTP status:
|
||||
* TOKEN_INVALID, TOKEN_EXPIRED → 401
|
||||
* PERMISSION_DENIED, TENANT_ACCESS_DENIED → 403
|
||||
* VALIDATION_FAILED → 400
|
||||
* RESOURCE_NOT_FOUND → 404
|
||||
* SYSTEM_ERROR → 500
|
||||
* </p>
|
||||
*/
|
||||
public enum ErrorCode {
|
||||
|
||||
// --- 401 Unauthorized ---
|
||||
@@ -28,12 +13,18 @@ public enum ErrorCode {
|
||||
|
||||
// --- 400 Bad Request ---
|
||||
VALIDATION_FAILED("VALIDATION_FAILED", "Validation failed", 400),
|
||||
BAD_REQUEST("400", "请求参数错误", 400),
|
||||
|
||||
// --- 404 Not Found ---
|
||||
RESOURCE_NOT_FOUND("RESOURCE_NOT_FOUND", "Resource not found", 404),
|
||||
NOT_FOUND("404", "资源不存在", 404),
|
||||
|
||||
// --- 500 Internal Server Error ---
|
||||
SYSTEM_ERROR("SYSTEM_ERROR", "Internal server error", 500);
|
||||
SYSTEM_ERROR("SYSTEM_ERROR", "Internal server error", 500),
|
||||
INTERNAL_ERROR("500", "系统异常", 500),
|
||||
|
||||
// --- alias for 403 ---
|
||||
FORBIDDEN("403", "权限不足", 403);
|
||||
|
||||
private final String code;
|
||||
private final String message;
|
||||
|
||||
@@ -54,6 +54,14 @@ public class Result<T> {
|
||||
return new Result<>(code, message, null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> fail(String code, String message) {
|
||||
return error(code, message);
|
||||
}
|
||||
|
||||
public static <T> Result<T> fail(int code, String message) {
|
||||
return error(String.valueOf(code), message);
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
@@ -85,4 +93,4 @@ public class Result<T> {
|
||||
public void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.ljwx.platform.web.security;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Centralized utility for extracting current user info from the security context.
|
||||
* <p>
|
||||
* Priority: UserContext fields → auth.getName() → fallback defaults.
|
||||
*/
|
||||
public final class SecurityUtil {
|
||||
|
||||
private SecurityUtil() {}
|
||||
|
||||
/**
|
||||
* Returns the current {@link UserContext}, or {@code null} if not available.
|
||||
*/
|
||||
public static UserContext currentUserContext() {
|
||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (auth != null && auth.getPrincipal() instanceof UserContext ctx) {
|
||||
return ctx;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isAuthenticated() {
|
||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
return auth != null && auth.isAuthenticated()
|
||||
&& !"anonymousUser".equals(auth.getPrincipal());
|
||||
}
|
||||
|
||||
/**
|
||||
* Current username. Falls back to auth.getName(), then "system".
|
||||
*/
|
||||
public static String currentUsername() {
|
||||
UserContext ctx = currentUserContext();
|
||||
if (ctx != null && ctx.getUsername() != null && !ctx.getUsername().isBlank()) {
|
||||
return ctx.getUsername();
|
||||
}
|
||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (auth != null && auth.getName() != null && !auth.getName().isBlank()
|
||||
&& !"anonymousUser".equals(auth.getName())) {
|
||||
return auth.getName();
|
||||
}
|
||||
return "system";
|
||||
}
|
||||
|
||||
/**
|
||||
* Current user ID. Falls back to 0.
|
||||
*/
|
||||
public static Long currentUserId() {
|
||||
UserContext ctx = currentUserContext();
|
||||
if (ctx != null && ctx.getUserId() != null) {
|
||||
return ctx.getUserId();
|
||||
}
|
||||
return 0L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current tenant ID. Falls back to 1 (default tenant).
|
||||
*/
|
||||
public static Long currentTenantId() {
|
||||
UserContext ctx = currentUserContext();
|
||||
if (ctx != null && ctx.getTenantId() != null) {
|
||||
return ctx.getTenantId();
|
||||
}
|
||||
return 1L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of authority strings for the current user.
|
||||
*/
|
||||
public static Set<String> currentAuthorities() {
|
||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (auth == null) return Collections.emptySet();
|
||||
Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
|
||||
if (authorities == null) return Collections.emptySet();
|
||||
return authorities.stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
@@ -2,53 +2,34 @@ package com.ljwx.platform.web.security;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 认证用户上下文,由 JWT Filter 解析后设置到 Authentication#principal。
|
||||
* 所有 AppService 通过此类获取当前用户信息。
|
||||
*/
|
||||
public class UserContext implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long userId;
|
||||
private String username;
|
||||
private Long tenantId;
|
||||
private String username;
|
||||
|
||||
public UserContext() {
|
||||
}
|
||||
public UserContext() {}
|
||||
|
||||
public UserContext(Long userId, String username, Long tenantId) {
|
||||
public UserContext(Long userId, Long tenantId, String username) {
|
||||
this.userId = userId;
|
||||
this.username = username;
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
|
||||
public Long getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(Long userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public Long getTenantId() {
|
||||
return tenantId;
|
||||
}
|
||||
public Long getUserId() { return userId; }
|
||||
public void setUserId(Long userId) { this.userId = userId; }
|
||||
|
||||
public void setTenantId(Long tenantId) {
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
public Long getTenantId() { return tenantId; }
|
||||
public void setTenantId(Long tenantId) { this.tenantId = tenantId; }
|
||||
|
||||
public String getUsername() { return username; }
|
||||
public void setUsername(String username) { this.username = username; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return username != null ? username : "UserContext[" + userId + "]";
|
||||
return "UserContext{userId=" + userId + ", tenantId=" + tenantId +
|
||||
", username='" + username + "'}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
package com.ljwx.platform.web.security;
|
||||
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class SecurityUtilTest {
|
||||
|
||||
@AfterEach
|
||||
void clearContext() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
// ── currentUsername ──
|
||||
|
||||
@Test
|
||||
void currentUsername_fromUserContext() {
|
||||
setUpAuth(new UserContext(1L, 1L, "alice"));
|
||||
assertEquals("alice", SecurityUtil.currentUsername());
|
||||
}
|
||||
|
||||
@Test
|
||||
void currentUsername_fallsBackToAuthName() {
|
||||
SecurityContextHolder.getContext().setAuthentication(
|
||||
new UsernamePasswordAuthenticationToken("bob", null, List.of()));
|
||||
assertEquals("bob", SecurityUtil.currentUsername());
|
||||
}
|
||||
|
||||
@Test
|
||||
void currentUsername_fallsBackToSystem() {
|
||||
SecurityContextHolder.clearContext();
|
||||
assertEquals("system", SecurityUtil.currentUsername());
|
||||
}
|
||||
|
||||
@Test
|
||||
void currentUsername_blankUsername_fallsBackToAuthName() {
|
||||
setUpAuth(new UserContext(1L, 1L, " "));
|
||||
// auth.getName() returns UserContext.toString(), not blank
|
||||
// so it will fall back to auth.getName()
|
||||
assertNotNull(SecurityUtil.currentUsername());
|
||||
}
|
||||
|
||||
// ── currentUserId ──
|
||||
|
||||
@Test
|
||||
void currentUserId_fromUserContext() {
|
||||
setUpAuth(new UserContext(42L, 1L, "admin"));
|
||||
assertEquals(42L, SecurityUtil.currentUserId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void currentUserId_fallsBackToZero() {
|
||||
SecurityContextHolder.clearContext();
|
||||
assertEquals(0L, SecurityUtil.currentUserId());
|
||||
}
|
||||
|
||||
// ── currentTenantId ──
|
||||
|
||||
@Test
|
||||
void currentTenantId_fromUserContext() {
|
||||
setUpAuth(new UserContext(1L, 5L, "admin"));
|
||||
assertEquals(5L, SecurityUtil.currentTenantId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void currentTenantId_fallsBackToOne() {
|
||||
SecurityContextHolder.clearContext();
|
||||
assertEquals(1L, SecurityUtil.currentTenantId());
|
||||
}
|
||||
|
||||
// ── isAuthenticated ──
|
||||
|
||||
@Test
|
||||
void isAuthenticated_true() {
|
||||
setUpAuth(new UserContext(1L, 1L, "admin"));
|
||||
assertTrue(SecurityUtil.isAuthenticated());
|
||||
}
|
||||
|
||||
@Test
|
||||
void isAuthenticated_false_noAuth() {
|
||||
SecurityContextHolder.clearContext();
|
||||
assertFalse(SecurityUtil.isAuthenticated());
|
||||
}
|
||||
|
||||
@Test
|
||||
void isAuthenticated_false_anonymous() {
|
||||
SecurityContextHolder.getContext().setAuthentication(
|
||||
new UsernamePasswordAuthenticationToken("anonymousUser", null, List.of()));
|
||||
assertFalse(SecurityUtil.isAuthenticated());
|
||||
}
|
||||
|
||||
// ── currentAuthorities ──
|
||||
|
||||
@Test
|
||||
void currentAuthorities_returnSet() {
|
||||
SecurityContextHolder.getContext().setAuthentication(
|
||||
new UsernamePasswordAuthenticationToken(
|
||||
new UserContext(1L, 1L, "admin"), null,
|
||||
List.of(new SimpleGrantedAuthority("role:read"),
|
||||
new SimpleGrantedAuthority("role:write"))));
|
||||
Set<String> auths = SecurityUtil.currentAuthorities();
|
||||
assertEquals(2, auths.size());
|
||||
assertTrue(auths.contains("role:read"));
|
||||
assertTrue(auths.contains("role:write"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void currentAuthorities_empty_noAuth() {
|
||||
SecurityContextHolder.clearContext();
|
||||
assertTrue(SecurityUtil.currentAuthorities().isEmpty());
|
||||
}
|
||||
|
||||
// ── currentUserContext ──
|
||||
|
||||
@Test
|
||||
void currentUserContext_returnsInstance() {
|
||||
UserContext ctx = new UserContext(1L, 1L, "admin");
|
||||
setUpAuth(ctx);
|
||||
assertSame(ctx, SecurityUtil.currentUserContext());
|
||||
}
|
||||
|
||||
@Test
|
||||
void currentUserContext_null_noAuth() {
|
||||
SecurityContextHolder.clearContext();
|
||||
assertNull(SecurityUtil.currentUserContext());
|
||||
}
|
||||
|
||||
// ── helpers ──
|
||||
|
||||
private void setUpAuth(UserContext ctx) {
|
||||
SecurityContextHolder.getContext().setAuthentication(
|
||||
new UsernamePasswordAuthenticationToken(ctx, null, List.of()));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user