refactor all

This commit is contained in:
Timi
2026-07-31 01:03:53 +08:00
parent 4e7f340cc6
commit e45809a0db
512 changed files with 14620 additions and 13519 deletions
@@ -0,0 +1,108 @@
package com.imyeyu.api.modules.user.bean;
import com.imyeyu.api.TimiServerAPI;
import com.imyeyu.api.bean.CoreRoleCode;
import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.modules.user.entity.Role;
import com.imyeyu.api.modules.user.entity.User;
import com.imyeyu.api.modules.user.entity.UserRole;
import com.imyeyu.api.modules.user.mapper.RoleMapper;
import com.imyeyu.api.modules.user.mapper.UserMapper;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.Set;
/**
* 自举数据常量
*
* @author 夜雨
* @since 2026-06-07 10:42
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class BootstrapData {
/** System 用户名 */
public static final String SYS_USER_NAME = "System";
/** System 用户昵称 */
public static final String SYS_USER_NICK = "System";
/** System 角色代码 */
public static final String SYS_ROLE_CODE = CoreRoleCode.SYSTEM.name();
/** 管理角色代码 */
public static final String ADMIN_ROLE_CODE = CoreRoleCode.ADMIN.name();
/** System 角色模块 */
public static final ModuleCode SYS_ROLE_MODULE_CODE = ModuleCode.CORE;
/** 管理角色模块 */
public static final ModuleCode ADMIN_ROLE_MODULE_CODE = ModuleCode.CORE;
/** System 角色名称 */
public static final String SYS_ROLE_NAME = CoreRoleCode.SYSTEM.getDefaultName();
/** 管理角色名称 */
public static final String ADMIN_ROLE_NAME = CoreRoleCode.ADMIN.getDefaultName();
/** 自举权限代码集合 */
public static final Set<String> PROTECTED_PERMISSION_CODE_SET = BuiltinAuthRegistry.SYSTEM_PERMISSION_CODE_SET;
public static boolean isProtectedRoleCode(ModuleCode moduleCode, String code) {
return BuiltinAuthRegistry.isProtectedRole(moduleCode, code);
}
public static void checkProtectedRoleCode(ModuleCode moduleCode, String code) {
if (BootstrapData.isProtectedRoleCode(moduleCode, code)) {
throw new TimiException(TimiCode.PERMISSION_ERROR);
}
}
/**
* 判断是否为自举权限
*
* @param code 权限码
* @return 是否为自举权限
*/
public static boolean isProtectedPermissionCode(BuiltinPermissionCode code) {
return code != null && code.isGrantToSystem();
}
public static boolean isProtectedPermissionCode(String code) {
return PROTECTED_PERMISSION_CODE_SET.contains(code);
}
public static void checkProtectedPermissionCode(BuiltinPermissionCode code) {
if (BootstrapData.isProtectedPermissionCode(code)) {
throw new TimiException(TimiCode.PERMISSION_ERROR);
}
}
public static boolean isProtectedUserRole(UserRole userRole) {
if (userRole == null) {
return false;
}
UserMapper userMapper = TimiServerAPI.applicationContext.getBean(UserMapper.class);
RoleMapper roleMapper = TimiServerAPI.applicationContext.getBean(RoleMapper.class);
User user = userMapper.select(userRole.getUserId());
if (user == null || !BootstrapData.SYS_USER_NAME.equals(user.getName())) {
return false;
}
Role role = roleMapper.select(userRole.getRoleId());
return isProtectedRole(role);
}
public static void checkProtectedUserRole(UserRole userRole) {
if (BootstrapData.isProtectedUserRole(userRole)) {
throw new TimiException(TimiCode.PERMISSION_ERROR);
}
}
public static boolean isProtectedRole(Role role) {
return role != null && isProtectedRoleCode(role.getModuleCode(), role.getCode());
}
}
@@ -0,0 +1,61 @@
package com.imyeyu.api.modules.user.bean;
import com.imyeyu.api.bean.CorePermissionCode;
import com.imyeyu.api.bean.CoreRoleCode;
import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
import com.imyeyu.api.modules.gao.bean.GaoRoleCode;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
///
/// 内建权限与角色注册中心
///
/// @author Codex
/// @since 2026-07-28
public final class BuiltinAuthRegistry {
private BuiltinAuthRegistry() {
}
/// 内建权限列表
public static final List<BuiltinPermissionCode> PERMISSION_CODE_LIST = Stream.concat(
Arrays.stream(CorePermissionCode.values()),
Arrays.stream(GaoPermissionCode.values())
).map(BuiltinPermissionCode.class::cast).toList();
/// 内建角色列表
public static final List<BuiltinRoleCode> ROLE_CODE_LIST = Stream.concat(
Arrays.stream(CoreRoleCode.values()),
Arrays.stream(GaoRoleCode.values())
).map(BuiltinRoleCode.class::cast).toList();
/// SYSTEM 角色必有权限集合
public static final Set<String> SYSTEM_PERMISSION_CODE_SET = PERMISSION_CODE_LIST.stream()
.filter(BuiltinPermissionCode::isGrantToSystem)
.map(BuiltinPermissionCode::getValue)
.collect(Collectors.toCollection(LinkedHashSet::new));
/// 受保护角色集合
public static final Set<String> PROTECTED_ROLE_KEY_SET = ROLE_CODE_LIST.stream()
.filter(BuiltinRoleCode::isProtected)
.map(item -> toRoleKey(item.getModuleCode(), ((Enum<?>) item).name()))
.collect(Collectors.toCollection(LinkedHashSet::new));
public static String toRoleKey(ModuleCode moduleCode, String code) {
return "%s:%s".formatted(moduleCode.name(), code);
}
public static String toPermissionKey(ModuleCode moduleCode, String code) {
return "%s:%s".formatted(moduleCode.name(), code);
}
public static boolean isProtectedRole(ModuleCode moduleCode, String code) {
return PROTECTED_ROLE_KEY_SET.contains(toRoleKey(moduleCode, code));
}
}
@@ -0,0 +1,36 @@
package com.imyeyu.api.modules.user.bean;
import com.imyeyu.api.bean.ModuleCode;
///
/// 内建权限定义
///
/// @author Codex
/// @since 2026-07-28
public interface BuiltinPermissionCode {
/// 权限模块
///
/// @return 模块代码
ModuleCode getModuleCode();
/// 权限值
///
/// @return 权限值
String getValue();
/// 默认名称
///
/// @return 默认名称
String getDefaultName();
/// true 为初始化时授权给 SYSTEM 角色
///
/// @return 是否授权给 SYSTEM 角色
boolean isGrantToSystem();
/// true 为初始化时授权给 ADMIN 角色
///
/// @return 是否授权给 ADMIN 角色
boolean isGrantToAdmin();
}
@@ -0,0 +1,31 @@
package com.imyeyu.api.modules.user.bean;
import com.imyeyu.api.bean.ModuleCode;
///
/// 内建角色定义
///
/// @author Codex
/// @since 2026-07-28
public interface BuiltinRoleCode {
/// 角色模块
///
/// @return 模块代码
ModuleCode getModuleCode();
/// 默认名称
///
/// @return 默认名称
String getDefaultName();
/// true 为受保护角色
///
/// @return 是否受保护
boolean isProtected();
/// 父角色代码
///
/// @return 父角色代码
String getParentCode();
}
@@ -0,0 +1,12 @@
package com.imyeyu.api.modules.user.bean;
///
///
/// @author 夜雨
/// @since 2026-07-30 14:35
public enum Gender {
MALE,
FEMALE,
}
@@ -0,0 +1,73 @@
package com.imyeyu.api.modules.user.controller;
import com.imyeyu.api.annotation.RequireCorePermission;
import com.imyeyu.api.annotation.RequireCoreRole;
import com.imyeyu.api.bean.CorePermissionCode;
import com.imyeyu.api.bean.CoreRoleCode;
import com.imyeyu.api.modules.user.entity.Permission;
import com.imyeyu.api.modules.user.service.PermissionService;
import com.imyeyu.api.modules.user.vo.permission.BatchCreateReq;
import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
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;
/**
* 权限 SDK 控制器
*
* @author 夜雨
* @since 2025-11-02 09:54
*/
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/user/permission")
public class PermissionController {
private final PermissionService service;
@RequireCoreRole(CoreRoleCode.ADMIN)
@RequireCorePermission(CorePermissionCode.PERMISSION_READ)
@PostMapping("/list")
public PageResult<Permission> list(@RequestBody Page<Permission> page) {
return service.page(page);
}
@AOPLog
@RequireCoreRole(CoreRoleCode.ADMIN)
@RequireCorePermission(CorePermissionCode.PERMISSION_READ)
@PostMapping("/create")
public void create(@RequestBody Permission permission) {
service.create(permission);
}
@AOPLog
@RequireCoreRole(CoreRoleCode.ADMIN)
@RequireCorePermission(CorePermissionCode.PERMISSION_CREATE)
@PostMapping("/create/batch")
public void createBatch(@RequestBody BatchCreateReq req) {
service.create(req);
}
@AOPLog
@RequireCoreRole(CoreRoleCode.ADMIN)
@RequireCorePermission(CorePermissionCode.PERMISSION_UPDATE)
@PostMapping("/update")
public void update(@RequestBody Permission permission) {
service.update(permission);
}
@AOPLog
@RequireCoreRole(CoreRoleCode.ADMIN)
@RequireCorePermission(CorePermissionCode.PERMISSION_DELETE)
@PostMapping("/delete")
public void delete(@RequestParam("id") String id) {
service.delete(id);
}
}
@@ -0,0 +1,115 @@
package com.imyeyu.api.modules.user.controller;
import com.imyeyu.api.annotation.RequireCorePermission;
import com.imyeyu.api.annotation.RequireCoreRole;
import com.imyeyu.api.bean.CorePermissionCode;
import com.imyeyu.api.bean.CoreRoleCode;
import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.modules.user.entity.Permission;
import com.imyeyu.api.modules.user.entity.Role;
import com.imyeyu.api.modules.user.service.PermissionService;
import com.imyeyu.api.modules.user.service.RoleService;
import com.imyeyu.api.modules.user.vo.role.UserRoleAuthorizeReq;
import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
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 java.util.List;
/**
* 角色 SDK 控制器
*
* @author 夜雨
* @since 2025-11-02 10:00
*/
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/user/role")
public class RoleController {
private final RoleService service;
private final PermissionService permissionService;
@RequireCoreRole(CoreRoleCode.ADMIN)
@RequireCorePermission(CorePermissionCode.ROLE_READ)
@PostMapping("/list")
public PageResult<Role> list(@RequestBody Page<Role> page) {
PageResult<Role> result = service.page(page);
if (result.getList() != null) {
result.getList().forEach(this::fillRoleDetail);
}
return result;
}
@RequireCoreRole(CoreRoleCode.ADMIN)
@RequireCorePermission(CorePermissionCode.ROLE_READ)
@PostMapping("/detail")
public Role detail(@RequestParam String id) {
Role role = service.get(id);
fillRoleDetail(role);
return role;
}
@AOPLog
@RequireCoreRole(CoreRoleCode.ADMIN)
@RequireCorePermission(CorePermissionCode.ROLE_CREATE)
@PostMapping("/create")
public void create(@RequestBody Role role) {
service.create(role);
}
@AOPLog
@RequireCoreRole(CoreRoleCode.ADMIN)
@RequireCorePermission(CorePermissionCode.ROLE_UPDATE)
@PostMapping("/update")
public void update(@RequestBody Role role) {
service.update(role);
}
@AOPLog
@RequireCoreRole(CoreRoleCode.ADMIN)
@RequireCorePermission(CorePermissionCode.ROLE_DELETE)
@PostMapping("/delete")
public void delete(@RequestParam String id) {
service.delete(id);
}
@RequireCoreRole(CoreRoleCode.ADMIN)
@RequireCorePermission(CorePermissionCode.USER_ROLE_READ)
@PostMapping("/authorized/list")
public List<Role> listUserRole(@RequestParam String userId, @RequestParam(required = false) ModuleCode moduleCode) {
if (moduleCode != null) {
return service.listByUserId(userId, moduleCode);
}
return service.listByUserId(userId);
}
@AOPLog
@RequireCoreRole(CoreRoleCode.ADMIN)
@RequireCorePermission(value = {CorePermissionCode.USER_ROLE_CREATE, CorePermissionCode.USER_ROLE_DELETE})
@PostMapping("/authorized/create")
public void authorizeUserRole(@RequestBody UserRoleAuthorizeReq req) {
service.authorizeUserRole(req);
}
@RequireCoreRole(CoreRoleCode.ADMIN)
@RequireCorePermission(CorePermissionCode.USER_ROLE_READ)
@PostMapping("/authorized/permission")
public List<Permission> listAuthorizedPermission(@RequestParam String userId) {
return service.listAllPermissionByUserId(userId);
}
private void fillRoleDetail(Role role) {
role.setPermissionList(permissionService.listByRoleId(role.getId()));
role.setAllPermissionList(service.listPermissionByRoleId(role.getId()));
role.setChildRoleList(service.listChildByParentRoleId(role.getId()));
}
}
@@ -0,0 +1,270 @@
package com.imyeyu.api.modules.user.controller;
import com.fasterxml.jackson.annotation.JsonView;
import com.imyeyu.api.annotation.RequireCorePermission;
import com.imyeyu.api.annotation.RequireCoreRole;
import com.imyeyu.api.bean.CorePermissionCode;
import com.imyeyu.api.bean.CoreRoleCode;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.common.vo.attach.BizUpdateReq;
import com.imyeyu.api.modules.user.entity.User;
import com.imyeyu.api.modules.user.service.UserLoginService;
import com.imyeyu.api.modules.user.service.UserService;
import com.imyeyu.api.modules.user.vo.CancelRequest;
import com.imyeyu.api.modules.user.vo.LoginRequest;
import com.imyeyu.api.modules.user.vo.LoginResponse;
import com.imyeyu.api.modules.user.vo.PhoneVerifyCodeSendRequest;
import com.imyeyu.api.modules.user.vo.PhoneVerifyRequest;
import com.imyeyu.api.modules.user.vo.UpdatePasswordRequest;
import com.imyeyu.spring.TimiSpring;
import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.CaptchaValid;
import com.imyeyu.spring.annotation.RequestRateLimit;
import com.imyeyu.spring.annotation.RequiredToken;
import com.imyeyu.spring.bean.CaptchaData;
import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.util.ResponseView;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
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 java.util.ArrayList;
import java.util.List;
/**
* 用户接口
*
* @author 夜雨
* @since 2021-02-23 21:38
*/
@Slf4j
@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/user")
public class UserController {
private final UserService userService;
private final UserLoginService userLoginService;
private final AttachmentService attachmentService;
@RequireCoreRole(CoreRoleCode.ADMIN)
@RequestRateLimit
@RequireCorePermission(CorePermissionCode.USER_READ)
@PostMapping("/list")
public PageResult<User> list(@RequestBody Page<User> page) {
return userService.page(page);
}
@AOPLog
@RequireCoreRole(CoreRoleCode.ADMIN)
@RequestRateLimit
@RequireCorePermission(CorePermissionCode.USER_READ)
@RequestMapping("/detail")
public User detail(@RequestParam String id) {
return userService.get(id);
}
@AOPLog
@RequireCoreRole(CoreRoleCode.ADMIN)
@RequestRateLimit
@RequireCorePermission(CorePermissionCode.USER_CREATE)
@PostMapping("/create")
public void create(@RequestBody User user) {
userService.create(user);
}
@AOPLog
@RequireCoreRole(CoreRoleCode.ADMIN)
@RequestRateLimit
@RequireCorePermission(CorePermissionCode.USER_UPDATE)
@PostMapping("/update")
public void update(@RequestBody User user) {
userService.update(user);
}
@AOPLog
@RequireCoreRole(CoreRoleCode.ADMIN)
@RequestRateLimit
@RequestMapping("/delete")
public void delete(@RequestParam String id) {
userService.delete(id);
}
/**
* 用户注册
*/
@AOPLog
@JsonView(ResponseView.Public.class)
@RequestRateLimit
@CaptchaValid
@PostMapping("/register")
public LoginResponse register(@RequestBody @Valid CaptchaData<User> user) {
userService.create(user.getData());
// 登录
LoginRequest loginRequest = new LoginRequest();
loginRequest.setUser(user.getData().getName());
loginRequest.setPassword(user.getData().getPassword());
return userLoginService.login(loginRequest);
}
/**
* 用户登录
*
* @param req 登录请求
* @return 登录结果
*/
@AOPLog
@JsonView(ResponseView.Public.class)
@RequestRateLimit
@PostMapping("/login")
public LoginResponse login(@RequestBody @Valid CaptchaData<LoginRequest> req) {
return userLoginService.login(req.getData());
}
@AOPLog
@RequiredToken
@RequestRateLimit
@RequestMapping("/login/token")
public LoginResponse token() {
return userLoginService.login(TimiSpring.getToken(), userLoginService.getRequireLoginUserId());
}
/**
* 更新当前用户资料
*
* @param req 用户资料
* @return 最新登录用户
*/
@AOPLog
@RequiredToken
@RequestRateLimit
@RequireCorePermission(CorePermissionCode.USER_UPDATE)
@PostMapping("/current/update")
public LoginResponse updateCurrent(@RequestBody User req) {
User loginUser = userLoginService.getRequireLoginUser();
User user = userService.get(loginUser.getId());
if (req.getName() != null) {
user.setName(req.getName());
}
if (req.getNick() != null) {
user.setNick(req.getNick());
}
if (req.getGender() != null) {
user.setGender(req.getGender());
}
if (req.getDescription() != null) {
user.setDescription(req.getDescription());
}
if (req.getAvatarType() != null) {
user.setAvatarType(req.getAvatarType());
}
if (req.getWrapperType() != null) {
user.setWrapperType(req.getWrapperType());
}
userService.update(user);
updateCurrentAvatar(loginUser.getId(), req.getTempFileIdList());
return userLoginService.login(TimiSpring.getToken(), loginUser.getId());
}
/**
* 用户登出
*/
@AOPLog
@PostMapping("/logout")
public void logout() {
userLoginService.logout();
}
/**
* 修改当前用户密码
*
* @param req 修改密码请求
*/
@AOPLog
@RequireCorePermission(CorePermissionCode.USER_UPDATE)
@PostMapping("/update/password")
public void updatePassword(@RequestBody @Valid UpdatePasswordRequest req) {
userLoginService.updatePassword(req.getOldValue(), req.getNewValue());
}
@AOPLog
@RequireCorePermission(CorePermissionCode.USER_UPDATE)
@PostMapping("/update/email")
public void updateEmail() {
}
/**
* 发送手机号验证码
*
* @param req 手机号验证码请求
*/
@AOPLog
@RequiredToken
@RequireCorePermission(CorePermissionCode.USER_UPDATE)
@PostMapping("/phone/verify/send")
public void sendPhoneCode(@RequestBody @Valid PhoneVerifyCodeSendRequest req) {
userLoginService.sendPhoneVerifyCode(req.getPhoneNo());
}
/**
* 验证手机号
*
* @param req 手机号验证请求
*/
@AOPLog
@RequiredToken
@RequireCorePermission(CorePermissionCode.USER_UPDATE)
@PostMapping("/phone/verify")
public void verifyPhone(@RequestBody @Valid PhoneVerifyRequest req) {
userLoginService.verifyPhoneNo(req.getPhoneNo(), req.getVerifyCode());
}
/**
* 注销当前用户
*
* @param req 注销请求
*/
@AOPLog
@RequireCorePermission(CorePermissionCode.USER_DELETE)
@PostMapping("/cancel")
public void cancel(@RequestBody @Valid CancelRequest req) {
userLoginService.deactivate(req.getPassword());
}
private void updateCurrentAvatar(String userId, List<String> tempFileIdList) {
if (tempFileIdList == null || tempFileIdList.isEmpty()) {
return;
}
BizUpdateReq req = new BizUpdateReq();
req.setBizType(Attachment.BizType.USER);
req.setBizId(userId);
List<Attachment> items = attachmentService.listByBizId(Attachment.BizType.USER, userId).stream()
.filter(item -> !User.AttachType.AVATAR.name().equals(item.getAttachType()))
.map(item -> {
Attachment attachment = new Attachment();
attachment.setId(item.getId());
return attachment;
})
.toList();
List<Attachment> avatarItems = tempFileIdList.stream().map(tempFileId -> {
Attachment attachment = new Attachment();
attachment.setTempFileId(tempFileId);
attachment.setAttachTypeValue(User.AttachType.AVATAR);
return attachment;
}).toList();
items = new ArrayList<>(items);
items.addAll(avatarItems);
req.setItems(items);
attachmentService.updateByBiz(req);
}
}
@@ -0,0 +1,35 @@
package com.imyeyu.api.modules.user.entity;
import com.imyeyu.api.annotation.MultilingualField;
import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.UUIDEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 权限
*
* @author 夜雨
* @since 2026-05-13 14:40
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class Permission extends UUIDEntity {
/** 模块代码 */
private ModuleCode moduleCode;
/** 代码 */
private String code;
/** 名称多语言映射 ID */
private String nameLangId;
/** 说明 */
private String description;
@Transient
@MultilingualField("nameLangId")
private String name;
}
@@ -0,0 +1,58 @@
package com.imyeyu.api.modules.user.entity;
import com.imyeyu.api.annotation.MultilingualField;
import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.UUIDEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
import java.util.Set;
/**
* 角色
*
* @author 夜雨
* @since 2026-05-13 14:40
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class Role extends UUIDEntity {
/** 模块代码 */
private ModuleCode moduleCode;
/** 代码 */
private String code;
/** 名称 */
private String nameLangId;
/** 说明 */
private String description;
@Transient
@MultilingualField("nameLangId")
private String name;
/** 角色自身的权限列表,用于编辑 */
@Transient
protected List<Permission> permissionList;
/** 所有权限列表,包含从子角色继承的权限 */
@Transient
protected List<Permission> allPermissionList;
/** 子角色列表 */
@Transient
protected List<Role> childRoleList;
/** 权限 ID 列表 */
@Transient
protected Set<String> permissionIdList;
/** 子角色 ID 列表 */
@Transient
protected Set<String> childRoleIdList;
}
@@ -0,0 +1,49 @@
package com.imyeyu.api.modules.user.entity;
import com.imyeyu.api.modules.user.bean.BootstrapData;
import com.imyeyu.spring.annotation.table.AutoUUID;
import com.imyeyu.spring.annotation.table.DeleteColumn;
import com.imyeyu.spring.annotation.table.Id;
import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.Creatable;
import com.imyeyu.spring.entity.Deletable;
import com.imyeyu.spring.entity.IDEntity;
import lombok.Data;
/**
* 角色权限
*
* @author 夜雨
* @since 2026-05-13 14:40
*/
@Data
public class RolePermission implements IDEntity<String>, Creatable, Deletable {
/** 主键 */
@Id
@AutoUUID
protected String id;
/** 角色 ID */
protected String roleId;
/** 权限 ID */
protected String permissionId;
/** 创建时间 */
protected Long createdAt;
/** 删除时间 */
@DeleteColumn
protected Long deletedAt;
@Transient
protected Role role;
@Transient
protected Permission permission;
public boolean isProtected() {
return BootstrapData.isProtectedRole(role) && BootstrapData.isProtectedPermissionCode(permission.getCode());
}
}
@@ -0,0 +1,37 @@
package com.imyeyu.api.modules.user.entity;
import com.imyeyu.spring.annotation.table.AutoUUID;
import com.imyeyu.spring.annotation.table.DeleteColumn;
import com.imyeyu.spring.annotation.table.Id;
import com.imyeyu.spring.entity.Creatable;
import com.imyeyu.spring.entity.Deletable;
import com.imyeyu.spring.entity.IDEntity;
import lombok.Data;
/**
* 角色关联
*
* @author 夜雨
* @since 2026-05-13 14:40
*/
@Data
public class RoleRelation implements IDEntity<String>, Creatable, Deletable {
/** 主键 */
@Id
@AutoUUID
private String id;
/** 上级角色 ID */
private String parentRoleId;
/** 子级角色 ID */
private String childRoleId;
/** 创建时间 */
private Long createdAt;
/** 删除时间 */
@DeleteColumn
private Long deletedAt;
}
@@ -0,0 +1,156 @@
package com.imyeyu.api.modules.user.entity;
import com.fasterxml.jackson.annotation.JsonView;
import com.imyeyu.api.modules.common.bean.ImageType;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.entity.Setting;
import com.imyeyu.api.modules.user.bean.Gender;
import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.UUIDEntity;
import com.imyeyu.spring.util.ResponseView;
import com.imyeyu.utils.Time;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
/**
* 用户
*
* @author 夜雨
* @since 2021-03-01 17:11
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class User extends UUIDEntity {
/**
*
*
* @author 夜雨
* @since 2024-02-21 14:48
*/
public enum AttachType {
AVATAR,
WRAPPER,
LICENSE,
DEFAULT_AVATAR,
DEFAULT_WRAPPER
}
/** 用户名 */
@JsonView(ResponseView.Public.class)
protected String name;
/** 昵称 */
@JsonView(ResponseView.Public.class)
protected String nick;
/** 密码 */
@JsonView(ResponseView.Admin.class)
protected String password;
/** 邮箱 */
@JsonView(ResponseView.Public.class)
protected String email;
/** 邮箱验证时间 */
@JsonView(ResponseView.Admin.class)
protected Long emailVerifyAt;
/** 手机号 */
@JsonView(ResponseView.Public.class)
protected String phoneNo;
/** 手机号验证时间 */
@JsonView(ResponseView.Admin.class)
protected Long phoneNoVerifyAt;
/** 背景图渲染算法 */
@JsonView(ResponseView.Public.class)
protected ImageType wrapperType;
/** 头像渲染算法 */
@JsonView(ResponseView.Public.class)
protected ImageType avatarType;
/** 经验值 */
@JsonView(ResponseView.Public.class)
protected Integer exp;
/** 性别 */
@JsonView(ResponseView.Public.class)
protected Gender gender;
/** 出生日期 */
@JsonView(ResponseView.Public.class)
protected Long birthdate;
/** QQ */
@JsonView(ResponseView.Public.class)
protected String qq;
/** 说明 */
@JsonView(ResponseView.Public.class)
protected String description;
/** 上次登录 IP */
@JsonView(ResponseView.Admin.class)
protected String lastLoginIP;
/** 上次登录时间 */
@JsonView(ResponseView.Admin.class)
protected Long lastLoginAt;
/** 解除禁言时间 */
@JsonView(ResponseView.Admin.class)
protected Long unmuteAt;
/** 解除封禁时间 */
@JsonView(ResponseView.Admin.class)
protected Long unbanAt;
@Transient
@JsonView(ResponseView.Public.class)
protected List<String> roleList;
@Transient
@JsonView(ResponseView.Public.class)
protected List<String> permissionList;
@Transient
@JsonView(ResponseView.Public.class)
protected List<Attachment> attachmentList;
@Transient
@JsonView(ResponseView.Public.class)
protected List<Setting> settingList;
@Transient
@JsonView(ResponseView.Public.class)
protected List<String> tempFileIdList;
/** @return true 为禁言中 */
public boolean isMuting() {
return unmuteAt != null && Time.now() < unmuteAt;
}
/** @return true 为封禁中 */
public boolean isBanning() {
return unbanAt != null && Time.now() < unbanAt;
}
public boolean isEmailVerified() {
return emailVerifyAt != null;
}
/** @return true 为手机号已验证 */
public boolean isPhoneNoVerified() {
return phoneNoVerifyAt != null;
}
}
@@ -0,0 +1,56 @@
package com.imyeyu.api.modules.user.entity;
import com.imyeyu.api.modules.common.entity.Comment;
import com.imyeyu.spring.annotation.table.AutoUUID;
import com.imyeyu.spring.annotation.table.DeleteColumn;
import com.imyeyu.spring.annotation.table.Id;
import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.Creatable;
import com.imyeyu.spring.entity.Deletable;
import com.imyeyu.spring.entity.IDEntity;
import lombok.Data;
/**
* 用户行为记录
*
* @author 夜雨
* @since 2026-05-27 17:10
*/
@Data
public class UserBehaviorRecord implements IDEntity<String>, Creatable, Deletable {
/** 主键 */
@Id
@AutoUUID
private String id;
/** 业务类型 */
private String bizType;
/** 用户 ID */
private String userId;
/** 目标类型 */
private String targetType;
/** 目标 ID */
private String targetId;
/** 行为类型 */
private String actionType;
/** 创建时间 */
private Long createdAt;
/** 删除时间 */
@DeleteColumn
private Long deletedAt;
/** 用户信息 */
@Transient
private User user;
/** 评论信息 */
@Transient
private Comment comment;
}
@@ -0,0 +1,37 @@
package com.imyeyu.api.modules.user.entity;
import com.imyeyu.spring.annotation.table.AutoUUID;
import com.imyeyu.spring.annotation.table.DeleteColumn;
import com.imyeyu.spring.annotation.table.Id;
import com.imyeyu.spring.entity.Creatable;
import com.imyeyu.spring.entity.Deletable;
import com.imyeyu.spring.entity.IDEntity;
import lombok.Data;
/**
* 用户角色
*
* @author 夜雨
* @since 2026-05-13 14:40
*/
@Data
public class UserRole implements IDEntity<String>, Creatable, Deletable {
/** 主键 */
@Id
@AutoUUID
private String id;
/** 用户 ID */
private String userId;
/** 角色 ID */
private String roleId;
/** 创建时间 */
private Long createdAt;
/** 删除时间 */
@DeleteColumn
private Long deletedAt;
}
@@ -0,0 +1,17 @@
package com.imyeyu.api.modules.user.mapper;
import com.imyeyu.api.modules.user.entity.Permission;
import com.imyeyu.spring.mapper.BaseMapper;
import java.util.List;
/**
* 权限 Mapper
*
* @author 夜雨
* @since 2026-05-13 14:46
*/
public interface PermissionMapper extends BaseMapper<Permission, String> {
List<Permission> selectByIdList(List<String> list);
}
@@ -0,0 +1,23 @@
package com.imyeyu.api.modules.user.mapper;
import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.modules.user.entity.Role;
import com.imyeyu.spring.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 角色 Mapper
*
* @author 夜雨
* @since 2026-05-13 14:46
*/
public interface RoleMapper extends BaseMapper<Role, String> {
List<Role> selectByIdList(List<String> idList);
List<Role> selectByModuleCodeAndCodeList(@Param("moduleCode") ModuleCode moduleCode, @Param("codeList") List<String> codeList);
List<Role> selectByModuleCode(@Param("moduleCode") ModuleCode moduleCode);
}
@@ -0,0 +1,28 @@
package com.imyeyu.api.modules.user.mapper;
import com.imyeyu.api.modules.user.entity.Permission;
import com.imyeyu.api.modules.user.entity.RolePermission;
import com.imyeyu.spring.mapper.BaseMapper;
import java.util.List;
/**
* 角色权限 Mapper
*
* @author 夜雨
* @since 2026-05-13 14:46
*/
public interface RolePermissionMapper extends BaseMapper<RolePermission, String> {
default List<RolePermission> selectByRoleId(String roleId) {
return selectByRoleIdList(List.of(roleId));
}
List<RolePermission> selectByRoleIdList(List<String> roleIdList);
List<Permission> selectPermissionByRoleIdList(List<String> roleIdList);
void createBatch(List<RolePermission> rolePermissionList);
void deleteByPermissionIdList(String roleId, List<String> permissionIdList);
}
@@ -0,0 +1,25 @@
package com.imyeyu.api.modules.user.mapper;
import com.imyeyu.api.modules.user.entity.RoleRelation;
import com.imyeyu.spring.mapper.BaseMapper;
import java.util.List;
/**
* 角色关联 Mapper
*
* @author 夜雨
* @since 2026-05-13 14:46
*/
public interface RoleRelationMapper extends BaseMapper<RoleRelation, String> {
List<RoleRelation> selectByParentRoleId(String parentRoleId);
List<String> selectChildRoleIdListByParentRoleId(String parentRoleId);
List<String> selectChildRoleIdListByParentRoleIdList(List<String> parentRoleIdList);
void createBatch(List<RoleRelation> roleRelationList);
void deleteByChildRoleIdList(String parentRoleId, List<String> childRoleIdList);
}
@@ -0,0 +1,39 @@
package com.imyeyu.api.modules.user.mapper;
import com.imyeyu.api.modules.user.entity.UserBehaviorRecord;
import com.imyeyu.spring.mapper.BaseMapper;
import java.util.List;
/**
* 用户行为记录 Mapper
*
* @author 夜雨
* @since 2026-05-27 17:10
*/
public interface UserBehaviorRecordMapper extends BaseMapper<UserBehaviorRecord, String> {
/**
* 查询有效行为记录
*
* @param bizType 业务类型
* @param userId 用户 ID
* @param targetType 目标类型
* @param targetId 目标 ID
* @param actionType 行为类型
* @return 行为记录,不存在时返回 {@code null}
*/
UserBehaviorRecord selectActive(String bizType, String userId, String targetType, String targetId, String actionType);
/**
* 查询用户有效行为目标 ID 列表
*
* @param bizType 业务类型
* @param userId 用户 ID
* @param targetType 目标类型
* @param actionType 行为类型
* @param targetIdList 目标 ID 列表
* @return 目标 ID 列表
*/
List<String> selectActiveTargetIdList(String bizType, String userId, String targetType, String actionType, List<String> targetIdList);
}
@@ -0,0 +1,36 @@
package com.imyeyu.api.modules.user.mapper;
import com.imyeyu.api.modules.user.entity.User;
import com.imyeyu.spring.mapper.BaseMapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* 用户
*
* @author 夜雨
* @since 2021-02-23 21:33
*/
public interface UserMapper extends BaseMapper<User, String> {
@Select("SELECT * FROM `user` WHERE `email` = #{email} AND `email_verify_at` IS NULL" + NOT_DELETE + LIMIT_1)
User selectByEmail(String email);
@Select("SELECT * FROM `user` WHERE `email` = #{email} AND `email_verify_at` < " + UNIX_TIME + NOT_DELETE + LIMIT_1)
User selectByVerifiedEmail(String email);
@Select("SELECT * FROM `user` WHERE `phone_no` = #{phoneNo}" + NOT_DELETE + LIMIT_1)
User selectByPhoneNo(String phoneNo);
@Select("SELECT * FROM `user` WHERE `phone_no` = #{phoneNo} AND `phone_no_verify_at` < " + UNIX_TIME + NOT_DELETE + LIMIT_1)
User selectByVerifiedPhoneNo(String phoneNo);
/**
* 根据 ID 列表查询用户
*
* @param idList 用户 ID 列表
* @return 用户列表
*/
List<User> listByIdList(List<String> idList);
}
@@ -0,0 +1,19 @@
package com.imyeyu.api.modules.user.mapper;
import com.imyeyu.api.modules.user.entity.UserRole;
import com.imyeyu.spring.mapper.BaseMapper;
import java.util.List;
/**
* 用户角色 Mapper
*
* @author 夜雨
* @since 2026-05-13 14:46
*/
public interface UserRoleMapper extends BaseMapper<UserRole, String> {
void createBatch(List<UserRole> userRoleList);
void deleteByRoleIdList(String userId, List<String> roleIdList);
}
@@ -0,0 +1,56 @@
package com.imyeyu.api.modules.user.service;
import com.imyeyu.api.bean.ModuleCode;
import java.util.Set;
/**
* 权限检查器
*
* @author 夜雨
* @since 2026-06-05 17:45
*/
public interface PermissionChecker {
/**
* 查询当前用户权限码集合
*
* @return 权限码集合
*/
Set<String> codeSet();
/**
* 查询指定用户权限码集合
*
* @param userId 用户 ID
* @return 权限码集合
*/
Set<String> codeSet(String userId);
/// 判断当前用户是否拥有全部权限
///
/// @param moduleCode 模块代码
/// @param codeList 权限代码列表
/// @return 是否拥有
boolean hasAll(ModuleCode moduleCode, String... codeList);
/// 判断当前用户是否拥有任一权限
///
/// @param moduleCode 模块代码
/// @param codeList 权限代码列表
/// @return 是否拥有
boolean hasAny(ModuleCode moduleCode, String... codeList);
/// 校验当前用户拥有全部权限
///
/// @param moduleCode 模块代码
/// @param codeList 权限代码列表
void checkAll(ModuleCode moduleCode, String... codeList);
/// 校验当前用户拥有任一权限
///
/// @param moduleCode 模块代码
/// @param codeList 权限代码列表
void checkAny(ModuleCode moduleCode, String... codeList);
}
@@ -0,0 +1,33 @@
package com.imyeyu.api.modules.user.service;
import com.imyeyu.api.modules.user.entity.Permission;
import com.imyeyu.api.modules.user.vo.permission.BatchCreateReq;
import com.imyeyu.spring.service.BaseService;
import java.util.List;
/// 权限服务
///
/// @author 夜雨
/// @since 2026-05-13 14:46
public interface PermissionService extends BaseService<Permission, String> {
/// 批量创建权限
///
/// @param req 批量创建请求
void create(BatchCreateReq req);
/// 查询角色权限
///
/// @param roleId 角色 ID
/// @return 权限列表
default List<Permission> listByRoleId(String roleId) {
return listByRoleId(List.of(roleId));
}
/// 查询多个角色的权限并集
///
/// @param roleIdList 角色 ID 列表
/// @return 权限列表
List<Permission> listByRoleId(List<String> roleIdList);
}
@@ -0,0 +1,44 @@
package com.imyeyu.api.modules.user.service;
import com.imyeyu.api.bean.ModuleCode;
import java.util.Set;
///
/// 角色检查器
///
/// @author 夜雨
/// @since 2026-06-05 17:45
public interface RoleChecker {
/// 查询当前用户角色代码集合
///
/// @return 角色代码集合,格式为 MODULE:ROLE
Set<String> codeSet();
/// 判断当前用户是否拥有全部角色
///
/// @param moduleCode 模块代码
/// @param roleCodeList 角色代码列表
/// @return 是否拥有
boolean hasAll(ModuleCode moduleCode, String... roleCodeList);
/// 判断当前用户是否拥有任一角色
///
/// @param moduleCode 模块代码
/// @param roleCodeList 角色代码列表
/// @return 是否拥有
boolean hasAny(ModuleCode moduleCode, String... roleCodeList);
/// 校验当前用户拥有全部角色
///
/// @param moduleCode 模块代码
/// @param roleCodeList 角色代码列表
void checkAll(ModuleCode moduleCode, String... roleCodeList);
/// 校验当前用户拥有任一角色
///
/// @param moduleCode 模块代码
/// @param roleCodeList 角色代码列表
void checkAny(ModuleCode moduleCode, String... roleCodeList);
}
@@ -0,0 +1,31 @@
package com.imyeyu.api.modules.user.service;
import com.imyeyu.api.modules.user.entity.RoleRelation;
import com.imyeyu.spring.service.BaseService;
import java.util.List;
/// 角色继承关系服务
///
/// @author 夜雨
/// @since 2026-05-13 14:46
public interface RoleRelationService extends BaseService<RoleRelation, String> {
/// 查询直属子角色关联
///
/// @param parentRoleId 父角色 ID
/// @return 角色关联列表
List<RoleRelation> listByParentRoleId(String parentRoleId);
/// 查询直属子角色 ID 列表
///
/// @param parentRoleId 父角色 ID
/// @return 子角色 ID 列表
List<String> listChildRoleIdByParentRoleId(String parentRoleId);
/// 递归展开全部子角色 ID
///
/// @param parentRoleIdList 父角色 ID 列表
/// @return 全部子角色 ID 列表
List<String> listAllChildRoleIdByParentRoleIdList(List<String> parentRoleIdList);
}
@@ -0,0 +1,83 @@
package com.imyeyu.api.modules.user.service;
import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.modules.user.entity.Permission;
import com.imyeyu.api.modules.user.entity.Role;
import com.imyeyu.api.modules.user.vo.role.UserRoleAuthorizeReq;
import com.imyeyu.spring.service.BaseService;
import java.util.List;
/// 角色服务
///
/// @author 夜雨
/// @since 2026-05-13 14:46
public interface RoleService extends BaseService<Role, String> {
/// 给用户授权角色
///
/// @param req 用户角色授权请求
void authorizeUserRole(UserRoleAuthorizeReq req);
/// 查询用户直属角色
///
/// @param userId 用户 ID
/// @return 角色列表
List<Role> listByUserId(String userId);
/// 查询用户指定模块直属角色
///
/// @param userId 用户 ID
/// @param moduleCode 模块代码
/// @return 角色列表
List<Role> listByUserId(String userId, ModuleCode moduleCode);
/// 查询模块角色
///
/// @param moduleCode 模块代码
/// @return 角色列表
List<Role> listByModuleCode(ModuleCode moduleCode);
/// 查询用户直属角色编码
///
/// @param userId 用户 ID
/// @return 角色编码列表,格式为 MODULE:ROLE
default List<String> listCodeByUserId(String userId) {
return listByUserId(userId).stream().map(role -> "%s:%s".formatted(role.getModuleCode().name(), role.getCode())).toList();
}
/// 查询用户全部角色编码
///
/// @param userId 用户 ID
/// @return 包含子角色的角色编码列表,格式为 MODULE:ROLE
List<String> listAllCodeByUserId(String userId);
/// 查询用户全部权限
///
/// @param userId 用户 ID
/// @return 权限列表
List<Permission> listAllPermissionByUserId(String userId);
/// 查询用户全部权限编码
///
/// @param userId 用户 ID
/// @return 权限编码列表,格式为 MODULE:PERMISSION
default List<String> listAllPermissionCodeByUserId(String userId) {
return listAllPermissionByUserId(userId).stream()
.map(permission -> "%s:%s".formatted(permission.getModuleCode().name(), permission.getCode()))
.distinct()
.toList();
}
/// 查询直属子角色
///
/// @param parentRoleId 父角色 ID
/// @return 子角色列表
List<Role> listChildByParentRoleId(String parentRoleId);
/// 查询角色全部权限
///
/// @param roleId 角色 ID
/// @return 权限列表
List<Permission> listPermissionByRoleId(String roleId);
}
@@ -0,0 +1,48 @@
package com.imyeyu.api.modules.user.service;
import com.imyeyu.api.modules.user.entity.UserBehaviorRecord;
import com.imyeyu.spring.service.BaseService;
import java.util.List;
import java.util.Set;
/**
* 用户行为记录服务
*
* @author 夜雨
* @since 2026-05-27 17:10
*/
public interface UserBehaviorRecordService extends BaseService<UserBehaviorRecord, String> {
/**
* 记录用户行为
*
* @param entity 行为记录
* @return true 为新增记录
*/
boolean record(UserBehaviorRecord entity);
/**
* 取消用户行为
*
* @param bizType 业务类型
* @param userId 用户 ID
* @param targetType 目标类型
* @param targetId 目标 ID
* @param actionType 行为类型
* @return true 为已取消记录
*/
boolean cancel(String bizType, String userId, String targetType, String targetId, String actionType);
/**
* 查询用户有效行为目标 ID 集合
*
* @param bizType 业务类型
* @param userId 用户 ID
* @param targetType 目标类型
* @param actionType 行为类型
* @param targetIdList 目标 ID 列表
* @return 目标 ID 集合
*/
Set<String> getActiveTargetIdSet(String bizType, String userId, String targetType, String actionType, List<String> targetIdList);
}
@@ -0,0 +1,76 @@
package com.imyeyu.api.modules.user.service;
import com.imyeyu.api.modules.user.entity.User;
import com.imyeyu.api.modules.user.vo.LoginRequest;
import com.imyeyu.api.modules.user.vo.LoginResponse;
/**
* 用户登录服务
*
* @author 夜雨
* @since 2026-06-09
*/
public interface UserLoginService {
/**
* 用户登录
*
* @param req 登录请求
* @return 登录响应
*/
LoginResponse login(LoginRequest req);
LoginResponse login(String token, String userId);
/**
* 根据令牌获取用户
*
* @return 用户
*/
User getLoginUser();
/**
* 根据令牌获取必须有效登录的用户,否则抛出错误
*
* @return 用户
*/
User getRequireLoginUser();
default String getRequireLoginUserId() {
return getRequireLoginUser().getId();
}
/**
* 用户登出
*
*/
void logout();
/**
* 更新当前用户密码
*
*/
void updatePassword(String oldPassword, String newPassword);
/**
* 发送手机号验证码
*
* @param phoneNo 手机号
*/
void sendPhoneVerifyCode(String phoneNo);
/**
* 验证当前用户手机号
*
* @param phoneNo 手机号
* @param verifyCode 验证码
*/
void verifyPhoneNo(String phoneNo, String verifyCode);
/**
* 注销当前用户
*
* @param password 当前密码
*/
void deactivate(String password);
}
@@ -0,0 +1,39 @@
package com.imyeyu.api.modules.user.service;
import com.imyeyu.api.modules.user.entity.UserRole;
import com.imyeyu.spring.service.BaseService;
import java.util.List;
/// 用户角色绑定服务
///
/// @author 夜雨
/// @since 2026-05-13 14:46
public interface UserRoleService extends BaseService<UserRole, String> {
/// 批量创建用户角色绑定
///
/// @param list 用户角色绑定列表
void createBatch(List<UserRole> list);
/// 查询用户角色绑定
///
/// @param userId 用户 ID
/// @return 用户角色绑定列表
List<UserRole> listByUserId(String userId);
/// 查询用户直属角色 ID
///
/// @param userId 用户 ID
/// @return 角色 ID 列表
default List<String> listRoleIdByUserId(String userId) {
List<UserRole> bindingList = listByUserId(userId);
return bindingList.stream().map(UserRole::getRoleId).toList();
}
/// 删除用户指定角色绑定
///
/// @param userId 用户 ID
/// @param roleIdList 角色 ID 列表
void deleteByRoleIdList(String userId, List<String> roleIdList);
}
@@ -0,0 +1,23 @@
package com.imyeyu.api.modules.user.service;
import com.imyeyu.api.modules.user.entity.User;
import com.imyeyu.spring.service.BaseService;
/**
* 用户管理服务
*
* @author 夜雨
* @since 2021-02-23 21:32
*/
public interface UserService extends BaseService<User, String> {
User getByName(String name);
User getByEmail(String email);
User getByVerifiedEmail(String email);
User getByPhoneNo(String phoneNo);
User getByVerifiedPhoneNo(String phoneNo);
}
@@ -0,0 +1,75 @@
package com.imyeyu.api.modules.user.service.implement;
import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.modules.user.service.PermissionChecker;
import com.imyeyu.api.modules.user.service.RoleService;
import com.imyeyu.api.modules.user.service.UserLoginService;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* 权限检查器实现
*
* @author 夜雨
* @since 2026-06-05 17:45
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class PermissionCheckerImplement implements PermissionChecker {
private final RoleService roleService;
private final UserLoginService userLoginService;
public Set<String> codeSet() {
return codeSet(userLoginService.getRequireLoginUserId());
}
@Override
public Set<String> codeSet(String userId) {
return new HashSet<>(roleService.listAllPermissionCodeByUserId(userId));
}
@Override
public boolean hasAll(ModuleCode moduleCode, String... permissionCodeList) {
Set<String> codeSet = codeSet();
return Arrays.stream(permissionCodeList).allMatch(permissionCode -> codeSet.contains(buildPermissionKey(moduleCode, permissionCode)));
}
@Override
public boolean hasAny(ModuleCode moduleCode, String... permissionCodeList) {
Set<String> codeSet = codeSet();
return Arrays.stream(permissionCodeList).anyMatch(permissionCode -> codeSet.contains(buildPermissionKey(moduleCode, permissionCode)));
}
@Override
public void checkAll(ModuleCode moduleCode, String... permissionCodeList) {
if (!hasAll(moduleCode, permissionCodeList)) {
String codeList = buildPermissionKeys(moduleCode, permissionCodeList).toString();
throw new TimiException(TimiCode.PERMISSION_ERROR).msgKey("权限不足:%s".formatted(codeList));
}
}
@Override
public void checkAny(ModuleCode moduleCode, String... permissionCodeList) {
if (!hasAny(moduleCode, permissionCodeList)) {
String codeList = buildPermissionKeys(moduleCode, permissionCodeList).toString();
throw new TimiException(TimiCode.PERMISSION_ERROR).msgKey("权限不足:%s".formatted(codeList));
}
}
private String buildPermissionKey(ModuleCode moduleCode, String permissionCode) {
return "%s:%s".formatted(moduleCode.name(), permissionCode);
}
private Set<String> buildPermissionKeys(ModuleCode moduleCode, String... permissionCodeList) {
return Arrays.stream(permissionCodeList).map(permissionCode -> buildPermissionKey(moduleCode, permissionCode)).collect(java.util.stream.Collectors.toSet());
}
}
@@ -0,0 +1,87 @@
package com.imyeyu.api.modules.user.service.implement;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.common.mapper.MultilingualMapper;
import com.imyeyu.api.modules.common.service.MultilingualService;
import com.imyeyu.api.modules.user.entity.Permission;
import com.imyeyu.api.modules.user.mapper.PermissionMapper;
import com.imyeyu.api.modules.user.mapper.RolePermissionMapper;
import com.imyeyu.api.modules.user.service.PermissionService;
import com.imyeyu.api.modules.user.vo.permission.BatchCreateReq;
import com.imyeyu.api.util.RedisMultilingual;
import com.imyeyu.java.TimiJava;
import com.imyeyu.spring.TimiSpring;
import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService;
import com.imyeyu.utils.Time;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/// 权限服务实现
///
/// @author 夜雨
/// @since 2026-05-13 14:46
@Service
@RequiredArgsConstructor
public class PermissionServiceImplement extends AbstractEntityService<Permission, String> implements PermissionService {
private final PermissionMapper mapper;
private final RolePermissionMapper rolePermissionMapper;
private final MultilingualMapper multilingualMapper;
private final MultilingualService multilingualService;
private final RedisMultilingual multilingual;
@Override
protected BaseMapper<Permission, String> mapper() {
return mapper;
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void create(Permission permission) {
permission.setNameLangId(multilingualService.create(permission.getName()));
super.create(permission);
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void create(BatchCreateReq req) {
if (TimiJava.isEmpty(req.getTypes())) {
return;
}
List<Permission> permissionList = new ArrayList<>();
for (BatchCreateReq.Type type : req.getTypes()) {
Permission permission = new Permission();
permission.setId(UUID.randomUUID().toString());
permission.setModuleCode(req.getModuleCode());
permission.setCode(req.getPrefixCode() + "_" + type.name());
permission.setName(req.getPrefixName() + multilingual.map(TimiSpring.getLanguage()).text(type.name().toLowerCase()));
permission.setCreatedAt(Time.now());
permissionList.add(permission);
}
for (Permission permission : permissionList) {
super.create(permission);
}
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void update(Permission permission) {
permission.setNameLangId(multilingualService.createIfDifferent(permission.getNameLangId(), permission.getName()));
mapper.updateSelective(permission);
}
@Override
public List<Permission> listByRoleId(List<String> roleIdList) {
if (TimiJava.isEmpty(roleIdList)) {
return List.of();
}
return rolePermissionMapper.selectPermissionByRoleIdList(roleIdList);
}
}
@@ -0,0 +1,68 @@
package com.imyeyu.api.modules.user.service.implement;
import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.modules.user.service.RoleChecker;
import com.imyeyu.api.modules.user.service.RoleService;
import com.imyeyu.api.modules.user.service.UserLoginService;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
///
/// 角色检查器实现
///
/// @author 夜雨
/// @since 2026-06-05 17:45
@Slf4j
@Service
@RequiredArgsConstructor
public class RoleCheckerImplement implements RoleChecker {
private final RoleService roleService;
private final UserLoginService userLoginService;
@Override
public Set<String> codeSet() {
return new HashSet<>(roleService.listAllCodeByUserId(userLoginService.getRequireLoginUserId()));
}
@Override
public boolean hasAll(ModuleCode moduleCode, String... roleCodeList) {
Set<String> codeSet = codeSet();
return Arrays.stream(roleCodeList).allMatch(roleCode -> codeSet.contains(buildRoleKey(moduleCode, roleCode)));
}
@Override
public boolean hasAny(ModuleCode moduleCode, String... roleCodeList) {
Set<String> codeSet = codeSet();
return Arrays.stream(roleCodeList).anyMatch(roleCode -> codeSet.contains(buildRoleKey(moduleCode, roleCode)));
}
@Override
public void checkAll(ModuleCode moduleCode, String... roleCodeList) {
if (!hasAll(moduleCode, roleCodeList)) {
throw new TimiException(TimiCode.PERMISSION_ERROR).msgKey("require role: %s".formatted(buildRoleKeys(moduleCode, roleCodeList)));
}
}
@Override
public void checkAny(ModuleCode moduleCode, String... roleCodeList) {
if (!hasAny(moduleCode, roleCodeList)) {
throw new TimiException(TimiCode.PERMISSION_ERROR).msgKey("require role: %s".formatted(buildRoleKeys(moduleCode, roleCodeList)));
}
}
private String buildRoleKey(ModuleCode moduleCode, String roleCode) {
return "%s:%s".formatted(moduleCode.name(), roleCode);
}
private Set<String> buildRoleKeys(ModuleCode moduleCode, String... roleCodeList) {
return Arrays.stream(roleCodeList).map(roleCode -> buildRoleKey(moduleCode, roleCode)).collect(java.util.stream.Collectors.toSet());
}
}
@@ -0,0 +1,68 @@
package com.imyeyu.api.modules.user.service.implement;
import com.imyeyu.api.modules.user.entity.RoleRelation;
import com.imyeyu.api.modules.user.mapper.RoleRelationMapper;
import com.imyeyu.api.modules.user.service.RoleRelationService;
import com.imyeyu.java.TimiJava;
import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/// 角色关联服务实现
///
/// @author 夜雨
/// @since 2026-05-13 14:46
@Service
@RequiredArgsConstructor
public class RoleRelationServiceImplement extends AbstractEntityService<RoleRelation, String> implements RoleRelationService {
private final RoleRelationMapper mapper;
@Override
protected BaseMapper<RoleRelation, String> mapper() {
return mapper;
}
@Override
public List<RoleRelation> listByParentRoleId(String parentRoleId) {
if (TimiJava.isEmpty(parentRoleId)) {
return List.of();
}
RoleRelation example = new RoleRelation();
example.setParentRoleId(parentRoleId);
return mapper.selectAllByExample(example);
}
@Override
public List<String> listChildRoleIdByParentRoleId(String parentRoleId) {
return listByParentRoleId(parentRoleId).stream().map(RoleRelation::getChildRoleId).distinct().toList();
}
@Override
public List<String> listAllChildRoleIdByParentRoleIdList(List<String> parentRoleIdList) {
if (TimiJava.isEmpty(parentRoleIdList)) {
return List.of();
}
Set<String> result = new HashSet<>();
List<String> currentRoleIdList = new ArrayList<>(parentRoleIdList);
while (TimiJava.isNotEmpty(currentRoleIdList)) {
List<String> childRoleIdList = currentRoleIdList.stream()
.flatMap(roleId -> listChildRoleIdByParentRoleId(roleId).stream())
.filter(childRoleId -> !result.contains(childRoleId))
.distinct()
.toList();
if (TimiJava.isEmpty(childRoleIdList)) {
break;
}
result.addAll(childRoleIdList);
currentRoleIdList = childRoleIdList;
}
return new ArrayList<>(result);
}
}
@@ -0,0 +1,294 @@
package com.imyeyu.api.modules.user.service.implement;
import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.common.entity.Multilingual;
import com.imyeyu.api.modules.common.mapper.MultilingualMapper;
import com.imyeyu.api.modules.common.service.MultilingualService;
import com.imyeyu.api.modules.user.entity.Permission;
import com.imyeyu.api.modules.user.entity.Role;
import com.imyeyu.api.modules.user.entity.RolePermission;
import com.imyeyu.api.modules.user.entity.RoleRelation;
import com.imyeyu.api.modules.user.entity.UserRole;
import com.imyeyu.api.modules.user.mapper.RoleMapper;
import com.imyeyu.api.modules.user.mapper.RolePermissionMapper;
import com.imyeyu.api.modules.user.mapper.RoleRelationMapper;
import com.imyeyu.api.modules.user.service.PermissionService;
import com.imyeyu.api.modules.user.service.RoleService;
import com.imyeyu.api.modules.user.service.UserRoleService;
import com.imyeyu.api.modules.user.vo.role.UserRoleAuthorizeReq;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService;
import com.imyeyu.utils.Time;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
/// 角色服务实现
///
/// @author 夜雨
/// @since 2026-05-13 14:46
@Service
@RequiredArgsConstructor
public class RoleServiceImplement extends AbstractEntityService<Role, String> implements RoleService {
private final UserRoleService userRoleService;
private final PermissionService permissionService;
private final MultilingualMapper multilingualMapper;
private final MultilingualService multilingualService;
private final RoleMapper mapper;
private final RoleRelationMapper roleRelationMapper;
private final RolePermissionMapper rolePermissionMapper;
@Override
protected BaseMapper<Role, String> mapper() {
return mapper;
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void create(Role role) {
role.setNameLangId(multilingualService.create(role.getName()));
super.create(role);
if (TimiJava.isNotEmpty(role.getPermissionIdList())) {
List<RolePermission> rolePermissionList = new ArrayList<>();
long now = Time.now();
for (String permissionId : role.getPermissionIdList()) {
RolePermission rolePermission = new RolePermission();
rolePermission.setId(UUID.randomUUID().toString());
rolePermission.setRoleId(role.getId());
rolePermission.setPermissionId(permissionId);
rolePermission.setCreatedAt(now);
rolePermissionList.add(rolePermission);
}
rolePermissionMapper.createBatch(rolePermissionList);
}
if (TimiJava.isNotEmpty(role.getChildRoleIdList())) {
List<RoleRelation> roleRelationList = new ArrayList<>();
long now = Time.now();
for (String childRoleId : role.getChildRoleIdList()) {
RoleRelation roleRelation = new RoleRelation();
roleRelation.setId(UUID.randomUUID().toString());
roleRelation.setParentRoleId(role.getId());
roleRelation.setChildRoleId(childRoleId);
roleRelation.setCreatedAt(now);
roleRelationList.add(roleRelation);
}
roleRelationMapper.createBatch(roleRelationList);
}
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void update(Role role) {
role.setNameLangId(multilingualService.createIfDifferent(role.getNameLangId(), role.getName()));
mapper.updateSelective(role);
{
Set<String> dbIdSet = rolePermissionMapper.selectByRoleId(role.getId()).stream().map(RolePermission::getPermissionId).collect(Collectors.toSet());
Set<String> newIdSet = TimiJava.defaultIfEmpty(new HashSet<>(role.getPermissionIdList()), new HashSet<>());
Set<String> deleteIdList = new HashSet<>(dbIdSet);
deleteIdList.removeAll(newIdSet);
Set<String> addIdList = new HashSet<>(newIdSet);
addIdList.removeAll(dbIdSet);
if (TimiJava.isNotEmpty(deleteIdList)) {
rolePermissionMapper.deleteByPermissionIdList(role.getId(), new ArrayList<>(deleteIdList));
}
if (TimiJava.isNotEmpty(addIdList)) {
List<RolePermission> rolePermissionList = new ArrayList<>();
long now = Time.now();
for (String permissionId : addIdList) {
RolePermission rolePermission = new RolePermission();
rolePermission.setId(UUID.randomUUID().toString());
rolePermission.setRoleId(role.getId());
rolePermission.setPermissionId(permissionId);
rolePermission.setCreatedAt(now);
rolePermissionList.add(rolePermission);
}
rolePermissionMapper.createBatch(rolePermissionList);
}
}
{
Set<String> dbIdSet = roleRelationMapper.selectByParentRoleId(role.getId()).stream().map(RoleRelation::getChildRoleId).collect(Collectors.toSet());
Set<String> newIdSet = TimiJava.defaultIfEmpty(new HashSet<>(role.getChildRoleIdList()), new HashSet<>());
Set<String> deleteIdList = new HashSet<>(dbIdSet);
deleteIdList.removeAll(newIdSet);
Set<String> addIdList = new HashSet<>(newIdSet);
addIdList.removeAll(dbIdSet);
if (TimiJava.isNotEmpty(deleteIdList)) {
roleRelationMapper.deleteByChildRoleIdList(role.getId(), new ArrayList<>(deleteIdList));
}
if (TimiJava.isNotEmpty(addIdList)) {
List<RoleRelation> roleRelationList = new ArrayList<>();
long now = Time.now();
for (String childRoleId : addIdList) {
RoleRelation roleRelation = new RoleRelation();
roleRelation.setId(UUID.randomUUID().toString());
roleRelation.setParentRoleId(role.getId());
roleRelation.setChildRoleId(childRoleId);
roleRelation.setCreatedAt(now);
roleRelationList.add(roleRelation);
}
roleRelationMapper.createBatch(roleRelationList);
}
}
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void authorizeUserRole(UserRoleAuthorizeReq req) {
TimiException.required(req, "not found req");
TimiException.required(req.getUserId(), "not found req.userId");
if (req.getModuleCode() == null) {
throw new TimiException(TimiCode.ARG_MISS).msgKey("moduleCode");
}
List<UserRole> bindingList = userRoleService.listByUserId(req.getUserId());
List<String> bindingRoleIdList = bindingList.stream().map(UserRole::getRoleId).toList();
Set<String> dbIdSet = mapper.selectByIdList(bindingRoleIdList).stream()
.filter(role -> req.getModuleCode() == role.getModuleCode())
.map(Role::getId)
.collect(Collectors.toSet());
Set<String> newIdSet = new HashSet<>();
if (TimiJava.isNotEmpty(req.getRoleIdList())) {
newIdSet.addAll(mapper.selectByIdList(req.getRoleIdList()).stream()
.filter(role -> req.getModuleCode() == role.getModuleCode())
.map(Role::getId)
.toList());
}
if (TimiJava.isNotEmpty(req.getRoleCodeList())) {
newIdSet.addAll(mapper.selectByModuleCodeAndCodeList(req.getModuleCode(), req.getRoleCodeList()).stream().map(Role::getId).toList());
}
Set<String> deleteIdList = new HashSet<>(dbIdSet);
deleteIdList.removeAll(newIdSet);
Set<String> addIdList = new HashSet<>(newIdSet);
addIdList.removeAll(dbIdSet);
if (TimiJava.isNotEmpty(deleteIdList)) {
userRoleService.deleteByRoleIdList(req.getUserId(), new ArrayList<>(deleteIdList));
}
if (TimiJava.isNotEmpty(addIdList)) {
List<UserRole> roleBindingList = new ArrayList<>();
long now = Time.now();
for (String roleId : addIdList) {
UserRole roleBinding = new UserRole();
roleBinding.setId(UUID.randomUUID().toString());
roleBinding.setUserId(req.getUserId());
roleBinding.setRoleId(roleId);
roleBinding.setCreatedAt(now);
roleBindingList.add(roleBinding);
}
userRoleService.createBatch(roleBindingList);
}
}
@Override
public List<Role> listByUserId(String userId) {
return mapper.selectByIdList(userRoleService.listRoleIdByUserId(userId));
}
@Override
public List<Role> listByUserId(String userId, ModuleCode moduleCode) {
TimiException.required(moduleCode, "not found moduleCode");
return listByUserId(userId).stream()
.filter(role -> moduleCode == role.getModuleCode())
.toList();
}
@Override
public List<Role> listByModuleCode(ModuleCode moduleCode) {
TimiException.required(moduleCode, "not found moduleCode");
return mapper.selectByModuleCode(moduleCode);
}
@Override
public List<String> listAllCodeByUserId(String userId) {
List<String> roleIdList = listByUserId(userId).stream().map(Role::getId).toList();
List<String> allRoleIdList = getAllRoleIdsIncludingChildren(roleIdList);
if (TimiJava.isEmpty(allRoleIdList)) {
return List.of();
}
return mapper.selectByIdList(allRoleIdList).stream().map(role -> "%s:%s".formatted(role.getModuleCode().name(), role.getCode())).distinct().toList();
}
@Override
public List<Permission> listAllPermissionByUserId(String userId) {
List<String> roleIdList = listByUserId(userId).stream().map(Role::getId).toList();
List<String> allRoleIdList = getAllRoleIdsIncludingChildren(roleIdList);
return permissionService.listByRoleId(allRoleIdList);
}
@Override
public List<Role> listChildByParentRoleId(String parentRoleId) {
List<String> childRoleIdList = roleRelationMapper.selectChildRoleIdListByParentRoleId(parentRoleId);
if (TimiJava.isEmpty(childRoleIdList)) {
return new ArrayList<>();
}
return mapper.selectByIdList(childRoleIdList);
}
@Override
public List<Permission> listPermissionByRoleId(String roleId) {
List<String> allRoleIdList = getAllRoleIdsIncludingChildren(List.of(roleId));
return permissionService.listByRoleId(allRoleIdList);
}
/// 递归获取角色及其所有子角色的 ID 列表
///
/// @param roleIdList 角色 ID 列表
/// @return 包含所有子角色的 ID 列表
private List<String> getAllRoleIdsIncludingChildren(List<String> roleIdList) {
if (TimiJava.isEmpty(roleIdList)) {
return new ArrayList<>();
}
Set<String> allRoleIdSet = new HashSet<>(roleIdList);
List<String> currentLevelRoleIdList = new ArrayList<>(roleIdList);
while (TimiJava.isNotEmpty(currentLevelRoleIdList)) {
List<String> childRoleIdList = roleRelationMapper.selectChildRoleIdListByParentRoleIdList(currentLevelRoleIdList);
if (TimiJava.isEmpty(childRoleIdList)) {
break;
}
List<String> newChildRoleIdList = childRoleIdList.stream().filter(id -> !allRoleIdSet.contains(id)).toList();
if (TimiJava.isEmpty(newChildRoleIdList)) {
break;
}
allRoleIdSet.addAll(newChildRoleIdList);
currentLevelRoleIdList = newChildRoleIdList;
}
return new ArrayList<>(allRoleIdSet);
}
private String saveNameMultilingual(String langId, String name) {
if (TimiJava.isEmpty(name)) {
return langId;
}
if (TimiJava.isEmpty(langId)) {
return multilingualService.create(name);
}
Multilingual multilingual = new Multilingual();
multilingual.setId(langId);
multilingual.setZhCN(name);
multilingualMapper.updateSelective(multilingual);
return langId;
}
}
@@ -0,0 +1,81 @@
package com.imyeyu.api.modules.user.service.implement;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.user.entity.UserBehaviorRecord;
import com.imyeyu.api.modules.user.mapper.UserBehaviorRecordMapper;
import com.imyeyu.api.modules.user.service.UserBehaviorRecordService;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 用户行为记录服务实现
*
* @author 夜雨
* @since 2026-05-27 17:10
*/
@Service
@RequiredArgsConstructor
public class UserBehaviorRecordServiceImplement extends AbstractEntityService<UserBehaviorRecord, String> implements UserBehaviorRecordService {
private final UserBehaviorRecordMapper mapper;
@Override
protected BaseMapper<UserBehaviorRecord, String> mapper() {
return mapper;
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void create(UserBehaviorRecord entity) {
record(entity);
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public boolean record(UserBehaviorRecord entity) {
TimiException.required(entity, "not found entity");
TimiException.required(entity.getBizType(), "not found entity.bizType");
TimiException.required(entity.getUserId(), "not found entity.userId");
TimiException.required(entity.getTargetType(), "not found entity.targetType");
TimiException.required(entity.getTargetId(), "not found entity.targetId");
TimiException.required(entity.getActionType(), "not found entity.actionType");
UserBehaviorRecord dbRecord = mapper.selectActive(entity.getBizType(), entity.getUserId(), entity.getTargetType(), entity.getTargetId(), entity.getActionType());
if (dbRecord != null) {
return false;
}
super.create(entity);
return true;
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public boolean cancel(String bizType, String userId, String targetType, String targetId, String actionType) {
if (TimiJava.isEmpty(bizType) || TimiJava.isEmpty(userId) || TimiJava.isEmpty(targetType) || TimiJava.isEmpty(targetId) || TimiJava.isEmpty(actionType)) {
return false;
}
UserBehaviorRecord dbRecord = mapper.selectActive(bizType, userId, targetType, targetId, actionType);
if (dbRecord == null) {
return false;
}
super.delete(dbRecord.getId());
return true;
}
@Override
public Set<String> getActiveTargetIdSet(String bizType, String userId, String targetType, String actionType, List<String> targetIdList) {
if (TimiJava.isEmpty(bizType) || TimiJava.isEmpty(userId) || TimiJava.isEmpty(targetType) || TimiJava.isEmpty(actionType) || TimiJava.isEmpty(targetIdList)) {
return Set.of();
}
return new HashSet<>(mapper.selectActiveTargetIdList(bizType, userId, targetType, actionType, targetIdList));
}
}
@@ -0,0 +1,195 @@
package com.imyeyu.api.modules.user.service.implement;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.entity.Setting;
import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.api.modules.user.bean.BootstrapData;
import com.imyeyu.api.modules.user.entity.User;
import com.imyeyu.api.modules.user.service.RoleService;
import com.imyeyu.api.modules.user.service.UserLoginService;
import com.imyeyu.api.modules.user.service.UserService;
import com.imyeyu.api.modules.user.vo.LoginRequest;
import com.imyeyu.api.modules.user.vo.LoginResponse;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.TimiSpring;
import com.imyeyu.spring.util.Redis;
import com.imyeyu.utils.Digest;
import com.imyeyu.utils.Time;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.concurrent.ThreadLocalRandom;
import java.util.UUID;
/// 用户登录服务实现
///
/// @author 夜雨
/// @since 2026-06-09
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor_ = {@Lazy})
public class UserLoginServiceImplement implements UserLoginService, TimiJava {
private static final String REGEX_PHONE_NO = "^1\\d{10}$";
private static final String USER_TOKEN_KEY_PREFIX = "USER:TOKEN:";
private static final String PHONE_VERIFY_KEY_PREFIX = "USER:PHONE:VERIFY:";
private static final long PHONE_VERIFY_CODE_TTL = Time.M * 5;
private final UserService userService;
private final RoleService roleService;
private final SettingService settingService;
private final AttachmentService attachmentService;
private final Redis<String, String> redisUserToken;
@Qualifier("redisUserPhoneVerify")
private final Redis<String, String> redisUserPhoneVerify;
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public LoginResponse login(LoginRequest req) {
// 获取
User dbUser = userService.get(req.getUser());
if (dbUser == null && req.getUser().contains("@")) {
dbUser = userService.getByVerifiedEmail(req.getUser());
} else if (dbUser == null && isPhoneNo(req.getUser())) {
dbUser = userService.getByVerifiedPhoneNo(req.getUser());
} else if (dbUser == null) {
dbUser = userService.getByName(req.getUser());
}
TimiException.required(dbUser, "not found user");
// 校验
boolean isValid = Digest.argon2idVerify(dbUser.getPassword(), req.getPassword());
TimiException.requiredTrue(isValid, "invalid password");
TimiException.requiredTrue(!dbUser.isBanning(), "banned user");
// 登录
return login(UUID.randomUUID().toString(), dbUser.getId());
}
@Override
public LoginResponse login(String token, String userId) {
Long time = settingService.getSystem(Setting.Module.User.TOKEN_TTL).asTime();
long now = Time.now();
long expireAt = now + time;
redisUserToken.set(getUserTokenKey(token), userId, expireAt - now);
LoginResponse result = new LoginResponse();
result.setToken(token);
result.setExpireAt(expireAt);
result.setUser(userService.get(userId));
User user = result.getUser();
user.setPermissionList(roleService.listAllPermissionCodeByUserId(user.getId()));
user.setRoleList(roleService.listAllCodeByUserId(user.getId()));
user.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.USER, user.getId()));
return result;
}
@Override
public User getLoginUser() {
String token = TimiSpring.getToken();
TimiException.required(token, "not found token");
String userId = redisUserToken.get(getUserTokenKey(token));
if (TimiJava.isEmpty(userId)) {
return null;
}
return userService.get(userId);
}
@Override
public User getRequireLoginUser() {
String token = TimiSpring.getToken();
TimiException.required(token, "not found token");
String userId = redisUserToken.get(getUserTokenKey(token));
TimiException.required(userId, "invalid token");
return userService.get(userId);
}
@Override
public void logout() {
redisUserToken.destroy(getUserTokenKey(TimiSpring.getToken()));
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void updatePassword(String oldPassword, String newPassword) {
TimiException.requiredTrue(!oldPassword.equals(newPassword), "new password cannot be same as old password");
User user = getRequireLoginUser();
// 校验
boolean isValid = Digest.argon2idVerify(user.getPassword(), oldPassword);
TimiException.requiredTrue(isValid, "invalid old password");
// 更新
User update = new User();
update.setId(user.getId());
update.setPassword(Digest.argon2id(newPassword));
userService.update(update);
logout();
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void sendPhoneVerifyCode(String phoneNo) {
TimiException.requiredTrue(isPhoneNo(phoneNo), "invalid phone number");
User verifiedUser = userService.getByVerifiedPhoneNo(phoneNo);
User loginUser = getRequireLoginUser();
TimiException.requiredTrue(verifiedUser == null || verifiedUser.getId().equals(loginUser.getId()), "existed phone number");
String verifyCode = "%06d".formatted(ThreadLocalRandom.current().nextInt(1000000));
redisUserPhoneVerify.set(getPhoneVerifyKey(phoneNo), verifyCode, PHONE_VERIFY_CODE_TTL);
log.info("手机号验证码 phoneNo={}, code={}", phoneNo, verifyCode);
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void verifyPhoneNo(String phoneNo, String verifyCode) {
TimiException.requiredTrue(isPhoneNo(phoneNo), "invalid phone number");
TimiException.required(verifyCode, "not found verify code");
User user = getRequireLoginUser();
User verifiedUser = userService.getByVerifiedPhoneNo(phoneNo);
TimiException.requiredTrue(verifiedUser == null || verifiedUser.getId().equals(user.getId()), "existed phone number");
String cacheCode = redisUserPhoneVerify.get(getPhoneVerifyKey(phoneNo));
TimiException.required(cacheCode, "invalid verify code");
TimiException.requiredTrue(cacheCode.equals(verifyCode), "invalid verify code");
User update = new User();
update.setId(user.getId());
update.setPhoneNo(phoneNo);
update.setPhoneNoVerifyAt(Time.now());
userService.update(update);
redisUserPhoneVerify.destroy(getPhoneVerifyKey(phoneNo));
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void deactivate(String password) {
User user = getRequireLoginUser();
TimiException.requiredTrue(!BootstrapData.SYS_USER_NAME.equals(user.getName()), "can not deactivate System user");
boolean isValid = Digest.argon2idVerify(user.getPassword(), password);
TimiException.requiredTrue(isValid, "invalid password");
logout();
userService.delete(user.getId());
}
private boolean isPhoneNo(String value) {
return TimiJava.isNotEmpty(value) && value.matches(REGEX_PHONE_NO);
}
private String getPhoneVerifyKey(String phoneNo) {
return "%s%s".formatted(PHONE_VERIFY_KEY_PREFIX, phoneNo);
}
private String getUserTokenKey(String token) {
return "%s%s".formatted(USER_TOKEN_KEY_PREFIX, token);
}
}
@@ -0,0 +1,44 @@
package com.imyeyu.api.modules.user.service.implement;
import com.imyeyu.api.modules.user.entity.UserRole;
import com.imyeyu.api.modules.user.mapper.UserRoleMapper;
import com.imyeyu.api.modules.user.service.UserRoleService;
import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
/// 用户角色服务实现
///
/// @author 夜雨
/// @since 2026-05-13 14:46
@Service
@RequiredArgsConstructor
public class UserRoleServiceImplement extends AbstractEntityService<UserRole, String> implements UserRoleService {
private final UserRoleMapper mapper;
@Override
protected BaseMapper<UserRole, String> mapper() {
return mapper;
}
@Override
public void createBatch(List<UserRole> list) {
mapper.createBatch(list);
}
@Override
public List<UserRole> listByUserId(String userId) {
UserRole example = new UserRole();
example.setUserId(userId);
return mapper.selectAllByExample(example);
}
@Override
public void deleteByRoleIdList(String userId, List<String> roleIdList) {
mapper.deleteByRoleIdList(userId, roleIdList);
}
}
@@ -0,0 +1,143 @@
package com.imyeyu.api.modules.user.service.implement;
import com.imyeyu.api.modules.common.bean.ImageType;
import com.imyeyu.api.modules.user.bean.BootstrapData;
import com.imyeyu.api.modules.user.entity.User;
import com.imyeyu.api.modules.user.mapper.UserMapper;
import com.imyeyu.api.modules.user.service.UserService;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService;
import com.imyeyu.utils.Digest;
import com.imyeyu.utils.Regex;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
/**
* 用户管理服务实现
*
* @author 夜雨
* @since 2021-02-23 21:43
*/
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor_ = {@Lazy})
public class UserServiceImplement extends AbstractEntityService<User, String> implements UserService, TimiJava {
private static final String REGEX_PHONE_NO = "^1\\d{10}$";
private final UserMapper mapper;
@Override
protected BaseMapper<User, String> mapper() {
return mapper;
}
@Override
public void create(User user) {
TimiException.requiredTrue(Regex.isMatch(Regex.PASSWORD, user.getPassword()), "invalid password");
{
User byName = getByName(user.getName());
TimiException.requiredNull(byName, "existed user name");
}
{
if (TimiJava.isNotEmpty(user.getEmail())) {
User byEmail = getByVerifiedEmail(user.getEmail());
TimiException.requiredNull(byEmail, "existed email");
}
}
{
if (TimiJava.isNotEmpty(user.getPhoneNo())) {
validatePhoneNo(user.getPhoneNo());
User byPhoneNo = getByVerifiedPhoneNo(user.getPhoneNo());
TimiException.requiredNull(byPhoneNo, "existed phone number");
}
}
User dbUser = new User();
dbUser.setName(user.getName());
dbUser.setNick(TimiJava.defaultIfEmpty(user.getNick(), user.getName()));
dbUser.setWrapperType(TimiJava.defaultIfNull(user.getWrapperType(), ImageType.PIXELATED));
dbUser.setAvatarType(TimiJava.defaultIfNull(user.getAvatarType(), ImageType.PIXELATED));
dbUser.setPassword(Digest.argon2id(user.getPassword()));
dbUser.setEmail(user.getEmail());
dbUser.setPhoneNo(user.getPhoneNo());
dbUser.setExp(TimiJava.defaultIfNull(user.getExp(), 0));
super.create(dbUser);
user.setId(dbUser.getId());
}
@Override
public void update(User user) {
User dbUser = get(user.getId());
String name = TimiJava.defaultIfEmpty(user.getName(), dbUser.getName());
String email = TimiJava.defaultIfEmpty(user.getEmail(), dbUser.getEmail());
String phoneNo = TimiJava.defaultIfEmpty(user.getPhoneNo(), dbUser.getPhoneNo());
if (!dbUser.getName().equals(name)) {
// 修改名称
User byName = getByName(name);
TimiException.requiredTrue(byName == null || byName.getId().equals(dbUser.getId()), "existed user name");
TimiException.requiredTrue(!dbUser.getName().equals(BootstrapData.SYS_USER_NAME), "invalid action");
}
if (TimiJava.isNotEmpty(email) && !email.equals(dbUser.getEmail())) {
User byEmail = getByVerifiedEmail(email);
TimiException.requiredTrue(byEmail == null || byEmail.getId().equals(dbUser.getId()), "existed email");
}
if (TimiJava.isNotEmpty(phoneNo)) {
validatePhoneNo(phoneNo);
if (!phoneNo.equals(dbUser.getPhoneNo())) {
User byPhoneNo = getByVerifiedPhoneNo(phoneNo);
TimiException.requiredTrue(byPhoneNo == null || byPhoneNo.getId().equals(dbUser.getId()), "existed phone number");
}
}
super.update(user);
}
@Override
public void delete(String id) {
User user = get(id);
TimiException.requiredTrue(!user.getName().equals(BootstrapData.SYS_USER_NAME), "invalid action");
super.delete(id);
}
@Override
public void destroy(String id) {
User user = get(id);
TimiException.requiredTrue(!user.getName().equals(BootstrapData.SYS_USER_NAME), "invalid action");
super.destroy(id);
}
@Override
public User getByName(String name) {
User example = new User();
example.setName(name);
return mapper.selectByExample(example);
}
@Override
public User getByEmail(String email) {
return mapper.selectByEmail(email);
}
@Override
public User getByVerifiedEmail(String email) {
return mapper.selectByVerifiedEmail(email);
}
@Override
public User getByPhoneNo(String phoneNo) {
return mapper.selectByPhoneNo(phoneNo);
}
@Override
public User getByVerifiedPhoneNo(String phoneNo) {
return mapper.selectByVerifiedPhoneNo(phoneNo);
}
private void validatePhoneNo(String phoneNo) {
TimiException.requiredTrue(Regex.isMatch(REGEX_PHONE_NO, phoneNo), "invalid phone number");
}
}
@@ -0,0 +1,36 @@
package com.imyeyu.api.modules.user.validation;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import com.imyeyu.api.modules.user.validation.validtor.UserNameValidator;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
*
*
* @author 夜雨
* @since 2023-05-06 18:01
*/
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = UserNameValidator.class)
public @interface UserName {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@@ -0,0 +1,34 @@
package com.imyeyu.api.modules.user.validation;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import com.imyeyu.api.modules.user.validation.validtor.UserPasswordValidator;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* @author 夜雨
* @since 2023-05-07 00:05
*/
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = UserPasswordValidator.class)
public @interface UserPassword {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@@ -0,0 +1,35 @@
package com.imyeyu.api.modules.user.validation.validtor;
import com.imyeyu.api.modules.user.validation.UserName;
import com.imyeyu.java.TimiJava;
import com.imyeyu.spring.util.AbstractValidator;
import com.imyeyu.utils.Regex;
/**
* 用户名基本验证
*
* @author 夜雨
* @since 2023-05-06 18:01
*/
public class UserNameValidator extends AbstractValidator<UserName, String> {
@Override
protected String inspector(String userName) {
if (TimiJava.isEmpty(userName)) {
return "user.name.empty";
}
if (32 < userName.length()) {
return "user.name.too_long";
}
if (userName.contains("@")) {
return "user.name.contains_at";
}
if (Regex.isMatch("^[0-9]+.?[0-9]*$", userName)) {
return "user.name.only_number";
}
if (Regex.isNotMatch("^[A-Za-z0-9_一-龥]+$", userName)) {
return "user.name.not_match_regex";
}
return null;
}
}
@@ -0,0 +1,32 @@
package com.imyeyu.api.modules.user.validation.validtor;
import com.imyeyu.api.modules.user.validation.UserPassword;
import com.imyeyu.java.TimiJava;
import com.imyeyu.spring.util.AbstractValidator;
import com.imyeyu.utils.Regex;
/**
* 用户密码基本验证
*
* @author 夜雨
* @since 2023-05-06 18:01
*/
public class UserPasswordValidator extends AbstractValidator<UserPassword, String> {
@Override
protected String inspector(String password) {
if (TimiJava.isEmpty(password)) {
return "user.password.empty";
}
if (password.length() < 6) {
return "user.password.too_short";
}
if (32 < password.length()) {
return "user.password.too_long";
}
if (Regex.isNotMatch(Regex.PASSWORD, password)) {
return "user.password.not_match_regex";
}
return null;
}
}
@@ -0,0 +1,18 @@
package com.imyeyu.api.modules.user.vo;
import lombok.Data;
import com.imyeyu.api.modules.user.validation.UserPassword;
/**
* 注销请求
*
* @author 夜雨
* @since 2026-06-05 22:50
*/
@Data
public class CancelRequest {
/** 当前密码 */
@UserPassword
private String password;
}
@@ -0,0 +1,27 @@
package com.imyeyu.api.modules.user.vo;
import com.imyeyu.api.modules.user.validation.UserPassword;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 登录请求对象
*
* @author 夜雨
* @since 2023-04-25 17:26
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginRequest {
/** 用户(可能是 UID、邮箱或用户名) */
@NotBlank(message = "not found user")
private String user;
/** 明文密码 */
@UserPassword
private String password;
}
@@ -0,0 +1,30 @@
package com.imyeyu.api.modules.user.vo;
import com.fasterxml.jackson.annotation.JsonView;
import com.imyeyu.api.modules.user.entity.User;
import com.imyeyu.spring.util.ResponseView;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 登录返回对象
*
* @author 夜雨
* @since 2023-05-05 17:57
*/
@Data
@NoArgsConstructor
public class LoginResponse {
/** 令牌 */
@JsonView(ResponseView.Public.class)
private String token;
/** 过期时间 */
@JsonView(ResponseView.Public.class)
private Long expireAt;
/** 登录用户 */
@JsonView(ResponseView.Public.class)
private User user;
}
@@ -0,0 +1,18 @@
package com.imyeyu.api.modules.user.vo;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
/// 手机号验证码发送请求
///
/// @author 夜雨
/// @since 2026-07-27
@Data
public class PhoneVerifyCodeSendRequest {
/// 手机号
@NotBlank(message = "not found phone number")
@Pattern(regexp = "^1\\d{10}$", message = "invalid phone number")
private String phoneNo;
}
@@ -0,0 +1,23 @@
package com.imyeyu.api.modules.user.vo;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
/// 手机号验证请求
///
/// @author 夜雨
/// @since 2026-07-27
@Data
public class PhoneVerifyRequest {
/// 手机号
@NotBlank(message = "not found phone number")
@Pattern(regexp = "^1\\d{10}$", message = "invalid phone number")
private String phoneNo;
/// 验证码
@NotBlank(message = "not found verify code")
@Pattern(regexp = "^\\d{6}$", message = "invalid verify code")
private String verifyCode;
}
@@ -0,0 +1,19 @@
package com.imyeyu.api.modules.user.vo;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import com.imyeyu.api.modules.user.validation.UserPassword;
/**
* @author 夜雨
* @since 2023-07-15 11:23
*/
@Data
public class UpdatePasswordByKeyRequest {
@NotBlank
private String key;
@UserPassword
private String newPassword;
}
@@ -0,0 +1,18 @@
package com.imyeyu.api.modules.user.vo;
import lombok.Data;
import com.imyeyu.api.modules.user.validation.UserPassword;
/**
* @author 夜雨
* @since 2023-07-15 11:23
*/
@Data
public class UpdatePasswordRequest {
@UserPassword
private String oldValue;
@UserPassword
private String newValue;
}
@@ -0,0 +1,39 @@
package com.imyeyu.api.modules.user.vo.permission;
import com.imyeyu.api.bean.ModuleCode;
import lombok.Data;
/**
* 批量创建权限请求
*
* @author 夜雨
* @since 2025-11-18 16:49
*/
@Data
public class BatchCreateReq {
private ModuleCode moduleCode;
/**
* 权限类型枚举
*
* @author 夜雨
* @since 2025-11-18 16:50
*/
public enum Type {
CREATE,
READ,
UPDATE,
DELETE
}
private String prefixCode;
private String prefixName;
private Type[] types;
}
@@ -0,0 +1,22 @@
package com.imyeyu.api.modules.user.vo.role;
import com.imyeyu.api.bean.ModuleCode;
import lombok.Data;
import java.util.List;
///
///
/// @author 夜雨
/// @since 2026-07-17 14:16
@Data
public class UserRoleAuthorizeReq {
private String userId;
private ModuleCode moduleCode;
private List<String> roleIdList;
private List<String> roleCodeList;
}