Compare commits

..

No commits in common. 'bf23b3dd580bd138cfbc62741834ca4377838f8b' and '8bf1d4638cedea09b2c022990389352336577a10' have entirely different histories.

  1. 14
      common/common-core/src/main/java/apelet/common/core/advice/MyExceptionHandler.java
  2. 23
      common/common-core/src/main/java/apelet/common/core/cache/CacheKey.java
  3. 20
      common/common-core/src/main/java/apelet/common/core/exception/DataNotFoundException.java
  4. 21
      common/common-core/src/main/java/apelet/common/core/object/TokenData.java
  5. 15
      common/common-generator/src/main/java/apelet/common/generator/controller/OnlCgreportHeadController.java
  6. 15
      common/common-generator/src/main/java/apelet/common/generator/controller/OnlFormHeadController.java
  7. 4
      common/common-generator/src/main/java/apelet/common/generator/model/OnlCgreportHead.java
  8. 4
      common/common-generator/src/main/java/apelet/common/generator/model/OnlFormHead.java
  9. 5
      common/common-generator/src/main/java/apelet/common/generator/service/IOnlCgreportHeadService.java
  10. 214
      common/common-generator/src/main/java/apelet/common/generator/service/impl/OnlCgreportHeadServiceImpl.java
  11. 259
      common/common-generator/src/main/java/apelet/common/generator/service/impl/OnlFormHeadServiceImpl.java
  12. 188
      common/common-tenant-online/src/main/java/apelet/common/tenant/online/controller/TenantOnlCgreportHeadController.java
  13. 223
      common/common-tenant-online/src/main/java/apelet/common/tenant/online/controller/TenantOnlFormHeadController.java

14
common/common-core/src/main/java/apelet/common/core/advice/MyExceptionHandler.java

