init: UserBaseService (backend + deploy + scripts)
ci-userbase-service / build-and-push (push) Successful in 40s
ci-userbase-service / build-and-push (push) Successful in 40s
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
package com.yunhai.userbase.controller.api;
|
||||
|
||||
import com.yunhai.userbase.common.annotation.OperationLog;
|
||||
import com.yunhai.userbase.common.validation.ValidationGroups;
|
||||
import com.yunhai.userbase.config.Config;
|
||||
import com.yunhai.userbase.model.BasePageQuery;
|
||||
import com.yunhai.userbase.model.BasePageResult;
|
||||
import com.yunhai.userbase.model.approle.AppRoleAuthzAppVo;
|
||||
import com.yunhai.userbase.model.approle.AppRoleModuleAssignRequest;
|
||||
import com.yunhai.userbase.model.approle.AppRoleUserBindRequest;
|
||||
import com.yunhai.userbase.model.approle.AppRoleUserVo;
|
||||
import com.yunhai.userbase.model.approle.SysAppRoleQuery;
|
||||
import com.yunhai.userbase.model.approle.SysAppRoleRequest;
|
||||
import com.yunhai.userbase.model.approle.SysAppRoleVo;
|
||||
import com.yunhai.userbase.model.approle.UserAppRoleBindRequest;
|
||||
import com.yunhai.userbase.service.IAppRoleService;
|
||||
import com.yunhai.userbase.utils.PageUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.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;
|
||||
|
||||
/**
|
||||
* @author chenx
|
||||
* @date 2026/7/27
|
||||
* @description 应用业务角色管理(角色 CRUD + 角色模块授权 + 用户角色绑定)。<br>
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(value = Config.API_PATH + "/approle/")
|
||||
public class AppRoleController {
|
||||
|
||||
@Autowired
|
||||
private IAppRoleService appRoleService;
|
||||
|
||||
/**
|
||||
* 角色分页列表:name 模糊,appId/allAppFlag/disabled 精确;普通角色回填所属应用名称。
|
||||
*/
|
||||
@GetMapping("/RoleList")
|
||||
public BasePageResult<SysAppRoleVo> roleList(SysAppRoleQuery query, BasePageQuery pageQuery,
|
||||
@SortDefault(sort = "id", direction = Sort.Direction.DESC) Sort sort) {
|
||||
return PageUtils.toResult(appRoleService.list(query, PageUtils.toPageable(pageQuery, sort)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增角色:普通角色(allAppFlag=false,appId 必填,同应用下名称唯一);<br>
|
||||
* 全应用角色(allAppFlag=true,appId 为空,全应用角色之间名称唯一)。
|
||||
*/
|
||||
@OperationLog("新增角色")
|
||||
@PostMapping("RoleAdd")
|
||||
public void roleAdd(@RequestBody @Validated(ValidationGroups.Create.class) SysAppRoleRequest request) {
|
||||
appRoleService.add(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑角色:仅可改 name/remark/disabled;allAppFlag 与 appId 不允许修改。
|
||||
*/
|
||||
@OperationLog("编辑角色")
|
||||
@PostMapping("RoleEdit/{id}")
|
||||
public void roleEdit(@PathVariable Integer id,
|
||||
@RequestBody @Validated(ValidationGroups.Update.class) SysAppRoleRequest request) {
|
||||
appRoleService.edit(id, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除角色:级联清理该角色的模块授权与用户绑定记录。
|
||||
*/
|
||||
@OperationLog("删除角色")
|
||||
@PostMapping("RoleDelete/{id}")
|
||||
public void roleDelete(@PathVariable Integer id) {
|
||||
appRoleService.delete(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 角色模块授权回显:返回该角色可授权的应用及模块树(已授权 checked=true)。<br>
|
||||
* 普通角色仅本应用;全应用角色返回全部应用(可跨应用授权)。
|
||||
*/
|
||||
@GetMapping("/RoleModuleTree")
|
||||
public List<AppRoleAuthzAppVo> roleModuleTree(@RequestParam Integer roleId) {
|
||||
return appRoleService.findRoleModuleTree(roleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 角色模块授权保存:全量覆盖式更新该角色的模块授权记录。
|
||||
*/
|
||||
@OperationLog("保存角色模块授权")
|
||||
@PostMapping("SaveRoleModules")
|
||||
public void saveRoleModules(@RequestBody @Validated AppRoleModuleAssignRequest request) {
|
||||
appRoleService.saveRoleModules(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询角色已绑定的用户列表。
|
||||
*/
|
||||
@GetMapping("/RoleUsers")
|
||||
public List<AppRoleUserVo> roleUsers(@RequestParam Integer roleId) {
|
||||
return appRoleService.findRoleUsers(roleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 角色绑定用户保存(角色维度,全量覆盖)。
|
||||
*/
|
||||
@OperationLog("保存角色用户绑定")
|
||||
@PostMapping("SaveRoleUsers")
|
||||
public void saveRoleUsers(@RequestBody @Validated AppRoleUserBindRequest request) {
|
||||
appRoleService.saveRoleUsers(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户绑定角色保存(用户维度,全量覆盖)。
|
||||
*/
|
||||
@OperationLog("保存用户角色绑定")
|
||||
@PostMapping("SaveUserRoles")
|
||||
public void saveUserRoles(@RequestBody @Validated UserAppRoleBindRequest request) {
|
||||
appRoleService.saveUserRoles(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除单个用户与角色的绑定关系(精确删除单条记录,非全量重提)。<br>
|
||||
* 前端「用户管理 - 删除」调用此接口。
|
||||
*/
|
||||
@OperationLog("移除角色用户绑定")
|
||||
@PostMapping("RemoveRoleUser")
|
||||
public void removeRoleUser(@RequestParam Integer roleId, @RequestParam Integer userId) {
|
||||
appRoleService.removeRoleUser(roleId, userId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
package com.yunhai.userbase.controller.api;
|
||||
|
||||
import com.bestsch.utils.DException;
|
||||
import com.yunhai.userbase.common.annotation.OperationLog;
|
||||
import com.yunhai.userbase.common.context.UserContext;
|
||||
import com.yunhai.userbase.common.validation.ValidationGroups;
|
||||
import com.yunhai.userbase.config.Config;
|
||||
import com.yunhai.userbase.entity.ApplicationInfo;
|
||||
import com.yunhai.userbase.enums.BookType;
|
||||
import com.yunhai.userbase.model.BaseDict;
|
||||
import com.yunhai.userbase.model.BasePageQuery;
|
||||
import com.yunhai.userbase.model.BasePageResult;
|
||||
import com.yunhai.userbase.model.Operator;
|
||||
import com.yunhai.userbase.model.appauthz.AppAuthzCategoryVo;
|
||||
import com.yunhai.userbase.model.appcategory.AppCategoryQuery;
|
||||
import com.yunhai.userbase.model.appcategory.AppCategoryRequest;
|
||||
import com.yunhai.userbase.model.appcategory.AppCategoryVo;
|
||||
import com.yunhai.userbase.model.application.ApplicationInfoQuery;
|
||||
import com.yunhai.userbase.model.application.ApplicationInfoRequest;
|
||||
import com.yunhai.userbase.model.application.ApplicationInfoSortVo;
|
||||
import com.yunhai.userbase.model.application.ApplicationInfoVo;
|
||||
import com.yunhai.userbase.model.appmodule.AppModuleQuery;
|
||||
import com.yunhai.userbase.model.appmodule.AppModuleRequest;
|
||||
import com.yunhai.userbase.model.appmodule.AppModuleVo;
|
||||
import com.yunhai.userbase.service.IAppCategoryService;
|
||||
import com.yunhai.userbase.service.IApplicationInfoService;
|
||||
import com.yunhai.userbase.service.IAppModuleService;
|
||||
import com.yunhai.userbase.service.SignKeyCacheService;
|
||||
import com.yunhai.userbase.utils.DicUtil;
|
||||
import com.yunhai.userbase.utils.PageUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.tomcat.util.http.fileupload.util.Streams;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.Valid;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(value = Config.API_PATH + "/app/")
|
||||
public class AppSettingsController {
|
||||
|
||||
@Autowired
|
||||
private IAppCategoryService appCategoryService;
|
||||
|
||||
@Autowired
|
||||
private IApplicationInfoService applicationInfoService;
|
||||
|
||||
@Autowired
|
||||
private IAppModuleService appModuleService;
|
||||
|
||||
@Autowired
|
||||
private SignKeyCacheService signKeyCacheService;
|
||||
|
||||
|
||||
|
||||
@OperationLog("新增应用类型")
|
||||
@PostMapping("AddType")
|
||||
public void addType(@RequestBody @Validated(ValidationGroups.Create.class) AppCategoryRequest request) {
|
||||
appCategoryService.add(request);
|
||||
}
|
||||
|
||||
@OperationLog("编辑应用类型")
|
||||
@PostMapping("EditType/{id}")
|
||||
public void editType(@PathVariable Integer id, @RequestBody @Validated(ValidationGroups.Update.class) AppCategoryRequest request) {
|
||||
appCategoryService.edit(id, request);
|
||||
}
|
||||
|
||||
@OperationLog("删除应用类型")
|
||||
@PostMapping("DeleteType/{id}")
|
||||
public void deleteType(@PathVariable Integer id) {
|
||||
appCategoryService.delete(id);
|
||||
}
|
||||
|
||||
@GetMapping("/AppTypeList")
|
||||
public List<AppCategoryVo> appTypeList(AppCategoryQuery query) {
|
||||
return appCategoryService.list(query);
|
||||
}
|
||||
|
||||
@OperationLog("应用类型排序")
|
||||
@PostMapping("/AppTypeSort")
|
||||
public void appTypeSort(@RequestBody List<Integer> ids) {
|
||||
appCategoryService.sort(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用授权用:获取所有应用类型及其类型下的应用(每个应用含 id/name/icon)。<br>
|
||||
* 类型按 sortNo 升序,类型内应用按 sortNo、id 升序;无应用的类型 app 为空数组。
|
||||
*/
|
||||
@GetMapping("/AppAuthzList")
|
||||
public List<AppAuthzCategoryVo> appAuthzList() {
|
||||
return appCategoryService.listWithApps();
|
||||
}
|
||||
|
||||
@OperationLog("新增应用")
|
||||
@PostMapping("AddApp")
|
||||
public void addApp(@RequestBody @Validated(ValidationGroups.Create.class) ApplicationInfoRequest request) {
|
||||
applicationInfoService.add(request);
|
||||
}
|
||||
|
||||
@OperationLog("编辑应用")
|
||||
@PostMapping("EditApp/{id}")
|
||||
public void editApp(@PathVariable Integer id, @RequestBody @Validated(ValidationGroups.Update.class) ApplicationInfoRequest request) {
|
||||
applicationInfoService.edit(id, request);
|
||||
}
|
||||
|
||||
@OperationLog("删除应用")
|
||||
@PostMapping("DeleteApp/{id}")
|
||||
public void deleteApp(@PathVariable Integer id) {
|
||||
applicationInfoService.delete(id);
|
||||
}
|
||||
|
||||
@OperationLog("重置应用密钥")
|
||||
@PostMapping("ResetAppSecret/{id}")
|
||||
public String resetAppSecret(@PathVariable Integer id) {
|
||||
applicationInfoService.resetAppSecret(id);
|
||||
ApplicationInfo info= applicationInfoService.getById(id);
|
||||
return info.getAppSecret();
|
||||
}
|
||||
|
||||
@OperationLog("全量刷新应用签名密钥到Redis")
|
||||
@PostMapping("RefreshSignKeys")
|
||||
public Map<String, Object> refreshSignKeys() {
|
||||
int count = signKeyCacheService.refreshAll();
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("refreshed", count);
|
||||
return result;
|
||||
}
|
||||
|
||||
@GetMapping("/AppList")
|
||||
public BasePageResult<ApplicationInfoVo> appList(ApplicationInfoQuery query, BasePageQuery pageQuery,
|
||||
@SortDefault(sort = "sortNo", direction = Sort.Direction.ASC) Sort sort) {
|
||||
return PageUtils.toResult(applicationInfoService.list(query, PageUtils.toPageable(pageQuery, sort)));
|
||||
}
|
||||
|
||||
@OperationLog("应用排序")
|
||||
@PostMapping("/AppSort")
|
||||
public void appSort(@RequestBody List<Integer> ids) {
|
||||
applicationInfoService.sort(ids);
|
||||
}
|
||||
|
||||
@GetMapping("/AppSortList")
|
||||
public List<ApplicationInfoSortVo> appSortList(@RequestParam Integer categoryId) {
|
||||
return applicationInfoService.findListByCategoryId(categoryId);
|
||||
}
|
||||
|
||||
@OperationLog("新增模块")
|
||||
@PostMapping("AddModule")
|
||||
public void addModule(@RequestBody @Validated(ValidationGroups.Create.class) AppModuleRequest request) {
|
||||
appModuleService.add(request);
|
||||
}
|
||||
|
||||
@OperationLog("编辑模块")
|
||||
@PostMapping("EditModule/{id}")
|
||||
public void editModule(@PathVariable Integer id, @RequestBody @Validated(ValidationGroups.Update.class) AppModuleRequest request) {
|
||||
appModuleService.edit(id, request);
|
||||
}
|
||||
|
||||
@OperationLog("删除模块")
|
||||
@PostMapping("DeleteModule/{id}")
|
||||
public void deleteModule(@PathVariable Integer id) {
|
||||
appModuleService.delete(id);
|
||||
}
|
||||
|
||||
@GetMapping("/ModuleList")
|
||||
public List<AppModuleVo> moduleList(AppModuleQuery query) {
|
||||
return appModuleService.list(query);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package com.yunhai.userbase.controller.api;
|
||||
|
||||
import com.yunhai.platform.utils.model.BoaUser;
|
||||
import com.yunhai.userbase.config.Config;
|
||||
import com.yunhai.userbase.enums.*;
|
||||
import com.yunhai.userbase.utils.*;
|
||||
import com.bestsch.utils.DException;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import lombok.var;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.view.RedirectView;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(value = Config.API_PATH +"/base/")
|
||||
public class BaseController {
|
||||
|
||||
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
/**
|
||||
* 一般hashMap初始化大小
|
||||
*/
|
||||
public static final int initialCapacity = 6;
|
||||
|
||||
/**
|
||||
* 通用上传允许的子目录(与 MyWebAppConfigurer.addResourceHandlers 的资源映射一一对应)。<br>
|
||||
* 注:html 因 FileUtil 的危险后缀黑名单被禁止通过此接口上传,故未列入,避免存储可被浏览器执行的 HTML 引发 XSS。
|
||||
*/
|
||||
private static final Set<String> ALLOWED_UPLOAD_TYPES = new HashSet<>(Arrays.asList("image", "file", "icon", "word"));
|
||||
|
||||
@GetMapping("GetUserInfo")
|
||||
public Object getUserInfo(BoaUser authUserWithDept){
|
||||
return authUserWithDept;
|
||||
}
|
||||
|
||||
//返回系统枚举
|
||||
@GetMapping("GetSysDict")
|
||||
public Map getSysDict(BoaUser authUserWithDept){
|
||||
Map<String, List> resultMap = new HashMap<>(7);
|
||||
resultMap.put("grouptype", IEnum.toSimpleMapList(GroupTypeEnum.class, "k", "v"));
|
||||
resultMap.put("gender", IEnum.toSimpleMapList(GenderEnum.class, "k", "v"));
|
||||
resultMap.put("client", IEnum.toSimpleMapList(ClientEnum.class, "k", "v"));
|
||||
resultMap.put("integ", IEnum.toSimpleMapList(IntegEnum.class, "k", "v"));
|
||||
resultMap.put("status", IEnum.toSimpleMapList(StatusEnum.class, "k", "v"));
|
||||
resultMap.put("sysrole", IEnum.toSimpleMapList(SysRoleEnum.class, "k", "v"));
|
||||
resultMap.put("mode", IEnum.toSimpleMapList(SysModeEnum.class, "k", "v"));
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动管理 Redis Hash 缓存的示例(@Cacheable 注解无法满足的场景)。<br>
|
||||
* 适用场景:需要把一个对象的多个字段按 Hash 结构存储,<br>
|
||||
* 可单独读写/更新某个字段,而不像 @Cacheable 那样把整个对象序列化成一个大 value。<br>
|
||||
* 访问路径:GET {Config.API_PATH}/base/ManualHashCacheDemo。<br>
|
||||
* 注:手动缓存依赖 Redis;Redis 不可用时 RedisUtil 已内部捕获异常,操作失败但不影响接口本身。
|
||||
*
|
||||
* @return 本次演示各步骤的结果快照
|
||||
*/
|
||||
@GetMapping("ManualHashCacheDemo")
|
||||
public Map<String, Object> manualHashCacheDemo() {
|
||||
Map<String, Object> result = new HashMap<>(8);
|
||||
String hashKey = "demo:user:1001";
|
||||
// 1) 写入整张 hash(带 TTL):适合一次性缓存一个对象的多个属性
|
||||
Map<String, Object> profile = new HashMap<>(4);
|
||||
profile.put("name", "yunhai");
|
||||
profile.put("age", 18);
|
||||
profile.put("role", "admin");
|
||||
redisUtil.hmset(hashKey, profile, 300);
|
||||
// 2) 只更新其中一个字段,无需整体覆盖
|
||||
redisUtil.hset(hashKey, "role", "superadmin");
|
||||
// 3) 读取单个字段
|
||||
result.put("roleAfterUpdate", redisUtil.hget(hashKey, "role"));
|
||||
// 4) 读取整张 hash
|
||||
result.put("wholeHash", redisUtil.hmget(hashKey));
|
||||
// 5) 判断字段是否存在
|
||||
result.put("hasAgeField", redisUtil.hHasKey(hashKey, "age"));
|
||||
// 6) Cache-Aside(复用下方私有方法):字段不存在时回源并写回 Hash
|
||||
Object nickname = getHashFieldWithFallback(hashKey, "nickname", () -> "yunhai-nick");
|
||||
result.put("nickname", nickname);
|
||||
// 7) 删除某个字段
|
||||
redisUtil.hdel(hashKey, "age");
|
||||
result.put("afterDelAge", redisUtil.hmget(hashKey));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@GetMapping("RedirectTo/{id}")
|
||||
public RedirectView RedirectTo(BoaUser authUserWithDept, @PathVariable(name = "id") String id) {
|
||||
String url= OtherUtil.hexToString(id);
|
||||
return new RedirectView(url,true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用文件上传接口(一个接口覆盖所有类型,无需按类型拆分)。<br>
|
||||
* type 决定存储子目录,与 MyWebAppConfigurer.addResourceHandlers 的 /upload/{type}/** 映射一一对应,<br>
|
||||
* 上传成功后可直接用返回的 URL 访问,无需再改资源映射配置。<br>
|
||||
* 访问路径:POST {Config.API_PATH}/base/UploadFile(multipart/form-data)。<br>
|
||||
* 支持 type:image / file / icon / word(html 被 FileUtil 黑名单禁止上传)。<br>
|
||||
*
|
||||
* @param file 上传文件(必填,单文件上限 50MB,见 application.yml 的 multipart 配置)
|
||||
* @param type 存储子目录类型,默认 image
|
||||
* @return 文件对外可访问的 Web URL
|
||||
*/
|
||||
@PostMapping("UploadFile")
|
||||
public String uploadFile(@RequestParam("file") MultipartFile file,
|
||||
@RequestParam(value = "type", defaultValue = "image") String type) {
|
||||
if (!ALLOWED_UPLOAD_TYPES.contains(type)) {
|
||||
throw new DException("不支持的上传类型: " + type + ",可选: image/file/icon/word");
|
||||
}
|
||||
// FileUtil.upload 内部已处理空文件校验、危险后缀黑名单、目录创建与访问 URL 拼接
|
||||
return FileUtil.upload(file, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 私有方法(可复用):演示 Hash 结构的 Cache-Aside(缓存旁路)模式。<br>
|
||||
* 先从 Redis Hash 读字段;读不到则回源(此处用 fallback 模拟数据库查询),再把结果写回 Hash。<br>
|
||||
*
|
||||
* @param hashKey hash 键
|
||||
* @param field 字段名
|
||||
* @param fallback 回源加载器(真实场景替换为查数据库/远程调用)
|
||||
* @return 字段值
|
||||
*/
|
||||
private Object getHashFieldWithFallback(String hashKey, String field, Supplier<Object> fallback) {
|
||||
if (redisUtil.hHasKey(hashKey, field)) {
|
||||
return redisUtil.hget(hashKey, field);
|
||||
}
|
||||
Object value = fallback.get();
|
||||
if (value != null) {
|
||||
redisUtil.hset(hashKey, field, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package com.yunhai.userbase.controller.api;
|
||||
|
||||
import com.yunhai.userbase.common.annotation.OperationLog;
|
||||
import com.yunhai.userbase.config.Config;
|
||||
import com.yunhai.userbase.model.groupauthz.GroupAppAssignRequest;
|
||||
import com.yunhai.userbase.model.groupauthz.GroupAppVo;
|
||||
import com.yunhai.userbase.model.groupauthz.GroupAuthzAppVo;
|
||||
import com.yunhai.userbase.model.groupauthz.GroupAuthzCategoryVo;
|
||||
import com.yunhai.userbase.model.groupauthz.GroupModuleAuthzItem;
|
||||
import com.yunhai.userbase.model.groupauthz.GroupModulePermRequest;
|
||||
import com.yunhai.userbase.model.groupauthz.SchoolAppAuthzRequest;
|
||||
import com.yunhai.userbase.model.groupauthz.UserGroupNodeVo;
|
||||
import com.yunhai.userbase.model.groupauthz.UserPermissionCategoryVo;
|
||||
import com.yunhai.userbase.model.groupauthz.UserPermissionUserVo;
|
||||
import com.yunhai.userbase.model.groupauthz.UserRoleVo;
|
||||
import com.yunhai.userbase.service.IGroupAuthzService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import javax.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author chenx
|
||||
* @date 2026/7/23
|
||||
* @description 组应用权限管理(应用开通 + 组授权 + 权限判定)。<br>
|
||||
* 左侧组树复用 /sysgroup/GetTree;本控制器提供右侧数据加载与保存接口。<br>
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(value = Config.API_PATH + "/groupauthz/")
|
||||
public class GroupAuthzController {
|
||||
|
||||
@Autowired
|
||||
private IGroupAuthzService groupAuthzService;
|
||||
|
||||
/**
|
||||
* 功能1回显:点击左侧组后,返回系统全量应用列表,已开通的 checked=true。
|
||||
*/
|
||||
@GetMapping("/GroupAppList")
|
||||
public List<GroupAppVo> groupAppList(@RequestParam Integer groupId) {
|
||||
return groupAuthzService.listGroupApps(groupId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 功能1保存:全量覆盖式更新该组的应用开通记录。
|
||||
*/
|
||||
@OperationLog("保存组应用开通")
|
||||
@PostMapping("AssignApps")
|
||||
public void assignApps(@RequestBody @Validated GroupAppAssignRequest request) {
|
||||
groupAuthzService.assignAppsToGroup(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 功能2数据加载:返回该组已开通的应用及其模块权限树(含勾选与子组继承回显)。
|
||||
*/
|
||||
@GetMapping("/GroupModuleTree")
|
||||
public List<GroupAuthzAppVo> groupModuleTree(@RequestParam Integer groupId) {
|
||||
return groupAuthzService.findGroupAppsWithModules(groupId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 功能2数据加载(按应用分类分组):供组授权页面按分类面板展示,<br>
|
||||
* 返回全部分类及该组已开通的应用模块权限树。
|
||||
*/
|
||||
@GetMapping("/GroupModuleTreeByCategory")
|
||||
public List<GroupAuthzCategoryVo> groupModuleTreeByCategory(@RequestParam Integer groupId) {
|
||||
return groupAuthzService.findGroupAppsWithModulesByCategory(groupId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 功能2保存:全量覆盖式更新该组的模块权限(含子组继承开关)。
|
||||
*/
|
||||
@OperationLog("保存组模块权限")
|
||||
@PostMapping("SaveModulePerms")
|
||||
public void saveModulePerms(@RequestBody @Validated GroupModulePermRequest request) {
|
||||
groupAuthzService.saveModulePermissions(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限判定:判断用户对某模块是否有权限(供登录/拦截器/联调验证使用)。
|
||||
*/
|
||||
@GetMapping("/HasModulePerm")
|
||||
public boolean hasModulePerm(@RequestParam Integer userId, @RequestParam Integer moduleId) {
|
||||
return groupAuthzService.hasModulePermission(userId, moduleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限一览:根据组ID获取该组下的直接用户列表(不含子组,不分页)。
|
||||
*/
|
||||
@GetMapping("/GroupUsers")
|
||||
public List<UserPermissionUserVo> groupUsers(@RequestParam Integer groupId) {
|
||||
return groupAuthzService.findGroupUsers(groupId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限一览:全局搜索用户(按账号/姓名/身份证模糊、手机号精确匹配),不分页。<br>
|
||||
* 返回用户及其主归属部门/组织路径,供左侧用户搜索形态展示。
|
||||
*/
|
||||
@GetMapping("/SearchUsers")
|
||||
public List<UserPermissionUserVo> searchUsers(@RequestParam String keyword) {
|
||||
return groupAuthzService.searchUsers(keyword);
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限一览:根据用户ID聚合该用户的全部已授权应用模块树。<br>
|
||||
* 只返回有授权模块的应用,并按应用类型分组;无授权的应用类型和应用不显示。<br>
|
||||
* 角色授权并集:结果 = 组授权模块 ∪ 启用角色授权模块(角色模块不要求组先开通应用)。
|
||||
*/
|
||||
@GetMapping("/UserPermissionTree")
|
||||
public List<UserPermissionCategoryVo> userPermissionTree(@RequestParam Integer userId,
|
||||
@RequestParam(required = false) Integer groupId) {
|
||||
return groupAuthzService.findUserPermissionTree(groupId, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限一览:查询用户所属的业务角色列表(中间下方「所属角色」区域展示用)。<br>
|
||||
* 返回该用户绑定的全部角色(含禁用角色,disabled 字段标记),普通角色回填所属应用名称。
|
||||
*/
|
||||
@GetMapping("/UserRoles")
|
||||
public List<UserRoleVo> userRoles(@RequestParam Integer userId) {
|
||||
return groupAuthzService.findUserRoles(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限一览:查询用户所属部门树(左侧上方「所属部门」区域展示用)。<br>
|
||||
* 树仅由「用户直接归属的组 + 其全部祖先组」构成(如:云平台-学校中学-语文组)。
|
||||
*/
|
||||
@GetMapping("/UserGroupTree")
|
||||
public List<UserGroupNodeVo> userGroupTree(@RequestParam Integer userId) {
|
||||
return groupAuthzService.findUserGroupTree(userId);
|
||||
}
|
||||
|
||||
// ===================== 新增:树节点式增量授权(左侧树 + 右侧 appAuthzList 勾选交互) =====================
|
||||
|
||||
/**
|
||||
* 功能3回显:点击左侧树节点后,返回该组「已开通的应用ID列表」,供右侧 AppSettings.appAuthzList 回显勾选状态。<br>
|
||||
* <b>交互</b>:左树节点 → 右 appAuthzList(全量应用)→ 本接口拿到已选 id → 前端把对应多选框置为勾选。<br>
|
||||
* <b>实现</b>:应用开通不继承,直接查 sys_group_app_rel(group_id=nodeId) 返回 app_id 列表(详见 service.findAuthorizedAppIds)。<br>
|
||||
* 与 GroupAppList(返回全量应用+checked)互为补充:本接口更轻量,只给 id 集合。
|
||||
*/
|
||||
@GetMapping("/GetSchoolAppId")
|
||||
public List<Integer> getSchoolAppId(@RequestParam Integer nodeId) {
|
||||
return groupAuthzService.findAuthorizedAppIds(nodeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 功能3保存(应用维):点击应用多选框触发,对该组做应用开通/取消。<br>
|
||||
* <b>入参(请求体,JSON)</b>:{@link SchoolAppAuthzRequest} —— schid=组ID(nodeId);appid=单个应用ID(一次只传一个,逐次点击);mode=操作模式。<br>
|
||||
* <b>mode 语义(SysModeEnum)</b>:0=追加(开通应用) / 2=删除(取消开通);1=覆盖 不适用,会被拒绝。<br>
|
||||
* <b>实现</b>:应用开通不继承,仅操作本组;删除时同步清理该组在该应用下模块的权限残留(详见 service.setSchoolApps)。<br>
|
||||
* <b>注意</b>:appid 为单个 Integer,后端不再做多值/逗号解析;每次点击提交一次请求。
|
||||
*/
|
||||
@OperationLog("设置组应用开通")
|
||||
@PostMapping("SetSchoolApp")
|
||||
public void setSchoolApp(@RequestBody @Valid SchoolAppAuthzRequest request) {
|
||||
groupAuthzService.setSchoolApps(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 功能4保存(模块维):前端「组授权」面板逐条勾选/取消时提交,对 (组,应用,模块) 做增量授权/取消。<br>
|
||||
* <b>入参</b>:请求体为数组,元素见 {@link GroupModuleAuthzItem}:{groupId, appId, moduleId, mode, isAll}。<br>
|
||||
* <b>mode</b>:0=追加 / 2=删除(1=覆盖不适用)。<br>
|
||||
* <b>isAll</b>:是否对子组继承,映射 sys_group_module_perm.inherit_flag(1=子孙组自动继承 / 0=仅本组)。<br>
|
||||
* <b>moduleId=0</b>:表示「该应用下全部模块」,后端展开为应用下每个真实模块各写/删一条。<br>
|
||||
* <b>实现</b>:逐条处理、顺序即执行顺序;追加前要求该组已开通该应用;越权写别的应用模块会被拒绝(详见 service.setGroupModulePerms)。<br>
|
||||
* 与现有 SaveModulePerms(全量覆盖)共存:本接口是单条增量,二者操作同一张表、互不冲突。
|
||||
*/
|
||||
@OperationLog("设置组模块权限")
|
||||
@PostMapping("SetGroupModule")
|
||||
public void setGroupModule(@RequestBody @Valid List<GroupModuleAuthzItem> items) {
|
||||
groupAuthzService.setGroupModulePerms(items);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.yunhai.userbase.controller.api;
|
||||
|
||||
import com.yunhai.userbase.common.annotation.OperationLog;
|
||||
import com.yunhai.userbase.common.validation.ValidationGroups;
|
||||
import com.yunhai.userbase.config.Config;
|
||||
import com.yunhai.userbase.model.sysgroup.SysGroupBatchRequest;
|
||||
import com.yunhai.userbase.model.sysgroup.SysGroupMoveRequest;
|
||||
import com.yunhai.userbase.model.sysgroup.SysGroupQuery;
|
||||
import com.yunhai.userbase.model.sysgroup.SysGroupRequest;
|
||||
import com.yunhai.userbase.model.sysgroup.SysGroupSortRequest;
|
||||
import com.yunhai.userbase.model.sysgroup.SysGroupVo;
|
||||
import com.yunhai.userbase.service.ISysGroupService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author chenx
|
||||
* @date 2026/7/20 16:00
|
||||
* @description 系统组管理(组织架构树)
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(value = Config.API_PATH + "/sysgroup/")
|
||||
public class SysGroupController {
|
||||
|
||||
@Autowired
|
||||
private ISysGroupService sysGroupService;
|
||||
|
||||
@OperationLog("新增组")
|
||||
@PostMapping("Add")
|
||||
public void add(@RequestBody @Validated(ValidationGroups.Create.class) SysGroupRequest request) {
|
||||
sysGroupService.add(request);
|
||||
}
|
||||
|
||||
@OperationLog("编辑组")
|
||||
@PostMapping("Edit/{id}")
|
||||
public void edit(@PathVariable Integer id, @RequestBody @Validated(ValidationGroups.Update.class) SysGroupRequest request) {
|
||||
sysGroupService.edit(id, request);
|
||||
}
|
||||
|
||||
@OperationLog("删除组")
|
||||
@PostMapping("Delete/{id}")
|
||||
public void delete(@PathVariable Integer id) {
|
||||
sysGroupService.delete(id);
|
||||
}
|
||||
|
||||
@GetMapping("/List")
|
||||
public List<SysGroupVo> list(SysGroupQuery query) {
|
||||
return sysGroupService.list(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组织架构树;表为空时自动创建根节点「教育云中心」(pid=0)。
|
||||
*/
|
||||
@GetMapping("GetTree")
|
||||
public List<SysGroupVo> getTree() {
|
||||
return sysGroupService.getTree();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取「学校组」剪枝树:末端叶子一定是学校组;学校组下的组不显示;<br>
|
||||
* 子树中无学校组的分支整体剪掉;根节点始终保留。
|
||||
*/
|
||||
@GetMapping("GetSchoolTree")
|
||||
public List<SysGroupVo> getSchoolTree() {
|
||||
return sysGroupService.getSchoolTree();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量新增子组:请求体 { pid, type, items:[{name},...] }(JSON)。
|
||||
*/
|
||||
@OperationLog("批量新增子组")
|
||||
@PostMapping("BatchAdd")
|
||||
public void batchAdd(@RequestBody @Validated SysGroupBatchRequest request) {
|
||||
sysGroupService.batchAdd(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 子组排序:传入 pid 与有序 ids(必须是 pid 的直接子节点),按传入顺序落库。
|
||||
*/
|
||||
@OperationLog("子组排序")
|
||||
@PostMapping("SortChildren")
|
||||
public void sortChildren(@Validated SysGroupSortRequest request) {
|
||||
sysGroupService.sortChildren(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动组:将 moveId 移动到 pid 下(不支持移动到自身或自身后代)。
|
||||
*/
|
||||
@OperationLog("移动组")
|
||||
@PostMapping("Move")
|
||||
public void move(@Validated SysGroupMoveRequest request) {
|
||||
sysGroupService.move(request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.yunhai.userbase.controller.api;
|
||||
|
||||
import com.yunhai.userbase.config.Config;
|
||||
import com.yunhai.userbase.model.BasePageQuery;
|
||||
import com.yunhai.userbase.model.BasePageResult;
|
||||
import com.yunhai.userbase.model.oplog.SysOperationLogListVo;
|
||||
import com.yunhai.userbase.model.oplog.SysOperationLogQuery;
|
||||
import com.yunhai.userbase.service.ISysOperationLogService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 操作日志查询与下载接口。<br>
|
||||
* 本控制器自身不标注 @OperationLog,避免记录日志查看行为。<br>
|
||||
*
|
||||
* @author chenx
|
||||
* @date 2026/7/28
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping(value = Config.API_PATH + "/oplog/")
|
||||
public class SysOperationLogController {
|
||||
|
||||
@Autowired
|
||||
private ISysOperationLogService sysOperationLogService;
|
||||
|
||||
/**
|
||||
* 分页查询操作日志列表(不含响应结果)。
|
||||
*/
|
||||
@GetMapping("/List")
|
||||
public BasePageResult<SysOperationLogListVo> list(SysOperationLogQuery query, BasePageQuery pageQuery) {
|
||||
return sysOperationLogService.pageQuery(query, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载某条日志的响应结果(GZIP 解压后原文,文本文件)。<br>
|
||||
* 采用与项目 DownloadFileUtil 一致的 octet-stream + attachment 写法,确保浏览器稳定触发下载。<br>
|
||||
* 若该日志未记录响应数据,返回一段说明文本文件而非 404,便于排查。
|
||||
*/
|
||||
@GetMapping("/Download/{id}")
|
||||
public ResponseEntity<InputStreamResource> download(@PathVariable Long id) {
|
||||
String data = sysOperationLogService.getResponseData(id);
|
||||
if (data == null) {
|
||||
data = "该条日志未记录响应数据(saveResponse=false、或接口响应为空、或响应序列化失败),无法下载原始内容。";
|
||||
}
|
||||
try {
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
headers.add(HttpHeaders.CONTENT_DISPOSITION,
|
||||
"attachment; filename=" + new String(("response-" + id + ".txt").getBytes("gbk"), "iso8859-1"));
|
||||
headers.add("Pragma", "no-cache");
|
||||
headers.add("Expires", "0");
|
||||
return ResponseEntity.ok().headers(headers)
|
||||
.contentType(MediaType.parseMediaType("application/octet-stream"))
|
||||
.body(new InputStreamResource(bis));
|
||||
} catch (Exception e) {
|
||||
log.error("操作日志响应结果下载失败, id={}", id, e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.yunhai.userbase.controller.api;
|
||||
|
||||
import com.yunhai.userbase.common.annotation.OperationLog;
|
||||
import com.yunhai.userbase.common.validation.ValidationGroups;
|
||||
import com.yunhai.userbase.config.Config;
|
||||
import com.yunhai.userbase.model.BasePageQuery;
|
||||
import com.yunhai.userbase.model.BasePageResult;
|
||||
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.service.ISysUserService;
|
||||
import com.yunhai.userbase.utils.PageUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* @author chenx
|
||||
* @date 2026/7/20 16:14
|
||||
* @description 组用户管理
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(value = Config.API_PATH + "/sysuser/")
|
||||
public class SysUserController {
|
||||
|
||||
@Autowired
|
||||
private ISysUserService sysUserService;
|
||||
|
||||
@OperationLog("新增用户")
|
||||
@PostMapping("Add")
|
||||
public void add(@RequestBody @Validated(ValidationGroups.Create.class) SysUserRequest request) {
|
||||
sysUserService.add(request);
|
||||
}
|
||||
|
||||
@OperationLog("编辑用户")
|
||||
@PostMapping("Edit/{id}")
|
||||
public void edit(@PathVariable Integer id, @RequestBody @Validated(ValidationGroups.Update.class) SysUserRequest request) {
|
||||
sysUserService.edit(id, request);
|
||||
}
|
||||
|
||||
@OperationLog("删除用户")
|
||||
@PostMapping("Delete/{id}")
|
||||
public void delete(@PathVariable Integer id) {
|
||||
sysUserService.delete(id);
|
||||
}
|
||||
|
||||
/** 禁用用户(disabled=true) */
|
||||
@OperationLog("禁用用户")
|
||||
@PostMapping("Disable/{id}")
|
||||
public void disable(@PathVariable Integer id) {
|
||||
sysUserService.changeStatus(id, true);
|
||||
}
|
||||
|
||||
/** 启用用户(disabled=false) */
|
||||
@OperationLog("启用用户")
|
||||
@PostMapping("Enable/{id}")
|
||||
public void enable(@PathVariable Integer id) {
|
||||
sysUserService.changeStatus(id, false);
|
||||
}
|
||||
|
||||
@GetMapping("/List")
|
||||
public BasePageResult<SysUserVo> list(SysUserQuery query, BasePageQuery pageQuery,
|
||||
@SortDefault(sort = "id", direction = Sort.Direction.DESC) Sort sort) {
|
||||
return PageUtils.toResult(sysUserService.list(query, PageUtils.toPageable(pageQuery, sort)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.yunhai.userbase.controller.oapi;
|
||||
|
||||
import com.yunhai.platform.utils.model.BoaUser;
|
||||
import com.yunhai.userbase.service.UserCenterService;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 用户中心开放接口:供其他业务服务经 yh-app-auth 签名调用({@code /open/api/user/...})。<br><br>
|
||||
* 路径与基础平台 {@code DefaultUserCenterClient} 硬编码的契约严格一致:
|
||||
* {@code GET /open/api/user/byId?id=}、{@code /byLoginName?loginName=}、{@code /byPhone?phone=}。<br>
|
||||
* 接口本身不做验签——签名由 {@code OpenApiAuthEnforceConfiguration} 在过滤器层强制要求,
|
||||
* 未带 {@code X-App-Key/X-Timestamp/X-Signature} 的请求直接 401。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/open/api/user/")
|
||||
public class OpenBaseController {
|
||||
|
||||
@Autowired
|
||||
private UserCenterService userCenterService;
|
||||
|
||||
@GetMapping("byId")
|
||||
public BoaUser byId(@RequestParam("id") Integer id) {
|
||||
return userCenterService.getUserInfoWithDeptById(id);
|
||||
}
|
||||
|
||||
@GetMapping("byLoginName")
|
||||
public BoaUser byLoginName(@RequestParam("loginName") String loginName) {
|
||||
if (loginName == null || loginName.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return userCenterService.getUserInfoWithDeptByAccount(loginName);
|
||||
}
|
||||
|
||||
@GetMapping("byPhone")
|
||||
public BoaUser byPhone(@RequestParam("phone") String phone) {
|
||||
if (phone == null || phone.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return userCenterService.getUserInfoWithDeptByPhone(phone);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.yunhai.userbase.controller.oapi;
|
||||
|
||||
import com.yunhai.platform.appauth.AppAuthProperties;
|
||||
import com.yunhai.userbase.config.Config;
|
||||
import com.yunhai.userbase.entity.ApplicationInfo;
|
||||
import com.yunhai.userbase.repository.ApplicationInfoRepository;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 验签兜底端点:供其他业务服务在「共享 Redis 宕机且本地缓存也未命中」时直连获取某应用的密钥。<br><br>
|
||||
* 直接从 MySQL 读(绕过可能同宕的共享 Redis),是密钥的权威源;写入侧仍由
|
||||
* {@code SignKeyCacheService} 同步到 Redis。返回形状与 BasePlatform 读侧解析契约一致:<br>
|
||||
* {@code {"appKey":"100616","secret":"...","enabled":true}}。<br><br>
|
||||
* 解析优先级:<br>
|
||||
* 1) 本库 application_info(覆盖全部「已颁发」应用的实时密钥);<br>
|
||||
* 2) 配置式受信应用 {@code yh.app-auth.trusted-apps}(平台自身默认密钥,如 应用中心 100616,
|
||||
* 即便尚未落库也可解析,避免「默认 appKey 未建行」时此处返回 404 导致验签链路失败);<br>
|
||||
* 两者皆无 → 404。<br><br>
|
||||
* 注意:此端点返回的是应用密钥,必须仅内网可达(由部署网络隔离保障),切勿暴露到公网。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(Config.OPEN_API_PATH + "internal")
|
||||
public class SignKeyInternalController {
|
||||
|
||||
private final ApplicationInfoRepository applicationInfoRepository;
|
||||
private final AppAuthProperties appAuthProperties;
|
||||
|
||||
public SignKeyInternalController(ApplicationInfoRepository applicationInfoRepository,
|
||||
AppAuthProperties appAuthProperties) {
|
||||
this.applicationInfoRepository = applicationInfoRepository;
|
||||
this.appAuthProperties = appAuthProperties;
|
||||
}
|
||||
|
||||
@GetMapping("sign-key/{appId}")
|
||||
public ResponseEntity<Map<String, Object>> signKey(@PathVariable String appId) {
|
||||
Integer key;
|
||||
try {
|
||||
key = Integer.parseInt(appId.trim());
|
||||
} catch (NumberFormatException e) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
ApplicationInfo info = applicationInfoRepository.findByAppKey(key);
|
||||
if (info != null && info.getAppSecret() != null && !info.getAppSecret().trim().isEmpty()) {
|
||||
return ok(info.getAppKey() == null ? appId : String.valueOf(info.getAppKey()),
|
||||
info.getAppSecret());
|
||||
}
|
||||
// 库内无此应用密钥:回退到配置式受信应用(平台自身默认密钥 100616 等),
|
||||
// 避免「应用中心默认 appKey 未落库」时兜底查询返回 404。
|
||||
String cfgSecret = appAuthProperties.getTrustedApps().get(appId);
|
||||
if (cfgSecret != null && !cfgSecret.trim().isEmpty()) {
|
||||
return ok(appId, cfgSecret);
|
||||
}
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
private static ResponseEntity<Map<String, Object>> ok(String appKey, String secret) {
|
||||
Map<String, Object> body = new LinkedHashMap<>();
|
||||
body.put("appKey", appKey);
|
||||
body.put("secret", secret);
|
||||
body.put("enabled", true);
|
||||
return ResponseEntity.ok(body);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user