浏览代码

ai模式展示

zoujiajian 3 月之前
父节点
当前提交
02a06d88e0

+ 9 - 0
netflix-dao/src/main/java/com/cyksj/mapper/AiModelMapper.java

@@ -0,0 +1,9 @@
+package com.cyksj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.AiModel;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface AiModelMapper extends BaseMapper<AiModel> {
+}

+ 50 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/AiModel.java

@@ -0,0 +1,50 @@
+package com.cyksj.model.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+/**
+ * AI 模型配置
+ */
+@Getter
+@Setter
+@Accessors(chain = true)
+public class AiModel extends BaseEntity {
+
+    /** 提供商标识,如 ANTHROPIC、OPENAI */
+    private String provider;
+
+    /** 提供商展示名称,如 Anthropic、OpenAI */
+    private String providerName;
+
+    /** 提供商 logo 图片地址 */
+    private String providerLogo;
+
+    /** 模型唯一标识,如 claude-opus-4-5 */
+    private String modelCode;
+
+    /** 模型展示名称 */
+    private String modelName;
+
+    /** 模型图标地址 */
+    private String modelIcon;
+
+    /** 模型描述 */
+    private String description;
+
+    /** 功能标签,逗号分隔,如 支持推理,支持工具 */
+    private String tags;
+
+    /** 上下文窗口,如 200K、100K */
+    private String contextWindow;
+
+    /** 价格档位标签,如 多档、商品级品质、专门优化 */
+    private String pricingBadge;
+
+    /** 排序值,越小越靠前 */
+    private Integer sort;
+
+    /** 状态:0-禁用,1-启用 */
+    private Integer status;
+}

+ 43 - 0
netflix-dao/src/main/java/com/cyksj/model/views/AiModelGroupView.java

@@ -0,0 +1,43 @@
+package com.cyksj.model.views;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 用户端 AI 模型展示 - 按提供商分组
+ */
+@Data
+public class AiModelGroupView {
+
+    private String provider;
+
+    private String providerName;
+
+    private String providerLogo;
+
+    private List<AiModelItemView> models;
+
+    @Data
+    public static class AiModelItemView {
+
+        private Long id;
+
+        private String modelCode;
+
+        private String modelName;
+
+        private String modelIcon;
+
+        private String description;
+
+        /** 功能标签列表,由逗号分隔字段拆分而来 */
+        private List<String> tags;
+
+        private String contextWindow;
+
+        private String pricingBadge;
+
+        private Integer sort;
+    }
+}

+ 13 - 0
netflix-service/src/main/java/com/cyksj/service/aimodel/AiModelService.java

@@ -0,0 +1,13 @@
+package com.cyksj.service.aimodel;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.cyksj.model.entity.AiModel;
+import com.cyksj.model.views.AiModelGroupView;
+
+import java.util.List;
+
+public interface AiModelService extends IService<AiModel> {
+
+    /** 用户端:获取启用的模型列表,按提供商分组 */
+    List<AiModelGroupView> listGroupByProvider();
+}

+ 73 - 0
netflix-service/src/main/java/com/cyksj/service/aimodel/impl/AiModelServiceImpl.java

