这是基于笔者的一些经验设计并加以完善的方案,仅供参考。
处方流转平台需要严格的权限控制,确保:
用户(User) ← 用户-角色关联 → 角色(Role) ← 角色-权限关联 → 权限(Permission)
-- 用户表
CREATE TABLE users (
user_id VARCHAR(36) PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
real_name VARCHAR(100),
department_id VARCHAR(36),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 角色表
CREATE TABLE roles (
role_id VARCHAR(36) PRIMARY KEY,
role_name VARCHAR(50) NOT NULL UNIQUE,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 权限表
CREATE TABLE permissions (
permission_id VARCHAR(36) PRIMARY KEY,
permission_code VARCHAR(100) NOT NULL UNIQUE,
permission_name VARCHAR(100) NOT NULL,
resource_type VARCHAR(50) NOT NULL, -- 如"处方"、"患者"、"药品"等
action VARCHAR(50) NOT NULL, -- 如"create","read","update","delete"
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 用户-角色关联表
CREATE TABLE user_roles (
user_id VARCHAR(36) NOT NULL,
role_id VARCHAR(36) NOT NULL,
PRIMARY KEY (user_id, role_id),
FOREIGN KEY (user_id) REFERENCES users(user_id),
FOREIGN KEY (role_id) REFERENCES roles(role_id)
);
-- 角色-权限关联表
CREATE TABLE role_permissions (
role_id VARCHAR(36) NOT NULL,
permission_id VARCHAR(36) NOT NULL,
PRIMARY KEY (role_id, permission_id),
FOREIGN KEY (role_id) REFERENCES roles(role_id),
FOREIGN KEY (permission_id) REFERENCES permissions(permission_id)
);
医生(Doctor)
药师(Pharmacist)
药房管理员(PharmacyAdmin)
系统管理员(SystemAdmin)
患者(Patient)
1. 用户登录 → 获取JWT令牌(包含用户ID和角色)
2. 每次API请求携带JWT
3. 权限拦截器:
a. 解析JWT获取用户角色
b. 查询角色对应的权限集
c. 验证当前请求资源+操作是否在权限集中
4. 通过 → 继续处理; 拒绝 → 返回403
// 自定义权限注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiredPermission {
String resource();
String action();
}
// 权限拦截器
@Component
public class PermissionInterceptor implements HandlerInterceptor {
@Autowired
private PermissionService permissionService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 1. 获取当前用户角色
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String roleId = auth.getPrincipal().getRoleId();
// 2. 获取请求资源和方法
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
String requestUri = request.getRequestURI();
// 3. 检查方法级权限注解
if (method.isAnnotationPresent(RequiredPermission.class)) {
RequiredPermission permission = method.getAnnotation(RequiredPermission.class);
if (!permissionService.checkPermission(roleId, permission.resource(), permission.action())) {
throw new AccessDeniedException("权限不足");
}
}
// 4. 额外业务规则检查(如数据归属)
checkDataOwnership(auth, request);
return true;
}
private void checkDataOwnership(Authentication auth, HttpServletRequest request) {
// 示例:验证患者只能访问自己的处方
if (request.getRequestURI().startsWith("/api/prescriptions") && "PATIENT".equals(auth.getRole())) {
String prescriptionId = request.getParameter("id");
if (!prescriptionService.isOwnedByPatient(prescriptionId, auth.getUserId())) {
throw new AccessDeniedException("无权访问该资源");
}
}
}
}
@Service
public class PermissionServiceImpl implements PermissionService {
@Autowired
private RolePermissionMapper rolePermissionMapper;
@Override
public boolean checkPermission(String roleId, String resource, String action) {
// 1. 查询角色所有权限
List<Permission> permissions = rolePermissionMapper.findByRoleId(roleId);
// 2. 检查是否包含所需权限
return permissions.stream()
.anyMatch(p -> p.getResourceType().equals(resource)
&& p.getAction().equals(action));
}
@Override
public Set<String> getUserPermissions(String userId) {
// 获取用户所有权限(用于前端动态菜单)
return rolePermissionMapper.findPermissionsByUserId(userId)
.stream()
.map(p -> p.getResourceType() + ":" + p.getAction())
.collect(Collectors.toSet());
}
}
除常规RBAC外,增加数据过滤规则:
// 在数据访问层自动添加过滤条件
@Repository
public class PrescriptionRepositoryImpl implements PrescriptionRepositoryCustom {
@Autowired
private SecurityContext securityContext;
@Override
public List<Prescription> findAccessiblePrescriptions() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Prescription> query = cb.createQuery(Prescription.class);
Root<Prescription> root = query.from(Prescription.class);
// 根据用户角色添加不同过滤条件
if (securityContext.isDoctor()) {
query.where(cb.equal(root.get("creatorId"), securityContext.getUserId()));
} else if (securityContext.isPharmacist()) {
// 药师可查看所有处方
} else if (securityContext.isPatient()) {
query.where(cb.equal(root.get("patientId"), securityContext.getUserId()));
}
return entityManager.createQuery(query).getResultList();
}
}
处方流转平台的权限控制模块设计应:
这种设计可以满足处方流转平台复杂的权限控制需求,同时保持系统的可维护性和扩展性。