Aias00 commented on code in PR #6145:
URL: https://github.com/apache/shenyu/pull/6145#discussion_r2371280892
##########
shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/AbstractDataChangedListener.java:
##########
@@ -190,6 +197,16 @@ public void onPluginChanged(final List<PluginData>
changed, final DataEventTypeE
* @param eventType the event type
*/
protected void afterPluginChanged(final List<PluginData> changed, final
DataEventTypeEnum eventType, final String namespaceId) {
+ // When AI Proxy plugin is synchronized, also refresh proxy apikey
cache so it is included in the same sync cycle
+ if (CollectionUtils.isEmpty(changed)) {
+ return;
+ }
+ boolean containsAiProxy = changed.stream()
+ .filter(Objects::nonNull)
+ .anyMatch(p ->
org.apache.commons.lang3.StringUtils.equalsIgnoreCase(p.getName(),
org.apache.shenyu.common.enums.PluginEnum.AI_PROXY.getName()));
Review Comment:
just import the class, not like this:
"org.apache.commons.lang3.StringUtils.equalsIgnoreCase"
##########
shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/AiProxySelectorResolverInvalidator.java:
##########
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.admin.listener;
+
+import org.apache.shenyu.admin.service.support.AiProxyRealKeyResolver;
+import org.apache.shenyu.common.dto.ProxySelectorData;
+import org.apache.shenyu.common.dto.SelectorData;
+import org.apache.shenyu.common.enums.DataEventTypeEnum;
+import org.apache.shenyu.common.enums.PluginEnum;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.support.TransactionSynchronization;
+import
org.springframework.transaction.support.TransactionSynchronizationManager;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Invalidate AiProxy real-key resolver cache only when aiProxy selector
changes.
+ */
+@Component
+public class AiProxySelectorResolverInvalidator implements DataChangedListener
{
+
+ private static final Logger LOG =
LoggerFactory.getLogger(AiProxySelectorResolverInvalidator.class);
+
+ private final AiProxyRealKeyResolver resolver;
+
+ private final org.apache.shenyu.admin.service.AiProxyConnectionService
aiProxyConnectionService;
+
+ public AiProxySelectorResolverInvalidator(final AiProxyRealKeyResolver
resolver,
+ final
org.apache.shenyu.admin.service.AiProxyConnectionService
aiProxyConnectionService) {
+ this.resolver = resolver;
+ this.aiProxyConnectionService = aiProxyConnectionService;
+ }
+
+ @Override
+ public void onSelectorChanged(final List<SelectorData> changed, final
DataEventTypeEnum eventType) {
+ if (Objects.isNull(changed) || changed.isEmpty()) {
Review Comment:
CollectionUtils.isEmpty
##########
shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/AiProxyApiKeyController.java:
##########
@@ -0,0 +1,214 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.admin.controller;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.shenyu.admin.aspect.annotation.RestApi;
+import org.apache.shenyu.admin.model.dto.BatchCommonDTO;
+import org.apache.shenyu.admin.model.dto.ProxyApiKeyDTO;
+import org.apache.shenyu.admin.model.page.CommonPager;
+import org.apache.shenyu.admin.model.page.PageParameter;
+import org.apache.shenyu.admin.model.query.ProxyApiKeyQuery;
+import org.apache.shenyu.admin.model.result.ShenyuAdminResult;
+import org.apache.shenyu.admin.model.vo.ProxyApiKeyVO;
+import org.apache.shenyu.admin.service.AiProxyApiKeyService;
+import org.apache.shenyu.admin.utils.ShenyuResultMessage;
+import org.apache.shenyu.admin.service.PageService;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+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.RequestParam;
+
+import org.apache.shenyu.admin.model.dto.BatchIdsDTO;
+import org.apache.shenyu.admin.mapper.SelectorMapper;
+import org.apache.shenyu.admin.model.entity.SelectorDO;
+import org.apache.shenyu.common.constant.AdminConstants;
+import java.util.ArrayList;
+import java.util.Objects;
+
+/** AiProxyApiKeyController. */
+@RestApi("/selector/{selectorId}/ai-proxy-apikey")
+public class AiProxyApiKeyController implements
PagedController<ProxyApiKeyQuery, ProxyApiKeyVO> {
+
+ private final AiProxyApiKeyService aiProxyApiKeyService;
+
+ private final SelectorMapper selectorMapper;
+
+ public AiProxyApiKeyController(final AiProxyApiKeyService
aiProxyApiKeyService, final SelectorMapper selectorMapper) {
+ this.aiProxyApiKeyService = aiProxyApiKeyService;
+ this.selectorMapper = selectorMapper;
+ }
+
+ /**
+ * Create or bind real->proxy api key mapping.
+ *
+ * @param selectorId selector id
+ * @param dto dto
+ * @return result
+ */
+ @PostMapping
+ @RequiresPermissions("system:aiProxyApiKey:add")
+ public ShenyuAdminResult create(@PathVariable("selectorId") final String
selectorId,
+ @Valid @RequestBody final ProxyApiKeyDTO
dto) {
+ // derive namespaceId from selector to avoid mismatch
+ final SelectorDO selector = selectorMapper.selectById(selectorId);
+ if (Objects.nonNull(selector)) {
+ dto.setNamespaceId(selector.getNamespaceId());
+ }
+ int rows = aiProxyApiKeyService.create(dto, selectorId);
+ if (rows > 0) {
+ final ProxyApiKeyVO vo =
aiProxyApiKeyService.findById(dto.getId());
+ return
ShenyuAdminResult.success(ShenyuResultMessage.CREATE_SUCCESS, vo);
+ }
+ return ShenyuAdminResult.error(ShenyuResultMessage.PARAMETER_ERROR);
+ }
+
+ /**
+ * List mappings by page.
+ *
+ * @param selectorId selector id
+ * @param namespaceId namespace id
+ * @param currentPage current page
+ * @param pageSize page size
+ * @param proxyApiKey optional filter
+ * @return list pager
+ */
+ @GetMapping
+ @RequiresPermissions("system:aiProxyApiKey:list")
+ public ShenyuAdminResult list(
+ @PathVariable("selectorId") final String selectorId,
+ @RequestParam(value = "namespaceId", required = false) final
String namespaceId,
+ @RequestParam("currentPage") @NotNull final Integer currentPage,
+ @RequestParam("pageSize") @NotNull final Integer pageSize,
+ @RequestParam(value = "proxyApiKey", required = false) final
String proxyApiKey) {
+ // derive namespaceId from selectorId to ensure filtering is bound to
selector
+ final SelectorDO selector = selectorMapper.selectById(selectorId);
+ if (Objects.isNull(selector)) {
+ return ShenyuAdminResult.error(AdminConstants.ID_NOT_EXIST);
+ }
+ ProxyApiKeyQuery query = new ProxyApiKeyQuery();
+ // only filter by selectorId to ensure strict scoping
+ query.setSelectorId(selectorId);
+ query.setNamespaceId(selector.getNamespaceId());
+ query.setProxyApiKey(proxyApiKey);
+ query.setPageParameter(new PageParameter(currentPage, pageSize));
+ CommonPager<ProxyApiKeyVO> pager =
aiProxyApiKeyService.listByPage(query);
+ return ShenyuAdminResult.success(ShenyuResultMessage.QUERY_SUCCESS,
pager);
+ }
+
+ /**
+ * Update mapping description or enabled flag.
+ *
+ * @param selectorId selector id
+ * @param id id
+ * @param dto dto
+ * @return result
+ */
+ @PutMapping("/{id}")
+ @RequiresPermissions("system:aiProxyApiKey:edit")
+ public ShenyuAdminResult update(
+ @PathVariable("selectorId") final String selectorId,
+ @PathVariable("id") final String id, @RequestBody final
ProxyApiKeyDTO dto) {
+ final ProxyApiKeyVO exist = aiProxyApiKeyService.findById(id);
+ if (Objects.isNull(exist)) {
+ return ShenyuAdminResult.error(AdminConstants.ID_NOT_EXIST);
+ }
+ if (!selectorId.equals(exist.getSelectorId())) {
+ return
ShenyuAdminResult.error(AdminConstants.PROXY_SELECTOR_ID_IS_NOT_EXIST);
+ }
+ dto.setId(id);
+ int rows = aiProxyApiKeyService.update(dto);
+ return rows > 0
+ ? ShenyuAdminResult.success(ShenyuResultMessage.UPDATE_SUCCESS)
+ : ShenyuAdminResult.error(ShenyuResultMessage.PARAMETER_ERROR);
+ }
+
+ /**
+ * Batch delete.
+ *
+ * @param selectorId selector id
+ * @param request ids container
+ * @return result
+ */
+ @PostMapping("/batchDelete")
+ @RequiresPermissions("system:aiProxyApiKey:delete")
+ public ShenyuAdminResult batchDelete(@PathVariable("selectorId") final
String selectorId,
+ @Valid @RequestBody final BatchIdsDTO
request) {
+ final java.util.List<String> allIds = request.getIds();
+ final java.util.List<String> validIds = new ArrayList<>();
+ if (Objects.nonNull(allIds)) {
+ for (String id : allIds) {
+ final ProxyApiKeyVO exist = aiProxyApiKeyService.findById(id);
+ if (Objects.nonNull(exist) &&
selectorId.equals(exist.getSelectorId())) {
+ validIds.add(id);
+ }
+ }
+ }
+ if (validIds.isEmpty()) {
+ return
ShenyuAdminResult.error(AdminConstants.PROXY_SELECTOR_ID_IS_NOT_EXIST);
+ }
+ int rows = aiProxyApiKeyService.delete(validIds);
+ return rows > 0
+ ? ShenyuAdminResult.success(ShenyuResultMessage.DELETE_SUCCESS)
+ : ShenyuAdminResult.error(ShenyuResultMessage.DELETE_FAIL);
+ }
+
+ /**
+ * Batch enable/disable.
+ *
+ * @param selectorId selector id
+ * @param batchCommonDTO ids and enabled
+ * @return result
+ */
+ @PostMapping("/batchEnabled")
+ @RequiresPermissions("system:aiProxyApiKey:disable")
+ public ShenyuAdminResult batchEnabled(@PathVariable("selectorId") final
String selectorId,
+ @Valid @RequestBody final
BatchCommonDTO batchCommonDTO) {
+ final java.util.List<String> allIds = batchCommonDTO.getIds();
Review Comment:
not use "java.util.List", just import
##########
shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/AiProxySelectorResolverInvalidator.java:
##########
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.admin.listener;
+
+import org.apache.shenyu.admin.service.support.AiProxyRealKeyResolver;
+import org.apache.shenyu.common.dto.ProxySelectorData;
+import org.apache.shenyu.common.dto.SelectorData;
+import org.apache.shenyu.common.enums.DataEventTypeEnum;
+import org.apache.shenyu.common.enums.PluginEnum;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.support.TransactionSynchronization;
+import
org.springframework.transaction.support.TransactionSynchronizationManager;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Invalidate AiProxy real-key resolver cache only when aiProxy selector
changes.
+ */
+@Component
+public class AiProxySelectorResolverInvalidator implements DataChangedListener
{
+
+ private static final Logger LOG =
LoggerFactory.getLogger(AiProxySelectorResolverInvalidator.class);
+
+ private final AiProxyRealKeyResolver resolver;
+
+ private final org.apache.shenyu.admin.service.AiProxyConnectionService
aiProxyConnectionService;
+
+ public AiProxySelectorResolverInvalidator(final AiProxyRealKeyResolver
resolver,
+ final
org.apache.shenyu.admin.service.AiProxyConnectionService
aiProxyConnectionService) {
+ this.resolver = resolver;
+ this.aiProxyConnectionService = aiProxyConnectionService;
+ }
+
+ @Override
+ public void onSelectorChanged(final List<SelectorData> changed, final
DataEventTypeEnum eventType) {
+ if (Objects.isNull(changed) || changed.isEmpty()) {
+ return;
+ }
+ for (SelectorData d : changed) {
+ if (Objects.isNull(d) || Objects.isNull(d.getId())) {
+ continue;
+ }
+ if (!PluginEnum.AI_PROXY.getName().equals(d.getPluginName())) {
+ continue;
+ }
+ resolver.invalidate(d.getId());
+ LOG.info("[AiProxyResolverInvalidator] invalidated selectorId={}
due to {} (selector)", d.getId(), eventType);
+ try {
+ if
(TransactionSynchronizationManager.isSynchronizationActive()) {
+
TransactionSynchronizationManager.registerSynchronization(new
TransactionSynchronization() {
+ @Override
+ public void afterCommit() {
+
aiProxyConnectionService.refreshApiKeysBySelectorId(d.getId());
+ }
+ });
+ } else {
+
aiProxyConnectionService.refreshApiKeysBySelectorId(d.getId());
+ }
+ } catch (Exception ex) {
+ LOG.warn("[AiProxyResolverInvalidator] failed to refresh
AI_PROXY_API_KEY for selectorId={}: {}", d.getId(), ex.getMessage());
+ }
+ }
+ }
+
+ @Override
+ public void onProxySelectorChanged(final List<ProxySelectorData> changed,
final DataEventTypeEnum eventType) {
+ if (Objects.isNull(changed) || changed.isEmpty()) {
Review Comment:
change this
##########
shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/AiProxyApiKeyServiceImpl.java:
##########
@@ -0,0 +1,287 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.admin.service.impl;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.shenyu.admin.listener.DataChangedEvent;
+import org.apache.shenyu.admin.mapper.AiProxyApiKeyMapper;
+import org.apache.shenyu.admin.model.dto.ProxyApiKeyDTO;
+import org.apache.shenyu.admin.model.entity.ProxyApiKeyDO;
+import org.apache.shenyu.admin.model.page.CommonPager;
+import org.apache.shenyu.admin.model.page.PageParameter;
+import org.apache.shenyu.admin.model.query.ProxyApiKeyQuery;
+import org.apache.shenyu.admin.model.vo.ProxyApiKeyVO;
+import org.apache.shenyu.admin.service.AiProxyApiKeyService;
+import org.apache.shenyu.admin.transfer.ProxyApiKeyTransfer;
+import org.apache.shenyu.admin.service.support.AiProxyRealKeyResolver;
+import org.apache.shenyu.admin.utils.ShenyuResultMessage;
+import org.apache.shenyu.common.dto.ProxyApiKeyData;
+import org.apache.shenyu.common.enums.ConfigGroupEnum;
+import org.apache.shenyu.common.enums.DataEventTypeEnum;
+import org.apache.shenyu.common.utils.SignUtils;
+import org.apache.shenyu.common.utils.UUIDUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.shenyu.common.constant.Constants;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import org.apache.shenyu.common.constant.AdminConstants;
+import org.apache.shenyu.common.exception.ShenyuException;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/** Implementation of AiProxyApiKeyService. */
+@Service
+public class AiProxyApiKeyServiceImpl implements AiProxyApiKeyService {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(AiProxyApiKeyServiceImpl.class);
+
+ private final AiProxyApiKeyMapper mapper;
+
+ private final ApplicationEventPublisher eventPublisher;
+
+ private final AiProxyRealKeyResolver realKeyResolver;
+
+ @Autowired
+ public AiProxyApiKeyServiceImpl(
+ final AiProxyApiKeyMapper mapper, final ApplicationEventPublisher
eventPublisher,
+ final AiProxyRealKeyResolver realKeyResolver) {
+ this.mapper = mapper;
+ this.eventPublisher = eventPublisher;
+ this.realKeyResolver = realKeyResolver;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public int create(final ProxyApiKeyDTO dto, final String selectorId) {
+ final ProxyApiKeyDO entity =
ProxyApiKeyTransfer.INSTANCE.mapToEntity(dto);
+ if (StringUtils.isBlank(entity.getId())) {
+ entity.setId(UUIDUtils.getInstance().generateShortUuid());
+ }
+ if (StringUtils.isBlank(entity.getProxyApiKey())) {
+ entity.setProxyApiKey(SignUtils.generateKey());
+ }
+ // validate namespace
+ if (StringUtils.isBlank(entity.getNamespaceId())) {
+ throw new ShenyuException(ShenyuResultMessage.PARAMETER_ERROR);
+ }
+ // validate selector id
+ if (StringUtils.isBlank(selectorId)) {
+ throw new ShenyuException(ShenyuResultMessage.PARAMETER_ERROR);
+ }
+ entity.setSelectorId(selectorId);
+ // unique check for proxyApiKey if provided
+ if (StringUtils.isNotBlank(entity.getProxyApiKey()) &&
Boolean.TRUE.equals(mapper.proxyApiKeyExisted(selectorId,
entity.getProxyApiKey()))) {
+ throw new
ShenyuException(ShenyuResultMessage.UNIQUE_INDEX_CONFLICT_ERROR);
+ }
+ if (Objects.isNull(entity.getEnabled())) {
+ entity.setEnabled(Boolean.TRUE);
+ }
+ // back fill generated fields to response dto first
+ dto.setId(entity.getId());
+ dto.setProxyApiKey(entity.getProxyApiKey());
+ dto.setEnabled(entity.getEnabled());
+ final int rows = mapper.insert(entity);
+ publishChange(DataEventTypeEnum.CREATE, entity);
+ return rows;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public int update(final ProxyApiKeyDTO dto) {
+ final ProxyApiKeyDO entity =
ProxyApiKeyTransfer.INSTANCE.mapToEntity(dto);
+ // id is required for update
+ if (Objects.isNull(entity) || StringUtils.isBlank(entity.getId())) {
+ return 0;
+ }
+ int rows = mapper.updateSelective(entity);
+ publishChange(DataEventTypeEnum.UPDATE, entity);
+ return rows;
+ }
+
+ @Override
+ public ProxyApiKeyVO findById(final String id) {
+ final ProxyApiKeyVO vo =
ProxyApiKeyTransfer.INSTANCE.mapToVO(mapper.selectById(id));
+ if (Objects.nonNull(vo)) {
+ final String real =
realKeyResolver.resolveRealKey(vo.getSelectorId()).orElse(null);
+ vo.setRealApiKey(real);
+ }
+ return vo;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public int delete(final List<String> ids) {
+ final List<ProxyApiKeyDO> toDelete = mapper.selectByIds(ids);
+ int rows = mapper.deleteByIds(ids);
+ if (rows > 0 && Objects.nonNull(toDelete) && !toDelete.isEmpty()) {
+ publishChangeList(DataEventTypeEnum.DELETE, toDelete);
+ }
+ return rows;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public String enabled(final List<String> ids, final Boolean enabled) {
+ if (Objects.isNull(ids) || ids.isEmpty() || Objects.isNull(enabled)) {
+ return ShenyuResultMessage.PARAMETER_ERROR;
+ }
+ int rows = mapper.updateEnableBatch(ids, enabled);
+ if (rows > 0) {
+ final List<ProxyApiKeyDO> updated = mapper.selectByIds(ids);
+ if (Objects.nonNull(updated) && !updated.isEmpty()) {
+ for (ProxyApiKeyDO e : updated) {
+ e.setEnabled(enabled);
+ }
+ publishChangeList(DataEventTypeEnum.UPDATE, updated);
+ }
+ return StringUtils.EMPTY;
+ }
+ return AdminConstants.ID_NOT_EXIST;
+ }
+
+ @Override
+ public CommonPager<ProxyApiKeyVO> listByPage(final ProxyApiKeyQuery query)
{
+ final int current = query.getPageParameter().getCurrentPage();
+ final int size = query.getPageParameter().getPageSize();
+ PageHelper.startPage(current, size);
+ final List<ProxyApiKeyVO> list = mapper.selectByCondition(query);
+ final PageInfo<ProxyApiKeyVO> pageInfo = new PageInfo<>(list);
+ return new CommonPager<>(new PageParameter(current, size, (int)
pageInfo.getTotal()), list);
+ }
+
+ @Override
+ public List<ProxyApiKeyVO> searchByCondition(final ProxyApiKeyQuery
condition) {
+ return mapper.selectByCondition(condition);
+ }
+
+ // ===================== sync & listAll =====================
+
+ @Override
+ public List<ProxyApiKeyData> listAll() {
+ List<ProxyApiKeyData> list =
mapper.selectAll().stream().map(this::convert).collect(Collectors.toList());
+ LOG.info("[AiProxySync] listAll size:{}", list.size());
+ return list;
+ }
+
+ @Override
+ public void syncData() {
+ // group by namespace and publish REFRESH respectively
+ List<ProxyApiKeyDO> all = mapper.selectAll();
+ if (Objects.isNull(all) || all.isEmpty()) {
+ return;
+ }
+ LOG.info("[AiProxySync] syncData triggered, total records:{}",
all.size());
+ all.stream()
+ .collect(Collectors.groupingBy(ProxyApiKeyDO::getNamespaceId))
+ .values()
+ .forEach(list -> publishRefresh(list));
+ }
+
+ @Override
+ public void syncDataByNamespaceId(final String namespaceId) {
+ final String target = normalizeNamespace(namespaceId);
+ List<ProxyApiKeyDO> all = mapper.selectAll();
+ List<ProxyApiKeyDO> list = Objects.isNull(all) ?
java.util.Collections.emptyList()
+ : all.stream()
+ .filter(e ->
StringUtils.equals(normalizeNamespace(e.getNamespaceId()), target))
+ .collect(Collectors.toList());
+ LOG.info("[AiProxySync] syncDataByNamespaceId {}, normalized:{},
matched:{} of total:{}",
+ namespaceId, target, list.size(), Objects.isNull(all) ? 0 :
all.size());
+ if (list.isEmpty()) {
+ return;
+ }
+ publishRefresh(list);
+ }
+
+ // ===================== private utils =====================
Review Comment:
change the comment format
##########
shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/SyncDataServiceImpl.java:
##########
@@ -205,6 +217,10 @@ public boolean syncPluginData(final String namespaceId,
final String pluginId) {
eventPublisher.publishEvent(new
DataChangedEvent(ConfigGroupEnum.RULE, DataEventTypeEnum.REFRESH,
allRuleDataList));
}
+ if ("aiProxy".equalsIgnoreCase(namespacePluginVO.getName())) {
Review Comment:
use constant
##########
shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/AiProxyApiKeyController.java:
##########
@@ -0,0 +1,214 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.admin.controller;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.shenyu.admin.aspect.annotation.RestApi;
+import org.apache.shenyu.admin.model.dto.BatchCommonDTO;
+import org.apache.shenyu.admin.model.dto.ProxyApiKeyDTO;
+import org.apache.shenyu.admin.model.page.CommonPager;
+import org.apache.shenyu.admin.model.page.PageParameter;
+import org.apache.shenyu.admin.model.query.ProxyApiKeyQuery;
+import org.apache.shenyu.admin.model.result.ShenyuAdminResult;
+import org.apache.shenyu.admin.model.vo.ProxyApiKeyVO;
+import org.apache.shenyu.admin.service.AiProxyApiKeyService;
+import org.apache.shenyu.admin.utils.ShenyuResultMessage;
+import org.apache.shenyu.admin.service.PageService;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+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.RequestParam;
+
+import org.apache.shenyu.admin.model.dto.BatchIdsDTO;
+import org.apache.shenyu.admin.mapper.SelectorMapper;
+import org.apache.shenyu.admin.model.entity.SelectorDO;
+import org.apache.shenyu.common.constant.AdminConstants;
+import java.util.ArrayList;
+import java.util.Objects;
+
+/** AiProxyApiKeyController. */
+@RestApi("/selector/{selectorId}/ai-proxy-apikey")
+public class AiProxyApiKeyController implements
PagedController<ProxyApiKeyQuery, ProxyApiKeyVO> {
+
+ private final AiProxyApiKeyService aiProxyApiKeyService;
+
+ private final SelectorMapper selectorMapper;
+
+ public AiProxyApiKeyController(final AiProxyApiKeyService
aiProxyApiKeyService, final SelectorMapper selectorMapper) {
+ this.aiProxyApiKeyService = aiProxyApiKeyService;
+ this.selectorMapper = selectorMapper;
+ }
+
+ /**
+ * Create or bind real->proxy api key mapping.
+ *
+ * @param selectorId selector id
+ * @param dto dto
+ * @return result
+ */
+ @PostMapping
+ @RequiresPermissions("system:aiProxyApiKey:add")
+ public ShenyuAdminResult create(@PathVariable("selectorId") final String
selectorId,
+ @Valid @RequestBody final ProxyApiKeyDTO
dto) {
+ // derive namespaceId from selector to avoid mismatch
+ final SelectorDO selector = selectorMapper.selectById(selectorId);
+ if (Objects.nonNull(selector)) {
+ dto.setNamespaceId(selector.getNamespaceId());
+ }
+ int rows = aiProxyApiKeyService.create(dto, selectorId);
+ if (rows > 0) {
+ final ProxyApiKeyVO vo =
aiProxyApiKeyService.findById(dto.getId());
+ return
ShenyuAdminResult.success(ShenyuResultMessage.CREATE_SUCCESS, vo);
+ }
+ return ShenyuAdminResult.error(ShenyuResultMessage.PARAMETER_ERROR);
+ }
+
+ /**
+ * List mappings by page.
+ *
+ * @param selectorId selector id
+ * @param namespaceId namespace id
+ * @param currentPage current page
+ * @param pageSize page size
+ * @param proxyApiKey optional filter
+ * @return list pager
+ */
+ @GetMapping
+ @RequiresPermissions("system:aiProxyApiKey:list")
+ public ShenyuAdminResult list(
+ @PathVariable("selectorId") final String selectorId,
+ @RequestParam(value = "namespaceId", required = false) final
String namespaceId,
+ @RequestParam("currentPage") @NotNull final Integer currentPage,
+ @RequestParam("pageSize") @NotNull final Integer pageSize,
+ @RequestParam(value = "proxyApiKey", required = false) final
String proxyApiKey) {
+ // derive namespaceId from selectorId to ensure filtering is bound to
selector
+ final SelectorDO selector = selectorMapper.selectById(selectorId);
+ if (Objects.isNull(selector)) {
+ return ShenyuAdminResult.error(AdminConstants.ID_NOT_EXIST);
+ }
+ ProxyApiKeyQuery query = new ProxyApiKeyQuery();
+ // only filter by selectorId to ensure strict scoping
+ query.setSelectorId(selectorId);
+ query.setNamespaceId(selector.getNamespaceId());
+ query.setProxyApiKey(proxyApiKey);
+ query.setPageParameter(new PageParameter(currentPage, pageSize));
+ CommonPager<ProxyApiKeyVO> pager =
aiProxyApiKeyService.listByPage(query);
+ return ShenyuAdminResult.success(ShenyuResultMessage.QUERY_SUCCESS,
pager);
+ }
+
+ /**
+ * Update mapping description or enabled flag.
+ *
+ * @param selectorId selector id
+ * @param id id
+ * @param dto dto
+ * @return result
+ */
+ @PutMapping("/{id}")
+ @RequiresPermissions("system:aiProxyApiKey:edit")
+ public ShenyuAdminResult update(
+ @PathVariable("selectorId") final String selectorId,
+ @PathVariable("id") final String id, @RequestBody final
ProxyApiKeyDTO dto) {
+ final ProxyApiKeyVO exist = aiProxyApiKeyService.findById(id);
+ if (Objects.isNull(exist)) {
+ return ShenyuAdminResult.error(AdminConstants.ID_NOT_EXIST);
+ }
+ if (!selectorId.equals(exist.getSelectorId())) {
+ return
ShenyuAdminResult.error(AdminConstants.PROXY_SELECTOR_ID_IS_NOT_EXIST);
+ }
+ dto.setId(id);
+ int rows = aiProxyApiKeyService.update(dto);
+ return rows > 0
+ ? ShenyuAdminResult.success(ShenyuResultMessage.UPDATE_SUCCESS)
+ : ShenyuAdminResult.error(ShenyuResultMessage.PARAMETER_ERROR);
+ }
+
+ /**
+ * Batch delete.
+ *
+ * @param selectorId selector id
+ * @param request ids container
+ * @return result
+ */
+ @PostMapping("/batchDelete")
+ @RequiresPermissions("system:aiProxyApiKey:delete")
+ public ShenyuAdminResult batchDelete(@PathVariable("selectorId") final
String selectorId,
+ @Valid @RequestBody final BatchIdsDTO
request) {
+ final java.util.List<String> allIds = request.getIds();
+ final java.util.List<String> validIds = new ArrayList<>();
+ if (Objects.nonNull(allIds)) {
+ for (String id : allIds) {
+ final ProxyApiKeyVO exist = aiProxyApiKeyService.findById(id);
+ if (Objects.nonNull(exist) &&
selectorId.equals(exist.getSelectorId())) {
+ validIds.add(id);
+ }
+ }
+ }
+ if (validIds.isEmpty()) {
+ return
ShenyuAdminResult.error(AdminConstants.PROXY_SELECTOR_ID_IS_NOT_EXIST);
+ }
+ int rows = aiProxyApiKeyService.delete(validIds);
+ return rows > 0
+ ? ShenyuAdminResult.success(ShenyuResultMessage.DELETE_SUCCESS)
+ : ShenyuAdminResult.error(ShenyuResultMessage.DELETE_FAIL);
+ }
+
+ /**
+ * Batch enable/disable.
+ *
+ * @param selectorId selector id
+ * @param batchCommonDTO ids and enabled
+ * @return result
+ */
+ @PostMapping("/batchEnabled")
+ @RequiresPermissions("system:aiProxyApiKey:disable")
+ public ShenyuAdminResult batchEnabled(@PathVariable("selectorId") final
String selectorId,
+ @Valid @RequestBody final
BatchCommonDTO batchCommonDTO) {
+ final java.util.List<String> allIds = batchCommonDTO.getIds();
+ final java.util.List<String> validIds = new ArrayList<>();
+ if (Objects.nonNull(allIds)) {
Review Comment:
CollcetonUtils.isNotEmpty
##########
shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/SelectorServiceImpl.java:
##########
@@ -124,6 +127,10 @@ public class SelectorServiceImpl implements
SelectorService {
private final DiscoveryProcessorHolder discoveryProcessorHolder;
+ @Autowired
+ @Lazy
Review Comment:
why lazy?
##########
shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/AiProxyConnectionServiceImpl.java:
##########
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.admin.service.impl;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.shenyu.admin.listener.DataChangedEvent;
+import org.apache.shenyu.admin.mapper.AiProxyApiKeyMapper;
+import org.apache.shenyu.admin.model.entity.ProxyApiKeyDO;
+import org.apache.shenyu.admin.service.AiProxyConnectionService;
+import org.apache.shenyu.admin.service.support.AiProxyRealKeyResolver;
+import org.apache.shenyu.common.dto.ProxyApiKeyData;
+import org.apache.shenyu.common.enums.ConfigGroupEnum;
+import org.apache.shenyu.common.enums.DataEventTypeEnum;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * The type Ai proxy connection service.
+ */
+@Service
+public class AiProxyConnectionServiceImpl implements AiProxyConnectionService {
Review Comment:
I don not understand what is this class`s meaning for
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]