From 1961adb6ae3bac26804edd49071de1d7880fddbf Mon Sep 17 00:00:00 2001 From: gitee Date: Mon, 11 Aug 2025 17:41:17 +0800 Subject: [PATCH] =?UTF-8?q?1.=E6=B7=BB=E5=8A=A0=20=E7=94=9F=E6=88=90?= =?UTF-8?q?=E6=95=B0=E5=AD=97=E4=BA=BA=E9=94=80=E5=94=AE=E8=AF=9D=E6=9C=AF?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=202.=20=E8=B0=83=E6=95=B4=E8=AE=A4=E8=AF=81?= =?UTF-8?q?=E8=BF=87=E6=BB=A4=E5=99=A8=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../constant/PromptTemplate.java | 30 ++++ .../controller/AuthController.java | 14 -- .../controller/LiveDigitalController.java | 31 ++++ .../filter/JwtAuthenticationFilter.java | 9 +- .../service/LiveDigitalService.java | 10 ++ .../service/impl/AuthServiceImpl.java | 10 -- .../service/impl/LiveDigitalServiceImpl.java | 51 ++++++ .../vo/ProductSpecification.java | 17 ++ .../vo/SalesPitchReqVo.java | 44 +++++ src/main/resources/logback.xml | 160 ++++++++++++++++++ 10 files changed, 351 insertions(+), 25 deletions(-) create mode 100644 src/main/java/com/supervision/livedigitalavatarmanage/constant/PromptTemplate.java create mode 100644 src/main/java/com/supervision/livedigitalavatarmanage/controller/LiveDigitalController.java create mode 100644 src/main/java/com/supervision/livedigitalavatarmanage/service/LiveDigitalService.java create mode 100644 src/main/java/com/supervision/livedigitalavatarmanage/service/impl/LiveDigitalServiceImpl.java create mode 100644 src/main/java/com/supervision/livedigitalavatarmanage/vo/ProductSpecification.java create mode 100644 src/main/java/com/supervision/livedigitalavatarmanage/vo/SalesPitchReqVo.java create mode 100644 src/main/resources/logback.xml diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/constant/PromptTemplate.java b/src/main/java/com/supervision/livedigitalavatarmanage/constant/PromptTemplate.java new file mode 100644 index 0000000..035e824 --- /dev/null +++ b/src/main/java/com/supervision/livedigitalavatarmanage/constant/PromptTemplate.java @@ -0,0 +1,30 @@ +package com.supervision.livedigitalavatarmanage.constant; + +/** + * 提示词缓存 + */ +public class PromptTemplate { + + /** + * 生成销售话术模板 + */ + public static final String GENERATE_SALESPITCH_TEMPLATE = """ + 你是一个直播销售专家,请根据产品信息给出销售话术。 + 要求: + 1. 要求话术中包含产品的商品名、商品规格、商品详情,不要遗漏商品的任何信息。 + 2. 在遵循产品信息的基础下尽可能的吸引顾客,对提供的信息进行拓展描述。要求字数大于500字。 + 3. 生成结果中不要包含任何表情。 + 4. 生成的结果中所有的**尺寸数字**和单位**替换**为中文。 + 5. 依次生成10组销售话术,每组话术之间**禁止**重复。 + 6. 响应结果必须是jsonArray。格式["话术1","话术2"]/no_think + """; + + private String aa_back = """ + 商品名:北京实木广岛椅 + 商品规格:1、尺寸:12X43cm,售价:100元 2、尺寸:16X54cm,售价:120元 + 商品详情:价格有点小贵,只因质量更好便宜的够好,为什么要买贵的?选材好东南亚FAS级橡胶木*拼板少原木方直出 木纹流畅更环保品牌净味环保油漆【FAS级橡胶木) + 。原木直出,木纹均匀清晰,不易变形 + 。天然选材,原木清香、拒绝甲醛 + ·质地坚硬细腻,稳固性好、防蛀虫、防腐增添家居氛围实木软包藤编椅品质实术,透气藤椅,倒V椅腿极简追光素色温柔在素色的温柔空间中,打造了如光影般的自在与宁静这种几乎无染、无色的生活,也投射了某种观念上的极简一切都展现出感性而放松的设计哲学,从内在寻找生活的自在DESIGNCONCEPTI设计理念四大亮点优势颜值与舒适体验的融合带来品质生活。/no_think + """; +} diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/controller/AuthController.java b/src/main/java/com/supervision/livedigitalavatarmanage/controller/AuthController.java index d23d8d7..ddabab5 100644 --- a/src/main/java/com/supervision/livedigitalavatarmanage/controller/AuthController.java +++ b/src/main/java/com/supervision/livedigitalavatarmanage/controller/AuthController.java @@ -3,13 +3,10 @@ package com.supervision.livedigitalavatarmanage.controller; import cn.hutool.core.lang.Assert; import com.supervision.livedigitalavatarmanage.domain.Users; import com.supervision.livedigitalavatarmanage.dto.R; -import com.supervision.livedigitalavatarmanage.dto.UserDetail; import com.supervision.livedigitalavatarmanage.service.AuthService; import com.supervision.livedigitalavatarmanage.util.UserUtil; import com.supervision.livedigitalavatarmanage.vo.LoginReqVo; import lombok.RequiredArgsConstructor; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; @RestController @@ -31,17 +28,6 @@ public class AuthController { return R.ok(token); } - /** - * 心跳接口,用于保持用户会话活跃 - * @return - */ - @GetMapping("/heartbeat") - public R heartbeat() { - UserDetail userDetail = UserUtil.currentUser(); - authService.heartbeat(userDetail.getUserId()); - return R.ok(); - } - @GetMapping("/me") public R getCurrentUserDetails() { diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/controller/LiveDigitalController.java b/src/main/java/com/supervision/livedigitalavatarmanage/controller/LiveDigitalController.java new file mode 100644 index 0000000..3abd112 --- /dev/null +++ b/src/main/java/com/supervision/livedigitalavatarmanage/controller/LiveDigitalController.java @@ -0,0 +1,31 @@ +package com.supervision.livedigitalavatarmanage.controller; + +import com.supervision.livedigitalavatarmanage.dto.R; +import com.supervision.livedigitalavatarmanage.service.LiveDigitalService; +import com.supervision.livedigitalavatarmanage.vo.SalesPitchReqVo; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import java.util.List; + +@RestController +@RequestMapping("/liveDigital") +@RequiredArgsConstructor +public class LiveDigitalController { + + private final LiveDigitalService liveDigitalService; + + /** + * 生成数字人销售话术 + * @param salespitchReqVo + * @return + */ + @PostMapping("/generate/salespitch") + public R> generateSalesPitch(@RequestBody SalesPitchReqVo salespitchReqVo) { + + List salesPitchResVos = liveDigitalService.generateSalesPitch(salespitchReqVo); + return R.ok(salesPitchResVos); + } +} diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/filter/JwtAuthenticationFilter.java b/src/main/java/com/supervision/livedigitalavatarmanage/filter/JwtAuthenticationFilter.java index aed07d0..48130e5 100644 --- a/src/main/java/com/supervision/livedigitalavatarmanage/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/supervision/livedigitalavatarmanage/filter/JwtAuthenticationFilter.java @@ -10,6 +10,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.core.context.SecurityContextHolder; @@ -20,6 +21,7 @@ import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +@Slf4j @Component @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { @@ -41,6 +43,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { String authHeader = request.getHeader("Authorization"); //2:根本没有 Authorization 头 if (authHeader == null || !authHeader.startsWith("Bearer ")) { + log.info("用户未携带token信息,请求路径:{}", request.getRequestURI()); if (permitAllRequest) { filterChain.doFilter(request, response); return; @@ -55,6 +58,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { try { username = jwtUtils.getUsernameFromToken(token); } catch (Exception e) { + log.error("token认证失败,请求路径:{},permitRequest is {}", request.getRequestURI(),permitAllRequest,e); if (permitAllRequest) { filterChain.doFilter(request, response); return; @@ -77,13 +81,14 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); - + log.info("用户 {} token认证成功,请求路径:{}", userDetails.getUsername(), request.getRequestURI()); //成功后放行,进入 Controller filterChain.doFilter(request, response); return; } else { //Token 过期 或 登录态不一致 + log.info("用户 {} token认证失败,请求路径:{},permitRequest is {}", userDetails.getUsername(), request.getRequestURI(),permitAllRequest); if (permitAllRequest) { filterChain.doFilter(request, response); return; @@ -93,6 +98,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { } } catch (UsernameNotFoundException e) { + log.error("token认证失败,请求路径:{},permitRequest is {}", request.getRequestURI(),permitAllRequest,e); if (permitAllRequest) { filterChain.doFilter(request, response); return; @@ -100,6 +106,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { writeTokenErrorResponse(response, "用户不存在"); return; } catch (Exception e) { + log.error("token认证失败,请求路径:{},permitRequest is {}", request.getRequestURI(),permitAllRequest,e); if (permitAllRequest) { filterChain.doFilter(request, response); return; diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/service/LiveDigitalService.java b/src/main/java/com/supervision/livedigitalavatarmanage/service/LiveDigitalService.java new file mode 100644 index 0000000..b7a30c3 --- /dev/null +++ b/src/main/java/com/supervision/livedigitalavatarmanage/service/LiveDigitalService.java @@ -0,0 +1,10 @@ +package com.supervision.livedigitalavatarmanage.service; + +import com.supervision.livedigitalavatarmanage.vo.SalesPitchReqVo; + +import java.util.List; + +public interface LiveDigitalService { + List generateSalesPitch(SalesPitchReqVo salespitchReqVo); + +} diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/service/impl/AuthServiceImpl.java b/src/main/java/com/supervision/livedigitalavatarmanage/service/impl/AuthServiceImpl.java index e57aaff..48afb69 100644 --- a/src/main/java/com/supervision/livedigitalavatarmanage/service/impl/AuthServiceImpl.java +++ b/src/main/java/com/supervision/livedigitalavatarmanage/service/impl/AuthServiceImpl.java @@ -3,7 +3,6 @@ package com.supervision.livedigitalavatarmanage.service.impl; import com.supervision.livedigitalavatarmanage.domain.Users; import com.supervision.livedigitalavatarmanage.dto.UserDetail; import com.supervision.livedigitalavatarmanage.enums.UserDisabledEnum; -import com.supervision.livedigitalavatarmanage.exception.BusinessException; import com.supervision.livedigitalavatarmanage.service.AuthService; import com.supervision.livedigitalavatarmanage.service.UsersService; import com.supervision.livedigitalavatarmanage.util.JwtUtils; @@ -13,9 +12,7 @@ import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; - import java.time.LocalDateTime; @Service @@ -28,9 +25,6 @@ public class AuthServiceImpl implements AuthService { private final UsersService userService; - // 掉线时间,单位:秒 - private long offlineTime = 90; // 默认90秒 - /** * 登录认证,返回JWT Token */ @@ -43,10 +37,6 @@ public class AuthServiceImpl implements AuthService { if (UserDisabledEnum.DISABLED.getCode().equals(user.getDisabled())) { throw new RuntimeException("用户已被禁用"); } - LocalDateTime lastActive = user.getLastActive(); - if (null != lastActive && lastActive.plusSeconds(offlineTime).isAfter(LocalDateTime.now())){ - throw new BusinessException("一个账号同时只能登录一个设备,请先下线其他设备"); - } try { Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(username, password) diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/service/impl/LiveDigitalServiceImpl.java b/src/main/java/com/supervision/livedigitalavatarmanage/service/impl/LiveDigitalServiceImpl.java new file mode 100644 index 0000000..ca1faab --- /dev/null +++ b/src/main/java/com/supervision/livedigitalavatarmanage/service/impl/LiveDigitalServiceImpl.java @@ -0,0 +1,51 @@ +package com.supervision.livedigitalavatarmanage.service.impl; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import com.supervision.livedigitalavatarmanage.constant.PromptTemplate; +import com.supervision.livedigitalavatarmanage.service.LiveDigitalService; +import com.supervision.livedigitalavatarmanage.vo.SalesPitchReqVo; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.ollama.OllamaChatModel; +import org.springframework.stereotype.Service; +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class LiveDigitalServiceImpl implements LiveDigitalService { + + private final OllamaChatModel ollamaChatModel; + @Override + public List generateSalesPitch(SalesPitchReqVo salespitchReqVo) { + Assert.notEmpty(salespitchReqVo.getProductName(), "产品名称不能为空"); + Assert.notEmpty(salespitchReqVo.getSpecifications(), "产品规格不能为空"); + Assert.notEmpty(salespitchReqVo.getDetail(), "产品详情不能为空"); + + for (int i = 0; i < 3; i++) { + try { + String salesPitchTemplate = PromptTemplate.GENERATE_SALESPITCH_TEMPLATE; + SystemMessage systemMessage = new SystemMessage(salesPitchTemplate); + UserMessage userMessage = new UserMessage(salespitchReqVo.buildTemplate()); + Prompt prompt = new Prompt(systemMessage, userMessage); + + ChatResponse call = ollamaChatModel.call(prompt); + String text = call.getResult().getOutput().getText(); + // 去除think标签 + if (StrUtil.isNotBlank(text) && text.contains("") && text.contains("")) { + text = text.replaceAll("(?s).*?", "").trim(); + } + return JSONUtil.toList(text, String.class); + }catch (Exception e) { + log.error("生成销售话术失败,尝试第 {} 次。请求内容:{}", i + 1, salespitchReqVo.buildTemplate(),e); + } + } + throw new RuntimeException("生成销售话术失败,请稍后重试"); + } +} diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/vo/ProductSpecification.java b/src/main/java/com/supervision/livedigitalavatarmanage/vo/ProductSpecification.java new file mode 100644 index 0000000..c05b081 --- /dev/null +++ b/src/main/java/com/supervision/livedigitalavatarmanage/vo/ProductSpecification.java @@ -0,0 +1,17 @@ +package com.supervision.livedigitalavatarmanage.vo; + +import lombok.Data; + +@Data +public class ProductSpecification { + + /** + * 尺寸 + */ + private String size; + + /** + * 价格 + */ + private String price; +} diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/vo/SalesPitchReqVo.java b/src/main/java/com/supervision/livedigitalavatarmanage/vo/SalesPitchReqVo.java new file mode 100644 index 0000000..364b226 --- /dev/null +++ b/src/main/java/com/supervision/livedigitalavatarmanage/vo/SalesPitchReqVo.java @@ -0,0 +1,44 @@ +package com.supervision.livedigitalavatarmanage.vo; + +import lombok.Data; + +import java.util.List; + +/** + * @author Administrator + * @description 销售话术请求对象 + * @createDate 2025-07-28 14:11:27 + */ +@Data +public class SalesPitchReqVo { + + /** + * 产品名称 + */ + private String productName; + + /** + * 产品类型 + */ + private List specifications; + + /** + * 产品详情 + */ + private String detail; + + + public String buildTemplate() { + StringBuilder template = new StringBuilder(); + template.append("请根据产品信息生成销售话术:"); + template.append("\n"); + template.append("产品名称:").append(productName).append("\n"); + template.append("产品规格:"); + for (ProductSpecification specification : specifications) { + template.append("产品类型:").append(specification.getSize()).append("\n"); + template.append("产品价格:").append(specification.getPrice()).append("\n"); + } + template.append("产品详情:").append(detail).append("/no_think"); + return template.toString(); + } +} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..290c5e1 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,160 @@ + + + logback + + + + + + + + + + + + + + + + + debug + + + %d{yyyy-MM-dd HH:mm:ss.SSS}---[%thread] %-5level %logger{50} - %msg%n + + UTF-8 + + + + + + + + ${log.path}/web_debug.log + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + + + ${log.path}/web-debug-%d{yyyy-MM-dd}.%i.log + + 100MB + + + 30 + 500MB + + + + debug + ACCEPT + DENY + + + + + + + ${log.path}/web_info.log + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + + + ${log.path}/web-info-%d{yyyy-MM-dd}.%i.log + + 100MB + + + 30 + 1GB + + + + info + ACCEPT + DENY + + + + + + + ${log.path}/web_warn.log + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + + ${log.path}/web-warn-%d{yyyy-MM-dd}.%i.log + + 100MB + + + 30 + 500MB + + + + warn + ACCEPT + DENY + + + + + + + ${log.path}/web_error.log + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + + ${log.path}/web-error-%d{yyyy-MM-dd}.%i.log + + 100MB + + + 30 + 500MB + + + + ERROR + ACCEPT + DENY + + + + + + + + + + + + + + + \ No newline at end of file