@@ -0,0 +1,73 @@
+package com.cyksj.service.aimodel.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.cyksj.mapper.AiModelMapper;
+import com.cyksj.model.entity.AiModel;
+import com.cyksj.model.views.AiModelGroupView;
+import com.cyksj.service.aimodel.AiModelService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Service
+@RequiredArgsConstructor
+public class AiModelServiceImpl extends ServiceImpl<AiModelMapper, AiModel> implements AiModelService {
+
+    @Override
+    public List<AiModelGroupView> listGroupByProvider() {
+        List<AiModel> models = list(
+                Wrappers.lambdaQuery(AiModel.class)
+                        .eq(AiModel::getStatus, 1)
+                        .orderByAsc(AiModel::getSort)
+        );
+        if (models.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        // 按 provider 保持顺序分组
+        Map<String, List<AiModel>> providerMap = new LinkedHashMap<>();
+        for (AiModel model : models) {
+            providerMap.computeIfAbsent(model.getProvider(), k -> new ArrayList<>()).add(model);
+        }
+
+        List<AiModelGroupView> result = new ArrayList<>();
+        for (Map.Entry<String, List<AiModel>> entry : providerMap.entrySet()) {
+            AiModel first = entry.getValue().get(0);
+            AiModelGroupView group = new AiModelGroupView();
+            group.setProvider(first.getProvider());
+            group.setProviderName(first.getProviderName());
+            group.setProviderLogo(first.getProviderLogo());
+
+            List<AiModelGroupView.AiModelItemView> items = entry.getValue().stream().map(m -> {
+                AiModelGroupView.AiModelItemView item = new AiModelGroupView.AiModelItemView();
+                item.setId(m.getId());
+                item.setModelCode(m.getModelCode());
+                item.setModelName(m.getModelName());
+                item.setModelIcon(m.getModelIcon());
+                item.setDescription(m.getDescription());
+                item.setContextWindow(m.getContextWindow());
+                item.setPricingBadge(m.getPricingBadge());
+                item.setSort(m.getSort());
+                if (StrUtil.isNotBlank(m.getTags())) {
+                    item.setTags(Arrays.asList(m.getTags().split(",")));
+                } else {
+                    item.setTags(Collections.emptyList());
+                }
+                return item;
+            }).collect(Collectors.toList());
+
+            group.setModels(items);
+            result.add(group);
+        }
+        return result;
+    }
+}

+ 31 - 0
netflix-web/src/main/java/com/cyksj/web/controller/aimodel/AiModelFrontController.java

@@ -0,0 +1,31 @@
+package com.cyksj.web.controller.aimodel;
+
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.model.views.AiModelGroupView;
+import com.cyksj.service.aimodel.AiModelService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 用户端 - AI 模型列表
+ */
+@Slf4j
+@RequiredArgsConstructor
+@RequestMapping("/applet/aiModel")
+@RestController
+public class AiModelFrontController {
+
+    private final AiModelService aiModelService;
+
+    /** 获取模型列表(按提供商分组) */
+    @GetMapping("/list")
+    public Result<List<AiModelGroupView>> list() {
+        return GatewayResponse.SUCCESS.newBuilder().toResult(aiModelService.listGroupByProvider());
+    }
+}

+ 87 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/aimodel/CmsAiModelController.java

@@ -0,0 +1,87 @@
+package com.cyksj.web.controller.manage.aimodel;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.model.entity.AiModel;
+import com.cyksj.service.aimodel.AiModelService;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 管理端 - AI 模型管理
+ */
+@Slf4j
+@RequiredArgsConstructor
+@RequestMapping("/manage/aiModel")
+@RestController
+public class CmsAiModelController {
+
+    private final AiModelService aiModelService;
+    private final BeanSearcher beanSearcher;
+    private final HttpServletRequest request;
+
+    /** 分页查询模型列表 */
+    @GetMapping("/get")
+    public Result<SearchResult<AiModel>> get() {
+        SearchResult<AiModel> result = beanSearcher.search(AiModel.class,
+                MapUtils.flatBuilder(request.getParameterMap())
+                        .orderBy(AiModel::getSort).asc()
+                        .build());
+        return GatewayResponse.SUCCESS.newBuilder().toResult(result);
+    }
+
+    /** 新增模型 */
+    @PostMapping("/post")
+    public Result<String> add(@RequestBody AiModel aiModel) {
+        validateModelCode(aiModel.getModelCode(), null);
+        aiModelService.save(aiModel);
+        return GatewayResponse.SUCCESS.newBuilder().toResult("新增成功");
+    }
+
+    /** 编辑模型 */
+    @PutMapping("/put")
+    public Result<String> update(@RequestBody AiModel aiModel) {
+        if (aiModel.getId() == null) {
+            throw BusinessRuntimeException.getInstance("模型不存在");
+        }
+        validateModelCode(aiModel.getModelCode(), aiModel.getId());
+        aiModelService.updateById(aiModel);
+        return GatewayResponse.SUCCESS.newBuilder().toResult("编辑成功");
+    }
+
+    /** 删除模型 */
+    @DeleteMapping("/delete/{id}")
+    public Result<String> delete(@PathVariable Long id) {
+        aiModelService.removeById(id);
+        return GatewayResponse.SUCCESS.newBuilder().toResult("删除成功");
+    }
+
+    /** 启用/禁用 */
+    @PutMapping("/status/{id}/{status}")
+    public Result<String> updateStatus(@PathVariable Long id, @PathVariable Integer status) {
+        aiModelService.update(Wrappers.lambdaUpdate(AiModel.class)
+                .set(AiModel::getStatus, status)
+                .eq(AiModel::getId, id));
+        return GatewayResponse.SUCCESS.newBuilder().toResult("操作成功");
+    }
+
+    private void validateModelCode(String modelCode, Long excludeId) {
+        if (modelCode == null || modelCode.isBlank()) {
+            throw BusinessRuntimeException.getInstance("模型标识不能为空");
+        }
+        long count = aiModelService.count(Wrappers.lambdaQuery(AiModel.class)
+                .eq(AiModel::getModelCode, modelCode)
+                .ne(excludeId != null, AiModel::getId, excludeId));
+        if (count > 0) {
+            throw BusinessRuntimeException.getInstance("模型标识已存在");
+        }
+    }
+}