@@ -0,0 +1,12 @@
|
||||
package com.yunhai.userhome;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class UserHomeApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(UserHomeApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.yunhai.userhome.common;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* JPA 实体基类(与 UserBase/BasePlatform 约定一致):自增主键 + 创建/更新时间。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
|
||||
@MappedSuperclass
|
||||
public abstract class BaseEntity implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@EqualsAndHashCode.Include
|
||||
@Column(name = "id")
|
||||
private Integer id;
|
||||
|
||||
@Column(name = "create_time")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Column(name = "update_time")
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.yunhai.userhome.config;
|
||||
|
||||
/**
|
||||
* 路径前缀约定(与 UserBase 保持一致):
|
||||
* {@code /{platform}} 普通接口(CAS 登录态),{@code /oapi/} 开放接口(yh-app-auth 签名)。
|
||||
*/
|
||||
public enum Config {
|
||||
Inst;
|
||||
|
||||
// 末尾不要带斜杠:Boot 2.6+ 默认 PathPatternParser 不合并连续斜杠,会导致 404。
|
||||
public static final String API_PATH = "/{platform}";
|
||||
public static final String OPEN_API_PATH = "/oapi/";
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package com.yunhai.userhome.controller.api;
|
||||
|
||||
import com.yunhai.platform.utils.model.BoaApp;
|
||||
import com.yunhai.platform.utils.model.BoaAppType;
|
||||
import com.yunhai.platform.utils.model.BoaUser;
|
||||
import com.yunhai.userhome.config.Config;
|
||||
import com.yunhai.userhome.entity.UsedApp;
|
||||
import com.yunhai.userhome.model.SetType;
|
||||
import com.yunhai.userhome.service.AppRemoteService;
|
||||
import com.yunhai.userhome.service.UsedAppService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 常用应用接口(CAS 登录态,路径 {@code /{platform}/usedapp/}),对齐旧 UserHome契约
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(value = Config.API_PATH + "/usedapp/")
|
||||
public class UsedAppController {
|
||||
|
||||
@Autowired
|
||||
private UsedAppService usedAppService;
|
||||
|
||||
@Autowired
|
||||
private AppRemoteService appRemoteService;
|
||||
|
||||
/**
|
||||
* 取当前角色生效的常用应用(BoaApp 详情,按 appIds 顺序)。
|
||||
*
|
||||
* @param roleType 角色类型
|
||||
* @param schId 学校ID(0 时按用户主学校/主归属组推断)
|
||||
* @param isSch 是否学校级(超管设置学校常用时 true,isSch 时 uid=schId)
|
||||
*/
|
||||
@GetMapping("getUsedApp")
|
||||
public List<BoaApp> getUsedApp(BoaUser authUserWithDept, Integer roleType, Integer schId,
|
||||
@RequestParam(name = "isSch", defaultValue = "false") Boolean isSch) {
|
||||
if (schId == null || schId == 0) {
|
||||
schId = resolveSchId(authUserWithDept);
|
||||
}
|
||||
Integer uid = authUserWithDept.getId();
|
||||
if (authUserWithDept.getType() != null && authUserWithDept.getType() == 1) {
|
||||
isSch = false; // 超管设置学校常用时走用户维度
|
||||
}
|
||||
if (isSch) {
|
||||
uid = schId;
|
||||
}
|
||||
return usedAppService.getUsedAppList(schId, uid, roleType, authUserWithDept);
|
||||
}
|
||||
|
||||
/**
|
||||
* 覆盖式保存常用应用(≤8 由前端控制)。
|
||||
* 参数:appIds(逗号分隔)、type(0系统/1学校/2用户)、roleType、schId;超管强制 type=0。
|
||||
*/
|
||||
@PostMapping("saveUsedApp")
|
||||
public void saveUsedApp(BoaUser authUserWithDept, @RequestBody Map<String, Object> params) {
|
||||
String appIds = params.get("appIds") == null ? "" : String.valueOf(params.get("appIds"));
|
||||
Integer type = params.get("type") == null ? SetType.USER.id()
|
||||
: Integer.valueOf(String.valueOf(params.get("type")));
|
||||
Integer roleType = params.get("roleType") == null ? -1
|
||||
: Integer.valueOf(String.valueOf(params.get("roleType")));
|
||||
Integer schId = params.get("schId") == null ? 0
|
||||
: Integer.valueOf(String.valueOf(params.get("schId")));
|
||||
if (schId == 0) {
|
||||
schId = resolveSchId(authUserWithDept);
|
||||
}
|
||||
|
||||
UsedApp usedApp = new UsedApp();
|
||||
usedApp.setAppIds(appIds);
|
||||
usedApp.setType(SetType.byId(type));
|
||||
usedApp.setRoleType(roleType);
|
||||
usedApp.setCDate(new Date());
|
||||
usedApp.setCId(authUserWithDept.getId());
|
||||
usedApp.setSchId(schId);
|
||||
usedApp.setSetId(usedApp.getType() == SetType.SCHOOL ? schId : authUserWithDept.getId());
|
||||
if (authUserWithDept.getType() != null && authUserWithDept.getType() == 1) {
|
||||
// 超管保存 → 系统级
|
||||
usedApp.setType(SetType.BASE);
|
||||
usedApp.setSetId(authUserWithDept.getId());
|
||||
}
|
||||
usedAppService.save(usedApp);
|
||||
}
|
||||
|
||||
/** 用户级常用(type=USER, roleType=-1)精简映射(id/name/icon/url)。 */
|
||||
@GetMapping("getUsedAppById")
|
||||
public List<Map<String, Object>> getUsedAppById(BoaUser authUserWithDept) {
|
||||
return usedAppService.getUsedAppMapList(authUserWithDept, authUserWithDept.getId());
|
||||
}
|
||||
|
||||
/** 用户级保存常用(type=USER, roleType=-1)。 */
|
||||
@PostMapping("saveUsedAppById")
|
||||
public void saveUsedAppById(BoaUser authUserWithDept, @RequestBody Map<String, Object> params) {
|
||||
String appIds = params.get("appIds") == null ? "" : String.valueOf(params.get("appIds"));
|
||||
UsedApp usedApp = new UsedApp();
|
||||
usedApp.setAppIds(appIds);
|
||||
usedApp.setType(SetType.USER);
|
||||
usedApp.setRoleType(-1);
|
||||
usedApp.setCDate(new Date());
|
||||
usedApp.setCId(authUserWithDept.getId());
|
||||
usedApp.setSetId(authUserWithDept.getId());
|
||||
usedAppService.saveUserId(usedApp);
|
||||
}
|
||||
|
||||
/** 按 appIds(逗号分隔)取应用精简映射(id/name/icon/url),在全集中查找。 */
|
||||
@GetMapping("getAppByIds")
|
||||
public List<Map<String, Object>> getAppByIds(BoaUser authUserWithDept, String appIds) {
|
||||
List<Map<String, Object>> list = new ArrayList<>();
|
||||
if (appIds == null || appIds.trim().isEmpty()) {
|
||||
return list;
|
||||
}
|
||||
List<BoaApp> appList = appRemoteService.getUserApps(authUserWithDept).stream()
|
||||
.filter(t -> t.getApps() != null)
|
||||
.flatMap(t -> t.getApps().stream())
|
||||
.collect(Collectors.toList());
|
||||
for (String appId : appIds.split(",")) {
|
||||
try {
|
||||
Integer id = Integer.parseInt(appId.trim());
|
||||
appList.stream().filter(a -> id.equals(a.getId())).findFirst().ifPresent(app -> {
|
||||
Map<String, Object> m = new HashMap<>();
|
||||
m.put("id", app.getId());
|
||||
m.put("name", app.getName());
|
||||
m.put("icon", app.getIcon());
|
||||
m.put("url", app.getWebAddr());
|
||||
list.add(m);
|
||||
});
|
||||
} catch (NumberFormatException ignore) {
|
||||
// 脏数据容错
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/** schId=0 时推断:主学校 → 主归属组(bureau)→ 0。 */
|
||||
private Integer resolveSchId(BoaUser user) {
|
||||
if (user.getSchool() != null) {
|
||||
return user.getSchool().getId();
|
||||
}
|
||||
if (user.getBureau() != null) {
|
||||
return user.getBureau().getId();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.yunhai.userhome.controller.api;
|
||||
|
||||
import com.yunhai.platform.apistd.BizException;
|
||||
import com.yunhai.platform.openapi.client.YschBaseOpenApi;
|
||||
import com.yunhai.platform.utils.exception.ErrorCode;
|
||||
import com.yunhai.platform.utils.model.BoaApp;
|
||||
import com.yunhai.platform.utils.model.BoaAppType;
|
||||
import com.yunhai.platform.utils.model.BoaRoleInfo;
|
||||
import com.yunhai.platform.utils.model.BoaUser;
|
||||
import com.yunhai.userhome.config.Config;
|
||||
import com.yunhai.userhome.entity.UserCurrentRole;
|
||||
import com.yunhai.userhome.service.AppRemoteService;
|
||||
import com.yunhai.userhome.service.IUserCurrentRoleService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 用户首页接口(CAS 登录态,路径 {@code /{platform}/user/})。
|
||||
* 控制器直接 return 对象,由 yh-api-std 包成 {@code {code:0,data}}。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(value = Config.API_PATH + "/user/")
|
||||
public class UserController {
|
||||
|
||||
@Autowired
|
||||
private AppRemoteService appRemoteService;
|
||||
|
||||
@Autowired
|
||||
private IUserCurrentRoleService userCurrentRoleService;
|
||||
|
||||
/** 当前登录用户信息(CAS 注入)。 */
|
||||
@GetMapping("GetUserInfo")
|
||||
public BoaUser getUserInfo(BoaUser authUserWithDept) {
|
||||
return authUserWithDept;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前用户可访问的应用全集(跨仓 UserBase 实时拉取,按应用类型分组)。
|
||||
* 前端据此:渲染分类 tab(含「全部」)+ 图标网格;常用区由 getUsedApp 独立提供。
|
||||
*
|
||||
* @param schId 兼容旧前端入参(yh-platform getUserApp 当前按 userId 全局权限过滤,schId 暂不参与)
|
||||
* @param userId 指定用户(缺省用登录用户)
|
||||
*/
|
||||
@GetMapping("GetUserApp")
|
||||
public List<BoaAppType> getUserApp(BoaUser authUserWithDept, Integer schId,
|
||||
@RequestParam(name = "userId", required = false) Integer userId) {
|
||||
return appRemoteService.getUserApps(authUserWithDept);
|
||||
}
|
||||
|
||||
/** 按应用ID取单个应用(在已拉取全集中查找,免额外跨仓请求)。 */
|
||||
@GetMapping("GetAppInfoById")
|
||||
public BoaApp getAppInfoById(BoaUser authUserWithDept, @RequestParam(name = "id", required = false) Integer id) {
|
||||
return appRemoteService.getAppById(authUserWithDept, id).orElse(null);
|
||||
}
|
||||
|
||||
/** 当前用户角色身份列表(跨仓 UserBase,含 学校/角色/子节点)。 */
|
||||
@GetMapping("getUserRole")
|
||||
public List<BoaRoleInfo> getUserRole(BoaUser authUserWithDept) {
|
||||
return YschBaseOpenApi.getUserRoleList(authUserWithDept.getId());
|
||||
}
|
||||
|
||||
/** 当前用户激活的角色(本地 user_current_role,可能为空)。 */
|
||||
@GetMapping("findUserRole")
|
||||
public UserCurrentRole findUserRole(BoaUser authUserWithDept) {
|
||||
return userCurrentRoleService.findById(authUserWithDept.getId()).orElse(null);
|
||||
}
|
||||
|
||||
/** 保存当前激活角色(校验所选 学校+角色 属于用户角色列表,防越权)。 */
|
||||
@PostMapping("savUserRole")
|
||||
public void savUserRole(BoaUser authUserWithDept, @RequestBody UserCurrentRole roleInfo) {
|
||||
List<BoaRoleInfo> roleList = YschBaseOpenApi.getUserRoleList(authUserWithDept.getId());
|
||||
boolean exists = roleList.stream().anyMatch(r ->
|
||||
r.getRoleId() != null && r.getRoleId().equals(roleInfo.getRoleId())
|
||||
&& r.getSchId() != null && r.getSchId().equals(roleInfo.getSchId()));
|
||||
if (!exists) {
|
||||
throw new BizException(ErrorCode.FORBIDDEN, "非法操作,没有权限");
|
||||
}
|
||||
roleInfo.setUserId(authUserWithDept.getId());
|
||||
userCurrentRoleService.save(roleInfo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.yunhai.userhome.entity;
|
||||
|
||||
import com.yunhai.userhome.common.BaseEntity;
|
||||
import com.yunhai.userhome.model.SetType;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.Table;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 用户「常用应用」设置(本地表)。
|
||||
*
|
||||
* <p>对齐旧 UserHome used_app 结构:<b>一行存一组常用</b>(appIds 逗号分隔),
|
||||
* 按 {@code setType(0系统/1学校/2用户) + setId + roleType + schId} 四维唯一覆盖保存。
|
||||
* 应用元数据不落本地,跨仓来自 UserBase。</p>
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@Entity
|
||||
@Table(
|
||||
name = "used_app",
|
||||
indexes = {
|
||||
@Index(name = "idx_used_app_set", columnList = "set_id,set_type,role_type,sch_id")
|
||||
}
|
||||
)
|
||||
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
|
||||
public class UsedApp extends BaseEntity {
|
||||
|
||||
/** 常用应用ID,逗号分隔字符串(按展示顺序)。 */
|
||||
@Column(name = "app_ids", length = 500)
|
||||
private String appIds;
|
||||
|
||||
/** 设置级别:0=系统 1=学校 2=用户。 */
|
||||
@Enumerated(EnumType.ORDINAL)
|
||||
@Column(name = "set_type")
|
||||
private SetType type;
|
||||
|
||||
/** 归属维度ID:系统=超管用户ID、学校=schId、用户=用户ID。 */
|
||||
@Column(name = "set_id")
|
||||
private Integer setId;
|
||||
|
||||
/** 学校ID(逻辑外键 → UserBase sys_group.id,schId=0 表示非学校群组)。 */
|
||||
@Column(name = "sch_id")
|
||||
private Integer schId;
|
||||
|
||||
/** 角色类型(-1 表示用户级不区分角色)。 */
|
||||
@Column(name = "role_type")
|
||||
private Integer roleType;
|
||||
|
||||
/** 操作人ID(逻辑外键 → UserBase 用户)。 */
|
||||
@Column(name = "c_id")
|
||||
private Integer cId;
|
||||
|
||||
/** 操作时间。 */
|
||||
@Column(name = "c_date")
|
||||
private Date cDate;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.yunhai.userhome.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.UniqueConstraint;
|
||||
|
||||
/**
|
||||
* 用户当前激活角色(会话态,本地表 user_current_role)。
|
||||
* 对应旧 UserHome 同名单表:一用户一行,记录当前角色 + 学校。
|
||||
*/
|
||||
@Data
|
||||
@Entity
|
||||
@Table(
|
||||
name = "user_current_role",
|
||||
uniqueConstraints = @UniqueConstraint(columnNames = {"userId"}),
|
||||
indexes = {@Index(columnList = "userId")}
|
||||
)
|
||||
public class UserCurrentRole {
|
||||
|
||||
@Id
|
||||
@JsonIgnore
|
||||
private Integer userId;
|
||||
|
||||
private Integer roleId;
|
||||
|
||||
private String roleName;
|
||||
|
||||
private Integer schId;
|
||||
|
||||
private String schName;
|
||||
|
||||
private Integer childId;
|
||||
|
||||
private String childName;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.yunhai.userhome.model;
|
||||
|
||||
/**
|
||||
* 常用应用设置级别(对齐旧 UserHome 约定):
|
||||
* 0=系统(超管默认)、1=学校(按学校+角色)、2=用户(按用户+角色)。
|
||||
*/
|
||||
public enum SetType {
|
||||
|
||||
BASE(0, "系统"),
|
||||
SCHOOL(1, "学校"),
|
||||
USER(2, "用户");
|
||||
|
||||
private final int mId;
|
||||
private final String mName;
|
||||
|
||||
SetType(int id, String name) {
|
||||
this.mId = id;
|
||||
this.mName = name;
|
||||
}
|
||||
|
||||
public int id() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
public static SetType byId(int id) {
|
||||
for (SetType t : values()) {
|
||||
if (t.mId == id) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.yunhai.userhome.repository;
|
||||
|
||||
import com.yunhai.userhome.entity.UsedApp;
|
||||
import com.yunhai.userhome.model.SetType;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface UsedAppRepository extends JpaRepository<UsedApp, Integer>, JpaSpecificationExecutor<UsedApp> {
|
||||
|
||||
/** 按「归属+级别+角色+学校」四维查唯一记录(覆盖保存前判重)。 */
|
||||
Optional<UsedApp> findFirstBySetIdAndTypeAndRoleTypeAndSchId(Integer setId, SetType type, Integer roleType, Integer schId);
|
||||
|
||||
/** 按「归属+级别+角色」查(用户级保存,schId 不参与)。 */
|
||||
Optional<UsedApp> findFirstBySetIdAndTypeAndRoleType(Integer setId, SetType type, Integer roleType);
|
||||
|
||||
/** 兼容旧查询(供 Specification 之外的简单场景)。 */
|
||||
List<UsedApp> findBySetIdAndTypeAndRoleTypeAndSchId(Integer setId, SetType type, Integer roleType, Integer schId);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.yunhai.userhome.repository;
|
||||
|
||||
import com.yunhai.userhome.entity.UserCurrentRole;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface UserCurrentRoleRepository extends JpaRepository<UserCurrentRole, Integer> {
|
||||
|
||||
Optional<UserCurrentRole> findByUserId(Integer userId);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.yunhai.userhome.service;
|
||||
|
||||
import com.yunhai.platform.utils.model.BoaApp;
|
||||
import com.yunhai.platform.utils.model.BoaAppType;
|
||||
import com.yunhai.platform.utils.model.BoaUser;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 远程「用户可访问应用」数据服务(跨仓 UserBase)。
|
||||
*
|
||||
* <p>实现 {@link com.yunhai.userhome.service.impl.OpenApiAppRemoteServiceImpl}
|
||||
* 经 {@code YschBaseOpenApi.getUserApp} 实时拉取(自动带 yh-app-auth 签名),
|
||||
* 数据权威在 UserBase;本模块不冗余应用元数据。</p>
|
||||
*/
|
||||
public interface AppRemoteService {
|
||||
|
||||
/** 取当前用户可访问的应用全集(按应用类型分组,BoaAppType.id/name/apps)。 */
|
||||
List<BoaAppType> getUserApps(BoaUser user);
|
||||
|
||||
/** 按应用ID取单个应用(在已拉取的全集中查找,避免额外跨仓请求)。 */
|
||||
Optional<BoaApp> getAppById(BoaUser user, Integer appId);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.yunhai.userhome.service;
|
||||
|
||||
import com.yunhai.userhome.entity.UserCurrentRole;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 用户当前激活角色(user_current_role 表读写)。
|
||||
*/
|
||||
public interface IUserCurrentRoleService {
|
||||
|
||||
/** 取用户当前角色(可能不存在)。 */
|
||||
Optional<UserCurrentRole> findById(Integer userId);
|
||||
|
||||
/** 保存/覆盖用户当前角色。 */
|
||||
void save(UserCurrentRole role);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.yunhai.userhome.service;
|
||||
|
||||
import com.yunhai.platform.utils.model.BoaApp;
|
||||
import com.yunhai.platform.utils.model.BoaUser;
|
||||
import com.yunhai.userhome.entity.UsedApp;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 本地「常用应用」服务(used_app 表读写,对齐旧 UserHome 四维结构)。
|
||||
*/
|
||||
public interface UsedAppService {
|
||||
|
||||
/** 覆盖式保存(同 setId+type+roleType+schId 唯一,存在则更新)。 */
|
||||
void save(UsedApp usedApp);
|
||||
|
||||
/**
|
||||
* 按「学校+用户+角色」取生效的常用设置(多级:用户 > 学校 > 系统;schId==uid 直接取学校级)。
|
||||
*
|
||||
* @param schId 学校ID(0 时按用户主学校推断后由调用方处理)
|
||||
* @param uid 用户ID(isSch 时为 schId)
|
||||
* @param roleType 角色类型(用户级 -1)
|
||||
*/
|
||||
Optional<UsedApp> findById(Integer schId, Integer uid, Integer roleType);
|
||||
|
||||
/** 取常用应用详情列表(BoaApp,按 appIds 顺序从可访问全集过滤)。 */
|
||||
List<BoaApp> getUsedAppList(Integer schId, Integer uid, Integer roleType, BoaUser user);
|
||||
|
||||
/** 用户级保存(type=USER, roleType=-1)。 */
|
||||
void saveUserId(UsedApp usedApp);
|
||||
|
||||
/** 用户级常用(type=USER, roleType=-1)。 */
|
||||
Optional<UsedApp> findByUserId(Integer uid);
|
||||
|
||||
/** 常用应用精简映射(id/name/icon/url),用于 getUsedAppById / getAppByIds。 */
|
||||
List<Map<String, Object>> getUsedAppMapList(BoaUser user, Integer uid);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.yunhai.userhome.service.impl;
|
||||
|
||||
import com.yunhai.platform.openapi.client.YschBaseOpenApi;
|
||||
import com.yunhai.platform.utils.enums.ClientEnum;
|
||||
import com.yunhai.platform.utils.model.BoaApp;
|
||||
import com.yunhai.platform.utils.model.BoaAppType;
|
||||
import com.yunhai.platform.utils.model.BoaUser;
|
||||
import com.yunhai.userhome.service.AppRemoteService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* {@link AppRemoteService} 真实实现:经 yh-platform {@code YschBaseOpenApi} 调 UserBase
|
||||
* {@code GET /oapi/app/list?userId=&clientType=}(自动带 app-auth 签名)。
|
||||
*/
|
||||
@Service
|
||||
public class OpenApiAppRemoteServiceImpl implements AppRemoteService {
|
||||
|
||||
@Override
|
||||
public List<BoaAppType> getUserApps(BoaUser user) {
|
||||
if (user == null || user.getId() == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// PC 浏览器端;UserBase 侧已按 userId 权限过滤(组开通 ∪ 角色模块所属应用,超管全量)
|
||||
return YschBaseOpenApi.getUserApp(user.getId(), ClientEnum.PC_BROWSER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<BoaApp> getAppById(BoaUser user, Integer appId) {
|
||||
if (appId == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return getUserApps(user).stream()
|
||||
.filter(t -> t.getApps() != null)
|
||||
.flatMap(t -> t.getApps().stream())
|
||||
.filter(a -> appId.equals(a.getId()))
|
||||
.findFirst();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package com.yunhai.userhome.service.impl;
|
||||
|
||||
import com.yunhai.platform.utils.model.BoaApp;
|
||||
import com.yunhai.platform.utils.model.BoaAppType;
|
||||
import com.yunhai.platform.utils.model.BoaUser;
|
||||
import com.yunhai.userhome.entity.UsedApp;
|
||||
import com.yunhai.userhome.model.SetType;
|
||||
import com.yunhai.userhome.repository.UsedAppRepository;
|
||||
import com.yunhai.userhome.service.AppRemoteService;
|
||||
import com.yunhai.userhome.service.UsedAppService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class UsedAppServiceImpl implements UsedAppService {
|
||||
|
||||
@Autowired
|
||||
private UsedAppRepository usedAppRepository;
|
||||
|
||||
@Autowired
|
||||
private AppRemoteService appRemoteService;
|
||||
|
||||
@Override
|
||||
public void save(UsedApp usedApp) {
|
||||
Optional<UsedApp> model = usedAppRepository
|
||||
.findFirstBySetIdAndTypeAndRoleTypeAndSchId(usedApp.getSetId(), usedApp.getType(),
|
||||
usedApp.getRoleType(), usedApp.getSchId());
|
||||
if (model.isPresent()) {
|
||||
usedApp.setId(model.get().getId());
|
||||
}
|
||||
usedAppRepository.save(usedApp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<UsedApp> findById(Integer schId, Integer uid, Integer roleType) {
|
||||
List<UsedApp> list = usedAppRepository.findAll(select(schId, uid, roleType));
|
||||
Optional<UsedApp> baseMod = list.stream()
|
||||
.filter(x -> x.getType() == SetType.BASE)
|
||||
.sorted(Comparator.comparing(UsedApp::getCDate).reversed())
|
||||
.findFirst();
|
||||
Optional<UsedApp> schMod = list.stream()
|
||||
.filter(x -> x.getType() == SetType.SCHOOL)
|
||||
.sorted(Comparator.comparing(UsedApp::getCDate).reversed())
|
||||
.findFirst();
|
||||
Optional<UsedApp> userMod = list.stream()
|
||||
.filter(x -> x.getType() == SetType.USER)
|
||||
.sorted(Comparator.comparing(UsedApp::getCDate).reversed())
|
||||
.findFirst();
|
||||
if (schId != null && schId.equals(uid)) {
|
||||
return schMod;
|
||||
}
|
||||
// 优先级:用户 > 学校 > 系统
|
||||
if (userMod.isPresent()) {
|
||||
return userMod;
|
||||
}
|
||||
if (schMod.isPresent()) {
|
||||
return schMod;
|
||||
}
|
||||
return baseMod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BoaApp> getUsedAppList(Integer schId, Integer uid, Integer roleType, BoaUser user) {
|
||||
List<String> appIdList = new ArrayList<>();
|
||||
Optional<UsedApp> data = findById(schId, uid, roleType);
|
||||
if (data.isPresent() && data.get().getAppIds() != null) {
|
||||
String usedStr = data.get().getAppIds();
|
||||
if (!usedStr.trim().isEmpty()) {
|
||||
appIdList = Arrays.asList(usedStr.split(","));
|
||||
}
|
||||
}
|
||||
if (appIdList.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<BoaApp> appList = flattenApps(appRemoteService.getUserApps(user));
|
||||
Map<Integer, BoaApp> appMap = appList.stream()
|
||||
.collect(Collectors.toMap(BoaApp::getId, a -> a, (a, b) -> a));
|
||||
List<BoaApp> result = new ArrayList<>();
|
||||
for (String appId : appIdList) {
|
||||
try {
|
||||
BoaApp app = appMap.get(Integer.parseInt(appId.trim()));
|
||||
if (app != null) {
|
||||
result.add(app);
|
||||
}
|
||||
} catch (NumberFormatException ignore) {
|
||||
// 脏数据容错
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveUserId(UsedApp usedApp) {
|
||||
Optional<UsedApp> model = usedAppRepository
|
||||
.findFirstBySetIdAndTypeAndRoleType(usedApp.getSetId(), SetType.USER, -1);
|
||||
if (model.isPresent()) {
|
||||
usedApp.setId(model.get().getId());
|
||||
}
|
||||
usedAppRepository.save(usedApp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<UsedApp> findByUserId(Integer uid) {
|
||||
return usedAppRepository.findFirstBySetIdAndTypeAndRoleType(uid, SetType.USER, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> getUsedAppMapList(BoaUser user, Integer uid) {
|
||||
List<Map<String, Object>> list = new ArrayList<>();
|
||||
Optional<UsedApp> data = findByUserId(uid);
|
||||
if (!data.isPresent() || data.get().getAppIds() == null) {
|
||||
return list;
|
||||
}
|
||||
List<String> appIds = Arrays.asList(data.get().getAppIds().split(","));
|
||||
List<BoaApp> appList = flattenApps(appRemoteService.getUserApps(user));
|
||||
Map<Integer, BoaApp> appMap = appList.stream()
|
||||
.collect(Collectors.toMap(BoaApp::getId, a -> a, (a, b) -> a));
|
||||
for (String appId : appIds) {
|
||||
try {
|
||||
BoaApp app = appMap.get(Integer.parseInt(appId.trim()));
|
||||
if (app != null) {
|
||||
Map<String, Object> m = new HashMap<>();
|
||||
m.put("id", app.getId());
|
||||
m.put("name", app.getName());
|
||||
m.put("icon", app.getIcon());
|
||||
m.put("url", app.getWebAddr());
|
||||
list.add(m);
|
||||
}
|
||||
} catch (NumberFormatException ignore) {
|
||||
// 脏数据容错
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/** BoaAppType 分组平铺为 BoaApp 列表。 */
|
||||
private List<BoaApp> flattenApps(List<BoaAppType> types) {
|
||||
List<BoaApp> apps = new ArrayList<>();
|
||||
if (types == null) {
|
||||
return apps;
|
||||
}
|
||||
for (BoaAppType type : types) {
|
||||
if (type.getApps() != null) {
|
||||
apps.addAll(type.getApps());
|
||||
}
|
||||
}
|
||||
return apps;
|
||||
}
|
||||
|
||||
/** 一级常用生效范围:BASE(按角色) OR SCHOOL(setId=schId+角色) OR USER(setId=uid+角色+schId)。 */
|
||||
private Specification<UsedApp> select(Integer schId, Integer uid, Integer roleType) {
|
||||
return (Root<UsedApp> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> {
|
||||
Predicate basePre = cb.and(
|
||||
cb.equal(root.get("type"), SetType.BASE),
|
||||
cb.equal(root.get("roleType"), roleType));
|
||||
Predicate schPer = cb.and(
|
||||
cb.equal(root.get("setId"), schId),
|
||||
cb.equal(root.get("type"), SetType.SCHOOL),
|
||||
cb.equal(root.get("roleType"), roleType));
|
||||
Predicate userPer = cb.and(
|
||||
cb.equal(root.get("setId"), uid),
|
||||
cb.equal(root.get("type"), SetType.USER),
|
||||
cb.equal(root.get("roleType"), roleType),
|
||||
cb.equal(root.get("schId"), schId));
|
||||
return cb.or(basePre, schPer, userPer);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.yunhai.userhome.service.impl;
|
||||
|
||||
import com.yunhai.userhome.entity.UserCurrentRole;
|
||||
import com.yunhai.userhome.repository.UserCurrentRoleRepository;
|
||||
import com.yunhai.userhome.service.IUserCurrentRoleService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class UserCurrentRoleServiceImpl implements IUserCurrentRoleService {
|
||||
|
||||
@Autowired
|
||||
private UserCurrentRoleRepository repository;
|
||||
|
||||
@Override
|
||||
public Optional<UserCurrentRole> findById(Integer userId) {
|
||||
return repository.findByUserId(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(UserCurrentRole role) {
|
||||
repository.save(role);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
server:
|
||||
port: 8070
|
||||
servlet:
|
||||
context-path: /
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: userhome
|
||||
# 关闭 Spring Cloud 对 nacos 配置中心的强制 spring.config.import 校验:
|
||||
# 本项目用 spring-cloud-starter-bootstrap + bootstrap.yml 的老机制加载 nacos(enabled 控制开关),
|
||||
# 不依赖 2.4+ 的 configdata import 机制——否则 classpath 含 nacos-config 会强制要求声明 spring.config.import,
|
||||
# 且该 import 在本地 bootstrap.yml 尚未加载时因 server-addr 为空而硬连失败。
|
||||
# 本地 enabled=false 纯走 application.yml;CI 注入 NACOS_CONFIG_ENABLED=true 走 bootstrap.yml 老路径拉配置。
|
||||
cloud:
|
||||
nacos:
|
||||
config:
|
||||
import-check:
|
||||
enabled: false
|
||||
datasource:
|
||||
url: jdbc:mysql://localhost:3306/bsch_userhome?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||
username: root
|
||||
password: 123456
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: update
|
||||
show-sql: false
|
||||
open-in-view: false
|
||||
properties:
|
||||
hibernate:
|
||||
jdbc:
|
||||
time_zone: Asia/Shanghai
|
||||
redis:
|
||||
host: localhost
|
||||
port: 6379
|
||||
database: 0
|
||||
|
||||
# ===== 基础平台 yh 配置(遵循 yh-* spring-boot-starter 约定,具体 key 以对应 starter 版本为准)=====
|
||||
yh:
|
||||
auth:
|
||||
# CAS 单点登录(浏览器 SSO 重定向 / ticket 校验)
|
||||
cas:
|
||||
server-url-prefix: https://sso.yunhaixingtong.com/cas
|
||||
service: http://localhost:8070
|
||||
app-auth:
|
||||
# 应用间签名鉴权(UserHome 作为调用方访问 UserBase 跨仓接口时使用)
|
||||
enabled: true
|
||||
# UserHome 在 UserBase 注册得到的 appKey/appSecret(应用注册后填入)
|
||||
app-key: 100000
|
||||
app-secret: change-me-in-phase0
|
||||
open:
|
||||
# yh-openapi 开放接口客户端(前缀必须是 yh.open,OpenApiProperties 绑定;base-host + base-path 拼出
|
||||
# 完整跨仓基址,openapi 客户端再拼 /oapi/** 端点;app-key/app-secret 同时驱动应用间签名)
|
||||
enabled: true
|
||||
base-host: http://localhost:8060
|
||||
base-path: /userbase
|
||||
app-key: 100000
|
||||
app-secret: change-me-in-phase0
|
||||
|
||||
logging:
|
||||
level:
|
||||
com.yunhai.userhome: debug
|
||||
@@ -0,0 +1,45 @@
|
||||
spring:
|
||||
application:
|
||||
name: userhome
|
||||
# 本地默认 local(不连 nacos);CI 注入 SPRING_PROFILES_ACTIVE=prod
|
||||
profiles:
|
||||
active: ${SPRING_PROFILES_ACTIVE:local}
|
||||
cloud:
|
||||
nacos:
|
||||
config:
|
||||
# ⭐ 本地默认关闭 nacos(profile=local 时完全不连,无 403 噪音,纯走 application.yml)
|
||||
# CI 注入 NACOS_CONFIG_ENABLED=true 才启用,拉 prod namespace 配置覆盖 application.yml
|
||||
enabled: ${NACOS_CONFIG_ENABLED:false}
|
||||
# Nacos 配置中心地址;CI 注入 NACOS_ADDR=nacos:8848(容器内网别名)
|
||||
server-addr: ${NACOS_ADDR:nacos.yunhaixingtong.com:8848}
|
||||
# 默认指向 prod namespace UUID,避免本地误开时连到空的 dev 命名空间
|
||||
# CI 用 SPRING_CLOUD_NACOS_CONFIG_NAMESPACE 覆盖(注意:NACOS_NAMESPACE 这个 env 名不生效,必须用 SPRING_CLOUD_NACOS_CONFIG_NAMESPACE)
|
||||
namespace: ${SPRING_CLOUD_NACOS_CONFIG_NAMESPACE:6196f0de-a28d-4894-8f10-e2982b57c6bc}
|
||||
# 主配置 group:userhome.yaml 在 USERHOME group(与下方 shared-configs 的 DEFAULT_GROUP 不同组,各自指定)
|
||||
group: USERHOME
|
||||
# 配置文件格式;最终主配置 dataId = ${spring.application.name}.${file-extension} = userhome.yaml
|
||||
file-extension: yaml
|
||||
encode: UTF-8
|
||||
# nacos 不可达时不强制退出,回退到本地 application.yml 兜底
|
||||
fail-fast: false
|
||||
# 只读账号(仅读取配置权限);CI 注入 SPRING_CLOUD_NACOS_USERNAME/PASSWORD
|
||||
username: ${SPRING_CLOUD_NACOS_USERNAME:yh-reader}
|
||||
password: ${SPRING_CLOUD_NACOS_PASSWORD:123456}
|
||||
|
||||
# ============================================================
|
||||
# 共享配置加载顺序(决定优先级!:排在数组越靠后 = 优先级越高 = 覆盖前面的同 key)
|
||||
# 低 1) public 命名空间 application-default.yaml(跨环境公共)
|
||||
# 中 2) prod 命名空间 application-infra.yaml(仅生产:mysql/redis 等中间件连接)
|
||||
# 高 3) prod 命名空间 userhome.yaml(应用级主配置,覆盖前两个)
|
||||
# 最高 4) 启动命令 -D 参数 / 环境变量(覆盖一切)
|
||||
# ============================================================
|
||||
shared-configs:
|
||||
# ① public 命名空间 application-default.yaml
|
||||
- data-id: application-default.yaml
|
||||
group: DEFAULT_GROUP
|
||||
namespace: ""
|
||||
refresh: false
|
||||
# ② prod 命名空间 application-infra.yaml
|
||||
- data-id: application-infra.yaml
|
||||
group: DEFAULT_GROUP
|
||||
refresh: false
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<!-- 引入 Spring Boot 默认彩色日志变量 -->
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||
|
||||
<!-- 控制台:所有环境都有,且带颜色 -->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
|
||||
<charset>${CONSOLE_LOG_CHARSET}</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 文件日志:只有非 local 环境才启用 -->
|
||||
<springProfile name="!local">
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>/app/logs/app.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>/app/logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<maxHistory>30</maxHistory>
|
||||
<totalSizeCap>5GB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
</springProfile>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
<springProfile name="!local">
|
||||
<appender-ref ref="FILE" />
|
||||
</springProfile>
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -0,0 +1,39 @@
|
||||
-- ============================================================
|
||||
-- UserHome 初始化脚本(used_app 常用应用表 + user_current_role 当前角色表)
|
||||
-- 说明:JPA ddl-auto=update 会自动建表;本脚本用于生产环境显式初始化 / 回滚参考。
|
||||
-- 2026-08-20:used_app 结构升级对齐旧 UserHome(app_ids 一行 + set_type/set_id/sch_id/role_type 四维唯一)。
|
||||
-- 旧表若已有数据(user_id/app_id/sort_no 结构),请先备份后 DROP 重建,避免残留列。
|
||||
-- ============================================================
|
||||
|
||||
-- 常用应用设置表:一行存一组常用(appIds 逗号分隔),按 set_type+set_id+role_type+sch_id 覆盖保存
|
||||
CREATE TABLE IF NOT EXISTS `used_app` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`app_ids` VARCHAR(500) NULL COMMENT '常用应用ID,逗号分隔(按展示顺序)',
|
||||
`set_type` INT NULL COMMENT '设置级别:0=系统 1=学校 2=用户',
|
||||
`set_id` INT NULL COMMENT '归属维度ID:系统=超管用户ID、学校=schId、用户=用户ID',
|
||||
`sch_id` INT NULL COMMENT '学校ID(逻辑外键 → UserBase sys_group.id,0=非学校群组)',
|
||||
`role_type` INT NULL COMMENT '角色类型(-1 表示用户级不区分角色)',
|
||||
`c_id` INT NULL COMMENT '操作人ID(逻辑外键 → UserBase 用户)',
|
||||
`c_date` DATETIME NULL COMMENT '操作时间',
|
||||
`create_time` DATETIME NULL COMMENT '创建时间',
|
||||
`update_time` DATETIME NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_used_app_set` (`set_id`, `set_type`, `role_type`, `sch_id`)
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4
|
||||
COMMENT = '用户「常用应用」设置(本地表,不冗余应用元数据)';
|
||||
|
||||
-- 用户当前激活角色表(一用户一行)
|
||||
CREATE TABLE IF NOT EXISTS `user_current_role` (
|
||||
`user_id` INT NOT NULL COMMENT '用户ID(逻辑外键 → UserBase 用户)',
|
||||
`role_id` INT NULL COMMENT '角色ID',
|
||||
`role_name` VARCHAR(64) NULL COMMENT '角色名称',
|
||||
`sch_id` INT NULL COMMENT '学校ID(逻辑外键 → UserBase sys_group.id)',
|
||||
`sch_name` VARCHAR(128) NULL COMMENT '学校名称',
|
||||
`child_id` INT NULL COMMENT '子节点ID(班级/学段等)',
|
||||
`child_name` VARCHAR(128) NULL COMMENT '子节点名称',
|
||||
PRIMARY KEY (`user_id`),
|
||||
KEY `idx_user_current_role_user` (`user_id`)
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4
|
||||
COMMENT = '用户当前激活角色(会话态)';
|
||||
Reference in New Issue
Block a user