diff --git a/src/main/java/com/yunhai/userbase/common/converter/ClientEnumConverter.java b/src/main/java/com/yunhai/userbase/common/converter/ClientEnumConverter.java index e7a51c2..1899280 100644 --- a/src/main/java/com/yunhai/userbase/common/converter/ClientEnumConverter.java +++ b/src/main/java/com/yunhai/userbase/common/converter/ClientEnumConverter.java @@ -1,6 +1,6 @@ package com.yunhai.userbase.common.converter; -import com.yunhai.userbase.enums.ClientEnum; +import com.yunhai.platform.utils.enums.ClientEnum; import javax.persistence.AttributeConverter; diff --git a/src/main/java/com/yunhai/userbase/common/converter/CommonIntEnumConverter.java b/src/main/java/com/yunhai/userbase/common/converter/CommonIntEnumConverter.java index 33c8dc6..47be82a 100644 --- a/src/main/java/com/yunhai/userbase/common/converter/CommonIntEnumConverter.java +++ b/src/main/java/com/yunhai/userbase/common/converter/CommonIntEnumConverter.java @@ -1,6 +1,6 @@ package com.yunhai.userbase.common.converter; -import com.yunhai.userbase.enums.IEnum; +import com.yunhai.platform.utils.enums.IEnum; import javax.persistence.AttributeConverter; diff --git a/src/main/java/com/yunhai/userbase/controller/api/BaseController.java b/src/main/java/com/yunhai/userbase/controller/api/BaseController.java index 6ff2ea8..ee159c5 100644 --- a/src/main/java/com/yunhai/userbase/controller/api/BaseController.java +++ b/src/main/java/com/yunhai/userbase/controller/api/BaseController.java @@ -1,6 +1,8 @@ package com.yunhai.userbase.controller.api; import com.yunhai.platform.utils.model.BoaUser; +import com.yunhai.platform.utils.enums.ClientEnum; +import com.yunhai.platform.utils.enums.IEnum; import com.yunhai.userbase.config.Config; import com.yunhai.userbase.enums.*; import com.yunhai.userbase.utils.*; diff --git a/src/main/java/com/yunhai/userbase/controller/oapi/AppController.java b/src/main/java/com/yunhai/userbase/controller/oapi/AppController.java new file mode 100644 index 0000000..37ea302 --- /dev/null +++ b/src/main/java/com/yunhai/userbase/controller/oapi/AppController.java @@ -0,0 +1,45 @@ +package com.yunhai.userbase.controller.oapi; + +import com.yunhai.platform.utils.enums.ClientEnum; +import com.yunhai.platform.utils.enums.IEnum; +import com.yunhai.platform.utils.model.BoaAppType; +import com.yunhai.userbase.config.Config; +import com.yunhai.userbase.service.IAppCategoryService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +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; + +/** + * 应用域开放接口:供其他业务服务经 yh-app-auth 签名调用({@code /oapi/app/...})。
+ * 接口本身不做验签——签名由 {@code OpenApiAuthEnforceConfiguration} 在过滤器层强制要求 + * (URL pattern {@code /oapi/*} 已覆盖本控制器),未带 {@code X-App-Key/X-Timestamp/X-Signature} 的请求直接 401。 + */ +@RestController +@RequestMapping(Config.OPEN_API_PATH + "app/") +public class AppController { + + @Autowired + private IAppCategoryService appCategoryService; + + /** + * 查询用户在指定客户端类型下有权访问的应用列表(按应用类型分组)。
+ * 权限过滤复用 {@code IGroupAuthzService.findUserAuthorizedAppIds}(组开通 ∪ 启用角色模块所属应用), + * 超级管理员返回全部;不透出 appSecret。
+ * clientType 传枚举 key(2 的幂值,如 1=PC 2=移动App 4=系统模块…);非法/缺失按不限制端处理。 + * + * @param userId 用户ID(必填) + * @param clientType 客户端类型 key(可选) + * @return 应用类型分组列表;无权限时各类型 apps 为空数组 + */ + @GetMapping("list") + public List list(@RequestParam("userId") Integer userId, + @RequestParam(value = "clientType", required = false) Integer clientType) { + ClientEnum clientEnum = (clientType == null) ? null + : IEnum.getByKey(ClientEnum.class, clientType); + return appCategoryService.listBoaAppType(userId, clientEnum); + } +} diff --git a/src/main/java/com/yunhai/userbase/controller/oapi/UserController.java b/src/main/java/com/yunhai/userbase/controller/oapi/UserController.java index c34b6ba..e070276 100644 --- a/src/main/java/com/yunhai/userbase/controller/oapi/UserController.java +++ b/src/main/java/com/yunhai/userbase/controller/oapi/UserController.java @@ -4,12 +4,14 @@ import com.yunhai.platform.appauth.AppAuthFilter; import com.yunhai.platform.utils.exception.DException; import com.yunhai.platform.utils.model.BoaGroup; import com.yunhai.platform.utils.model.BoaModule; +import com.yunhai.platform.utils.model.BoaRoleInfo; import com.yunhai.platform.utils.model.BoaUser; import com.yunhai.userbase.common.validation.ValidationGroups; import com.yunhai.userbase.config.Config; import com.yunhai.userbase.entity.ApplicationInfo; import com.yunhai.userbase.model.sysuser.SysUserGroupDeleteItem; import com.yunhai.userbase.model.sysuser.SysUserMoveRequest; +import com.yunhai.userbase.model.sysuser.SysUserPasswordRequest; import com.yunhai.userbase.model.sysuser.SysUserRequest; import com.yunhai.userbase.repository.ApplicationInfoRepository; import com.yunhai.userbase.service.IGroupAuthzService; @@ -209,4 +211,22 @@ public class UserController { sysUserService.moveUserWithinSchool(request.getUserId(), request.getGroupId(), request.getSchoolId()); return userCenterService.getUserInfoWithDeptById(request.getUserId()); } + + /** + * 查询用户的业务角色身份列表(角色 + 主归属学校组上下文)。
+ * 鉴权由 OpenApiAuthEnforceConfiguration 在过滤器层强制签名。 + */ + @GetMapping("roles") + public List roles(@RequestParam("userId") Integer userId) { + return sysUserService.findUserRoles(userId); + } + + /** + * 修改用户密码:先校验旧密码,再写入新密码(BCrypt)。
+ * 鉴权由 OpenApiAuthEnforceConfiguration 在过滤器层强制签名;此处不记操作日志(避免明文密码写入 request_params)。 + */ + @PostMapping("password") + public void password(@RequestBody @Validated SysUserPasswordRequest request) { + sysUserService.changePassword(request.getUserId(), request.getOldPassword(), request.getNewPassword()); + } } diff --git a/src/main/java/com/yunhai/userbase/enums/ClientEnum.java b/src/main/java/com/yunhai/userbase/enums/ClientEnum.java deleted file mode 100644 index 1451dc8..0000000 --- a/src/main/java/com/yunhai/userbase/enums/ClientEnum.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.yunhai.userbase.enums; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * @author chenx - * @date 2026/07/13 - * @description 客户端来源 - */ -@Getter -@AllArgsConstructor -public enum ClientEnum implements IEnum { - - PC_BROWSER(1, "Pc浏览器"), - MOBILE_APP(2, "移动App"), - SYSTEM_MODULE(4, "系统模块"), - WECHAT_OFFICIAL(8, "微信公众号"), - WORK_WECHAT(16, "企业微信"), - DING_TALK(32, "钉钉"), - STUDENT_DEVICE(64, "学伴机"); - - private Integer key; - private String value; - - @Override - public Integer getKey() { - return this.key; - } - - @Override - public String getValue() { - return this.value; - } -} \ No newline at end of file diff --git a/src/main/java/com/yunhai/userbase/enums/GenderEnum.java b/src/main/java/com/yunhai/userbase/enums/GenderEnum.java index 9650178..f456f66 100644 --- a/src/main/java/com/yunhai/userbase/enums/GenderEnum.java +++ b/src/main/java/com/yunhai/userbase/enums/GenderEnum.java @@ -1,5 +1,7 @@ package com.yunhai.userbase.enums; +import com.yunhai.platform.utils.enums.IEnum; + import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/com/yunhai/userbase/enums/GroupTypeEnum.java b/src/main/java/com/yunhai/userbase/enums/GroupTypeEnum.java index 19c3c55..2c7fd11 100644 --- a/src/main/java/com/yunhai/userbase/enums/GroupTypeEnum.java +++ b/src/main/java/com/yunhai/userbase/enums/GroupTypeEnum.java @@ -1,5 +1,7 @@ package com.yunhai.userbase.enums; +import com.yunhai.platform.utils.enums.IEnum; + import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/com/yunhai/userbase/enums/IEnum.java b/src/main/java/com/yunhai/userbase/enums/IEnum.java deleted file mode 100644 index 491f759..0000000 --- a/src/main/java/com/yunhai/userbase/enums/IEnum.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.yunhai.userbase.enums; - -import com.yunhai.userbase.utils.EnumUtils; -import java.util.List; -import java.util.Map; - -/** - * 统一枚举规范接口,仅定义契约getKey/getValue - * 内置静态工具方法,业务直接 IEnum.xxx(枚举类) 调用 - * @param key类型 - */ -public interface IEnum { - - /** 获取编码key */ - T getKey(); - - /** 获取中文展示value */ - String getValue(); - - // ====================== 静态工具入口(统一对外调用) ====================== - /** 获取枚举全部实例列表 */ - static & IEnum> List toList(Class clazz) { - return EnumUtils.toList(clazz); - } - - /** 根据key获取枚举,无匹配返回null */ - static & IEnum> E getByKey(Class clazz, Object key) { - return EnumUtils.getEnumByKey(clazz, key); - } - - /** 根据key严格获取,不存在抛异常(对应fromKey) */ - static & IEnum> E fromKey(Class clazz, Object key) { - return EnumUtils.fromKey(clazz, key); - } - - /** 根据key获取中文value,无匹配返回空串 */ - static & IEnum> String getValueByKey(Class clazz, Object key) { - return EnumUtils.getValue(clazz, key); - } - - /** 根据中文value获取key字符串,无匹配返回空串 */ - static & IEnum> String getKeyByValue(Class clazz, String value) { - return EnumUtils.getKey(clazz, value); - } - - /** 判断key是否存在 */ - static & IEnum> boolean existsKey(Class clazz, Object key) { - return EnumUtils.isExistKey(clazz, key); - } - - /** 判断value是否存在 */ - static & IEnum> boolean existsValue(Class clazz, String value) { - return EnumUtils.isExistValue(clazz, value); - } - - /** 转完整Map列表(包含enum实例、name、key、value) */ - static & IEnum> List> toFullMapList(Class clazz) { - return EnumUtils.toMapList(clazz); - } - - /** 转下拉简易Map列表,自定义key/value字段名 */ - static & IEnum> List> toSimpleMapList(Class clazz, String keyMapName, String valueMapName) { - return EnumUtils.toMapList(clazz, keyMapName, valueMapName); - } -} \ No newline at end of file diff --git a/src/main/java/com/yunhai/userbase/enums/IntegEnum.java b/src/main/java/com/yunhai/userbase/enums/IntegEnum.java index 81095fb..bb13031 100644 --- a/src/main/java/com/yunhai/userbase/enums/IntegEnum.java +++ b/src/main/java/com/yunhai/userbase/enums/IntegEnum.java @@ -1,5 +1,7 @@ package com.yunhai.userbase.enums; +import com.yunhai.platform.utils.enums.IEnum; + import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/com/yunhai/userbase/enums/StatusEnum.java b/src/main/java/com/yunhai/userbase/enums/StatusEnum.java index 0531e46..fe5b7b3 100644 --- a/src/main/java/com/yunhai/userbase/enums/StatusEnum.java +++ b/src/main/java/com/yunhai/userbase/enums/StatusEnum.java @@ -1,5 +1,7 @@ package com.yunhai.userbase.enums; +import com.yunhai.platform.utils.enums.IEnum; + import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/com/yunhai/userbase/enums/SysModeEnum.java b/src/main/java/com/yunhai/userbase/enums/SysModeEnum.java index 1f4a58b..fa19a8a 100644 --- a/src/main/java/com/yunhai/userbase/enums/SysModeEnum.java +++ b/src/main/java/com/yunhai/userbase/enums/SysModeEnum.java @@ -1,5 +1,7 @@ package com.yunhai.userbase.enums; +import com.yunhai.platform.utils.enums.IEnum; + import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/com/yunhai/userbase/enums/SysRoleEnum.java b/src/main/java/com/yunhai/userbase/enums/SysRoleEnum.java index 059d55e..0bb3c67 100644 --- a/src/main/java/com/yunhai/userbase/enums/SysRoleEnum.java +++ b/src/main/java/com/yunhai/userbase/enums/SysRoleEnum.java @@ -1,5 +1,7 @@ package com.yunhai.userbase.enums; +import com.yunhai.platform.utils.enums.IEnum; + import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/com/yunhai/userbase/enums/UserTypeEnum.java b/src/main/java/com/yunhai/userbase/enums/UserTypeEnum.java index f83297b..beb2653 100644 --- a/src/main/java/com/yunhai/userbase/enums/UserTypeEnum.java +++ b/src/main/java/com/yunhai/userbase/enums/UserTypeEnum.java @@ -1,5 +1,7 @@ package com.yunhai.userbase.enums; +import com.yunhai.platform.utils.enums.IEnum; + import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/com/yunhai/userbase/model/other/RoleInfoModel.java b/src/main/java/com/yunhai/userbase/model/other/RoleInfoModel.java deleted file mode 100644 index fd8d6da..0000000 --- a/src/main/java/com/yunhai/userbase/model/other/RoleInfoModel.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.yunhai.userbase.model.other; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * @program: UserProfile - * @description: 用户当前角色信息 - * @author: chenx - * @create: 2024-07-09 17:30 - **/ - -@AllArgsConstructor -@NoArgsConstructor -@Data -public class RoleInfoModel { - - private Integer roleId; - private String roleName; - private Integer schId; - private String schName; - private Integer childId; - private String childName; -} diff --git a/src/main/java/com/yunhai/userbase/model/sysuser/SysUserPasswordRequest.java b/src/main/java/com/yunhai/userbase/model/sysuser/SysUserPasswordRequest.java new file mode 100644 index 0000000..78454c2 --- /dev/null +++ b/src/main/java/com/yunhai/userbase/model/sysuser/SysUserPasswordRequest.java @@ -0,0 +1,33 @@ +package com.yunhai.userbase.model.sysuser; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 修改用户密码请求(oapi 开放接口专用)。 + * + * @author chenx + */ +@Data +public class SysUserPasswordRequest { + + /** + * 用户ID(必填)。 + */ + @NotNull + private Integer userId; + + /** + * 旧密码(必填,须与库中 BCrypt 密文匹配)。 + */ + @NotBlank + private String oldPassword; + + /** + * 新密码(必填,走弱密码规则校验后 BCrypt 加密写入)。 + */ + @NotBlank + private String newPassword; +} diff --git a/src/main/java/com/yunhai/userbase/service/IAppCategoryService.java b/src/main/java/com/yunhai/userbase/service/IAppCategoryService.java index 05678d1..13772b4 100644 --- a/src/main/java/com/yunhai/userbase/service/IAppCategoryService.java +++ b/src/main/java/com/yunhai/userbase/service/IAppCategoryService.java @@ -1,5 +1,7 @@ package com.yunhai.userbase.service; +import com.yunhai.platform.utils.enums.ClientEnum; +import com.yunhai.platform.utils.model.BoaAppType; import com.yunhai.userbase.entity.AppCategory; import com.yunhai.userbase.model.appauthz.AppAuthzCategoryVo; import com.yunhai.userbase.model.appcategory.AppCategoryQuery; @@ -33,4 +35,18 @@ public interface IAppCategoryService { */ List listWithApps(); + /** + * 开放接口专用:按用户 + 客户端类型查询其有权访问的应用列表(按应用类型分组)。
+ * 规则:
+ * ① 权限过滤:仅返回该用户有权访问的应用(复用 {@code IGroupAuthzService.findUserAuthorizedAppIds});
+ * ② 客户端过滤:仅返回支持该客户端类型的应用({@code app.clients} 位掩码与 clientType 有交集;clients 为空视为不限端);
+ * ③ 不透出 appSecret。
+ * 类型按 sortNo 升序,类型内应用按 sortNo、id 升序;无有权应用的类型 apps 为空数组。 + * + * @param userId 用户ID + * @param clientType 客户端类型(null=不按端过滤) + * @return 应用类型 + 有权应用列表 + */ + List listBoaAppType(Integer userId, ClientEnum clientType); + } diff --git a/src/main/java/com/yunhai/userbase/service/IGroupAuthzService.java b/src/main/java/com/yunhai/userbase/service/IGroupAuthzService.java index a69597b..95ef9c6 100644 --- a/src/main/java/com/yunhai/userbase/service/IGroupAuthzService.java +++ b/src/main/java/com/yunhai/userbase/service/IGroupAuthzService.java @@ -176,4 +176,15 @@ public interface IGroupAuthzService { * @param items 模块授权条目列表(groupId/appId/moduleId/mode/isAll) */ void setGroupModulePerms(List items); + + /** + * 查询用户有权访问的应用ID集合(oapi 开放接口 getUserApp 权限过滤用)。
+ * 规则与 {@link #hasModulePermission(Integer, Integer)} 对齐:
+ * - 超级管理员:全部应用;
+ * - 普通用户:用户所属组(沿祖先链)开通的应用 ∪ 启用角色模块所属应用。
+ * + * @param userId 用户ID + * @return 有权应用ID列表(无则空列表) + */ + List findUserAuthorizedAppIds(Integer userId); } diff --git a/src/main/java/com/yunhai/userbase/service/ISysUserService.java b/src/main/java/com/yunhai/userbase/service/ISysUserService.java index 6eafe45..d3bb66f 100644 --- a/src/main/java/com/yunhai/userbase/service/ISysUserService.java +++ b/src/main/java/com/yunhai/userbase/service/ISysUserService.java @@ -1,5 +1,6 @@ package com.yunhai.userbase.service; +import com.yunhai.platform.utils.model.BoaRoleInfo; import com.yunhai.userbase.model.sysuser.BatchImportResult; import com.yunhai.userbase.model.sysuser.SysUserGroupDeleteItem; import com.yunhai.userbase.model.sysuser.SysUserQuery; @@ -160,4 +161,22 @@ public interface ISysUserService { * @param response 直接写入 Excel 流 */ void exportUsers(Integer groupId, Integer withSub, String name, HttpServletResponse response) throws IOException; + + /** + * 查询用户的业务角色身份列表(oapi 开放接口专用)。 + * + * @param userId 用户ID + * @return 角色身份列表(roleId/roleName + 主归属学校组上下文) + */ + List findUserRoles(Integer userId); + + /** + * 修改用户密码:先校验旧密码,再写入新密码(BCrypt)。
+ * oapi 开放接口专用;不加操作日志(避免明文密码入日志)。 + * + * @param userId 用户ID + * @param oldPassword 旧密码(须与库中 BCrypt 密文匹配) + * @param newPassword 新密码(弱密码规则校验后加密写入) + */ + void changePassword(Integer userId, String oldPassword, String newPassword); } diff --git a/src/main/java/com/yunhai/userbase/service/impl/AppCategoryService.java b/src/main/java/com/yunhai/userbase/service/impl/AppCategoryService.java index bc98b2b..0fbbb21 100644 --- a/src/main/java/com/yunhai/userbase/service/impl/AppCategoryService.java +++ b/src/main/java/com/yunhai/userbase/service/impl/AppCategoryService.java @@ -1,8 +1,12 @@ package com.yunhai.userbase.service.impl; +import com.yunhai.platform.utils.enums.ClientEnum; import com.yunhai.platform.utils.exception.DException; +import com.yunhai.platform.utils.model.BoaApp; +import com.yunhai.platform.utils.model.BoaAppType; import com.yunhai.userbase.entity.AppCategory; +import com.yunhai.userbase.entity.ApplicationInfo; import com.yunhai.userbase.model.appauthz.AppAuthzAppRow; import com.yunhai.userbase.model.appauthz.AppAuthzCategoryVo; import com.yunhai.userbase.model.appcategory.AppCategoryQuery; @@ -11,6 +15,7 @@ import com.yunhai.userbase.model.appcategory.AppCategoryVo; import com.yunhai.userbase.repository.AppCategoryRepository; import com.yunhai.userbase.repository.ApplicationInfoRepository; import com.yunhai.userbase.service.IAppCategoryService; +import com.yunhai.userbase.service.IGroupAuthzService; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -18,9 +23,14 @@ import org.springframework.util.StringUtils; import javax.transaction.Transactional; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; /** @@ -36,6 +46,9 @@ public class AppCategoryService implements IAppCategoryService { @Autowired private ApplicationInfoRepository applicationInfoRepository; + @Autowired + private IGroupAuthzService groupAuthzService; + @Override @Transactional @@ -133,6 +146,97 @@ public class AppCategoryService implements IAppCategoryService { return vo; } + @Override + public List listBoaAppType(Integer userId, ClientEnum clientType) { + Set authorizedAppIds = new HashSet<>(groupAuthzService.findUserAuthorizedAppIds(userId)); + List categories = appCategoryRepository.findAllByOrderBySortNoAsc(); + List apps = applicationInfoRepository.findAll(); + apps.sort(Comparator.comparing(ApplicationInfo::getSortNo, Comparator.nullsLast(Integer::compareTo)) + .thenComparing(ApplicationInfo::getId, Comparator.nullsLast(Integer::compareTo))); + // categoryId -> 该类型下应用列表(插入顺序即排序) + Map> appMap = new LinkedHashMap<>(); + for (ApplicationInfo app : apps) { + if (app.getCategoryId() == null) { + continue; + } + appMap.computeIfAbsent(app.getCategoryId(), k -> new ArrayList<>()).add(app); + } + List result = new ArrayList<>(); + for (AppCategory category : categories) { + BoaAppType type = new BoaAppType(); + type.setId(category.getId()); + type.setName(category.getName()); + List boaApps = new ArrayList<>(); + for (ApplicationInfo app : appMap.getOrDefault(category.getId(), Collections.emptyList())) { + // 用户权限过滤 + if (!authorizedAppIds.contains(app.getId())) { + continue; + } + // 客户端类型位掩码过滤:clients 为空视为不限端 + if (clientType != null && !containsClient(app.getClients(), clientType.getKey())) { + continue; + } + boaApps.add(convertToBoaApp(app)); + } + type.setApps(boaApps); + result.add(type); + } + return result; + } + + /** + * ApplicationInfo -> BoaApp 映射。不透出 appSecret;模块列表留空(由调用方按需填充)。 + */ + private BoaApp convertToBoaApp(ApplicationInfo app) { + BoaApp boa = new BoaApp(); + boa.setId(app.getId()); + boa.setName(app.getName()); + boa.setIcon(app.getPcIcon()); + boa.setType(app.getCategoryId()); + boa.setWebAddr(app.getPcUrl()); + boa.setWebAddress(app.getPcUrl()); + boa.setAppKey(app.getAppKey()); + boa.setOrderNumber(app.getSortNo()); + boa.setCreateUser(app.getCreateUser()); + boa.setCreateTime(app.getCreateTime()); + boa.setMobiIcon(app.getAppIcon()); + boa.setClients(toClientMask(app.getClients())); + boa.setWxgzhAddress(app.getOfficialAccountUrl()); + boa.setDingAddress(app.getDingtalkUrl()); + return boa; + } + + /** + * clients 列表([1,2,4])折叠为位掩码叠加值(7)。 + */ + private Integer toClientMask(List clients) { + if (clients == null || clients.isEmpty()) { + return null; + } + int mask = 0; + for (Integer c : clients) { + if (c != null) { + mask |= c; + } + } + return mask; + } + + /** + * 判断客户端类型位掩码是否与 app.clients 有交集;clients 为空视为不限端。 + */ + private boolean containsClient(List clients, Integer clientKey) { + if (clients == null || clients.isEmpty()) { + return true; + } + for (Integer c : clients) { + if (c != null && (c & clientKey) != 0) { + return true; + } + } + return false; + } + } diff --git a/src/main/java/com/yunhai/userbase/service/impl/GroupAuthzService.java b/src/main/java/com/yunhai/userbase/service/impl/GroupAuthzService.java index 471df81..4e9af69 100644 --- a/src/main/java/com/yunhai/userbase/service/impl/GroupAuthzService.java +++ b/src/main/java/com/yunhai/userbase/service/impl/GroupAuthzService.java @@ -6,7 +6,7 @@ import com.yunhai.platform.utils.model.BoaModule; import com.yunhai.platform.utils.model.BoaUser; import com.yunhai.userbase.entity.*; import com.yunhai.userbase.enums.GroupTypeEnum; -import com.yunhai.userbase.enums.IEnum; +import com.yunhai.platform.utils.enums.IEnum; import com.yunhai.userbase.enums.SysModeEnum; import com.yunhai.userbase.enums.UserTypeEnum; import com.yunhai.userbase.model.groupauthz.GroupAppAssignRequest; @@ -1599,4 +1599,40 @@ public class GroupAuthzService implements IGroupAuthzService { } return moduleIds; } + + @Override + public List findUserAuthorizedAppIds(Integer userId) { + Objects.requireNonNull(userId, "用户ID不能为空"); + SysUser user = sysUserRepository.findById(userId).orElse(null); + if (user == null) { + throw new DException("用户不存在"); + } + // 超级管理员:全部应用 + if (isSuperAdmin(user.getType(), userId)) { + List allIds = new ArrayList<>(); + for (ApplicationInfo app : applicationInfoRepository.findAll()) { + allIds.add(app.getId()); + } + return allIds; + } + Set appIds = new LinkedHashSet<>(); + // 1. 组开通应用(用户所属组沿祖先链的开通池并集) + List userRels = sysUserGroupRelRepository.findByUserId(userId); + for (SysUserGroupRel rel : userRels) { + if (rel.getGroupId() == null) { + continue; + } + appIds.addAll(collectAppIdsForAuthzScope(rel.getGroupId())); + } + // 2. 启用角色模块所属应用(角色授权不要求组先开通应用) + Set roleModuleIds = collectEnabledRoleModuleIds(userId); + if (!roleModuleIds.isEmpty()) { + for (AppModule m : appModuleRepository.findAllById(roleModuleIds)) { + if (m.getAppId() != null) { + appIds.add(m.getAppId()); + } + } + } + return new ArrayList<>(appIds); + } } diff --git a/src/main/java/com/yunhai/userbase/service/impl/IdentityService.java b/src/main/java/com/yunhai/userbase/service/impl/IdentityService.java index ecbdd3e..16992b1 100644 --- a/src/main/java/com/yunhai/userbase/service/impl/IdentityService.java +++ b/src/main/java/com/yunhai/userbase/service/impl/IdentityService.java @@ -4,7 +4,7 @@ import com.yunhai.platform.utils.exception.DException; import com.yunhai.userbase.entity.SysGroup; import com.yunhai.userbase.entity.SysUserCurrentRole; import com.yunhai.userbase.entity.SysUserRoleRel; -import com.yunhai.userbase.enums.IEnum; +import com.yunhai.platform.utils.enums.IEnum; import com.yunhai.userbase.enums.StatusEnum; import com.yunhai.userbase.enums.SysRoleEnum; import com.yunhai.userbase.model.identity.IdentityCurrentVo; diff --git a/src/main/java/com/yunhai/userbase/service/impl/SysGroupService.java b/src/main/java/com/yunhai/userbase/service/impl/SysGroupService.java index ceb29b5..f7e292d 100644 --- a/src/main/java/com/yunhai/userbase/service/impl/SysGroupService.java +++ b/src/main/java/com/yunhai/userbase/service/impl/SysGroupService.java @@ -4,7 +4,7 @@ import com.yunhai.userbase.entity.SysGroup; import com.yunhai.platform.utils.exception.DException; import com.yunhai.platform.utils.model.BoaGroup; import com.yunhai.userbase.enums.GroupTypeEnum; -import com.yunhai.userbase.enums.IEnum; +import com.yunhai.platform.utils.enums.IEnum; import com.yunhai.userbase.event.DataSyncEvent; import com.yunhai.userbase.common.context.CallerAppContext; import com.yunhai.userbase.model.push.PushInterfaceType; diff --git a/src/main/java/com/yunhai/userbase/service/impl/SysUserService.java b/src/main/java/com/yunhai/userbase/service/impl/SysUserService.java index da27f4d..0b6507f 100644 --- a/src/main/java/com/yunhai/userbase/service/impl/SysUserService.java +++ b/src/main/java/com/yunhai/userbase/service/impl/SysUserService.java @@ -1,18 +1,22 @@ package com.yunhai.userbase.service.impl; import com.yunhai.platform.utils.exception.DException; +import com.yunhai.platform.utils.model.BoaRoleInfo; +import com.yunhai.userbase.entity.SysAppRole; import com.yunhai.userbase.entity.SysGroup; -import com.yunhai.userbase.enums.IEnum; +import com.yunhai.platform.utils.enums.IEnum; import com.yunhai.userbase.enums.StatusEnum; import com.yunhai.userbase.enums.SysRoleEnum; import com.yunhai.userbase.enums.UserTypeEnum; import com.yunhai.userbase.entity.SysUser; +import com.yunhai.userbase.entity.SysUserAppRoleRel; import com.yunhai.userbase.entity.SysUserGroupRel; import com.yunhai.userbase.model.sysuser.BatchImportResult; import com.yunhai.userbase.model.sysuser.SysUserGroupDeleteItem; import com.yunhai.userbase.model.sysuser.SysUserQuery; import com.yunhai.userbase.model.sysuser.SysUserRequest; import com.yunhai.userbase.model.sysuser.SysUserVo; +import com.yunhai.userbase.repository.SysAppRoleRepository; import com.yunhai.userbase.repository.SysGroupRepository; import com.yunhai.userbase.repository.SysUserAppRoleRelRepository; import com.yunhai.userbase.repository.SysUserGroupRelRepository; @@ -96,6 +100,9 @@ public class SysUserService implements ISysUserService { @Autowired private ISysGroupService sysGroupService; + @Autowired + private SysAppRoleRepository sysAppRoleRepository; + @Autowired private PasswordEncoder passwordEncoder; @@ -799,6 +806,68 @@ public class SysUserService implements ISysUserService { return single; } + @Override + public List findUserRoles(Integer userId) { + if (userId == null) { + throw new DException("用户ID不能为空"); + } + List result = new ArrayList<>(); + // 主归属组(学校/部门)上下文:schId/schName + Integer mainGroupId = null; + String mainGroupName = null; + List groupRels = sysUserGroupRelRepository.findByUserId(userId); + for (SysUserGroupRel gr : groupRels) { + if (gr.getIsMain() != null && gr.getIsMain() == IS_MAIN) { + mainGroupId = gr.getGroupId(); + break; + } + } + if (mainGroupId != null) { + SysGroup mainGroup = sysGroupRepository.findById(mainGroupId).orElse(null); + if (mainGroup != null) { + mainGroupName = mainGroup.getName(); + } + } + // 业务角色:用户-角色关系 → 角色名称 + List rels = sysUserAppRoleRelRepository.findByUserId(userId); + for (SysUserAppRoleRel rel : rels) { + if (rel.getRoleId() == null) { + continue; + } + SysAppRole role = sysAppRoleRepository.findById(rel.getRoleId()).orElse(null); + if (role == null) { + continue; + } + BoaRoleInfo info = new BoaRoleInfo(); + info.setRoleId(role.getId()); + info.setRoleName(role.getName()); + info.setSchId(mainGroupId); + info.setSchName(mainGroupName); + result.add(info); + } + return result; + } + + @Override + @Transactional + public void changePassword(Integer userId, String oldPassword, String newPassword) { + SysUser entity = sysUserRepository.findById(userId) + .orElseThrow(() -> new DException("系统用户不存在")); + if (StringUtils.isBlank(oldPassword) || StringUtils.isBlank(newPassword)) { + throw new DException("新旧密码不能为空"); + } + if (!passwordEncoder.matches(oldPassword, entity.getPassword())) { + throw new DException("原密码不正确"); + } + String weakReason = ValidatorUtil.checkPasswordStrength(newPassword); + if (weakReason != null) { + throw new DException(weakReason); + } + entity.setPassword(passwordEncoder.encode(newPassword)); + entity.setUpdateUser(SYSTEM_USER_ID); + sysUserRepository.save(entity); + } + /** 校验手机号与身份证(有值才校验)。 */ private void validateProfile(SysUserRequest request) { if (StringUtils.isNotBlank(request.getPhone()) && !ValidatorUtil.isPhone(request.getPhone())) { diff --git a/src/main/java/com/yunhai/userbase/utils/EnumUtils.java b/src/main/java/com/yunhai/userbase/utils/EnumUtils.java deleted file mode 100644 index 63fbf11..0000000 --- a/src/main/java/com/yunhai/userbase/utils/EnumUtils.java +++ /dev/null @@ -1,134 +0,0 @@ -package com.yunhai.userbase.utils; - -import cn.hutool.core.util.StrUtil; -import com.yunhai.userbase.enums.IEnum; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -/** - * 枚举底层工具实现,适配 Hutool 5.8.36 - * 对外统一通过 IEnum.xxx() 静态调用,不直接使用本类 - */ -public final class EnumUtils { - - private EnumUtils() { - throw new AssertionError("工具类禁止实例化"); - } - - /** - * 获取全部枚举实例(依赖Hutool5.8.36支持的EnumUtil.toList) - */ - public static & IEnum> List toList(Class targetEnumClazz) { - return new ArrayList<>(Arrays.asList(targetEnumClazz.getEnumConstants())); - } - - /** - * 枚举转完整Map列表(包含enum、name、key、value) - */ - public static & IEnum> List> toMapList(Class targetEnumClazz) { - List enumList = toList(targetEnumClazz); - List> resultList = new ArrayList<>(enumList.size()); - - for (T enumItem : enumList) { - Map map = new HashMap<>(4); - map.put("enum", enumItem); - map.put("name", enumItem.name()); - map.put("key", enumItem.getKey()); - map.put("value", enumItem.getValue()); - resultList.add(map); - } - return resultList; - } - - /** - * 通过key获取对应value文本 - */ - public static & IEnum> String getValue(Class targetEnumClazz, Object key) { - if (key == null || StrUtil.isBlank(String.valueOf(key))) { - return StrUtil.EMPTY; - } - Optional matchEnum = findByKey(targetEnumClazz, key); - return matchEnum.map(IEnum::getValue).orElse(StrUtil.EMPTY); - } - - /** - * 通过value文本获取对应key字符串 - */ - public static & IEnum> String getKey(Class targetEnumClazz, String value) { - if (StrUtil.isBlank(value)) { - return StrUtil.EMPTY; - } - Optional matchEnum = findByValue(targetEnumClazz, value); - return matchEnum.map(item -> String.valueOf(item.getKey())).orElse(StrUtil.EMPTY); - } - - /** - * 判断key是否存在 - */ - public static & IEnum> boolean isExistKey(Class targetEnumClazz, Object key) { - return findByKey(targetEnumClazz, key).isPresent(); - } - - /** - * 判断value是否存在 - */ - public static & IEnum> boolean isExistValue(Class targetEnumClazz, String value) { - return findByValue(targetEnumClazz, value).isPresent(); - } - - /** - * 根据key获取枚举实例,无匹配返回null - */ - public static & IEnum> T getEnumByKey(Class targetEnumClazz, Object key) { - return findByKey(targetEnumClazz, key).orElse(null); - } - - /** - * 根据key严格获取枚举,不存在抛出异常(fromKey) - */ - public static & IEnum> T fromKey(Class targetEnumClazz, Object key) { - T enumItem = getEnumByKey(targetEnumClazz, key); - if (enumItem == null) { - throw new IllegalArgumentException( - String.format("枚举不存在,key=%s,枚举类=%s", key, targetEnumClazz.getName()) - ); - } - return enumItem; - } - - /** - * 自定义返回字段名,生成下拉框简易列表 - */ - public static & IEnum> List> toMapList( - Class targetEnumClazz, String keyMapName, String valueMapName - ) { - List enumList = toList(targetEnumClazz); - List> result = new ArrayList<>(enumList.size()); - for (T enumItem : enumList) { - Map map = new HashMap<>(2); - map.put(keyMapName, enumItem.getKey()); - map.put(valueMapName, enumItem.getValue()); - result.add(map); - } - return result; - } - - // ====================== 私有底层查询方法(基于接口方法,无反射硬编码) ====================== - private static & IEnum> Optional findByKey(Class clazz, Object key) { - String targetKey = String.valueOf(key); - return toList(clazz).stream() - .filter(item -> targetKey.equals(String.valueOf(item.getKey()))) - .findFirst(); - } - - private static & IEnum> Optional findByValue(Class clazz, String value) { - return toList(clazz).stream() - .filter(item -> value.equals(item.getValue())) - .findFirst(); - } -} \ No newline at end of file