pom.xml
@@ -30,6 +30,29 @@ <dependencies> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.9</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>dashscope-sdk-java</artifactId> <version>2.21.5</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.volcengine</groupId> <artifactId>volcengine-java-sdk-ark-runtime</artifactId> <version>LATEST</version> src/main/java/cc/mrbird/febs/ai/controller/productQuestion/AiProductQuestionController.java
@@ -1,6 +1,5 @@ package cc.mrbird.febs.ai.controller.productQuestion; import cc.mrbird.febs.ai.entity.AiProductPoint; import cc.mrbird.febs.ai.entity.AiProductQuestion; import cc.mrbird.febs.ai.req.AiProductQuestionAiDto; import cc.mrbird.febs.ai.service.AiProductQuestionService; @@ -53,6 +52,13 @@ return aiProductQuestionService.add(dto); } @PostMapping("aiAdd") @ControllerEndpoint(operation = "AI生成题目", exceptionMessage = "操作失败") public FebsResponse aiAdd(@RequestBody @Valid AiProductQuestionAiDto dto) { return aiProductQuestionService.aiAdd(dto); } @PostMapping("update") @ControllerEndpoint(operation = "更新", exceptionMessage = "操作失败") public FebsResponse update(@RequestBody @Valid AiProductQuestion dto) { @@ -67,13 +73,6 @@ ) { return aiProductQuestionService.delete(id); } @PostMapping("aiCreate/{id}") @ControllerEndpoint(operation = "AI生成题目", exceptionMessage = "操作失败") public FebsResponse aiCreate(@RequestBody @Valid AiProductQuestionAiDto dto) { return aiProductQuestionService.aiCreate(dto); } @GetMapping(value = "/questionTree") src/main/java/cc/mrbird/febs/ai/controller/productQuestion/ViewController.java
@@ -45,6 +45,13 @@ return FebsUtil.view("modules/ai/productQuestion/add"); } @GetMapping(value = "/aiAdd") @RequiresPermissions("productQuestionList:aiAdd") public String aiAdd() { return FebsUtil.view("modules/ai/productQuestion/aiAdd"); } @GetMapping("info/{id}") @RequiresPermissions("productQuestionList:info") public String artInfo(@PathVariable String id, Model model) { src/main/java/cc/mrbird/febs/ai/req/AiProductQuestionAiDto.java
@@ -7,6 +7,8 @@ private String productCategoryId; private Integer difficulty; private String query; private String promptAiSystem; src/main/java/cc/mrbird/febs/ai/service/AiProductQuestionService.java
@@ -39,5 +39,5 @@ List<AiProductQuestion> productQuestionTree(LambdaQueryWrapper<AiProductQuestion> aiProductQuestionLambdaQueryWrapper); FebsResponse aiCreate(AiProductQuestionAiDto dto); FebsResponse aiAdd(AiProductQuestionAiDto dto); } src/main/java/cc/mrbird/febs/ai/service/impl/AiProductQuestionServiceImpl.java
@@ -11,6 +11,9 @@ import cc.mrbird.febs.common.entity.QueryRequest; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; @@ -166,9 +169,121 @@ } @Override public FebsResponse aiCreate(AiProductQuestionAiDto dto) { public FebsResponse aiAdd(AiProductQuestionAiDto dto) { String jsonFormat = "{\"question_list\":[{\"title\": \"消费者对透明质酸的主要担忧是什么?\",\"answer_list\":[{\"answer\": \"消费者担心透明质酸维持时间短,效果不明显。\",\"type\": 1,\"analysis\": \"\"},{\"answer\": \"消费者担心透明质酸注射后可能出现移位、凹陷、馒化、僵硬以及炎症反应。\",\"type\": 1,\"analysis\": \"\"},{\"answer\": \"消费者的主要担忧包括效果不明显、维持时间短、注射后出现移位、凹陷、馒化、僵硬以及炎症反应。他们希望既达到理想效果,又避免这些副作用。\",\"type\": 2,\"analysis\": \"标准答案涵盖了消费者对透明质酸的所有主要担忧,既包括效果问题,也涵盖副作用风险,全面反映消费者心理需求。\"}]}]} "; dto.setJsonFormat(jsonFormat); String questionAndAnswerStr = aiService.llmInvokeNonStreaming(dto); return null; // 解析AI返回的结果 List<JSONObject> questionList = parseAiQuestionResponse(questionAndAnswerStr); String productCategoryId = dto.getProductCategoryId(); Integer difficulty = dto.getDifficulty(); Date createdTime = new Date(); // 处理解析后的问题列表 for (JSONObject questionObj : questionList) { String title = questionObj.getStr("title"); AiProductQuestion aiProductQuestion = new AiProductQuestion(); aiProductQuestion.setId(UUID.getSimpleUUIDString()); aiProductQuestion.setProductCategoryId(productCategoryId); aiProductQuestion.setTitle(title); aiProductQuestion.setDifficulty(difficulty); aiProductQuestion.setCreatedTime(createdTime); this.save(aiProductQuestion); JSONArray answerList = questionObj.getJSONArray("answer_list"); System.out.println("问题: " + title); for (int i = 0; i < answerList.size(); i++) { JSONObject answer = answerList.getJSONObject(i); System.out.println("答案" + (i+1) + ": " + answer.getStr("answer")); System.out.println("类型: " + answer.getStr("type")); System.out.println("分析: " + answer.getStr("analysis")); AiProductQuestionItem aiProductQuestionItem = new AiProductQuestionItem(); aiProductQuestionItem.setId(UUID.getSimpleUUIDString()); aiProductQuestionItem.setProductQuestionId(aiProductQuestion.getId()); aiProductQuestionItem.setTitle(aiProductQuestion.getTitle()); aiProductQuestionItem.setAnswer(answer.getStr("answer")); aiProductQuestionItem.setCorrectAnswer(answer.getInt("type") == 2 ? 1 : 0); aiProductQuestionItem.setAnswerAnalysis(answer.getStr("analysis")); aiProductQuestionItem.setCreatedTime(createdTime); aiProductQuestionItemService.getBaseMapper().insert(aiProductQuestionItem); } } return new FebsResponse().success().message("操作成功"); } /** * 解析AI返回的包含问题和答案的JSON字符串 * @param aiResponse AI返回的原始响应字符串 * @return 解析后的问题列表 */ public List<JSONObject> parseAiQuestionResponse(String aiResponse) { try { // 解析外层JSON JSONObject outerJson = JSONUtil.parseObj(aiResponse); // 提取output字段 String output = outerJson.getStr("output"); // 去除<start>和</start>标签,并清理多余字符 String jsonContent = output.replace("<start>", "") .replace("</start>", "") .replace("\\n", "") .replace("\\\"", "\"") .trim(); // 解析内部JSON JSONObject innerJson = JSONUtil.parseObj(jsonContent); // 提取question_list JSONArray questionList = innerJson.getJSONArray("question_list"); // 转换为List<JSONObject>返回 return questionList.toList(JSONObject.class); } catch (Exception e) { log.error("解析AI问题响应失败: ", e); throw new RuntimeException("解析AI响应失败", e); } } public static void main(String[] args) { String questionStr = "{\"output\":\"<start>\\n{\\n \\\"question_list\\\": [\\n {\\n \\\"title\\\": \\\"1. 如何通过菲欧曼品牌背景和价值塑造透明质酸产品的高端形象?\\\",\\n \\\"answer_list\\\": [\\n {\\n \\\"answer\\\": \\\"强调菲欧曼拥有45年历史,是法国第一家医学美容实验室,产品行销全球60多个国家,树立其国际权威形象。\\\",\\n \\\"type\\\": \\\"2\\\",\\n \\\"analysis\\\": \\\"该答案全面结合品牌历史、科研投入及市场影响力,系统塑造高端定位,符合标准答案要求。\\\"\\n },\\n {\\n \\\"answer\\\": \\\"通过讲述菲欧曼产品获得欧盟CE、中国NMPA等国际认证,强调其安全性和合规性。\\\",\\n \\\"type\\\": \\\"1\\\",\\n \\\"analysis\\\": \\\"\\\"\\n },\\n {\\n \\\"answer\\\": \\\"通过明星使用、医生推荐等口碑效应,提升菲欧曼在消费者心中的高端认知。\\\",\\n \\\"type\\\": \\\"1\\\",\\n \\\"analysis\\\": \\\"\\\"\\n }\\n ]\\n }\\n ]\\n}\\n</start>\"}"; // 第一步:解析外层JSON JSONObject outerJson = JSONUtil.parseObj(questionStr); // 第二步:提取output字段 String output = outerJson.getStr("output"); // 第三步:去除<start>和</start>标签 String jsonContent = output.replace("<start>", "").replace("</start>", "").trim(); // 第四步:解析内部的JSON JSONObject innerJson = JSONUtil.parseObj(jsonContent); // 第五步:提取question_list JSONArray questionList = innerJson.getJSONArray("question_list"); // 遍历问题列表 for (int i = 0; i < questionList.size(); i++) { JSONObject question = questionList.getJSONObject(i); System.out.println("问题标题: " + question.getStr("title")); JSONArray answerList = question.getJSONArray("answer_list"); System.out.println("答案列表:"); // 遍历答案列表 for (int j = 0; j < answerList.size(); j++) { JSONObject answer = answerList.getJSONObject(j); System.out.println(" 答案 " + (j+1) + ": " + answer.getStr("answer")); System.out.println(" 类型: " + answer.getStr("type")); System.out.println(" 分析: " + answer.getStr("analysis")); System.out.println(); } } } } src/main/java/cc/mrbird/febs/ai/service/impl/AiServiceImpl.java
@@ -5,6 +5,13 @@ import cc.mrbird.febs.common.exception.FebsException; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import com.alibaba.dashscope.app.Application; import com.alibaba.dashscope.app.ApplicationParam; import com.alibaba.dashscope.app.ApplicationResult; import com.alibaba.dashscope.app.FlowStreamMode; import com.alibaba.dashscope.exception.InputRequiredException; import com.alibaba.dashscope.exception.NoApiKeyException; import com.alibaba.dashscope.utils.JsonUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -35,38 +42,24 @@ bizParams.put(bizParam_3,dto.getJsonFormat()); String query = dto.getQuery(); long startTime = System.currentTimeMillis(); // ApplicationParam param = ApplicationParam.builder() // // 若没有配置环境变量,可用百炼API Key将下行替换为:.apiKey("sk-xxx")。但不建议在生产环境中直接将API Key硬编码到代码中,以减少API Key泄露风险。 // .apiKey(apiKey) // .appId(appId) //替换为实际的应用 ID // .flowStreamMode(FlowStreamMode.MESSAGE_FORMAT) // .prompt(query) // .bizParams(JsonUtils.toJsonObject( bizParams)) // .build(); // // Application application = new Application(); // Flowable<ApplicationResult> result; // try { // result = application.streamCall(param); // } catch (NoApiKeyException | InputRequiredException e) { // throw new FebsException(StrUtil.format("百炼工作流输出失败:{}",e.getMessage())); // } // // return Flux.from(result) // .map(message -> { // HashMap<String, String> stringStringHashMap = new HashMap<>(); // if (!message.getOutput().getFinishReason().equals("stop")){ // stringStringHashMap.put(LlmStrategyContextEnum.CONTENT.name(),message.getOutput().getWorkflowMessage().getMessage().getContent()); // } // return new FebsResponse().success().data(stringStringHashMap); // }) // .doOnComplete(() -> { // long endTime = System.currentTimeMillis(); // System.out.println("百炼工作流输出:" + (endTime - startTime) + "毫秒"); // }) // .doOnError(error -> { // throw new FebsException(StrUtil.format("百炼工作流输出失败:{}",error)); // }); return null; ApplicationParam param = ApplicationParam.builder() // 若没有配置环境变量,可用百炼API Key将下行替换为:.apiKey("sk-xxx")。但不建议在生产环境中直接将API Key硬编码到代码中,以减少API Key泄露风险。 .apiKey(apiKey) .appId(appId) //替换为实际的应用 ID .flowStreamMode(FlowStreamMode.MESSAGE_FORMAT) .prompt(query) .bizParams(JsonUtils.toJsonObject( bizParams)) .build(); Application application = new Application(); ApplicationResult result = null; try { result = application.call(param); } catch (NoApiKeyException e) { throw new FebsException("百炼工作流调用异常"); } catch (InputRequiredException e) { throw new FebsException("百炼工作流调用异常"); } return result.getOutput().getText(); } } src/main/resources/templates/febs/views/modules/ai/productQuestion/aiAdd.html
New file @@ -0,0 +1,148 @@ <div class="layui-fluid layui-anim febs-anim" id="febs-productQuestion-ai-add" lay-title="新增"> <div class="layui-row febs-container"> <div class="layui-col-md12"> <div class="layui-fluid" id="productQuestion-add"> <form class="layui-form" action="" lay-filter="productQuestion-add-form"> <div class="layui-tab layui-tab-brief" lay-filter="docDemoTabBrief"> <ul class="layui-tab-title"> <li class="layui-this">基础信息</li> </ul> <div class="layui-tab-content"> <div class="layui-tab-item layui-show"> <div class="layui-row layui-col-space10 layui-form-item"> <div class="layui-col-lg6"> <label class="layui-form-label febs-form-item-require">分类:</label> <div class="layui-input-block"> <div id="product-category"></div> </div> </div> </div> <div class="layui-row layui-col-space10 layui-form-item"> <div class="layui-col-lg6"> <label class="layui-form-label febs-form-item-require">AI角色设定:</label> <div class="layui-input-block"> <input type="text" name="promptAiSystem" lay-verify="required" placeholder="" autocomplete="off" class="layui-input"> </div> </div> </div> <div class="layui-row layui-col-space10 layui-form-item"> <div class="layui-col-lg6"> <label class="layui-form-label febs-form-item-require">题目数量:</label> <div class="layui-input-block"> <input type="number" name="questionCnt" lay-verify="required" placeholder="" autocomplete="off" class="layui-input"> </div> </div> </div> <div class="layui-row layui-col-space10 layui-form-item"> <div class="layui-col-lg6"> <label class="layui-form-label febs-form-item-require">核心内容:</label> <div class="layui-input-block"> <input type="text" name="query" lay-verify="required" placeholder="" autocomplete="off" class="layui-input"> </div> </div> </div> <div class="layui-row layui-col-space10 layui-form-item"> <div class="layui-col-lg6"> <label class="layui-form-label febs-form-item-require">难度:</label> <div class="layui-input-block"> <input type="radio" name="difficulty" value="1" title="简单" checked=""> <input type="radio" name="difficulty" value="2" title="中等" > <input type="radio" name="difficulty" value="3" title="困难" > </div> </div> </div> </div> </div> </div> <div class="layui-form-item febs-hide"> <button class="layui-btn" lay-submit="" lay-filter="productQuestion-add-form-submit" id="submit">保存</button> </div> </form> </div> </div> </div> </div> <!-- 表格操作栏 end --> <script data-th-inline="javascript"> layui.use(['febs', 'form', 'formSelects', 'validate', 'treeSelect', 'eleTree','dropdown', 'laydate', 'layedit', 'upload', 'element', 'table', 'xmSelect','jquery'], function () { var $ = layui.jquery, febs = layui.febs, layer = layui.layer, table = layui.table, formSelects = layui.formSelects, treeSelect = layui.treeSelect, form = layui.form, laydate = layui.laydate, eleTree = layui.eleTree, $view = $('#productQuestion-add'), layedit = layui.layedit, upload = layui.upload, validate = layui.validate, element = layui.element; form.render(); var category = xmSelect.render({ el: '#product-category', language: 'zn', prop : { value : 'id', children : 'child' }, iconfont: { parent: 'hidden', }, tips: '请选择', filterable: true, radio: true, clickClose: true, tree: { show: true, //非严格模式 strict: false, }, data: [] }) febs.get(ctx + 'admin/productCategory/categoryTree', null, function(res) { category.update({ data : res.data, autoRow: true, }); }) form.on('submit(productQuestion-add-form-submit)', function (data) { data.field.productCategoryId = category.getValue('valueStr'); $.ajax({ 'url':ctx + 'admin/productQuestion/aiAdd', 'type':'post', 'dataType':'json', 'headers' : {'Content-Type' : 'application/json;charset=utf-8'}, //接口json格式 'traditional': true,//ajax传递数组必须添加属性 'data':JSON.stringify(data.field), 'success':function (data) { if(data.code==200){ layer.closeAll(); febs.alert.success(data.message); $('#febs-productQuestion').find('#query').click(); }else{ febs.alert.warn(data.message); } }, 'error':function () { febs.alert.warn('服务器繁忙'); } }) return false; }); }); </script> src/main/resources/templates/febs/views/modules/ai/productQuestion/list.html
@@ -45,7 +45,8 @@ <script type="text/html" id="productQuestionToolbar"> <div class="layui-btn-container"> <button class="layui-btn layui-btn-sm layui-btn-primary febs-button-green-plain" shiro:hasPermission="productQuestionList:add" lay-event="productQuestionAdd">新增</button> <button class="layui-btn layui-btn-sm layui-btn-primary febs-button-green-plain" shiro:hasPermission="productQuestionList:add" lay-event="productQuestionAdd">手动新增</button> <button class="layui-btn layui-btn-sm layui-btn-primary febs-button-green-plain" shiro:hasPermission="productQuestionList:aiAdd" lay-event="productQuestionAiAdd">AI新增</button> </div> </script> @@ -157,6 +158,18 @@ } }); } if(layEvent === 'productQuestionAiAdd'){ febs.modal.open('新增', 'modules/ai/productQuestion/aiAdd/', { btn: ['提交', '取消'], area:['100%','100%'], yes: function (index, layero) { $('#febs-productQuestion-ai-add').find('#submit').trigger('click'); }, btn2: function () { layer.closeAll(); } }); } }); function initProductQuestionTable() {