diff --git a/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java b/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java index b4b361ad..b197c506 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java +++ b/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java @@ -199,7 +199,7 @@ public interface Constant { /** * 版本号 */ - public static final String VERSION = "0.4.3"; + public static final String VERSION = "0.4.4"; /** * 无效固件URL diff --git a/main/manager-api/src/main/java/xiaozhi/common/exception/RenExceptionHandler.java b/main/manager-api/src/main/java/xiaozhi/common/exception/RenExceptionHandler.java index 0d0aa118..df9023ae 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/exception/RenExceptionHandler.java +++ b/main/manager-api/src/main/java/xiaozhi/common/exception/RenExceptionHandler.java @@ -1,7 +1,10 @@ package xiaozhi.common.exception; import org.apache.shiro.authz.UnauthorizedException; +import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.dao.DuplicateKeyException; +import org.springframework.validation.ObjectError; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.resource.NoResourceFoundException; @@ -10,6 +13,8 @@ import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import xiaozhi.common.utils.Result; +import java.util.List; +import java.util.Objects; /** * 异常处理器 * Copyright (c) 人人开源 All rights reserved. @@ -60,4 +65,16 @@ public class RenExceptionHandler { return new Result().error(404, "资源不存在"); } + @ExceptionHandler(MethodArgumentNotValidException.class) + public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) { + StringBuilder sb = new StringBuilder(); + List allErrors = ex.getBindingResult().getAllErrors(); + String errorMsg = allErrors.stream() + .filter(Objects::nonNull) + .map(DefaultMessageSourceResolvable::getDefaultMessage) + .findFirst() + .orElse(""); + return new Result().error(400, errorMsg); + } + } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/common/service/impl/BaseServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/common/service/impl/BaseServiceImpl.java index 9d2ba5d5..9d06c09a 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/service/impl/BaseServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/common/service/impl/BaseServiceImpl.java @@ -1,6 +1,7 @@ package xiaozhi.common.service.impl; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; @@ -18,6 +19,7 @@ import com.baomidou.mybatisplus.core.enums.SqlMethod; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.OrderItem; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.core.toolkit.Constants; import com.baomidou.mybatisplus.core.toolkit.ReflectionKit; import com.baomidou.mybatisplus.core.toolkit.StringUtils; @@ -45,6 +47,12 @@ public abstract class BaseServiceImpl, T> implements Bas * @param params 分页查询参数 * @param defaultOrderField 默认排序字段 * @param isAsc 排序方式 + * @see xiaozhi.common.constant.Constant + * params.put(Constant.PAGE, "1"); + * params.put(Constant.LIMIT, "10"); + * params.put(Constant.ORDER_FIELD, "field"); // 单个字段 + * params.put(Constant.ORDER_FIELD, List.of("field1", "field2")); // 多个字段 + * params.put(Constant.ORDER, "asc"); */ protected IPage getPage(Map params, String defaultOrderField, boolean isAsc) { // 分页参数 @@ -65,28 +73,34 @@ public abstract class BaseServiceImpl, T> implements Bas params.put(Constant.PAGE, page); // 排序字段 - String orderField = (String) params.get(Constant.ORDER_FIELD); + Object orderField = params.get(Constant.ORDER_FIELD); String order = (String) params.get(Constant.ORDER); - // 前端字段排序 - if (StringUtils.isNotBlank(orderField) && StringUtils.isNotBlank(order)) { - if (Constant.ASC.equalsIgnoreCase(order)) { - return page.addOrder(OrderItem.asc(orderField)); + List orderFields = new ArrayList<>(); + + // 处理排序字段 + if (orderField instanceof String) { + orderFields.add((String) orderField); + } else if (orderField instanceof List) { + orderFields.addAll((List) orderField); + } + + // 有排序字段则排序 + if (CollectionUtils.isNotEmpty(orderFields)) { + if (StringUtils.isNotBlank(order) && Constant.ASC.equalsIgnoreCase(order)) { + return page.addOrder(OrderItem.ascs(orderFields.toArray(new String[0]))); } else { - return page.addOrder(OrderItem.desc(orderField)); + return page.addOrder(OrderItem.descs(orderFields.toArray(new String[0]))); } } - // 没有排序字段,则不排序 - if (StringUtils.isBlank(defaultOrderField)) { - return page; - } - - // 默认排序 - if (isAsc) { - page.addOrder(OrderItem.asc(defaultOrderField)); - } else { - page.addOrder(OrderItem.desc(defaultOrderField)); + // 没有排序字段,使用默认排序 + if (StringUtils.isNotBlank(defaultOrderField)) { + if (isAsc) { + page.addOrder(OrderItem.asc(defaultOrderField)); + } else { + page.addOrder(OrderItem.desc(defaultOrderField)); + } } return page; diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelProviderController.java b/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelProviderController.java new file mode 100644 index 00000000..fc534a0a --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelProviderController.java @@ -0,0 +1,68 @@ +package xiaozhi.modules.model.controller; + +import java.util.List; + +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; +import xiaozhi.common.page.PageData; +import xiaozhi.common.utils.Result; +import xiaozhi.common.validator.group.UpdateGroup; +import xiaozhi.modules.model.dto.ModelProviderDTO; +import xiaozhi.modules.model.service.ModelProviderService; + +@AllArgsConstructor +@RestController +@RequestMapping("/models/provider") +@Tag(name = "模型供应器") +public class ModelProviderController { + + private final ModelProviderService modelProviderService; + + @GetMapping + @Operation(summary = "获取模型供应器列表") + @RequiresPermissions("sys:role:superAdmin") + public Result> getListPage(ModelProviderDTO modelProviderDTO, + @RequestParam(required = true, defaultValue = "0") String page, + @RequestParam(required = true, defaultValue = "10") String limit) { + return new Result>() + .ok(modelProviderService.getListPage(modelProviderDTO, page, limit)); + } + + @PostMapping + @Operation(summary = "新增模型供应器") + @RequiresPermissions("sys:role:superAdmin") + public Result add(@RequestBody @Validated ModelProviderDTO modelProviderDTO) { + ModelProviderDTO resp = modelProviderService.add(modelProviderDTO); + return new Result().ok(resp); + } + + @PutMapping + @Operation(summary = "修改模型供应器") + @RequiresPermissions("sys:role:superAdmin") + public Result edit(@RequestBody @Validated(UpdateGroup.class) ModelProviderDTO modelProviderDTO) { + ModelProviderDTO resp = modelProviderService.edit(modelProviderDTO); + return new Result().ok(resp); + } + + @PostMapping("/delete") + @Operation(summary = "删除模型供应器") + @RequiresPermissions("sys:role:superAdmin") + @Parameter(name = "ids", description = "ID数组", required = true) + public Result delete(@RequestBody List ids) { + modelProviderService.delete(ids); + return new Result<>(); + } + +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelProviderDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelProviderDTO.java index 605ed13a..317820d5 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelProviderDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelProviderDTO.java @@ -8,29 +8,37 @@ import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.Data; +import xiaozhi.common.validator.group.UpdateGroup; @Data @Schema(description = "模型供应器/商") public class ModelProviderDTO implements Serializable { - // - // @Schema(description = "主键") - // private Long id; + @Schema(description = "主键") + @NotBlank(message = "id不能为空", groups = UpdateGroup.class) + private String id; @Schema(description = "模型类型(Memory/ASR/VAD/LLM/TTS)") + @NotBlank(message = "modelType不能为空") private String modelType; @Schema(description = "供应器类型") + @NotBlank(message = "providerCode不能为空") private String providerCode; @Schema(description = "供应器名称") + @NotBlank(message = "name不能为空") private String name; @Schema(description = "供应器字段列表(JSON格式)") @TableField(typeHandler = JacksonTypeHandler.class) + @NotBlank(message = "fields(JSON格式)不能为空") private String fields; @Schema(description = "排序") + @NotNull(message = "sort不能为空") private Integer sort; @Schema(description = "更新者") diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/entity/ModelProviderEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/model/entity/ModelProviderEntity.java index 62e84f67..00904759 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/entity/ModelProviderEntity.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/entity/ModelProviderEntity.java @@ -3,10 +3,8 @@ package xiaozhi.modules.model.entity; import java.util.Date; import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -30,7 +28,6 @@ public class ModelProviderEntity { private String name; @Schema(description = "供应器字段列表(JSON格式)") - @TableField(typeHandler = JacksonTypeHandler.class) private String fields; @Schema(description = "排序") diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java index 2ace101e..d41766f6 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java @@ -2,8 +2,8 @@ package xiaozhi.modules.model.service; import java.util.List; +import xiaozhi.common.page.PageData; import xiaozhi.modules.model.dto.ModelProviderDTO; -import xiaozhi.modules.model.entity.ModelProviderEntity; public interface ModelProviderService { @@ -11,11 +11,15 @@ public interface ModelProviderService { List getListByModelType(String modelType); - ModelProviderDTO add(ModelProviderEntity modelProviderEntity); + ModelProviderDTO add(ModelProviderDTO modelProviderDTO); - ModelProviderDTO edit(ModelProviderEntity modelProviderEntity); + ModelProviderDTO edit(ModelProviderDTO modelProviderDTO); - void delete(); + void delete(String id); + + void delete(List id); + + PageData getListPage(ModelProviderDTO modelProviderDTO, String page, String limit); List getList(String modelType, String provideCode); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelProviderServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelProviderServiceImpl.java index fb06e4d5..ffeec288 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelProviderServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelProviderServiceImpl.java @@ -1,19 +1,29 @@ package xiaozhi.modules.model.service.impl; +import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import cn.hutool.json.JSONArray; import lombok.AllArgsConstructor; +import xiaozhi.common.constant.Constant; +import xiaozhi.common.exception.RenException; +import xiaozhi.common.page.PageData; import xiaozhi.common.service.impl.BaseServiceImpl; +import xiaozhi.common.user.UserDetail; import xiaozhi.common.utils.ConvertUtils; import xiaozhi.modules.model.dao.ModelProviderDao; import xiaozhi.modules.model.dto.ModelProviderDTO; import xiaozhi.modules.model.entity.ModelProviderEntity; import xiaozhi.modules.model.service.ModelProviderService; +import xiaozhi.modules.security.user.SecurityUser; @Service @AllArgsConstructor @@ -32,18 +42,78 @@ public class ModelProviderServiceImpl extends BaseServiceImpl getListPage(ModelProviderDTO modelProviderDTO, String page, String limit) { + + Map params = new HashMap(); + params.put(Constant.PAGE, page); + params.put(Constant.LIMIT, limit); + params.put(Constant.ORDER_FIELD, List.of("model_type", "sort")); + params.put(Constant.ORDER, "asc"); + + IPage pageParam = getPage(params, null, true); + + QueryWrapper wrapper = new QueryWrapper(); + + if (StringUtils.isNotBlank(modelProviderDTO.getModelType())) { + wrapper.eq("model_type", modelProviderDTO.getModelType()); + } + + if (StringUtils.isNotBlank(modelProviderDTO.getName())) { + wrapper.and(w -> w.like("name", modelProviderDTO.getName()) + .or() + .like("provider_code", modelProviderDTO.getName())); + } + return getPageData(modelProviderDao.selectPage(pageParam, wrapper), ModelProviderDTO.class); + } + + public static void main(String[] args) { + String jsonString = "\"[]\""; + JSONArray jsonArray = new JSONArray(jsonString); + System.out.println("字符串转 JSONArray: " + jsonArray.toString()); } @Override - public ModelProviderDTO edit(ModelProviderEntity modelProviderEntity) { - return null; + public ModelProviderDTO add(ModelProviderDTO modelProviderDTO) { + UserDetail user = SecurityUser.getUser(); + modelProviderDTO.setCreator(user.getId()); + modelProviderDTO.setUpdater(user.getId()); + modelProviderDTO.setCreateDate(new Date()); + modelProviderDTO.setUpdateDate(new Date()); + // 去除Fields左右的双引号 + + modelProviderDTO.setFields(modelProviderDTO.getFields()); + ModelProviderEntity entity = ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderEntity.class); + if (modelProviderDao.insert(entity) == 0) { + throw new RenException("新增数据失败"); + } + + return ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderDTO.class); } @Override - public void delete() { + public ModelProviderDTO edit(ModelProviderDTO modelProviderDTO) { + UserDetail user = SecurityUser.getUser(); + modelProviderDTO.setUpdater(user.getId()); + modelProviderDTO.setUpdateDate(new Date()); + if (modelProviderDao + .updateById(ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderEntity.class)) == 0) { + throw new RenException("修改数据失败"); + } + return ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderDTO.class); + } + @Override + public void delete(String id) { + if (modelProviderDao.deleteById(id) == 0) { + throw new RenException("删除数据失败"); + } + } + + @Override + public void delete(List ids) { + if (modelProviderDao.deleteBatchIds(ids) == 0) { + throw new RenException("删除数据失败"); + } } @Override diff --git a/main/manager-web/src/apis/module/model.js b/main/manager-web/src/apis/module/model.js index ec0ea32f..08615693 100644 --- a/main/manager-web/src/apis/module/model.js +++ b/main/manager-web/src/apis/module/model.js @@ -166,20 +166,20 @@ export default { configJson: formData.configJson }; RequestService.sendRequest() - .url(`${getServiceUrl()}/models/${modelType}/${provideCode}/${id}`) - .method('PUT') - .data(payload) - .success((res) => { - RequestService.clearRequestTime(); - callback(res); - }) - .fail((err) => { - console.error('更新模型失败:', err); - this.$message.error(err.msg || '更新模型失败'); - RequestService.reAjaxFun(() => { - this.updateModel(params, callback); - }); - }).send(); + .url(`${getServiceUrl()}/models/${modelType}/${provideCode}/${id}`) + .method('PUT') + .data(payload) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail((err) => { + console.error('更新模型失败:', err); + this.$message.error(err.msg || '更新模型失败'); + RequestService.reAjaxFun(() => { + this.updateModel(params, callback); + }); + }).send(); }, // 设置默认模型 setDefaultModel(id, callback) { @@ -197,5 +197,112 @@ export default { this.setDefaultModel(id, callback) }) }).send() - } + }, + + /** + * 获取模型配置列表(支持查询参数) + * @param {Object} params - 查询参数对象,例如 { name: 'test', modelType: 1 } + * @param {Function} callback - 回调函数 + */ + getModelProvidersPage(params, callback) { + // 构建查询参数 + const queryParams = new URLSearchParams(); + if (params.name) queryParams.append('name', params.name); + if (params.modelType !== undefined) queryParams.append('modelType', params.modelType); + if (params.page !== undefined) queryParams.append('page', params.page); + if (params.limit !== undefined) queryParams.append('limit', params.limit); + + RequestService.sendRequest() + .url(`${getServiceUrl()}/models/provider?${queryParams.toString()}`) + .method('GET') + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail((err) => { + this.$message.error(err.msg || '获取供应器列表失败'); + RequestService.reAjaxFun(() => { + this.getModelProviders(params, callback); + }); + }).send(); + }, + + /** + * 新增模型供应器配置 + * @param {Object} params - 请求参数对象,例如 { modelType: '1', providerCode: '1', name: '1', fields: '1', sort: 1 } + * @param {Function} callback - 成功回调函数 + */ + addModelProvider(params, callback) { + const postData = { + modelType: params.modelType || '', + providerCode: params.providerCode || '', + name: params.name || '', + fields: JSON.stringify(params.fields || []), + sort: params.sort || 0 + }; + + RequestService.sendRequest() + .url(`${getServiceUrl()}/models/provider`) + .method('POST') + .data(postData) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail((err) => { + console.error('新增模型供应器失败:', err) + this.$message.error(err.msg || '新增模型供应器失败') + RequestService.reAjaxFun(() => { + this.addModelProvider(params, callback); + }); + }).send(); + }, + + /** + * 更新模型供应器配置 + * @param {Object} params - 请求参数对象,例如 { id: '111', modelType: '1', providerCode: '1', name: '1', fields: '1', sort: 1 } + * @param {Function} callback - 成功回调函数 + */ + updateModelProvider(params, callback) { + const putData = { + id: params.id || '', + modelType: params.modelType || '', + providerCode: params.providerCode || '', + name: params.name || '', + fields: JSON.stringify(params.fields || []), + sort: params.sort || 0 + }; + + RequestService.sendRequest() + .url(`${getServiceUrl()}/models/provider`) + .method('PUT') + .data(putData) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail((err) => { + this.$message.error(err.msg || '更新模型供应器失败') + RequestService.reAjaxFun(() => { + this.updateModelProvider(params, callback); + }); + }).send(); + }, + // 删除 + deleteModelProviderByIds(ids, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/models/provider/delete`) + .method('POST') + .data(ids) + .success((res) => { + RequestService.clearRequestTime() + callback(res); + }) + .fail((err) => { + this.$message.error(err.msg || '删除模型供应器失败') + RequestService.reAjaxFun(() => { + this.deleteModelProviderByIds(ids, callback) + }) + }).send() + }, } diff --git a/main/manager-web/src/components/ProviderDialog.vue b/main/manager-web/src/components/ProviderDialog.vue index 844282ee..7fd1fcf8 100644 --- a/main/manager-web/src/components/ProviderDialog.vue +++ b/main/manager-web/src/components/ProviderDialog.vue @@ -1,5 +1,6 @@