@ -27,20 +27,6 @@ import java.util.concurrent.TimeoutException; @@ -27,20 +27,6 @@ import java.util.concurrent.TimeoutException;
@RestControllerAdvice("apelet")
public class MyExceptionHandler {
/**
* 数据不存在或不属于当前租户:统一返回 404,避免越权探测(PRD FR-4.16)。
*
* @param ex 异常对象。
* @param request http请求。
* @return 应答对象。
*/
@ExceptionHandler(value = DataNotFoundException.class)
public ResponseResult<Void> dataNotFoundExceptionHandle(DataNotFoundException ex, HttpServletRequest request) {
log.warn("Data not found or not owned by current tenant from URL [{}]", request.getRequestURI());
ContextUtil.getHttpResponse().setStatus(HttpServletResponse.SC_NOT_FOUND);
return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
}
/**
* 通用异常处理方法。
*

23
common/common-core/src/main/java/apelet/common/core/cache/CacheKey.java vendored

@ -1,7 +1,5 @@ @@ -1,7 +1,5 @@
package apelet.common.core.cache;
import apelet.common.core.object.TokenData;
/**
* 在线表单缓存
* @author Soon
@ -21,15 +19,6 @@ public class CacheKey { @@ -21,15 +19,6 @@ public class CacheKey {
*/
public final static String FORM_FIELD_BY_HEAD_Id = "FORM_FIELD_BY_HEAD_Id:";
/**
* 租户维度片段:A 端为租户Id,B 端及无请求上下文(MQ、定时任务)统一为 base。
* 租户库的表单元数据 id 可能相同(由管理端复制而来),不加租户维度会导致缓存互相覆盖(PRD FR-4.18)。
*/
private static String tenantSegment() {
Long tenantId = TokenData.currentTenantId();
return tenantId == null ? "base" : String.valueOf(tenantId);
}
public static String makeEntityTableKey(String datasourceVariableName) {
return "EntityTable:" + datasourceVariableName;
}
@ -37,32 +26,32 @@ public class CacheKey { @@ -37,32 +26,32 @@ public class CacheKey {
public static String makeTableKey(Long id) {
return "FORM_HEAD_TABLE:" + tenantSegment() + ":" + id;
return "FORM_HEAD_TABLE:" + id;
}
public static String makeFormHeadByTableId(Long id) {
return "FORM_HEAD_BY_TABLE_ID:" + tenantSegment() + ":" + id;
return "FORM_HEAD_BY_TABLE_ID:" + id;
}
public static String makeTableKey(String tableName) {
return "FORM_HEAD_TABLE_NAME:" + tenantSegment() + ":" + tableName;
return "FORM_HEAD_TABLE_NAME:" + tableName;
}
public static String makeChildFieldListKey(Long id) {
return "FORM_CHILD_FIELD:" + tenantSegment() + ":" + id;
return "FORM_CHILD_FIELD:" + id;
}
public static String makeOneToManyTable(String tableName) {
return "FORM_ONETOMANY_TABLE:" + tenantSegment() + ":" + tableName;
return "FORM_ONETOMANY_TABLE:" + tableName;
}
public static String getFormFieldByHeadId(long headId) {
return FORM_FIELD_BY_HEAD_Id + tenantSegment() + ":" + headId;
return FORM_FIELD_BY_HEAD_Id + headId;
}

20
common/common-core/src/main/java/apelet/common/core/exception/DataNotFoundException.java

@ -1,20 +0,0 @@ @@ -1,20 +0,0 @@
package apelet.common.core.exception;
/**
* 数据不存在或不属于当前租户。
*
* <p>用于按 id 查询/更新/删除时的归属校验(PRD FR-4.16):
* 记录不存在与记录属于其他租户统一返回 404,避免通过状态码探测数据是否存在。</p>
*
* @author apelet
*/
public class DataNotFoundException extends MyRuntimeException {
public DataNotFoundException() {
super();
}
public DataNotFoundException(String msg) {
super(msg);
}
}

21
common/common-core/src/main/java/apelet/common/core/object/TokenData.java

@ -144,27 +144,6 @@ public class TokenData { @@ -144,27 +144,6 @@ public class TokenData {
}
/**
* 当前请求的租户Id。B 端(管理端)为 null,A 端(租户端)为登录用户的租户Id。
* 无 HTTP 请求上下文(如 MQ 消费、定时任务)时同样返回 null。
*
* @return 当前租户Id,可能为 null。
*/
public static Long currentTenantId() {
TokenData tokenData = takeFromRequest();
return tokenData == null ? null : tokenData.getTenantId();
}
/**
* 当前请求的应用编码。为空表示不按应用维度过滤(PRD FR-4.14)。
*
* @return 当前应用编码,可能为 null。
*/
public static String currentAppCode() {
TokenData tokenData = takeFromRequest();
return tokenData == null ? null : tokenData.getAppCode();
}
/**
* 从Http Request对象中获取令牌对象。
*
* @return 令牌对象。

15
common/common-generator/src/main/java/apelet/common/generator/controller/OnlCgreportHeadController.java

@ -6,7 +6,6 @@ import apelet.common.core.object.MyOrderParam; @@ -6,7 +6,6 @@ import apelet.common.core.object.MyOrderParam;
import apelet.common.core.object.MyPageData;
import apelet.common.core.object.MyPageParam;
import apelet.common.core.object.ResponseResult;
import apelet.common.core.object.TokenData;
import apelet.common.core.util.MyPageUtil;
import apelet.common.generator.dto.OnlCgreportHeadDto;
import apelet.common.generator.dto.SQLParseDto;
@ -138,17 +137,6 @@ public class OnlCgreportHeadController { @@ -138,17 +137,6 @@ public class OnlCgreportHeadController {
wq.like(StringUtils.isNotEmpty(OnlCgreportHeadDto.getCode()),OnlCgreportHead::getCode,OnlCgreportHeadDto.getCode())
.like(StringUtils.isNotEmpty(OnlCgreportHeadDto.getName()),OnlCgreportHead::getName,OnlCgreportHeadDto.getName());
}
//租户隔离(PRD FR-4.15):A 端只看本租户,B 端只看 tenant_id IS NULL
Long tenantId = TokenData.currentTenantId();
if (tenantId == null) {
wq.isNull(OnlCgreportHead::getTenantId);
} else {
wq.eq(OnlCgreportHead::getTenantId, tenantId);
}
String appCode = TokenData.currentAppCode();
if (StringUtils.isNotBlank(appCode)) {
wq.eq(OnlCgreportHead::getAppCode, appCode);
}
List<OnlCgreportHead> list = onlCgreportHeadService.list(wq);
long totalCount = 0L;
if (list instanceof com.github.pagehelper.Page) {
@ -164,8 +152,7 @@ public class OnlCgreportHeadController { @@ -164,8 +152,7 @@ public class OnlCgreportHeadController {
*/
@GetMapping(value = "/getById")
public ResponseResult<?> getById(@RequestParam("id") String id) {
//归属校验:不存在或不属于当前租户返回 404(PRD FR-4.16)
return ResponseResult.success(onlCgreportHeadService.getOwnedHead(id));
return ResponseResult.success(onlCgreportHeadService.getById(id));
}
/**
* 查询对应sql的字段

15
common/common-generator/src/main/java/apelet/common/generator/controller/OnlFormHeadController.java

@ -7,7 +7,6 @@ import apelet.common.core.object.MyOrderParam; @@ -7,7 +7,6 @@ import apelet.common.core.object.MyOrderParam;
import apelet.common.core.object.MyPageData;
import apelet.common.core.object.MyPageParam;
import apelet.common.core.object.ResponseResult;
import apelet.common.core.object.TokenData;
import apelet.common.core.util.MyPageUtil;
import apelet.common.eas.service.impl.EASSyncService;
import apelet.common.generator.dto.OnlFormCodeDto;
@ -102,18 +101,6 @@ public class OnlFormHeadController { @@ -102,18 +101,6 @@ public class OnlFormHeadController {
.like(ObjectUtil.isNotEmpty(dto.getTableType()), OnlFormHead::getTableType, dto.getTableType())
.like(ObjectUtil.isNotEmpty(dto.getTableTxt()), OnlFormHead::getTableTxt, dto.getTableTxt());
}
//租户隔离:A 端只看本租户,B 端只看 tenant_id IS NULL 的管理端数据
Long tenantId = TokenData.currentTenantId();
if (tenantId == null) {
wq.isNull(OnlFormHead::getTenantId);
} else {
wq.eq(OnlFormHead::getTenantId, tenantId);
}
//app_code 为可选维度:有值才过滤(PRD FR-4.14)
String appCode = TokenData.currentAppCode();
if (StringUtils.isNotBlank(appCode)) {
wq.eq(OnlFormHead::getAppCode, appCode);
}
wq.select(OnlFormHead.class, i -> !i.getProperty().equals("oldData"));
List<OnlFormHead> list = onlFormHeadService.list(wq);
long totalCount = 0L;
@ -400,7 +387,7 @@ public class OnlFormHeadController { @@ -400,7 +387,7 @@ public class OnlFormHeadController {
* @return
*/
@GetMapping(value = "/onlFormSync")
public ResponseResult<?> onlFormSync(@RequestParam("billType") String billType, @RequestParam("id") Long id) {
ResponseResult<?> onlFormSync(@RequestParam("billType") String billType, @RequestParam("id") Long id) {
try {
if (StringUtils.isEmpty(billType)) {
return ResponseResult.error("500", "标识为空 ");

4
common/common-generator/src/main/java/apelet/common/generator/model/OnlCgreportHead.java

@ -55,8 +55,4 @@ public class OnlCgreportHead extends BaseModel implements Serializable { @@ -55,8 +55,4 @@ public class OnlCgreportHead extends BaseModel implements Serializable {
@Schema(description = "租户id")
@TableField(value = "tenant_id")
private Long tenantId;
@Schema(description = "应用编码")
@TableField(value = "app_code")
private String appCode;
}

4
common/common-generator/src/main/java/apelet/common/generator/model/OnlFormHead.java

@ -118,10 +118,6 @@ public class OnlFormHead extends BaseModel implements Serializable { @@ -118,10 +118,6 @@ public class OnlFormHead extends BaseModel implements Serializable {
@TableField(value = "tenant_id")
private Long tenantId;
@Schema(description = "应用编码")
@TableField(value = "app_code")
private String appCode;
@Schema(description = "数据库属性分录")
@TableField(exist = false)
private List<OnlFormField> onlFormFieldList;

5
common/common-generator/src/main/java/apelet/common/generator/service/IOnlCgreportHeadService.java

@ -24,11 +24,6 @@ import java.util.Map; @@ -24,11 +24,6 @@ import java.util.Map;
public interface IOnlCgreportHeadService extends IService<OnlCgreportHead> {
Map<String, Object> getSql(String sql, String dbKey);
/**
* 按 id 查询并校验租户归属,不存在或不属于当前租户时抛 404。
*/
OnlCgreportHead getOwnedHead(String id);
List<OnlCgreportItem> getFieldsById(String id);
List<OnlCgreportParam> getParamsById(String id);

214
common/common-generator/src/main/java/apelet/common/generator/service/impl/OnlCgreportHeadServiceImpl.java

@ -1,6 +1,5 @@ @@ -1,6 +1,5 @@
package apelet.common.generator.service.impl;
import apelet.common.core.exception.DataNotFoundException;
import apelet.common.core.object.MyPageParam;
import apelet.common.core.object.TokenData;
import apelet.common.dbutil.util.SQLStringBuffer;
@ -67,49 +66,6 @@ public class OnlCgreportHeadServiceImpl extends ServiceImpl<OnlCgreportHeadMappe @@ -67,49 +66,6 @@ public class OnlCgreportHeadServiceImpl extends ServiceImpl<OnlCgreportHeadMappe
@Autowired
private EASSyncService easSyncService;
/**
* 给 onl_cgreport_head 查询追加租户隔离条件:
* A 端 tenant_id = 当前租户;B 端 tenant_id IS NULL。app_code 有值才参与过滤(FR-4.14)。
*/
private void applyTenantScope(LambdaQueryWrapper<OnlCgreportHead> wrapper) {
Long tenantId = TokenData.currentTenantId();
if (tenantId == null) {
wrapper.isNull(OnlCgreportHead::getTenantId);
} else {
wrapper.eq(OnlCgreportHead::getTenantId, tenantId);
}
String appCode = TokenData.currentAppCode();
if (StringUtils.isNotBlank(appCode)) {
wrapper.eq(OnlCgreportHead::getAppCode, appCode);
}
}
/**
* 按 id 查询并校验归属:不存在或不属于当前租户统一抛 404。
*
* <p>无 HTTP 请求上下文(MQ、定时任务)不做租户校验,避免阻断同步链路。</p>
*/
@Override
public OnlCgreportHead getOwnedHead(String id) {
OnlCgreportHead head = StringUtils.isEmpty(id) ? null : this.getById(id);
if (head == null) {
throw new DataNotFoundException();
}
TokenData tokenData = TokenData.takeFromRequest();
if (tokenData == null) {
return head;
}
if (!Objects.equals(head.getTenantId(), tokenData.getTenantId())) {
throw new DataNotFoundException();
}
//app_code 为可选维度:请求未携带 appCode 时不校验(FR-4.14)
String appCode = tokenData.getAppCode();
if (StringUtils.isNotBlank(appCode) && !Objects.equals(head.getAppCode(), appCode)) {
throw new DataNotFoundException();
}
return head;
}
@Override
public Map<String, Object> getSql(String sql, String dbKey) {
//检查sql是否有数据
@ -235,8 +191,6 @@ public class OnlCgreportHeadServiceImpl extends ServiceImpl<OnlCgreportHeadMappe @@ -235,8 +191,6 @@ public class OnlCgreportHeadServiceImpl extends ServiceImpl<OnlCgreportHeadMappe
if (StringUtils.isEmpty(id)) {
return new ArrayList<>();
}
//先校验父表归属,子表只通过 cgrhead_id 关联(子表不加 tenant_id)
getOwnedHead(id);
LambdaQueryWrapper<OnlCgreportItem> wq = new LambdaQueryWrapper<OnlCgreportItem>()
.eq(StringUtils.isNotEmpty(id), OnlCgreportItem::getCgrheadId, id);
return onlCgreportItemService.list(wq);
@ -247,7 +201,6 @@ public class OnlCgreportHeadServiceImpl extends ServiceImpl<OnlCgreportHeadMappe @@ -247,7 +201,6 @@ public class OnlCgreportHeadServiceImpl extends ServiceImpl<OnlCgreportHeadMappe
if (StringUtils.isEmpty(id)) {
return new ArrayList<>();
}
getOwnedHead(id);
LambdaQueryWrapper<OnlCgreportParam> wq = new LambdaQueryWrapper<OnlCgreportParam>()
.eq(StringUtils.isNotEmpty(id), OnlCgreportParam::getCgrheadId, id);
return onlCgreportParamService.list(wq);
@ -257,33 +210,17 @@ public class OnlCgreportHeadServiceImpl extends ServiceImpl<OnlCgreportHeadMappe @@ -257,33 +210,17 @@ public class OnlCgreportHeadServiceImpl extends ServiceImpl<OnlCgreportHeadMappe
@Transactional
public String addAll(OnlCgreportHeadDto dto) {
OnlCgreportHead head = BeanUtil.copyProperties(dto, OnlCgreportHead.class);
//写接口强制赋值(PRD FR-4.17):DTO 不暴露 tenantId/appCode,避免前端伪造。
Long tenantId = TokenData.currentTenantId();
if (tenantId != null) {
head.setTenantId(tenantId);
}
String appCode = TokenData.currentAppCode();
if (StringUtils.isNotBlank(appCode)) {
head.setAppCode(appCode);
}
baseMapper.insert(head);
String id = head.getId();
List<OnlCgreportItem> items = dto.getItems();
List<OnlCgreportParam> params = dto.getParams();
if (StringUtils.isNotEmpty(id)) {
//新增报表时子表行一定是新行:清掉前端传来的 id,避免复用他行主键导致主键冲突(任务 #8)
if (ObjectUtil.isNotEmpty(items)) {
List<OnlCgreportItem> itemList = items.stream().peek(x -> {
x.setId(null);
x.setCgrheadId(id);
}).collect(Collectors.toList());
List<OnlCgreportItem> itemList = items.parallelStream().peek(x -> x.setCgrheadId(id)).collect(Collectors.toList());
onlCgreportItemService.saveBatch(itemList);
}
if (ObjectUtil.isNotEmpty(params)) {
List<OnlCgreportParam> paramList = params.stream().peek(x -> {
x.setId(null);
x.setCgrheadId(id);
}).collect(Collectors.toList());
List<OnlCgreportParam> paramList = params.parallelStream().peek(x -> x.setCgrheadId(id)).collect(Collectors.toList());
onlCgreportParamService.saveBatch(paramList);
}
return id;
@ -294,137 +231,31 @@ public class OnlCgreportHeadServiceImpl extends ServiceImpl<OnlCgreportHeadMappe @@ -294,137 +231,31 @@ public class OnlCgreportHeadServiceImpl extends ServiceImpl<OnlCgreportHeadMappe
@Override
@Transactional
public String editAll(OnlCgreportHeadDto dto) {
//归属校验必须在更新前完成(PRD FR-4.16)
OnlCgreportHead origin = getOwnedHead(dto == null ? null : dto.getId());
OnlCgreportHead head = BeanUtil.copyProperties(dto, OnlCgreportHead.class);
//以库中归属为准,防止 updateById 把 tenant_id/app_code 刷成 NULL(FR-4.17)
head.setTenantId(origin.getTenantId());
head.setAppCode(origin.getAppCode());
baseMapper.updateById(head);
String id = head.getId();
if (StringUtils.isEmpty(id)) {
return "修改成功";
}
//子表改为增量 upsert(任务 #8):不再无条件“先全删再重插”。
// 1) items/params 为 null 表示本次没有提交该子表,库中配置保持不变;
// 2) 管理端(tenant_id 为空)提交的是完整配置,删除本次未提交的行,保证删字段/删参数能落库;
// 3) 租户端(tenant_id 有值)只 upsert 不做物理删除,与任务 #5 的口径一致。
boolean admin = origin.getTenantId() == null;
saveReportItems(id, dto.getItems(), admin);
saveReportParams(id, dto.getParams(), admin);
return id;
}
/**
* 增量保存报表字段(任务 #8)。
*
* @param headId 报表主键Id。
* @param items 本次提交的字段,null 表示本次未提交。
* @param admin 是否管理端;管理端以提交内容为准,会删除本次未提交的行。
*/
private void saveReportItems(String headId, List<OnlCgreportItem> items, boolean admin) {
if (items == null) {
return;
}
Map<String, OnlCgreportItem> existing = this.listReportItems(headId);
List<OnlCgreportItem> toSave = new ArrayList<>(items.size());
Set<String> submittedIds = new HashSet<>();
for (OnlCgreportItem item : items) {
//只允许更新本报表下的行,伪造或来自其它报表的 id 一律按新增处理,避免把他行数据挂到当前报表
if (StringUtils.isEmpty(item.getId()) || !existing.containsKey(item.getId())) {
item.setId(null);
} else {
submittedIds.add(item.getId());
}
item.setCgrheadId(headId);
toSave.add(item);
}
if (admin) {
List<String> removeIds = existing.keySet().stream()
.filter(existingId -> !submittedIds.contains(existingId))
.collect(Collectors.toList());
if (!removeIds.isEmpty()) {
onlCgreportItemService.removeByIds(removeIds);
}
}
if (!toSave.isEmpty()) {
onlCgreportItemService.saveOrUpdateBatch(toSave);
}
}
/**
* 增量保存报表参数(任务 #8),口径同 {@link #saveReportItems(String, List, boolean)}。
*
* @param headId 报表主键Id。
* @param params 本次提交的参数,null 表示本次未提交。
* @param admin 是否管理端;管理端以提交内容为准,会删除本次未提交的行。
*/
private void saveReportParams(String headId, List<OnlCgreportParam> params, boolean admin) {
if (params == null) {
return;
}
Map<String, OnlCgreportParam> existing = this.listReportParams(headId);
List<OnlCgreportParam> toSave = new ArrayList<>(params.size());
Set<String> submittedIds = new HashSet<>();
for (OnlCgreportParam param : params) {
if (StringUtils.isEmpty(param.getId()) || !existing.containsKey(param.getId())) {
param.setId(null);
} else {
submittedIds.add(param.getId());
List<OnlCgreportItem> items = dto.getItems();
List<OnlCgreportParam> params = dto.getParams();
//查询原数据没改动就不更新
if (StringUtils.isNotEmpty(id)) {
//删除原有数据
onlCgreportItemService.remove(new LambdaQueryWrapper<OnlCgreportItem>().eq(OnlCgreportItem::getCgrheadId, id));
onlCgreportParamService.remove(new LambdaQueryWrapper<OnlCgreportParam>().eq(OnlCgreportParam::getCgrheadId, id));
//重新添加
if (ObjectUtil.isNotEmpty(params)) {
onlCgreportParamService.saveOrUpdateBatch(params.stream().peek(x -> x.setCgrheadId(id)).collect(Collectors.toList()));
}
param.setCgrheadId(headId);
toSave.add(param);
}
if (admin) {
List<String> removeIds = existing.keySet().stream()
.filter(existingId -> !submittedIds.contains(existingId))
.collect(Collectors.toList());
if (!removeIds.isEmpty()) {
onlCgreportParamService.removeByIds(removeIds);
if (ObjectUtil.isNotEmpty(items)) {
onlCgreportItemService.saveOrUpdateBatch(items.stream().peek(x -> x.setCgrheadId(id)).collect(Collectors.toList()));
}
return id;
}
if (!toSave.isEmpty()) {
onlCgreportParamService.saveOrUpdateBatch(toSave);
}
}
/**
* 查询报表下的全部字段,按主键索引。
*
* @param headId 报表主键Id。
* @return 主键到字段对象的映射。
*/
private Map<String, OnlCgreportItem> listReportItems(String headId) {
List<OnlCgreportItem> list = onlCgreportItemService.list(
new LambdaQueryWrapper<OnlCgreportItem>().eq(OnlCgreportItem::getCgrheadId, headId));
Map<String, OnlCgreportItem> result = new HashMap<>(list.size());
for (OnlCgreportItem item : list) {
result.put(item.getId(), item);
}
return result;
}
/**
* 查询报表下的全部参数,按主键索引。
*
* @param headId 报表主键Id。
* @return 主键到参数对象的映射。
*/
private Map<String, OnlCgreportParam> listReportParams(String headId) {
List<OnlCgreportParam> list = onlCgreportParamService.list(
new LambdaQueryWrapper<OnlCgreportParam>().eq(OnlCgreportParam::getCgrheadId, headId));
Map<String, OnlCgreportParam> result = new HashMap<>(list.size());
for (OnlCgreportParam param : list) {
result.put(param.getId(), param);
}
return result;
return "修改成功";
}
@Override
@Transactional
public String removeAllById(Long id) {
//先校验归属再删(PRD FR-4.12 / FR-4.16)
getOwnedHead(id == null ? null : String.valueOf(id));
String msg = "删除数据成功";
baseMapper.deleteById(id);
onlCgreportItemService.remove(new LambdaQueryWrapper<OnlCgreportItem>()
@ -439,13 +270,10 @@ public class OnlCgreportHeadServiceImpl extends ServiceImpl<OnlCgreportHeadMappe @@ -439,13 +270,10 @@ public class OnlCgreportHeadServiceImpl extends ServiceImpl<OnlCgreportHeadMappe
@Override
public OnlFormHeadVo2 getRpColumns(String code) {
if (StringUtils.isEmpty(code)) throw new RuntimeException("传入的code编码不能为空");
//同一租户内按 code 定位,避免多租户同名 code 冲突(同步端允许 code 在不同租户重复)
LambdaQueryWrapper<OnlCgreportHead> wrapper = new LambdaQueryWrapper<OnlCgreportHead>()
.eq(OnlCgreportHead::getCode, code);
applyTenantScope(wrapper);
List<OnlCgreportHead> list = this.list(wrapper);
if (ObjectUtil.isEmpty(list)) throw new DataNotFoundException();
if (list.size() > 1) throw new RuntimeException("code编码 配置多条数据");
List<OnlCgreportHead> list = this.list(new LambdaQueryWrapper<OnlCgreportHead>()
.eq(OnlCgreportHead::getCode, code)
);
if (ObjectUtil.isEmpty(list) || list.size() > 1) throw new RuntimeException("code编码 配置多条数据");
//返回参数
OnlFormHeadVo2 result = new OnlFormHeadVo2();
OnlCgreportHead head = list.get(0);
@ -561,7 +389,7 @@ public class OnlCgreportHeadServiceImpl extends ServiceImpl<OnlCgreportHeadMappe @@ -561,7 +389,7 @@ public class OnlCgreportHeadServiceImpl extends ServiceImpl<OnlCgreportHeadMappe
@Override
public IPage<JSONObject> getData(JSONObject jsonObject, String id, MyPageParam pageParam) {
OnlCgreportHead head = getOwnedHead(id);
OnlCgreportHead head = this.getById(id);
if (ObjectUtil.isEmpty(head)) throw new RuntimeException("数据为空,查询失败");
String sql = head.getCgrSql();
//todo sql动态替换值

259
common/common-generator/src/main/java/apelet/common/generator/service/impl/OnlFormHeadServiceImpl.java

@ -2,7 +2,6 @@ package apelet.common.generator.service.impl; @@ -2,7 +2,6 @@ package apelet.common.generator.service.impl;
import apelet.common.core.cache.CacheKey;
import apelet.common.core.constant.GlobalDeletedFlag;
import apelet.common.core.exception.DataNotFoundException;
import apelet.common.core.object.TokenData;
import apelet.common.dict.model.GlobalDictItem;
import apelet.common.dict.service.GlobalDictItemService;
@ -52,7 +51,6 @@ import java.sql.ResultSet; @@ -52,7 +51,6 @@ import java.sql.ResultSet;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@ -134,55 +132,6 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo @@ -134,55 +132,6 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo
redissonClient.getBucket(tableIdKey).delete();
}
/**
* 当前请求的租户Id。B 端(管理端)为 null,A 端(租户端)为登录租户Id。
*/
private Long currentTenantId() {
return TokenData.currentTenantId();
}
/**
* 归属校验(PRD FR-4.16):有 HTTP 请求上下文时,记录的 tenant_id / app_code 必须与当前请求一致,
* 不匹配按“不存在”处理并返回 404,避免越权探测。
*
* <p>app_code 为可选维度:请求未携带 appCode 时不校验(FR-4.14)。</p>
*
* <p>无请求上下文(MQ 消费、定时任务)不做校验,否则租户同步链路无法删除/更新租户数据。</p>
*
* @param rowTenantId 库中记录的 tenant_id。
* @param rowAppCode 库中记录的 app_code。
*/
private void verifyOwnership(Long rowTenantId, String rowAppCode) {
TokenData tokenData = TokenData.takeFromRequest();
if (tokenData == null) {
return;
}
if (!Objects.equals(rowTenantId, tokenData.getTenantId())) {
throw new DataNotFoundException();
}
String appCode = tokenData.getAppCode();
if (StringUtils.isNotBlank(appCode) && !Objects.equals(rowAppCode, appCode)) {
throw new DataNotFoundException();
}
}
/**
* 给 onl_form_head 查询追加租户隔离条件(PRD FR-4.15):
* A 端 tenant_id = 当前租户;B 端 tenant_id IS NULL。app_code 有值才参与过滤(FR-4.14)。
*/
private void applyTenantScope(LambdaQueryWrapper<OnlFormHead> wrapper) {
Long tenantId = currentTenantId();
if (tenantId == null) {
wrapper.isNull(OnlFormHead::getTenantId);
} else {
wrapper.eq(OnlFormHead::getTenantId, tenantId);
}
String appCode = TokenData.currentAppCode();
if (StringUtils.isNotBlank(appCode)) {
wrapper.eq(OnlFormHead::getAppCode, appCode);
}
}
@Override
public OnlFormHeadVo listByHeadId(Long id) {
@ -197,15 +146,6 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo @@ -197,15 +146,6 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo
@Transactional
public OnlFormHeadDto addAll(OnlFormHeadDto dto) {
OnlFormHead head = BeanUtil.copyProperties(dto, OnlFormHead.class);
//写接口强制赋值(PRD FR-4.17):A 端落当前租户,B 端保持 null。DTO 不暴露 tenantId,避免前端伪造。
Long tenantId = currentTenantId();
if (tenantId != null) {
head.setTenantId(tenantId);
}
String appCode = TokenData.currentAppCode();
if (StringUtils.isNotBlank(appCode)) {
head.setAppCode(appCode);
}
String string = JSONUtil.parseObj(dto).toJSONString(0);
head.setOldData(string);
//新增基本数据
@ -220,10 +160,9 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo @@ -220,10 +160,9 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo
//主表名
String name = dto.getSort();
if (StringUtils.isNotEmpty(name)) {
LambdaQueryWrapper<OnlFormHead> mainWrapper = new LambdaQueryWrapper<OnlFormHead>()
.eq(OnlFormHead::getTableName, name);
applyTenantScope(mainWrapper);
List<OnlFormHead> list = this.list(mainWrapper);
List<OnlFormHead> list = this.list(new LambdaQueryWrapper<OnlFormHead>()
.eq(OnlFormHead::getTableName, name)
);
if (ObjectUtil.isNotEmpty(list)) {
for (OnlFormHead onlFormHead : list) {
String childName = onlFormHead.getChildName();
@ -257,18 +196,6 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo @@ -257,18 +196,6 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo
@Override
@Transactional
public OnlFormHeadDto editAll(OnlFormHeadDto dto,boolean isync) {
//归属校验(PRD FR-4.16):必须在删缓存之前完成,避免跨租户请求清掉别人的缓存。
//isync=true 是 EAS 异构同步,数据由外部系统驱动,不做租户归属拦截。
if (dto.getId() == null) {
throw new DataNotFoundException();
}
OnlFormHead origin = this.getById(dto.getId());
if (origin == null) {
throw new DataNotFoundException();
}
if (!isync) {
verifyOwnership(origin.getTenantId(), origin.getAppCode());
}
//检查是否为附表,且有关联字段
String sortFiled = dto.getSortFiled();
String tableType = dto.getTableType();
@ -284,10 +211,9 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo @@ -284,10 +211,9 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo
formkey = CacheKey.makeOneToManyTable(name);
redissonClient.getBucket(formkey).delete();
if (StringUtils.isNotEmpty(name)) {
LambdaQueryWrapper<OnlFormHead> mainWrapper = new LambdaQueryWrapper<OnlFormHead>()
.eq(OnlFormHead::getTableName, name);
applyTenantScope(mainWrapper);
OnlFormHead onlFormHead = this.getOne(mainWrapper);
OnlFormHead onlFormHead = this.getOne(
new LambdaQueryWrapper<OnlFormHead>().eq(OnlFormHead::getTableName, name)
);
if (onlFormHead!=null) {
// BeanUtil.copyProperties(dto,onlFormHead);
String childName = onlFormHead.getChildName();
@ -310,9 +236,6 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo @@ -310,9 +236,6 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo
}
//数据转换
OnlFormHead head = BeanUtil.copyProperties(dto, OnlFormHead.class);
//归属以库中记录为准,不信任提交内容,防止 updateById 把 tenant_id 刷成 NULL(PRD FR-4.17 / FR-4.16)
head.setTenantId(origin.getTenantId());
head.setAppCode(origin.getAppCode());
head.setVersion(String.valueOf(Integer.parseInt(head.getVersion()) + 1));
head.setStatus("0");
//更新基本数据
@ -329,22 +252,10 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo @@ -329,22 +252,10 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo
dto.setId(head.getId());
//更新分录数据
if (save && ObjectUtil.isNotEmpty(head.getId())) {
// 判定口径(PRD FR-4.13):tenant_id 有值 = 租户端(A),为空 = 管理端(B)。
// 用库中记录判断,DTO 没有 tenantId 字段,不能拿 DTO 判断。
// isync=true 是 EAS 异构同步,外部系统为权威源,同样保持“先清理再重插”。
if (isync || origin.getTenantId() == null) {
// B 端设计器提交的是完整字段集,删除字段/改字段名必须靠“先删后插”才能落库;
// 这里也是 removeChildData 在保存路径上的唯一保留点。
removeChildData(head.getId(), isync);
addChildData(dto);
} else {
// A 端保存:改为增量 upsert(PRD FR-4.9 / 任务 #5)。
// 原实现是“按 headId 整表删除 + 按提交内容重插”,租户端只提交部分字段时,
// 管理端下发但本次未提交的字段会被物理删除(P-9)。
// 注意:A 端的 tenantId 由任务 #6 在进入本方法前强制赋值(FR-4.17)。
upsertChildData(head.getId(), dto);
}
// TODO(FR-4.9): A 端删除自有 TENANT 字段本期不做(N-6 只能隐藏),后续按 source='TENANT' 补删
//先删除
removeChildData(head.getId(),isync);
//再添加
addChildData(dto);
}
return dto;
}
@ -352,12 +263,6 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo @@ -352,12 +263,6 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo
@Override
@Transactional
public Boolean removeAllById(Long id) {
//归属校验后再删除(PRD FR-4.16 / FR-4.12),避免传别家 id 直接删库
OnlFormHead origin = id == null ? null : this.getById(id);
if (origin == null) {
throw new DataNotFoundException();
}
verifyOwnership(origin.getTenantId(), origin.getAppCode());
//删除主数据
boolean remove = this.removeById(id);
if (remove) {
@ -1015,11 +920,7 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo @@ -1015,11 +920,7 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo
@Override
public Boolean checkTableName(String tableName) {
//同一租户内判重,避免跨租户误判表名已存在(PRD FR-4.15)
LambdaQueryWrapper<OnlFormHead> wrapper = new LambdaQueryWrapper<OnlFormHead>()
.eq(OnlFormHead::getTableName, tableName);
applyTenantScope(wrapper);
List<OnlFormHead> list = this.list(wrapper);
List<OnlFormHead> list = this.list(new LambdaQueryWrapper<OnlFormHead>().eq(OnlFormHead::getTableName, tableName));
return list.isEmpty();
}
@ -1027,11 +928,7 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo @@ -1027,11 +928,7 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo
public JSONObject tableInfo(Long id) {
JSONObject result = new JSONObject();
//主表
OnlFormHead main = id == null ? null : this.getById(id);
if (main == null) {
throw new DataNotFoundException();
}
verifyOwnership(main.getTenantId(), main.getAppCode());
OnlFormHead main = this.getById(id);
result.put("main", main);
String tableType = main.getTableType();
//风格
@ -1062,10 +959,9 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo @@ -1062,10 +959,9 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo
vo3.setNote("Tab风格");
list.add(vo3);
//附表
LambdaQueryWrapper<OnlFormHead> subWrapper = new LambdaQueryWrapper<OnlFormHead>()
.in(OnlFormHead::getTableName, main.getChildName().split(","));
applyTenantScope(subWrapper);
List<OnlFormHead> sub = this.list(subWrapper);
List<OnlFormHead> sub = this.list(new LambdaQueryWrapper<OnlFormHead>()
.in(OnlFormHead::getTableName, main.getChildName().split(","))
);
if (ObjectUtil.isNotEmpty(sub)) {
result.put("sub", sub);
List<OnlFormCodeDto> subList = new ArrayList<>();
@ -1103,123 +999,7 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo @@ -1103,123 +999,7 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo
}
/**
* 子数据增量写入:只 upsert,不做物理删除(PRD FR-4.9 / 任务 #5)。
*
* <p>规则与任务清单里的“已确认的关键决策”一致:</p>
* <ul>
* <li>提交里有、库里没有 → 插入;</li>
* <li>提交里有、库里也有 → 更新;</li>
* <li>库里有、提交里没有 → 保留(不删)。这是本方法存在的全部意义:租户端只提交自己关心的字段时,
* 管理端下发但本次未提交的字段不会被物理删除(P-9 / R-2)。</li>
* </ul>
*
* <p>为什么还需要回填 id:旧实现靠前面的 {@code removeChildData} 全删来保证“重插不产生重复行”。
* 去掉删除以后,若前端不传子行 id(新增行本来就没有 id),同一行重复提交会插出两行同名字段。
* 因此提交行没带 id(或带的 id 不属于当前 headId)时,按业务唯一键回填库里已有行的 id 后再走更新。</p>
*
* <p>子表语义说明:六张子表都只通过 {@code head_id} 从属父表、没有独立的删除入口(N-6 本期不做删除,
* A 端只能隐藏),所以统一按“不删”处理,不区分是哪张子表。</p>
*
* @param headId 父表 id,子行的 headId 会在 {@link #addChildData} 里被强制改写为该值。
* @param dto 本次提交的表单数据,方法会就地回填子行 id 并覆盖其子表集合。
*/
private void upsertChildData(Long headId, OnlFormHeadDto dto) {
dto.setOnlFormFieldList(this.mergeChildIds(dto.getOnlFormFieldList(),
onlFormFieldService.list(new LambdaQueryWrapper<OnlFormField>().eq(OnlFormField::getHeadId, headId)),
OnlFormField::getId, OnlFormField::setId, OnlFormField::getFieldName));
dto.setOnlFormFindList(this.mergeChildIds(dto.getOnlFormFindList(),
onlFormFindService.list(new LambdaQueryWrapper<OnlFormFind>().eq(OnlFormFind::getHeadId, headId)),
OnlFormFind::getId, OnlFormFind::setId, OnlFormFind::getFieldName));
dto.setOnlFormCheckList(this.mergeChildIds(dto.getOnlFormCheckList(),
onlFormCheckService.list(new LambdaQueryWrapper<OnlFormCheck>().eq(OnlFormCheck::getHeadId, headId)),
OnlFormCheck::getId, OnlFormCheck::setId, OnlFormCheck::getFieldName));
dto.setOnlFormIndexList(this.mergeChildIds(dto.getOnlFormIndexList(),
onlFormIndexService.list(new LambdaQueryWrapper<OnlFormIndex>().eq(OnlFormIndex::getHeadId, headId)),
OnlFormIndex::getId, OnlFormIndex::setId, OnlFormIndex::getIndexName));
dto.setOnlFormKeyList(this.mergeChildIds(dto.getOnlFormKeyList(),
onlFormKeyService.list(new LambdaQueryWrapper<OnlFormKey>().eq(OnlFormKey::getHeadId, headId)),
OnlFormKey::getId, OnlFormKey::setId, OnlFormKey::getFieldName));
dto.setOnlFormPageList(this.mergeChildIds(dto.getOnlFormPageList(),
onlFormPageService.list(new LambdaQueryWrapper<OnlFormPage>().eq(OnlFormPage::getHeadId, headId)),
OnlFormPage::getId, OnlFormPage::setId, OnlFormPage::getFieldName));
// addChildData 内部用 saveOrUpdateBatch:id 有值走更新、无值走插入,正好匹配上面的回填结果。
addChildData(dto);
}
/**
* 为提交的子行决定最终 id,并按 id / 业务唯一键去重(同一行重复提交只保留最后一条)。
*
* <p>三种情况:</p>
* <ol>
* <li>提交行 id 已存在于当前 headId 下 → 保持 id,更新该行;</li>
* <li>提交行 id 为空、或该 id 不属于当前 headId(可能是伪造/串种)→ 先按业务唯一键找库里同名的行,
* 找到就用它的 id(更新),找不到就置空 id(插入)。<b>不直接用提交进来的 id 落库</b>,
* 否则可以把别的父表下的子行“搬”到当前父表(对照 FR-4.16 的归属校验)。</li>
* <li>同一次提交里出现两条同一业务唯一键 → 后者覆盖前者。</li>
* </ol>
*
* @param submitted 本次提交的子行集合,允许为 null/空(返回 null,调用方不会删任何数据)。
* @param existing 库里当前 headId 下已有的子行。
* @param idGetter 子行 id 读取器。
* @param idSetter 子行 id 写回器(就地回填)。
* @param keyGetter 业务唯一键读取器(字段名 / 索引名)。
* @return 回填 id 并去重后的提交集合。
*/
private <T> List<T> mergeChildIds(List<T> submitted, List<T> existing,
Function<T, Long> idGetter, BiConsumer<T, Long> idSetter,
Function<T, String> keyGetter) {
if (ObjectUtil.isEmpty(submitted)) {
return submitted;
}
Set<Long> existingIds = new HashSet<>();
Map<String, Long> keyToId = new HashMap<>();
if (ObjectUtil.isNotEmpty(existing)) {
for (T row : existing) {
Long id = idGetter.apply(row);
if (id == null) {
continue;
}
existingIds.add(id);
String key = this.normalizeChildKey(keyGetter.apply(row));
if (key != null && !keyToId.containsKey(key)) {
keyToId.put(key, id);
}
}
}
Map<String, T> merged = new LinkedHashMap<>();
int newRowIndex = 0;
for (T row : submitted) {
Long id = idGetter.apply(row);
String key = this.normalizeChildKey(keyGetter.apply(row));
if (id != null && existingIds.contains(id)) {
merged.put("id:" + id, row);
continue;
}
if (key != null && keyToId.containsKey(key)) {
idSetter.accept(row, keyToId.get(key));
merged.put("key:" + key, row);
continue;
}
if (id != null) {
log.warn("子表数据提交了不属于当前元数据头的id,已按新增处理。id={}", id);
}
idSetter.accept(row, null);
merged.put(key != null ? "key:" + key : "new:" + (newRowIndex++), row);
}
return new ArrayList<>(merged.values());
}
/**
* 子行业务唯一键归一化:去空格、转小写,空串视为无键(不参与同名匹配)。
*/
private String normalizeChildKey(String key) {
String normalized = StringUtils.trimToNull(key);
return normalized == null ? null : normalized.toLowerCase(Locale.ROOT);
}
//删除子数据。removeAllById(整表物理删除)与 EAS 异构同步(isync=true,外部系统为准)仍走这里;
//表单设计器保存已改为 upsertChildData,不再调用本方法。
//删除子数据
private <T> void removeChildData(T id,boolean isync) {
onlFormFindService.remove(new LambdaQueryWrapper<OnlFormFind>()
.eq(OnlFormFind::getHeadId, id)
@ -1243,12 +1023,7 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo @@ -1243,12 +1023,7 @@ public class OnlFormHeadServiceImpl extends ServiceImpl<OnlFormHeadMapper, OnlFo
//获取子数据vo
private void getChildData(OnlFormHeadVo vo, Long id) {
OnlFormHead main = id == null ? null : this.getById(id);
if (main == null) {
throw new DataNotFoundException();
}
//按 id 查询时必须校验归属(PRD FR-4.16),子表通过 head_id 随之隔离
verifyOwnership(main.getTenantId(), main.getAppCode());
OnlFormHead main = this.getById(id);
BeanUtil.copyProperties(main, vo);
vo.setOnlFormFindList(onlFormFindService.list(new LambdaQueryWrapper<OnlFormFind>().eq(OnlFormFind::getHeadId, id)));
vo.setOnlFormFieldList(onlFormFieldService.queryOnlFormField(new LambdaQueryWrapper<OnlFormField>().eq(OnlFormField::getHeadId, id)));

188
common/common-tenant-online/src/main/java/apelet/common/tenant/online/controller/TenantOnlCgreportHeadController.java

@ -1,188 +0,0 @@ @@ -1,188 +0,0 @@
package apelet.common.tenant.online.controller;
import apelet.common.core.annotation.MyRequestBody;
import apelet.common.core.exception.DataNotFoundException;
import apelet.common.core.object.MyOrderParam;
import apelet.common.core.object.MyPageParam;
import apelet.common.core.object.ResponseResult;
import apelet.common.generator.controller.OnlCgreportHeadController;
import apelet.common.generator.dto.OnlCgreportHeadDto;
import apelet.common.tenant.annotation.SwitchTenantCommonDatasource;
import com.alibaba.fastjson.JSONObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
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;
/**
* 租户端在线报表元数据接口。
*
* <p>只开放报表配置的日常维护(查询、保存、删除、取数),<b>绝对不开放 {@code parseSql}</b>:
* 该接口会用 {@code jdbcTemplate} 直接执行调用方传入的任意 SQL,黑名单可被 union、子查询、
* information_schema 等绕过,租户用户可借此读取同库任意表(含用户密码哈希、其他租户数据)。</p>
*
* <p>注意:Spring MVC 会把父类带 {@code @RequestMapping} 的方法一并注册到子类路径下,
* 仅“不覆写”并不能屏蔽接口。因此这里除了覆写白名单方法外,还显式覆写了所有
* 黑名单方法并统一抛出 {@link DataNotFoundException},由全局异常处理器返回 404。</p>
*
* @author apelet
*/
@SwitchTenantCommonDatasource
@Tag(name = "租户在线报表元数据接口")
@Slf4j
@RestController
@RequestMapping("${common-online.urlPrefix}/tenantOnlCgreportHead")
public class TenantOnlCgreportHeadController extends OnlCgreportHeadController {
/**
* 租户端未开放的接口统一按 404 返回,避免通过状态码区分“不存在”和“无权限”。
*
* @return 404 业务异常。
*/
private DataNotFoundException notOpen() {
return new DataNotFoundException("租户端不开放该接口");
}
/**
* 分页列表查询。租户维度由父类按当前登录租户过滤(PRD FR-4.15)。
*
* @param onlCgreportHeadDto 过滤对象。
* @param orderParam 排序参数。
* @param pageParam 分页参数。
* @return 应答结果对象。
*/
@PostMapping("/page")
@Schema(description = "分页列表查询")
@Override
public ResponseResult<?> page(@MyRequestBody OnlCgreportHeadDto onlCgreportHeadDto,
@MyRequestBody MyOrderParam orderParam,
@MyRequestBody MyPageParam pageParam) {
return super.page(onlCgreportHeadDto, orderParam, pageParam);
}
/**
* 单条查询。
*
* @param id 报表主键Id。
* @return 应答结果对象。
*/
@GetMapping("/getById")
@Schema(description = "单条查询")
@Override
public ResponseResult<?> getById(@RequestParam("id") String id) {
return super.getById(id);
}
/**
* 查询对应 sql 的字段。
*
* @param id 报表主键Id。
* @return 应答结果对象。
*/
@GetMapping("/getFieldsById")
@Schema(description = "查询对应sql的字段")
@Override
public ResponseResult<?> getFieldsById(@RequestParam("id") String id) {
return super.getFieldsById(id);
}
/**
* 查询对应 sql 的参数。
*
* @param id 报表主键Id。
* @return 应答结果对象。
*/
@GetMapping("/getParamsById")
@Schema(description = "查询对应sql的参数")
@Override
public ResponseResult<?> getParamsById(@RequestParam("id") String id) {
return super.getParamsById(id);
}
/**
* 保存报表配置+字段+参数。
*
* @param onlCgreportHeadDto 新增对象。
* @return 应答结果对象。
*/
@PostMapping("/addAll")
@Schema(description = "保存报表配置+字段+参数")
@Override
public ResponseResult<?> addAll(@RequestBody OnlCgreportHeadDto onlCgreportHeadDto) {
return super.addAll(onlCgreportHeadDto);
}
/**
* 编辑报表配置+字段+参数。
*
* @param onlCgreportHeadDto 更新对象。
* @return 应答结果对象。
*/
@PostMapping("/editAll")
@Schema(description = "编辑报表配置+字段+参数")
@Override
public ResponseResult<?> editAll(@RequestBody OnlCgreportHeadDto onlCgreportHeadDto) {
return super.editAll(onlCgreportHeadDto);
}
/**
* 删除报表配置+字段+参数。
*
* @param id 报表主键Id。
* @return 应答结果对象。
*/
@PostMapping("/removeAllById")
@Schema(description = "删除报表配置+字段+参数")
@Override
public ResponseResult<?> removeAllById(@MyRequestBody Long id) {
return super.removeAllById(id);
}
/**
* 根据 code 查询所需的字段信息和搜索字段。
*
* @param code 报表编码。
* @return 应答结果对象。
*/
@GetMapping("/getRpColumns/{code}")
@Schema(description = "根据code查询所需的字段信息和搜索字段-基础版本")
@Override
public ResponseResult<?> getRpColumns(@PathVariable String code) {
return super.getRpColumns(code);
}
/**
* 根据 id 查询具体数据。
*
* @param dto 查询条件。
* @param id 报表主键Id。
* @param pageParam 分页参数。
* @return 应答结果对象。
*/
@PostMapping("/getData")
@Schema(description = "根据id查询具体数据接口-基础版本")
@Override
public ResponseResult<?> getData(@MyRequestBody JSONObject dto,
@MyRequestBody String id,
@MyRequestBody MyPageParam pageParam) {
return super.getData(dto, id, pageParam);
}
/**
* 租户端绝对不开放:会直接用 jdbcTemplate 执行调用方传入的任意 SQL,
* 且 dbKey=easSql 时还会把 SQL 转发到外部 EAS 系统执行(任务清单 #8)。
*/
@GetMapping("/parseSql")
@Schema(description = "租户端不开放")
@Override
public ResponseResult<?> parseSql(@RequestParam(name = "sql") String sql,
@RequestParam(name = "dbKey", required = false) String dbKey) {
throw notOpen();
}
}

223
common/common-tenant-online/src/main/java/apelet/common/tenant/online/controller/TenantOnlFormHeadController.java

@ -1,223 +0,0 @@ @@ -1,223 +0,0 @@
package apelet.common.tenant.online.controller;
import apelet.common.core.annotation.MyRequestBody;
import apelet.common.core.exception.DataNotFoundException;
import apelet.common.core.object.MyOrderParam;
import apelet.common.core.object.MyPageParam;
import apelet.common.core.object.ResponseResult;
import apelet.common.generator.controller.OnlFormHeadController;
import apelet.common.generator.dto.OnlFormCodeDto;
import apelet.common.generator.dto.OnlFormHeadDto;
import apelet.common.tenant.annotation.SwitchTenantCommonDatasource;
import com.alibaba.fastjson.JSONObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
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 javax.servlet.http.HttpServletResponse;
/**
* 租户端表单元数据接口。
*
* <p>只开放表单元数据的日常维护(查询、保存、删除、代码生成弹窗、元数据选择),
* 不开放会执行 DDL、生成源码、读取服务器目录、同步外部 EAS 等管理端能力。</p>
*
* <p>注意:Spring MVC 会把父类带 {@code @RequestMapping} 的方法一并注册到子类路径下,
* 仅“不覆写”并不能屏蔽接口。因此这里除了覆写白名单方法外,还显式覆写了所有
* 黑名单方法并统一抛出 {@link DataNotFoundException},由全局异常处理器返回 404。</p>
*
* @author apelet
*/
@SwitchTenantCommonDatasource
@Tag(name = "租户表单元数据接口")
@Slf4j
@RestController
@RequestMapping("${common-online.urlPrefix}/tenantOnlFormHead")
public class TenantOnlFormHeadController extends OnlFormHeadController {
/**
* 租户端未开放的接口统一按 404 返回,避免通过状态码区分“不存在”和“无权限”。
*
* @return 404 业务异常。
*/
private DataNotFoundException notOpen() {
return new DataNotFoundException("租户端不开放该接口");
}
/**
* 分页查询。租户维度由父类按当前登录租户过滤(PRD FR-4.15)。
*
* @param dto 过滤对象。
* @param orderParam 排序参数。
* @param pageParam 分页参数。
* @return 应答结果对象。
*/
@Schema(description = "分页查询")
@PostMapping("/page")
@Override
public ResponseResult<?> page(@MyRequestBody OnlFormHeadDto dto,
@MyRequestBody MyOrderParam orderParam,
@MyRequestBody MyPageParam pageParam) {
return super.page(dto, orderParam, pageParam);
}
/**
* 查询主数据和分录数据。
*
* @param id 主数据主键Id。
* @return 应答结果对象。
*/
@Schema(description = "查询主数据和分录数据")
@GetMapping("/listByHeadId")
@Override
public ResponseResult<?> listByHeadId(@RequestParam Long id) {
return super.listByHeadId(id);
}
/**
* 添加主数据和分录数据。
*
* @param dto 新增对象。
* @return 应答结果对象。
*/
@Schema(description = "添加主数据和分录数据")
@PostMapping("/addAll")
@Override
public ResponseResult<?> addAll(@MyRequestBody OnlFormHeadDto dto) {
return super.addAll(dto);
}
/**
* 编辑主数据和分录数据。
*
* @param dto 更新对象。
* @return 应答结果对象。
*/
@Schema(description = "编辑主数据和分录数据")
@PostMapping("/editAll")
@Override
public ResponseResult<?> editAll(@MyRequestBody OnlFormHeadDto dto) {
return super.editAll(dto);
}
/**
* 删除主数据和分录数据。
*
* @param id 主数据主键Id。
* @return 应答结果对象。
*/
@Schema(description = "删除")
@PostMapping("/removeAllById")
@Override
public ResponseResult<?> removeAllById(@MyRequestBody Long id) {
return super.removeAllById(id);
}
/**
* 代码生成按钮弹出信息。
*
* @param id 主数据主键Id。
* @return 应答结果对象。
*/
@Schema(description = "代码生成按钮弹出信息")
@GetMapping("/tableInfo")
@Override
public ResponseResult<?> tableInfo(@RequestParam Long id) {
return super.tableInfo(id);
}
/**
* 查询对象的所有元数据。
*
* @param name 查询对象name。
* @param type 是否查询字段关联的元数据 1:是 0:否。
* @param isChild 是否查询子表 1:是 0:否。
* @return 应答结果对象。
*/
@Schema(description = "查询对象的所有元数据")
@GetMapping("/selectOnlFrom")
@Override
public ResponseResult<?> selectOnlFrom(@RequestParam String name, @RequestParam String type,
@RequestParam(required = false) Integer isChild) {
return super.selectOnlFrom(name, type, isChild);
}
/**
* 租户端不开放:会执行 DDL。
*/
@Schema(description = "租户端不开放")
@GetMapping("/syncDB")
@Override
public ResponseResult<?> syncDB(@RequestParam("id") Long id, @RequestParam("type") Long type) {
throw notOpen();
}
/**
* 租户端不开放:会生成源码。
*/
@Schema(description = "租户端不开放")
@PostMapping("/generate")
@Override
public ResponseResult<?> generate(@RequestBody OnlFormCodeDto onlFormCodeDto) {
throw notOpen();
}
/**
* 租户端不开放:会读取服务器上的生成文件。
*/
@Schema(description = "租户端不开放")
@PostMapping("/download")
@Override
public void download(@RequestBody JSONObject jsonObject, HttpServletResponse response) {
throw notOpen();
}
/**
* 租户端不开放:返回管理端固定字段定义。
*/
@Schema(description = "租户端不开放")
@GetMapping("/defaultData")
@Override
public ResponseResult<?> defaultData(@RequestParam(required = false) String type,
@RequestParam(required = false) String treeType,
@RequestParam(required = false) String metaType,
@RequestParam(required = false) String metaTable) {
throw notOpen();
}
/**
* 租户端不开放:管理端表名判重。
*/
@Schema(description = "租户端不开放")
@GetMapping("/checkTableName")
@Override
public ResponseResult<?> checkTableName(@RequestParam String tableName) {
throw notOpen();
}
/**
* 租户端不开放:会读取服务器目录。
*/
@Schema(description = "租户端不开放")
@GetMapping("/getDir")
@Override
public ResponseResult<?> getDir(@RequestParam(required = false) String dir) {
throw notOpen();
}
/**
* 租户端不开放:会同步外部 EAS 元数据。
*/
@Schema(description = "租户端不开放")
@GetMapping("/onlFormSync")
@Override
public ResponseResult<?> onlFormSync(@RequestParam("billType") String billType, @RequestParam("id") Long id) {
throw notOpen();
}
}
Loading…
Cancel
Save