feat:后台用户关系绑定

This commit is contained in:
dzc committed 2026-07-20 15:48:59 +08:00
1 parent abb9e124f6
commit c2ab16c1fc
33 files changed
+1794 -3

No files matched your search

@@ -0,0 +1,31 @@
package com.bsd.pjcloud.admin.api.constant.user;
import cn.hutool.core.util.ObjectUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 用户身份
*/
@AllArgsConstructor
@Getter
public enum UserIdentityTypeEnum {
DEFAULT("DEFAULT", "默认"),
STUDENT("STUDENT", "学生"),
;
private final String type;
private final String description;
public static UserIdentityTypeEnum valueOfByType(String type) {
if (ObjectUtil.isEmpty(type)) return null;
return Arrays.stream(values())
.filter(value -> ObjectUtil.equals(type, value.getType()))
.findFirst()
.orElse(null);
}
}
@@ -0,0 +1,37 @@
package com.bsd.pjcloud.admin.api.constant.user;
import cn.hutool.core.util.ObjectUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 用户关系类型分类
*/
@AllArgsConstructor
@Getter
public enum UserRelationCategoryEnum {
FAMILY("family", "家庭关系"),
BUSINESS("business", "业务关系"),
SOCIAL("social", "社交关系");
/**
* 分类编码
*/
private final String category;
/**
* 分类名称
*/
private final String description;
public static UserRelationCategoryEnum valueOfByCategory(String category) {
if (ObjectUtil.isEmpty(category)) return null;
return Arrays.stream(values())
.filter(value -> ObjectUtil.equals(category, value.getCategory()))
.findFirst()
.orElse(null);
}
}
@@ -0,0 +1,61 @@
package com.bsd.pjcloud.admin.api.constant.user;
import cn.hutool.core.util.ObjectUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 用户关系类型
*/
@AllArgsConstructor
@Getter
public enum UserRelationTypeEnum {
// 家庭关系
CHILD_PARENT("CHILD_PARENT", "亲子关系"),
SPOUSE("SPOUSE", "夫妻关系"),
SIBLING("SIBLING", "兄弟姐妹关系"),
GRANDCHILD_GRANDPARENT("GRANDCHILD_GRANDPARENT", "祖孙关系"),
GUARDIAN_WARD("GUARDIAN_WARD", "监护关系"),
RELATIVE("RELATIVE", "亲属关系"),
// 社交关系
FRIEND("FRIEND", "朋友关系"),
CLASSMATE("CLASSMATE", "同学关系"),
COLLEAGUE("COLLEAGUE", "同事关系"),
NEIGHBOR("NEIGHBOR", "邻里关系"),
// 教育关系
STUDENT_TEACHER("STUDENT_TEACHER", "师生关系"),
// 工作关系
SUBORDINATE_LEADER("SUBORDINATE_LEADER", "上下级关系"),
EMPLOYEE_EMPLOYER("EMPLOYEE_EMPLOYER", "雇佣关系"),
// 客户关系
CUSTOMER_MANAGER("CUSTOMER_MANAGER", "客户关系"),
CUSTOMER_SERVICE("CUSTOMER_SERVICE", "客服关系"),
// 其他业务关系
REFEREE_REFERRER("REFEREE_REFERRER", "推荐关系"),
CONTACT("CONTACT", "联系人关系"),
OTHER("OTHER", "其他关系");
/**
* 关系编码
*/
private final String relationCode;
/**
* 关系名称
*/
private final String relationName;
public static UserRelationTypeEnum valueOfByType(String relationCode) {
if (ObjectUtil.isEmpty(relationCode)) return null;
return Arrays.stream(values()).filter(v -> ObjectUtil.equals(relationCode, v.getRelationCode())).findFirst().orElse(null);
}
}
@@ -19,6 +19,7 @@
package com.bsd.pjcloud.admin.api.dto;
import com.bsd.pjcloud.admin.api.dto.user.SysUserStudentDto;
import com.bsd.pjcloud.admin.api.entity.SysUser;
import com.bsd.pjcloud.admin.api.vo.base.BaseIsIncludeTreeInterface;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -54,6 +55,18 @@ public class UserDTO extends SysUser implements BaseIsIncludeTreeInterface {
*/
private List<Long> post;
/**
* 用户身份类型
*/
@Schema(description = "用户身份类型集合")
private List<String> identityTypes;
/**
* 学生身份扩展信息
*/
@Schema(description = "学生身份扩展信息")
private SysUserStudentDto studentInfo;
/**
* 新密码
*/
@@ -0,0 +1,45 @@
package com.bsd.pjcloud.admin.api.dto.user;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 为两个已有用户建立关系
*/
@Data
@Schema(description = "为两个已有用户建立关系")
public class SysUserRelationCreateDto {
@NotNull(message = "关系主语用户不能为空")
@Schema(description = "关系主语用户ID")
private Long subjectUserId;
@NotNull(message = "关系对象用户不能为空")
@Schema(description = "关系对象用户ID")
private Long objectUserId;
@NotNull(message = "关系类型不能为空")
@Schema(description = "关系类型ID")
private Long relationTypeId;
@Schema(description = "关系生效时间")
private LocalDateTime effectiveTime;
@Schema(description = "关系失效时间")
private LocalDateTime expireTime;
@Size(max = 500, message = "关系备注不能超过500个字符")
@Schema(description = "关系备注")
private String remark;
@AssertTrue(message = "关系失效时间不能早于生效时间")
@Schema(hidden = true)
public boolean isRelationTimeValid() {
return effectiveTime == null || expireTime == null || !expireTime.isBefore(effectiveTime);
}
}
@@ -0,0 +1,115 @@
package com.bsd.pjcloud.admin.api.dto.user;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
/**
* 创建用户并建立用户关系
*/
@Data
@Schema(description = "创建用户并建立用户关系")
public class SysUserRelationCreateUserDto {
@Schema(description = "关系主语用户ID;为空时由新建用户作为关系主语")
private Long subjectUserId;
@Schema(description = "关系对象用户ID;为空时由新建用户作为关系对象")
private Long objectUserId;
@NotNull(message = "关系类型不能为空")
@Schema(description = "关系类型ID")
private Long relationTypeId;
@Schema(description = "关系生效时间")
private LocalDateTime effectiveTime;
@Schema(description = "关系失效时间")
private LocalDateTime expireTime;
@Size(max = 500, message = "关系备注不能超过500个字符")
@Schema(description = "关系备注")
private String remark;
@NotBlank(message = "用户名不能为空")
@Size(min = 5, max = 60, message = "用户名长度必须介于5到60个字符之间")
@Pattern(regexp = "^[^\\u4e00-\\u9fa5]+$", message = "用户名不能包含中文")
@Schema(description = "用户名")
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 20, message = "密码长度必须介于6到20个字符之间")
@Schema(description = "密码")
private String password;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
@Schema(description = "手机号")
private String phone;
@NotBlank(message = "姓名不能为空")
@Size(max = 64, message = "姓名不能超过64个字符")
@Schema(description = "姓名")
private String name;
@Size(max = 64, message = "昵称不能超过64个字符")
@Schema(description = "昵称")
private String nickname;
@Email(message = "邮箱格式不正确")
@Size(max = 128, message = "邮箱不能超过128个字符")
@Schema(description = "邮箱")
private String email;
@Size(max = 255, message = "头像地址不能超过255个字符")
@Schema(description = "头像")
private String avatar;
@Schema(description = "所属部门ID")
private Long deptId;
@Schema(description = "角色ID集合,为空时使用默认角色")
private List<Long> role;
@Schema(description = "岗位ID集合")
private List<Long> post;
@Min(value = 0, message = "性别值不正确")
@Max(value = 2, message = "性别值不正确")
@Schema(description = "性别:0未知,1男,2女")
private Integer gender;
@Size(max = 32, message = "编制类型不能超过32个字符")
@Schema(description = "编制类型")
private String staffingType;
@Schema(description = "用户状态:0正常,9锁定")
private String lockFlag;
@Schema(description = "用户身份类型集合")
private List<String> identityTypes;
@Schema(description = "学生身份扩展信息")
private SysUserStudentDto studentInfo;
@AssertTrue(message = "关系主语用户和关系对象用户必须且只能填写一个")
@Schema(hidden = true)
public boolean isRelationEndpointValid() {
return (subjectUserId == null) != (objectUserId == null);
}
@AssertTrue(message = "关系失效时间不能早于生效时间")
@Schema(hidden = true)
public boolean isRelationTimeValid() {
return effectiveTime == null || expireTime == null || !expireTime.isBefore(effectiveTime);
}
}
@@ -0,0 +1,24 @@
package com.bsd.pjcloud.admin.api.dto.user;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 学生身份扩展信息
*/
@Data
@Schema(description = "学生身份扩展信息")
public class SysUserStudentDto {
@Schema(description = "学号")
private String studentNo;
@Schema(description = "学校名称")
private String schoolName;
@Schema(description = "班级名称")
private String className;
@Schema(description = "其他非核心扩展信息")
private String extJson;
}
@@ -0,0 +1,39 @@
package com.bsd.pjcloud.admin.api.entity.user;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* 用户身份表
*/
@Data
@TableName("sys_user_identity")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "用户身份表")
public class SysUserIdentityEntity extends Model<SysUserIdentityEntity> {
/**
* 用户ID
*/
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "用户ID")
private Long userId;
/**
* 身份类型:STUDENT学生、TEACHER教师等
*/
@Schema(description = "身份类型:STUDENT学生、TEACHER教师等")
private String identityType;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建时间")
private LocalDateTime createTime;
}
@@ -0,0 +1,106 @@
package com.bsd.pjcloud.admin.api.entity.user;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import com.bsd.pjcloud.common.core.util.TenantTable;
import java.time.LocalDateTime;
/**
* 用户多对多关系表
*/
@Data
@TenantTable
@TableName("sys_user_relation")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "用户多对多关系表")
public class SysUserRelationEntity extends Model<SysUserRelationEntity> {
/**
* 关系ID
*/
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "关系ID")
private Long relationId;
/**
* 主语用户ID
*/
@Schema(description = "主语用户ID")
private Long subjectUserId;
/**
* 对象用户ID
*/
@Schema(description = "对象用户ID")
private Long objectUserId;
/**
* 关系类型ID
*/
@Schema(description = "关系类型ID")
private Long relationTypeId;
/**
* 关系生效时间
*/
@Schema(description = "关系生效时间")
private LocalDateTime effectiveTime;
/**
* 关系失效时间
*/
@Schema(description = "关系失效时间")
private LocalDateTime expireTime;
/**
* 备注
*/
@Schema(description = "备注")
private String remark;
/**
* 创建人
*/
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建人")
private String createBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建时间")
private LocalDateTime createTime;
/**
* 修改人
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description = "修改人")
private String updateBy;
/**
* 修改时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description = "修改时间")
private LocalDateTime updateTime;
/**
* 删除标记,0未删除,1已删除
*/
@TableLogic
@TableField(fill = FieldFill.INSERT)
@Schema(description = "删除标记,0未删除,1已删除")
private String delFlag;
/**
* 所属租户ID
*/
@Schema(description = "所属租户ID")
private Long tenantId;
}
@@ -0,0 +1,124 @@
package com.bsd.pjcloud.admin.api.entity.user;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import com.bsd.pjcloud.common.core.util.TenantTable;
import java.time.LocalDateTime;
/**
* 用户关系类型表
*/
@Data
@TenantTable
@TableName("sys_user_relation_type")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "用户关系类型表")
public class SysUserRelationTypeEntity extends Model<SysUserRelationTypeEntity> {
/**
* 关系类型
*/
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "关系类型")
private Long relationTypeId;
/**
* 关系编码
*/
@Schema(description = "关系编码")
private String relationCode;
/**
* 关系名称
*/
@Schema(description = "关系名称")
private String relationName;
/**
* 主语一方称呼
*/
@Schema(description = "主语一方称呼")
private String subjectRoleName;
/**
* 对象一方称呼
*/
@Schema(description = "对象一方称呼")
private String objectRoleName;
/**
* 分类:family、business、social
*/
@Schema(description = "分类:family、business、social")
private String category;
/**
* 是否有方向:0否,1是
*/
@Schema(description = "是否有方向:0否,1是")
private String directedFlag;
/**
* 排序
*/
@Schema(description = "排序")
private Integer sortOrder;
/**
* 状态:0正常,1停用
*/
@Schema(description = "状态:0正常,1停用")
private String status;
/**
* 备注
*/
@Schema(description = "备注")
private String remark;
/**
* 创建人
*/
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建人")
private String createBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建时间")
private LocalDateTime createTime;
/**
* 修改人
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description = "修改人")
private String updateBy;
/**
* 修改时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description = "修改时间")
private LocalDateTime updateTime;
/**
* 删除标记
*/
@TableLogic
@TableField(fill = FieldFill.INSERT)
@Schema(description = "删除标记")
private String delFlag;
/**
* 所属租户ID
*/
@Schema(description = "所属租户ID")
private Long tenantId;
}
@@ -0,0 +1,72 @@
package com.bsd.pjcloud.admin.api.entity.user;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import com.bsd.pjcloud.common.core.util.TenantTable;
import java.time.LocalDateTime;
/**
* 学生用户扩展信息表
*/
@Data
@TenantTable
@TableName("sys_user_student")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "学生用户扩展信息表")
public class SysUserStudentEntity extends Model<SysUserStudentEntity> {
/**
* 用户ID
*/
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "用户ID")
private Long userId;
/**
* 学号
*/
@Schema(description = "学号")
private String studentNo;
/**
* 学校名称
*/
@Schema(description = "学校名称")
private String schoolName;
/**
* 班级名称
*/
@Schema(description = "班级名称")
private String className;
/**
* 其他非核心扩展信息
*/
@Schema(description = "其他非核心扩展信息")
private String extJson;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建时间")
private LocalDateTime createTime;
/**
* 修改时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description = "修改时间")
private LocalDateTime updateTime;
/**
* 所属租户ID
*/
@Schema(description = "所属租户ID")
private Long tenantId;
}
@@ -19,6 +19,7 @@
package com.bsd.pjcloud.admin.api.vo;
import com.bsd.pjcloud.admin.api.dto.user.SysUserStudentDto;
import com.bsd.pjcloud.admin.api.vo.base.BaseFacesInterface;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.bsd.pjcloud.admin.api.entity.SysPost;
@@ -211,6 +212,18 @@ public class UserVO implements BaseFacesInterface, Serializable {
*/
private List<SysPost> postList;
/**
* 用户身份类型
*/
@Schema(description = "用户身份类型集合")
private List<String> identityTypes;
/**
* 学生身份扩展信息
*/
@Schema(description = "学生身份扩展信息")
private SysUserStudentDto studentInfo;
/**
* 昵称
*/
@@ -0,0 +1,106 @@
package com.bsd.pjcloud.admin.api.vo.user;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* 用户关系图谱
*/
@Data
@Schema(description = "用户关系图谱")
public class SysUserRelationGraphVo implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "图谱节点ID,根节点为0,其他节点使用关系ID")
private Long nodeId;
@Schema(description = "父节点ID")
private Long parentNodeId;
@Schema(description = "是否根节点")
private Boolean root;
@Schema(description = "是否为已出现用户的重复引用节点,重复节点不再继续展开")
private Boolean repeated;
@Schema(description = "用户ID")
private Long userId;
@Schema(description = "用户名")
private String username;
@Schema(description = "姓名")
private String name;
@Schema(description = "昵称")
private String nickname;
@Schema(description = "图谱展示名称")
private String displayName;
@Schema(description = "头像")
private String avatar;
@Schema(description = "性别:0未知,1男,2女")
private Integer gender;
@Schema(description = "图谱中的用户数量,仅根节点返回,重复用户不重复计数")
private Integer userCount;
@Schema(description = "图谱中的关系数量,仅根节点返回")
private Integer relationCount;
@Schema(description = "关系ID,根节点为空")
private Long relationId;
@Schema(description = "关系主语用户ID")
private Long subjectUserId;
@Schema(description = "关系对象用户ID")
private Long objectUserId;
@Schema(description = "关系类型ID")
private Long relationTypeId;
@Schema(description = "关系编码")
private String relationCode;
@Schema(description = "关系名称")
private String relationName;
@Schema(description = "关系分类")
private String category;
@Schema(description = "是否有方向:0否,1是")
private String directedFlag;
@Schema(description = "当前树边方向:subject-to-object或object-to-subject")
private String relationDirection;
@Schema(description = "当前用户相对于父节点用户的角色,例如老师、监护人、学生")
private String userRoleName;
@Schema(description = "父节点用户相对于当前用户的角色")
private String parentRoleName;
@Schema(description = "完整关系描述,例如张老师是李同学的老师")
private String relationDescription;
@Schema(description = "关系生效时间")
private LocalDateTime effectiveTime;
@Schema(description = "关系失效时间")
private LocalDateTime expireTime;
@Schema(description = "关系备注")
private String remark;
@Schema(description = "下级关系节点")
private List<SysUserRelationGraphVo> children = new ArrayList<>();
}
@@ -0,0 +1,67 @@
package com.bsd.pjcloud.admin.controller.user;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bsd.pjcloud.admin.api.dto.user.SysUserRelationCreateUserDto;
import com.bsd.pjcloud.admin.api.dto.user.SysUserRelationCreateDto;
import com.bsd.pjcloud.admin.api.entity.user.SysUserRelationEntity;
import com.bsd.pjcloud.admin.api.vo.user.SysUserRelationGraphVo;
import com.bsd.pjcloud.admin.service.SysUserRelationService;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.log.annotation.SysLog;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.http.HttpHeaders;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
* 用户多对多关系表
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/sysUserRelation")
@Tag(description = "sysUserRelation", name = "用户多对多关系表管理")
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public class SysUserRelationController {
private final SysUserRelationService sysUserRelationService;
/**
* 查询用户关系图谱
*/
@Operation(summary = "关系图谱", description = "以指定用户为根节点查询用户关系树")
@GetMapping("/graph/{userId}")
// @PreAuthorize("@pms.hasPermission('dzc_sysUserRelation_view')")
public R<SysUserRelationGraphVo> getRelationGraph(@PathVariable Long userId) {
return R.ok(sysUserRelationService.getRelationGraph(userId));
}
/**
* 创建用户并建立关系,新用户可作为关系主语或关系对象
*/
@Operation(summary = "创建用户并建立关系", description = "创建用户且直接审核通过,再建立用户关系")
@SysLog("创建用户并建立关系")
@PostMapping("/create-user")
// @PreAuthorize("@pms.hasPermission('dzc_sysUserRelation_add')")
public R<Long> createUserAndRelation(@Valid @RequestBody SysUserRelationCreateUserDto dto) {
return R.ok(sysUserRelationService.createUserAndRelation(dto));
}
/**
* 为两个已有用户建立关系
*/
@Operation(summary = "建立用户关系", description = "为两个已经存在的用户建立关系")
@SysLog("建立用户关系")
@PostMapping("/create")
// @PreAuthorize("@pms.hasPermission('dzc_sysUserRelation_add')")
public R<Long> createRelation(@Valid @RequestBody SysUserRelationCreateDto dto) {
return R.ok(sysUserRelationService.createRelation(dto));
}
}
@@ -0,0 +1,87 @@
package com.bsd.pjcloud.admin.controller.user;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bsd.pjcloud.admin.api.entity.user.SysUserRelationTypeEntity;
import com.bsd.pjcloud.admin.service.SysUserRelationTypeService;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.log.annotation.SysLog;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.http.HttpHeaders;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
* 用户关系类型表
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/sysUserRelationType")
@Tag(description = "sysUserRelationType", name = "用户关系类型表管理")
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public class SysUserRelationTypeController {
private final SysUserRelationTypeService sysUserRelationTypeService;
/**
* 分页查询
*/
@Operation(summary = "分页查询", description = "分页查询")
@GetMapping("/page")
@PreAuthorize("@pms.hasPermission('dzc_sysUserRelationType_view')")
public R getSysUserRelationTypePage(@ParameterObject Page page, @ParameterObject SysUserRelationTypeEntity sysUserRelationType) {
LambdaQueryWrapper<SysUserRelationTypeEntity> wrapper = Wrappers.lambdaQuery();
return R.ok(sysUserRelationTypeService.page(page, wrapper));
}
/**
* 通过id查询用户关系类型表
*/
@Operation(summary = "通过id查询", description = "通过id查询")
@GetMapping("/{relationTypeId}")
@PreAuthorize("@pms.hasPermission('dzc_sysUserRelationType_view')")
public R getById(@PathVariable("relationTypeId") Long relationTypeId) {
return R.ok(sysUserRelationTypeService.getById(relationTypeId));
}
/**
* 新增用户关系类型表
*/
@Operation(summary = "新增用户关系类型表", description = "新增用户关系类型表")
@SysLog("新增用户关系类型表")
@PostMapping
@PreAuthorize("@pms.hasPermission('dzc_sysUserRelationType_add')")
public R save(@RequestBody SysUserRelationTypeEntity dto) {
return R.ok(sysUserRelationTypeService.create(dto));
}
/**
* 修改用户关系类型表
*/
@Operation(summary = "修改用户关系类型表", description = "修改用户关系类型表")
@SysLog("修改用户关系类型表")
@PutMapping
@PreAuthorize("@pms.hasPermission('dzc_sysUserRelationType_edit')")
public R updateById(@RequestBody SysUserRelationTypeEntity dto) {
return R.ok(sysUserRelationTypeService.edit(dto));
}
/**
* 通过id删除用户关系类型表
*/
@Operation(summary = "通过id删除用户关系类型表", description = "通过id删除用户关系类型表")
@SysLog("通过id删除用户关系类型表")
@DeleteMapping
@PreAuthorize("@pms.hasPermission('dzc_sysUserRelationType_del')")
public R removeById(@RequestBody Long[] ids) {
sysUserRelationTypeService.delById(CollUtil.toList(ids));
return R.ok();
}
}
@@ -0,0 +1,10 @@
package com.bsd.pjcloud.admin.mapper.user;
import com.bsd.pjcloud.admin.api.entity.user.SysUserIdentityEntity;
import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysUserIdentityMapper extends PjcloudBaseMapper<SysUserIdentityEntity> {
}
@@ -0,0 +1,10 @@
package com.bsd.pjcloud.admin.mapper.user;
import com.bsd.pjcloud.admin.api.entity.user.SysUserRelationEntity;
import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysUserRelationMapper extends PjcloudBaseMapper<SysUserRelationEntity> {
}
@@ -0,0 +1,10 @@
package com.bsd.pjcloud.admin.mapper.user;
import com.bsd.pjcloud.admin.api.entity.user.SysUserRelationTypeEntity;
import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysUserRelationTypeMapper extends PjcloudBaseMapper<SysUserRelationTypeEntity> {
}
@@ -0,0 +1,10 @@
package com.bsd.pjcloud.admin.mapper.user;
import com.bsd.pjcloud.admin.api.entity.user.SysUserStudentEntity;
import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysUserStudentMapper extends PjcloudBaseMapper<SysUserStudentEntity> {
}
@@ -0,0 +1,24 @@
package com.bsd.pjcloud.admin.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bsd.pjcloud.admin.api.dto.UserDTO;
import com.bsd.pjcloud.admin.api.entity.user.SysUserIdentityEntity;
import com.bsd.pjcloud.admin.api.vo.UserVO;
public interface SysUserIdentityService extends IService<SysUserIdentityEntity> {
/**
* 校验并规范化用户身份请求参数
*/
void validateDto(UserDTO userDto);
/**
* 保存用户身份及对应扩展信息
*/
void saveUserIdentity(Long userId, UserDTO userDto);
/**
* 填充用户身份及对应扩展信息
*/
void fillUserIdentity(UserVO userVo);
}
@@ -0,0 +1,34 @@
package com.bsd.pjcloud.admin.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bsd.pjcloud.admin.api.dto.user.SysUserRelationCreateUserDto;
import com.bsd.pjcloud.admin.api.dto.user.SysUserRelationCreateDto;
import com.bsd.pjcloud.admin.api.entity.user.SysUserRelationEntity;
import com.bsd.pjcloud.admin.api.vo.user.SysUserRelationGraphVo;
public interface SysUserRelationService extends IService<SysUserRelationEntity> {
/**
* 查询用户关系图谱
*
* @param userId 根用户ID
* @return 用户关系图谱
*/
SysUserRelationGraphVo getRelationGraph(Long userId);
/**
* 创建用户并建立关系,新用户作为关系主语
*
* @param dto 用户及关系信息
* @return 新用户ID
*/
Long createUserAndRelation(SysUserRelationCreateUserDto dto);
/**
* 为两个已有用户建立关系
*
* @param dto 关系信息
* @return 关系ID
*/
Long createRelation(SysUserRelationCreateDto dto);
}
@@ -0,0 +1,24 @@
package com.bsd.pjcloud.admin.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bsd.pjcloud.admin.api.entity.user.SysUserRelationTypeEntity;
import java.util.ArrayList;
public interface SysUserRelationTypeService extends IService<SysUserRelationTypeEntity> {
/**
* 新增用户关系类型表
*/
Long create(SysUserRelationTypeEntity dto);
/**
* 修改用户关系类型表
*/
Long edit(SysUserRelationTypeEntity dto);
/**
* 通过id删除用户关系类型表
*/
void delById(ArrayList<Long> ids);
}
@@ -111,6 +111,14 @@ public interface SysUserService extends IService<SysUser> {
*/
Long saveUser(UserDTO userDto);
/**
* 保存用户信息并直接审核通过
*
* @param userDto DTO 对象
* @return 用户ID
*/
Long saveUserWithoutAudit(UserDTO userDto);
/**
* 查询全部的用户
*
@@ -0,0 +1,12 @@
package com.bsd.pjcloud.admin.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bsd.pjcloud.admin.api.entity.user.SysUserStudentEntity;
public interface SysUserStudentService extends IService<SysUserStudentEntity> {
/**
* 判断学号是否已被其他用户使用
*/
boolean existsStudentNo(Long userId, String studentNo);
}
@@ -37,6 +37,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bsd.pjcloud.admin.api.constant.UserExpireEnum;
import com.bsd.pjcloud.admin.api.constant.UserStaffingTypeEnum;
import com.bsd.pjcloud.admin.api.constant.UserStateEnum;
import com.bsd.pjcloud.admin.api.constant.user.UserIdentityTypeEnum;
import com.bsd.pjcloud.admin.api.dto.ForgotPasswordDTO;
import com.bsd.pjcloud.admin.api.dto.SysUserFaceDto;
import com.bsd.pjcloud.admin.api.dto.UserDTO;
@@ -123,6 +124,8 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
private final SysUserTagService sysUserTagService;
private final SysUserIdentityService sysUserIdentityService;
@Lazy
private final SysUserFaceService sysUserFaceService;
private final SysUserFaceMapper sysUserFaceMapper;
@@ -165,12 +168,26 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
@Override
@Transactional(rollbackFor = Exception.class)
public Long saveUser(UserDTO userDto) {
return saveUser(userDto, false);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long saveUserWithoutAudit(UserDTO userDto) {
return saveUser(userDto, true);
}
private Long saveUser(UserDTO userDto, boolean skipAudit) {
sysUserIdentityService.validateDto(userDto);
SysUser sysUser = new SysUser();
BeanUtils.copyProperties(userDto, sysUser);
// 判断是否为部门管理员
PjcloudUser applyUser = SecurityUtils.getUser();
boolean isDeptAdmin = false;
if (null != applyUser) {
if (skipAudit) {
sysUser.setProcessStatus(TaskCheckStatusEnum.RESULT.getCode());
sysUser.setProcessTime(LocalDateTime.now());
} else if (null != applyUser) {
isDeptAdmin = sysRoleService.isDeptAdmin(applyUser.getId());
if (isDeptAdmin) {
List<SysRole> defaultRoles = sysRoleService.findRolesByCodes(List.of(CommonRoleConstant.DEFAULT_USER, CommonRoleConstant.AUTO_CREATE_WALLET));
@@ -194,6 +211,7 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
sysUser.setPasswordModifyTime(LocalDateTime.now());
sysUser.setPasswordExpireFlag(UserExpireEnum.EXPIRE.getCode());
baseMapper.insert(sysUser);
sysUserIdentityService.saveUserIdentity(sysUser.getUserId(), userDto);
// 保存用户岗位信息
Optional.ofNullable(userDto.getPost()).ifPresent(posts -> {
posts.stream().map(postId -> {
@@ -221,7 +239,7 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
return userRole;
}).forEach(sysUserRoleMapper::insert);
if (isDeptAdmin) {
if (isDeptAdmin && !skipAudit) {
getSelf().applyFlow(sysUser);
} else {
// 创建用户默认钱包、保证线程header
@@ -345,7 +363,9 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
*/
@Override
public UserVO selectUserVoById(Long id) {
return baseMapper.getUserVoById(id);
UserVO userVo = baseMapper.getUserVoById(id);
sysUserIdentityService.fillUserIdentity(userVo);
return userVo;
}
/**
@@ -405,6 +425,7 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
@Audit(name = "用户更新", spel = "@sysUserMapper.selectById(#userDto.userId)")
@CacheEvict(value = CacheConstants.USER_DETAILS, key = "#userDto.username")
public Boolean updateUser(UserDTO userDto) {
sysUserIdentityService.validateDto(userDto);
// 更新用户表信息
SysUser sysUser = new SysUser();
BeanUtils.copyProperties(userDto, sysUser);
@@ -423,6 +444,14 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
this.updateById(sysUser);
if (userDto.getIdentityTypes() != null
&& !userDto.getIdentityTypes().contains(UserIdentityTypeEnum.DEFAULT.getType())) {
baseMapper.update(null, Wrappers.<SysUser>lambdaUpdate()
.eq(SysUser::getUserId, userDto.getUserId())
.set(SysUser::getPhone, null));
}
sysUserIdentityService.saveUserIdentity(userDto.getUserId(), userDto);
// 更新用户角色表
if (userDto.getRole() != null) {
sysUserRoleMapper.delete(Wrappers.<SysUserRole>lambdaQuery().eq(SysUserRole::getUserId, userDto.getUserId()));
@@ -0,0 +1,156 @@
package com.bsd.pjcloud.admin.service.impl.user;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bsd.pjcloud.admin.api.constant.user.UserIdentityTypeEnum;
import com.bsd.pjcloud.admin.api.dto.UserDTO;
import com.bsd.pjcloud.admin.api.dto.user.SysUserStudentDto;
import com.bsd.pjcloud.admin.api.entity.SysUser;
import com.bsd.pjcloud.admin.api.entity.user.SysUserIdentityEntity;
import com.bsd.pjcloud.admin.api.entity.user.SysUserStudentEntity;
import com.bsd.pjcloud.admin.api.vo.UserVO;
import com.bsd.pjcloud.admin.mapper.SysUserMapper;
import com.bsd.pjcloud.admin.mapper.user.SysUserIdentityMapper;
import com.bsd.pjcloud.admin.service.SysUserIdentityService;
import com.bsd.pjcloud.admin.service.SysUserStudentService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* 用户身份表
*/
@Service
@RequiredArgsConstructor
public class SysUserIdentityServiceImpl extends ServiceImpl<SysUserIdentityMapper, SysUserIdentityEntity> implements SysUserIdentityService {
private static final int STUDENT_NO_MAX_LENGTH = 255;
private static final String PHONE_PATTERN = "^1[3-9]\\d{9}$";
private final SysUserStudentService sysUserStudentService;
private final SysUserMapper sysUserMapper;
@Override
public void validateDto(UserDTO userDto) {
if (userDto.getIdentityTypes() == null) {
return;
}
if (CollUtil.isEmpty(userDto.getIdentityTypes())) {
throw new IllegalArgumentException("用户身份不能为空");
}
Set<String> identityTypes = new LinkedHashSet<>();
for (String identityType : userDto.getIdentityTypes()) {
String normalizedType = StrUtil.trim(identityType);
if (UserIdentityTypeEnum.valueOfByType(normalizedType) == null) {
throw new IllegalArgumentException("用户身份类型不正确");
}
if (!identityTypes.add(normalizedType)) {
throw new IllegalArgumentException("用户身份不能重复");
}
}
userDto.setIdentityTypes(List.copyOf(identityTypes));
if (identityTypes.contains(UserIdentityTypeEnum.DEFAULT.getType())) {
validateDefaultIdentity(userDto);
} else {
userDto.setPhone(null);
}
if (!identityTypes.contains(UserIdentityTypeEnum.STUDENT.getType())) {
userDto.setStudentInfo(null);
return;
}
validateStudentInfo(userDto);
}
@Override
public void saveUserIdentity(Long userId, UserDTO userDto) {
if (userDto.getIdentityTypes() == null) {
return;
}
remove(Wrappers.<SysUserIdentityEntity>lambdaQuery()
.eq(SysUserIdentityEntity::getUserId, userId));
List<SysUserIdentityEntity> identities = userDto.getIdentityTypes().stream()
.map(identityType -> {
SysUserIdentityEntity identity = new SysUserIdentityEntity();
identity.setUserId(userId);
identity.setIdentityType(identityType);
return identity;
})
.toList();
saveBatch(identities);
if (!userDto.getIdentityTypes().contains(UserIdentityTypeEnum.STUDENT.getType())) {
sysUserStudentService.removeById(userId);
return;
}
SysUserStudentEntity student = new SysUserStudentEntity();
BeanUtils.copyProperties(userDto.getStudentInfo(), student);
student.setUserId(userId);
sysUserStudentService.saveOrUpdate(student);
}
@Override
public void fillUserIdentity(UserVO userVo) {
if (userVo == null || userVo.getUserId() == null) {
return;
}
List<String> identityTypes = list(Wrappers.<SysUserIdentityEntity>lambdaQuery()
.eq(SysUserIdentityEntity::getUserId, userVo.getUserId()))
.stream()
.map(SysUserIdentityEntity::getIdentityType)
.toList();
userVo.setIdentityTypes(identityTypes);
if (!identityTypes.contains(UserIdentityTypeEnum.STUDENT.getType())) {
return;
}
SysUserStudentEntity student = sysUserStudentService.getById(userVo.getUserId());
if (student != null) {
SysUserStudentDto studentInfo = new SysUserStudentDto();
BeanUtils.copyProperties(student, studentInfo);
userVo.setStudentInfo(studentInfo);
}
}
private void validateStudentInfo(UserDTO userDto) {
SysUserStudentDto studentInfo = userDto.getStudentInfo();
if (studentInfo == null || StrUtil.isBlank(studentInfo.getStudentNo())) {
throw new IllegalArgumentException("学号不能为空");
}
String studentNo = studentInfo.getStudentNo().trim();
if (studentNo.length() > STUDENT_NO_MAX_LENGTH) {
throw new IllegalArgumentException("学号不能超过255个字符");
}
if (sysUserStudentService.existsStudentNo(userDto.getUserId(), studentNo)) {
throw new IllegalArgumentException("学号已存在");
}
studentInfo.setStudentNo(studentNo);
}
private void validateDefaultIdentity(UserDTO userDto) {
if (StrUtil.isBlank(userDto.getPhone())) {
SysUser currentUser = userDto.getUserId() == null ? null : sysUserMapper.selectById(userDto.getUserId());
if (currentUser == null || StrUtil.isBlank(currentUser.getPhone())) {
throw new IllegalArgumentException("手机号不能为空");
}
return;
}
String phone = userDto.getPhone().trim();
if (!phone.matches(PHONE_PATTERN)) {
throw new IllegalArgumentException("手机号格式不正确");
}
userDto.setPhone(phone);
}
}
@@ -0,0 +1,386 @@
package com.bsd.pjcloud.admin.service.impl.user;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bsd.pjcloud.admin.api.dto.UserDTO;
import com.bsd.pjcloud.admin.api.dto.user.SysUserRelationCreateUserDto;
import com.bsd.pjcloud.admin.api.dto.user.SysUserRelationCreateDto;
import com.bsd.pjcloud.admin.api.entity.SysUser;
import com.bsd.pjcloud.admin.api.entity.user.SysUserRelationEntity;
import com.bsd.pjcloud.admin.api.entity.user.SysUserRelationTypeEntity;
import com.bsd.pjcloud.admin.api.vo.user.SysUserRelationGraphVo;
import com.bsd.pjcloud.admin.mapper.user.SysUserRelationMapper;
import com.bsd.pjcloud.admin.service.SysUserRelationService;
import com.bsd.pjcloud.admin.service.SysUserRelationTypeService;
import com.bsd.pjcloud.admin.service.SysUserService;
import com.bsd.pjcloud.common.core.constant.CommonConstants;
import com.bsd.pjcloud.common.core.exception.ErrorCodes;
import com.bsd.pjcloud.common.core.util.MsgUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 用户多对多关系表
*/
@Service
@RequiredArgsConstructor
public class SysUserRelationServiceImpl extends ServiceImpl<SysUserRelationMapper, SysUserRelationEntity> implements SysUserRelationService {
private static final Long TREE_ROOT_PARENT_ID = -1L;
private static final Long TREE_ROOT_NODE_ID = 0L;
private final SysUserService sysUserService;
private final SysUserRelationTypeService sysUserRelationTypeService;
@Override
public SysUserRelationGraphVo getRelationGraph(Long userId) {
SysUser rootUser = sysUserService.getById(userId);
if (rootUser == null) {
throw new RuntimeException("用户不存在");
}
RelationGraphData graphData = collectRelationGraph(userId);
Map<Long, SysUser> userMap = sysUserService.listByIds(graphData.userIds).stream()
.collect(Collectors.toMap(SysUser::getUserId, Function.identity(), (first, second) -> first));
Map<Long, SysUserRelationTypeEntity> relationTypeMap = graphData.relationTypeIds.isEmpty()
? Map.of()
: sysUserRelationTypeService.listByIds(graphData.relationTypeIds).stream()
.collect(Collectors.toMap(SysUserRelationTypeEntity::getRelationTypeId,
Function.identity(), (first, second) -> first));
Map<Long, SysUserRelationGraphVo> nodeMap = graphData.nodes.stream()
.map(node -> buildGraphNode(node, userMap, relationTypeMap))
.collect(Collectors.toMap(SysUserRelationGraphVo::getNodeId, Function.identity()));
SysUserRelationGraphVo rootNode = nodeMap.get(TREE_ROOT_NODE_ID);
graphData.nodes.stream()
.filter(node -> !Objects.equals(node.nodeId, TREE_ROOT_NODE_ID))
.forEach(node -> {
SysUserRelationGraphVo parentNode = nodeMap.get(node.parentNodeId);
SysUserRelationGraphVo currentNode = nodeMap.get(node.nodeId);
if (parentNode != null && currentNode != null) {
parentNode.getChildren().add(currentNode);
}
});
rootNode.setUserCount(graphData.userIds.size());
rootNode.setRelationCount(graphData.relationIds.size());
return rootNode;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long createUserAndRelation(SysUserRelationCreateUserDto dto) {
validateCreateUserAndRelation(dto);
UserDTO userDto = new UserDTO();
BeanUtils.copyProperties(dto, userDto);
Long newUserId = sysUserService.saveUserWithoutAudit(userDto);
Long subjectUserId = dto.getSubjectUserId() == null ? newUserId : dto.getSubjectUserId();
Long objectUserId = dto.getObjectUserId() == null ? newUserId : dto.getObjectUserId();
SysUserRelationTypeEntity relationType = validateRelation(
subjectUserId, objectUserId, dto.getRelationTypeId());
validateRelationNotExists(subjectUserId, objectUserId, relationType);
SysUserRelationEntity relation = new SysUserRelationEntity();
relation.setSubjectUserId(subjectUserId);
relation.setObjectUserId(objectUserId);
relation.setRelationTypeId(dto.getRelationTypeId());
relation.setEffectiveTime(dto.getEffectiveTime());
relation.setExpireTime(dto.getExpireTime());
relation.setRemark(dto.getRemark());
save(relation);
return newUserId;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long createRelation(SysUserRelationCreateDto dto) {
SysUserRelationTypeEntity relationType = validateRelation(
dto.getSubjectUserId(), dto.getObjectUserId(), dto.getRelationTypeId());
validateRelationNotExists(dto.getSubjectUserId(), dto.getObjectUserId(), relationType);
SysUserRelationEntity relation = new SysUserRelationEntity();
BeanUtils.copyProperties(dto, relation);
save(relation);
return relation.getRelationId();
}
private void validateCreateUserAndRelation(SysUserRelationCreateUserDto dto) {
Long existingUserId = dto.getSubjectUserId() == null ? dto.getObjectUserId() : dto.getSubjectUserId();
validateExistingUserAndRelationType(existingUserId, dto.getRelationTypeId());
boolean usernameExists = sysUserService.exists(Wrappers.<SysUser>lambdaQuery()
.eq(SysUser::getUsername, dto.getUsername()));
if (usernameExists) {
throw new RuntimeException(MsgUtils.getMessage(ErrorCodes.SYS_USER_USERNAME_EXISTING, dto.getUsername()));
}
if (StrUtil.isNotBlank(dto.getPhone())) {
boolean phoneExists = sysUserService.exists(Wrappers.<SysUser>lambdaQuery()
.eq(SysUser::getPhone, dto.getPhone()));
if (phoneExists) {
throw new RuntimeException(MsgUtils.getMessage(ErrorCodes.SYS_USER_PHONE_EXISTING, dto.getPhone()));
}
}
}
private SysUserRelationTypeEntity validateRelation(Long subjectUserId, Long objectUserId, Long relationTypeId) {
if (Objects.equals(subjectUserId, objectUserId)) {
throw new RuntimeException("不能与自己建立用户关系");
}
if (sysUserService.getById(subjectUserId) == null) {
throw new RuntimeException("关系主语用户不存在");
}
return validateObjectUserAndRelationType(objectUserId, relationTypeId);
}
private SysUserRelationTypeEntity validateObjectUserAndRelationType(Long objectUserId, Long relationTypeId) {
SysUserRelationTypeEntity relationType = validateExistingUserAndRelationType(objectUserId, relationTypeId);
return relationType;
}
private SysUserRelationTypeEntity validateExistingUserAndRelationType(Long userId, Long relationTypeId) {
if (sysUserService.getById(userId) == null) {
throw new RuntimeException("关联用户不存在");
}
SysUserRelationTypeEntity relationType = sysUserRelationTypeService.getById(relationTypeId);
if (relationType == null || !CommonConstants.STATUS_NORMAL.equals(relationType.getStatus())) {
throw new RuntimeException("关系类型不存在或已停用");
}
return relationType;
}
private void validateRelationNotExists(Long subjectUserId, Long objectUserId,
SysUserRelationTypeEntity relationType) {
boolean directed = "1".equals(relationType.getDirectedFlag());
boolean exists = exists(Wrappers.<SysUserRelationEntity>lambdaQuery()
.eq(SysUserRelationEntity::getRelationTypeId, relationType.getRelationTypeId())
.and(wrapper -> wrapper
.nested(current -> current.eq(SysUserRelationEntity::getSubjectUserId, subjectUserId)
.eq(SysUserRelationEntity::getObjectUserId, objectUserId))
.or(!directed, reverse -> reverse
.eq(SysUserRelationEntity::getSubjectUserId, objectUserId)
.eq(SysUserRelationEntity::getObjectUserId, subjectUserId))));
if (exists) {
throw new RuntimeException("所选用户之间已存在该类型的关系");
}
}
private RelationGraphData collectRelationGraph(Long rootUserId) {
RelationGraphData graphData = new RelationGraphData();
graphData.userIds.add(rootUserId);
graphData.nodes.add(RelationGraphNode.root(rootUserId));
Set<Long> expandedUserIds = new HashSet<>();
List<RelationNodeContext> currentLevel = List.of(new RelationNodeContext(TREE_ROOT_NODE_ID, rootUserId));
while (!currentLevel.isEmpty()) {
List<RelationNodeContext> expandableNodes = new ArrayList<>();
Set<Long> expandableUserIds = new LinkedHashSet<>();
for (RelationNodeContext context : currentLevel) {
if (expandedUserIds.add(context.userId)) {
expandableNodes.add(context);
expandableUserIds.add(context.userId);
}
}
if (expandableNodes.isEmpty()) {
break;
}
Map<Long, List<SysUserRelationEntity>> relationMap = listRelations(expandableUserIds).stream()
.flatMap(relation -> Stream.of(relation.getSubjectUserId(), relation.getObjectUserId())
.filter(Objects::nonNull)
.map(relationUserId -> Map.entry(relationUserId, relation)))
.collect(Collectors.groupingBy(Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue, Collectors.toList())));
List<RelationNodeContext> nextLevel = new ArrayList<>();
for (RelationNodeContext context : expandableNodes) {
for (SysUserRelationEntity relation : relationMap.getOrDefault(context.userId, List.of())) {
boolean parentIsSubject = Objects.equals(context.userId, relation.getSubjectUserId());
Long childUserId = parentIsSubject ? relation.getObjectUserId() : relation.getSubjectUserId();
if (childUserId == null || Objects.equals(childUserId, context.userId)) {
continue;
}
if (!graphData.relationIds.add(relation.getRelationId())) {
continue;
}
boolean repeated = !graphData.userIds.add(childUserId);
if (relation.getRelationTypeId() != null) {
graphData.relationTypeIds.add(relation.getRelationTypeId());
}
graphData.nodes.add(RelationGraphNode.child(
relation.getRelationId(), context.nodeId, context.userId, childUserId,
relation, parentIsSubject, repeated));
nextLevel.add(new RelationNodeContext(relation.getRelationId(), childUserId));
}
}
currentLevel = nextLevel;
}
return graphData;
}
private List<SysUserRelationEntity> listRelations(Set<Long> userIds) {
return list(Wrappers.<SysUserRelationEntity>lambdaQuery()
.and(wrapper -> wrapper.in(SysUserRelationEntity::getSubjectUserId, userIds)
.or()
.in(SysUserRelationEntity::getObjectUserId, userIds)));
}
private SysUserRelationGraphVo buildGraphNode(RelationGraphNode node, Map<Long, SysUser> userMap,
Map<Long, SysUserRelationTypeEntity> relationTypeMap) {
SysUser user = userMap.get(node.userId);
SysUserRelationEntity relation = node.relation;
SysUserRelationTypeEntity relationType = relation == null
? null : relationTypeMap.get(relation.getRelationTypeId());
SysUserRelationGraphVo graphNode = new SysUserRelationGraphVo();
graphNode.setNodeId(node.nodeId);
graphNode.setParentNodeId(node.parentNodeId);
graphNode.setRoot(relation == null);
graphNode.setRepeated(node.repeated);
graphNode.setUserId(node.userId);
graphNode.setUsername(user == null ? null : user.getUsername());
graphNode.setName(user == null ? null : user.getName());
graphNode.setNickname(user == null ? null : user.getNickname());
graphNode.setDisplayName(getUserDisplayName(user, node.userId));
graphNode.setAvatar(user == null ? null : user.getAvatar());
graphNode.setGender(user == null ? null : user.getGender());
if (relation != null) {
String userRoleName = getUserRoleName(relationType, node.parentIsSubject);
graphNode.setRelationId(relation.getRelationId());
graphNode.setSubjectUserId(relation.getSubjectUserId());
graphNode.setObjectUserId(relation.getObjectUserId());
graphNode.setRelationTypeId(relation.getRelationTypeId());
graphNode.setEffectiveTime(relation.getEffectiveTime());
graphNode.setExpireTime(relation.getExpireTime());
graphNode.setRemark(relation.getRemark());
graphNode.setRelationDirection(node.parentIsSubject ? "subject-to-object" : "object-to-subject");
graphNode.setRelationCode(relationType == null ? null : relationType.getRelationCode());
graphNode.setRelationName(relationType == null ? null : relationType.getRelationName());
graphNode.setCategory(relationType == null ? null : relationType.getCategory());
graphNode.setDirectedFlag(relationType == null ? null : relationType.getDirectedFlag());
graphNode.setUserRoleName(userRoleName);
graphNode.setParentRoleName(getParentRoleName(relationType, node.parentIsSubject));
graphNode.setRelationDescription(buildRelationDescription(
user, node.userId, userMap.get(node.parentUserId), node.parentUserId,
userRoleName, relationType));
}
return graphNode;
}
private String getUserDisplayName(SysUser user, Long userId) {
if (user == null) {
return String.valueOf(userId);
}
if (StrUtil.isNotBlank(user.getName())) {
return user.getName();
}
if (StrUtil.isNotBlank(user.getNickname())) {
return user.getNickname();
}
return StrUtil.blankToDefault(user.getUsername(), String.valueOf(userId));
}
private String getUserRoleName(SysUserRelationTypeEntity relationType, boolean parentIsSubject) {
if (relationType == null) {
return null;
}
return parentIsSubject ? relationType.getObjectRoleName() : relationType.getSubjectRoleName();
}
private String getParentRoleName(SysUserRelationTypeEntity relationType, boolean parentIsSubject) {
if (relationType == null) {
return null;
}
return parentIsSubject ? relationType.getSubjectRoleName() : relationType.getObjectRoleName();
}
private String buildRelationDescription(SysUser user, Long userId, SysUser parentUser, Long parentUserId,
String userRoleName, SysUserRelationTypeEntity relationType) {
String userName = getUserDisplayName(user, userId);
String parentUserName = getUserDisplayName(parentUser, parentUserId);
if (StrUtil.isNotBlank(userRoleName)) {
return userName + "是" + parentUserName + "的" + userRoleName;
}
if (relationType != null && StrUtil.isNotBlank(relationType.getRelationName())) {
return userName + "与" + parentUserName + "的关系是" + relationType.getRelationName();
}
return userName + "与" + parentUserName + "存在用户关系";
}
private static class RelationGraphData {
private final List<RelationGraphNode> nodes = new ArrayList<>();
private final Set<Long> userIds = new LinkedHashSet<>();
private final Set<Long> relationIds = new HashSet<>();
private final Set<Long> relationTypeIds = new HashSet<>();
}
private static class RelationGraphNode {
private final Long nodeId;
private final Long parentNodeId;
private final Long parentUserId;
private final Long userId;
private final SysUserRelationEntity relation;
private final boolean parentIsSubject;
private final boolean repeated;
private RelationGraphNode(Long nodeId, Long parentNodeId, Long parentUserId, Long userId,
SysUserRelationEntity relation, boolean parentIsSubject, boolean repeated) {
this.nodeId = nodeId;
this.parentNodeId = parentNodeId;
this.parentUserId = parentUserId;
this.userId = userId;
this.relation = relation;
this.parentIsSubject = parentIsSubject;
this.repeated = repeated;
}
private static RelationGraphNode root(Long userId) {
return new RelationGraphNode(TREE_ROOT_NODE_ID, TREE_ROOT_PARENT_ID, null,
userId, null, false, false);
}
private static RelationGraphNode child(Long nodeId, Long parentNodeId, Long parentUserId, Long userId,
SysUserRelationEntity relation, boolean parentIsSubject,
boolean repeated) {
return new RelationGraphNode(nodeId, parentNodeId, parentUserId,
userId, relation, parentIsSubject, repeated);
}
}
private static class RelationNodeContext {
private final Long nodeId;
private final Long userId;
private RelationNodeContext(Long nodeId, Long userId) {
this.nodeId = nodeId;
this.userId = userId;
}
}
}
@@ -0,0 +1,86 @@
package com.bsd.pjcloud.admin.service.impl.user;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bsd.pjcloud.admin.api.entity.user.SysUserRelationEntity;
import com.bsd.pjcloud.admin.api.entity.user.SysUserRelationTypeEntity;
import com.bsd.pjcloud.admin.mapper.user.SysUserRelationMapper;
import com.bsd.pjcloud.admin.mapper.user.SysUserRelationTypeMapper;
import com.bsd.pjcloud.admin.service.SysUserRelationTypeService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
/**
* 用户关系类型表
*/
@Service
@RequiredArgsConstructor
public class SysUserRelationTypeServiceImpl extends ServiceImpl<SysUserRelationTypeMapper, SysUserRelationTypeEntity> implements SysUserRelationTypeService {
private final SysUserRelationMapper sysUserRelationMapper;
/**
* 新增用户关系类型表
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Long create(SysUserRelationTypeEntity dto) {
validateUnique(dto, null);
baseMapper.insert(dto);
return dto.getRelationTypeId();
}
/**
* 修改用户关系类型表
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Long edit(SysUserRelationTypeEntity dto) {
if (dto.getRelationTypeId() == null) {
throw new IllegalArgumentException("关系类型ID不能为空");
}
if (getById(dto.getRelationTypeId()) == null) {
throw new IllegalArgumentException("用户关系类型不存在");
}
validateUnique(dto, dto.getRelationTypeId());
baseMapper.updateById(dto);
return dto.getRelationTypeId();
}
/**
* 通过id删除用户关系类型表
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void delById(ArrayList<Long> ids) {
if (ids == null || ids.isEmpty()) {
return;
}
Long relationCount = sysUserRelationMapper.selectCount(Wrappers.<SysUserRelationEntity>lambdaQuery()
.in(SysUserRelationEntity::getRelationTypeId, ids));
if (relationCount != null && relationCount > 0) {
throw new IllegalStateException("所选关系类型已被用户关系引用,无法删除");
}
removeBatchByIds(ids);
}
private void validateUnique(SysUserRelationTypeEntity dto, Long excludeId) {
if (dto.getRelationTypeId() != null) {
Long relationTypeIdCount = baseMapper.selectCount(Wrappers.<SysUserRelationTypeEntity>lambdaQuery()
.eq(SysUserRelationTypeEntity::getRelationTypeId, dto.getRelationTypeId())
.ne(excludeId != null, SysUserRelationTypeEntity::getRelationTypeId, excludeId));
if (relationTypeIdCount != null && relationTypeIdCount > 0) {
throw new IllegalArgumentException("关系类型ID不能重复");
}
}
Long categoryCount = baseMapper.selectCount(Wrappers.<SysUserRelationTypeEntity>lambdaQuery()
.eq(SysUserRelationTypeEntity::getCategory, dto.getCategory())
.ne(excludeId != null, SysUserRelationTypeEntity::getRelationTypeId, excludeId));
if (categoryCount != null && categoryCount > 0) {
throw new IllegalArgumentException("关系分类不能重复");
}
}
}
@@ -0,0 +1,26 @@
package com.bsd.pjcloud.admin.service.impl.user;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bsd.pjcloud.admin.api.entity.user.SysUserStudentEntity;
import com.bsd.pjcloud.admin.mapper.user.SysUserStudentMapper;
import com.bsd.pjcloud.admin.service.SysUserStudentService;
import org.springframework.stereotype.Service;
/**
* 学生用户扩展信息表
*/
@Service
public class SysUserStudentServiceImpl extends ServiceImpl<SysUserStudentMapper, SysUserStudentEntity> implements SysUserStudentService {
@Override
public boolean existsStudentNo(Long userId, String studentNo) {
if (StrUtil.isBlank(studentNo)) {
return false;
}
return exists(Wrappers.<SysUserStudentEntity>lambdaQuery()
.eq(SysUserStudentEntity::getStudentNo, studentNo.trim())
.ne(userId != null, SysUserStudentEntity::getUserId, userId));
}
}
@@ -0,0 +1,6 @@
<?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.bsd.pjcloud.admin.mapper.user.SysUserIdentityMapper">
</mapper>
@@ -0,0 +1,7 @@
<?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.bsd.pjcloud.admin.mapper.user.SysUserRelationMapper">
</mapper>
@@ -0,0 +1,7 @@
<?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.bsd.pjcloud.admin.mapper.user.SysUserRelationTypeMapper">
</mapper>
@@ -0,0 +1,6 @@
<?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.bsd.pjcloud.admin.mapper.user.SysUserStudentMapper">
</mapper>