(improvement)(headless) Add API interface to provide data services to other applications (#561)

Co-authored-by: jolunoluo
This commit is contained in:
LXW
2023-12-21 22:10:29 +08:00
committed by GitHub
parent 7b580b7c94
commit fa38e37be3
40 changed files with 1106 additions and 55 deletions

View File

@@ -0,0 +1,202 @@
package com.tencent.supersonic.headless.model.application;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import com.tencent.supersonic.common.pojo.exception.InvalidArgumentException;
import com.tencent.supersonic.common.pojo.exception.InvalidPermissionException;
import com.tencent.supersonic.common.util.BeanMapper;
import com.tencent.supersonic.common.util.PageUtils;
import com.tencent.supersonic.headless.api.model.enums.AppStatusEnum;
import com.tencent.supersonic.headless.api.model.pojo.AppConfig;
import com.tencent.supersonic.headless.api.model.request.AppQueryReq;
import com.tencent.supersonic.headless.api.model.request.AppReq;
import com.tencent.supersonic.headless.api.model.response.AppDetailResp;
import com.tencent.supersonic.headless.api.model.response.AppResp;
import com.tencent.supersonic.headless.api.model.response.DimensionResp;
import com.tencent.supersonic.headless.api.model.response.MetricResp;
import com.tencent.supersonic.headless.model.domain.AppService;
import com.tencent.supersonic.headless.model.domain.DimensionService;
import com.tencent.supersonic.headless.model.domain.MetricService;
import com.tencent.supersonic.headless.model.domain.dataobject.AppDO;
import com.tencent.supersonic.headless.model.domain.pojo.MetaFilter;
import com.tencent.supersonic.headless.model.infrastructure.mapper.AppMapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
public class AppServiceImpl extends ServiceImpl<AppMapper, AppDO> implements AppService {
private AppMapper appMapper;
private MetricService metricService;
private DimensionService dimensionService;
public AppServiceImpl(AppMapper appMapper, MetricService metricService,
DimensionService dimensionService) {
this.appMapper = appMapper;
this.metricService = metricService;
this.dimensionService = dimensionService;
}
@Override
public AppDetailResp save(AppReq app, User user) {
app.createdBy(user.getName());
AppDO appDO = new AppDO();
BeanMapper.mapper(app, appDO);
appDO.setStatus(AppStatusEnum.INIT.getCode());
appDO.setConfig(JSONObject.toJSONString(app.getConfig()));
appDO.setAppSecret(getUniqueId());
appMapper.insert(appDO);
return convertDetail(appDO);
}
@Override
public AppDetailResp update(AppReq app, User user) {
app.updatedBy(user.getName());
AppDO appDO = getById(app.getId());
checkAuth(appDO, user);
BeanMapper.mapper(app, appDO);
appDO.setConfig(JSONObject.toJSONString(app.getConfig()));
appMapper.updateById(appDO);
return convertDetail(appDO);
}
@Override
public void online(Integer id, User user) {
AppDO appDO = getAppDO(id);
checkAuth(appDO, user);
appDO.setStatus(AppStatusEnum.ONLINE.getCode());
appDO.setUpdatedAt(new Date());
appDO.setUpdatedBy(user.getName());
updateById(appDO);
}
@Override
public void offline(Integer id, User user) {
AppDO appDO = getAppDO(id);
checkAuth(appDO, user);
appDO.setStatus(AppStatusEnum.OFFLINE.getCode());
appDO.setUpdatedAt(new Date());
appDO.setUpdatedBy(user.getName());
updateById(appDO);
}
@Override
public void delete(Integer id, User user) {
AppDO appDO = getAppDO(id);
checkAuth(appDO, user);
appDO.setStatus(AppStatusEnum.DELETED.getCode());
appDO.setUpdatedAt(new Date());
appDO.setUpdatedBy(user.getName());
updateById(appDO);
}
@Override
public PageInfo<AppResp> pageApp(AppQueryReq appQueryReq, User user) {
PageInfo<AppDO> appDOPageInfo = PageHelper.startPage(appQueryReq.getCurrent(),
appQueryReq.getPageSize())
.doSelectPageInfo(() -> queryApp(appQueryReq));
PageInfo<AppResp> appPageInfo = PageUtils.pageInfo2PageInfoVo(appDOPageInfo);
Map<Long, MetricResp> metricResps = metricService.getMetrics(new MetaFilter())
.stream().collect(Collectors.toMap(MetricResp::getId, m -> m));
Map<Long, DimensionResp> dimensionResps = dimensionService.getDimensions(new MetaFilter())
.stream().collect(Collectors.toMap(DimensionResp::getId, m -> m));
appPageInfo.setList(appDOPageInfo.getList().stream().map(appDO
-> convert(appDO, dimensionResps, metricResps, user))
.collect(Collectors.toList()));
return appPageInfo;
}
public List<AppDO> queryApp(AppQueryReq appQueryReq) {
QueryWrapper<AppDO> appDOQueryWrapper = new QueryWrapper<>();
appDOQueryWrapper.lambda().ne(AppDO::getStatus, AppStatusEnum.DELETED.getCode());
if (StringUtils.isNotBlank(appQueryReq.getName())) {
appDOQueryWrapper.lambda().like(AppDO::getName, appQueryReq.getName());
}
if (!CollectionUtils.isEmpty(appQueryReq.getStatus())) {
appDOQueryWrapper.lambda().in(AppDO::getStatus, appQueryReq.getStatus());
}
if (StringUtils.isNotBlank(appQueryReq.getCreatedBy())) {
appDOQueryWrapper.lambda().eq(AppDO::getCreatedBy, appQueryReq.getCreatedBy());
}
return list(appDOQueryWrapper);
}
@Override
public AppDetailResp getApp(Integer id, User user) {
AppDO appDO = getAppDO(id);
checkAuth(appDO, user);
return convertDetail(appDO);
}
@Override
public AppDetailResp getApp(Integer id) {
AppDO appDO = getAppDO(id);
return convertDetail(appDO);
}
private AppDO getAppDO(Integer id) {
AppDO appDO = getById(id);
if (appDO == null) {
throw new InvalidArgumentException("该应用不存在");
}
return appDO;
}
private void checkAuth(AppDO appDO, User user) {
if (!hasAuth(appDO, user)) {
throw new InvalidPermissionException("您不是该应用的负责人, 暂无权查看或者修改");
}
}
private boolean hasAuth(AppDO appDO, User user) {
if (appDO.getCreatedBy().equalsIgnoreCase(user.getName())) {
return true;
}
return StringUtils.isNotBlank(appDO.getOwner())
&& appDO.getOwner().contains(user.getName());
}
private AppResp convert(AppDO appDO, Map<Long, DimensionResp> dimensionMap,
Map<Long, MetricResp> metricMap, User user) {
AppResp app = new AppResp();
BeanMapper.mapper(appDO, app);
AppConfig appConfig = JSONObject.parseObject(appDO.getConfig(), AppConfig.class);
appConfig.getItems().forEach(metricItem -> {
metricItem.setName(metricMap.getOrDefault(metricItem.getId(), new MetricResp()).getName());
metricItem.getRelateItems().forEach(dimensionItem -> {
dimensionItem.setName(dimensionMap.getOrDefault(dimensionItem.getId(), new DimensionResp()).getName());
});
});
app.setConfig(appConfig);
app.setAppStatus(AppStatusEnum.fromCode(appDO.getStatus()));
app.setHasAdminRes(hasAuth(appDO, user));
return app;
}
private AppDetailResp convertDetail(AppDO appDO) {
AppDetailResp app = new AppDetailResp();
BeanMapper.mapper(appDO, app);
app.setConfig(JSONObject.parseObject(appDO.getConfig(), AppConfig.class));
app.setAppStatus(AppStatusEnum.fromCode(appDO.getStatus()));
return app;
}
private String getUniqueId() {
return UUID.randomUUID().toString().replaceAll("_", "");
}
}

View File

@@ -242,7 +242,8 @@ public class MetricServiceImpl implements MetricService {
return metricResp;
}
private MetricResp getMetric(Long id) {
@Override
public MetricResp getMetric(Long id) {
MetricDO metricDO = metricRepository.getMetricById(id);
if (metricDO == null) {
return null;

View File

@@ -0,0 +1,28 @@
package com.tencent.supersonic.headless.model.domain;
import com.github.pagehelper.PageInfo;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import com.tencent.supersonic.headless.api.model.request.AppQueryReq;
import com.tencent.supersonic.headless.api.model.request.AppReq;
import com.tencent.supersonic.headless.api.model.response.AppDetailResp;
import com.tencent.supersonic.headless.api.model.response.AppResp;
public interface AppService {
AppDetailResp save(AppReq app, User user);
AppDetailResp update(AppReq app, User user);
void online(Integer id, User user);
void offline(Integer id, User user);
void delete(Integer id, User user);
PageInfo<AppResp> pageApp(AppQueryReq appQueryReq, User user);
AppDetailResp getApp(Integer id, User user);
AppDetailResp getApp(Integer id);
}

View File

@@ -34,6 +34,8 @@ public interface MetricService {
MetricResp getMetric(Long id, User user);
MetricResp getMetric(Long id);
List<String> mockAlias(MetricReq metricReq, String mockType, User user);
Set<String> getMetricTags();

View File

@@ -0,0 +1,41 @@
package com.tencent.supersonic.headless.model.domain.dataobject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
@Data
@TableName("s2_app")
public class AppDO {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String description;
private String config;
private Date endDate;
private Integer qps;
private String owner;
private Integer status;
private String appSecret;
private String createdBy;
private String updatedBy;
private Date createdAt;
private Date updatedAt;
}

View File

@@ -22,6 +22,12 @@ public class MetricQueryDefaultConfigDO {
private String defaultConfig;
private String appKey;
private String appSecret;
private String owner;
private Date createdAt;
private String createdBy;

View File

@@ -0,0 +1,10 @@
package com.tencent.supersonic.headless.model.infrastructure.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tencent.supersonic.headless.model.domain.dataobject.AppDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface AppMapper extends BaseMapper<AppDO> {
}

View File

@@ -0,0 +1,84 @@
package com.tencent.supersonic.headless.model.rest;
import com.github.pagehelper.PageInfo;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import com.tencent.supersonic.auth.api.authentication.utils.UserHolder;
import com.tencent.supersonic.headless.api.model.request.AppQueryReq;
import com.tencent.supersonic.headless.api.model.request.AppReq;
import com.tencent.supersonic.headless.api.model.response.AppDetailResp;
import com.tencent.supersonic.headless.api.model.response.AppResp;
import com.tencent.supersonic.headless.model.domain.AppService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RestController
@RequestMapping("/api/semantic/app")
public class AppController {
@Autowired
private AppService appService;
@PostMapping
public boolean save(@RequestBody AppReq app,
HttpServletRequest request, HttpServletResponse response) {
User user = UserHolder.findUser(request, response);
appService.save(app, user);
return true;
}
@PutMapping
public boolean update(@RequestBody AppReq app,
HttpServletRequest request, HttpServletResponse response) {
User user = UserHolder.findUser(request, response);
appService.update(app, user);
return true;
}
@PutMapping("/online/{id}")
public boolean online(@PathVariable("id") Integer id,
HttpServletRequest request, HttpServletResponse response) {
User user = UserHolder.findUser(request, response);
appService.online(id, user);
return true;
}
@PutMapping("/offline/{id}")
public boolean offline(@PathVariable("id") Integer id,
HttpServletRequest request, HttpServletResponse response) {
User user = UserHolder.findUser(request, response);
appService.offline(id, user);
return true;
}
@DeleteMapping("/{id}")
public boolean delete(@PathVariable("id") Integer id,
HttpServletRequest request, HttpServletResponse response) {
User user = UserHolder.findUser(request, response);
appService.delete(id, user);
return true;
}
@GetMapping("/{id}")
public AppDetailResp getApp(@PathVariable("id") Integer id,
HttpServletRequest request, HttpServletResponse response) {
User user = UserHolder.findUser(request, response);
return appService.getApp(id, user);
}
@PostMapping("/page")
public PageInfo<AppResp> pageApp(@RequestBody AppQueryReq appQueryReq,
HttpServletRequest request, HttpServletResponse response) {
User user = UserHolder.findUser(request, response);
return appService.pageApp(appQueryReq, user);
}
}

View File

@@ -4,6 +4,7 @@ import com.google.common.collect.Lists;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import com.tencent.supersonic.auth.api.authentication.service.UserService;
import com.tencent.supersonic.common.pojo.enums.AggOperatorEnum;
import com.tencent.supersonic.common.pojo.enums.StatusEnum;
import com.tencent.supersonic.headless.api.model.enums.DimensionTypeEnum;
import com.tencent.supersonic.headless.api.model.enums.IdentifyTypeEnum;
import com.tencent.supersonic.headless.api.model.pojo.Dim;
@@ -13,14 +14,14 @@ import com.tencent.supersonic.headless.api.model.pojo.Measure;
import com.tencent.supersonic.headless.api.model.pojo.ModelDetail;
import com.tencent.supersonic.headless.api.model.request.ModelReq;
import com.tencent.supersonic.headless.api.model.response.ModelResp;
import com.tencent.supersonic.headless.model.domain.DatabaseService;
import com.tencent.supersonic.headless.model.domain.DimensionService;
import com.tencent.supersonic.headless.model.domain.DomainService;
import com.tencent.supersonic.headless.model.domain.ModelService;
import com.tencent.supersonic.headless.model.domain.repository.DateInfoRepository;
import com.tencent.supersonic.headless.model.domain.DatabaseService;
import com.tencent.supersonic.headless.model.domain.MetricService;
import com.tencent.supersonic.headless.model.domain.ModelRelaService;
import com.tencent.supersonic.headless.model.domain.ModelService;
import com.tencent.supersonic.headless.model.domain.dataobject.ModelDO;
import com.tencent.supersonic.headless.model.domain.repository.DateInfoRepository;
import com.tencent.supersonic.headless.model.domain.repository.ModelRepository;
import com.tencent.supersonic.headless.model.domain.utils.ModelConverter;
import org.junit.jupiter.api.Assertions;
@@ -160,6 +161,8 @@ class ModelServiceImplTest {
modelResp.setAlias("访问次数统计,PVUV统计");
modelResp.setAdmins(Lists.newArrayList("admin", "tom"));
modelResp.setViewers(Lists.newArrayList("alice", "lucy"));
modelResp.setStatus(StatusEnum.ONLINE.getCode());
modelResp.createdBy("admin");
ModelDetail modelDetail = new ModelDetail();
List<Identify> identifiers = new ArrayList<>();
identifiers.add(new Identify("用户名", IdentifyTypeEnum.primary.name(), "user_name"));