Spring_Ai

Spring AI Alibaba 学习 学习 大模型调用、图片生成、语音合成、工具调用、云RAG、Agent 的实践 Spring Ai Alibaba Chat 简单的流式大模型问答接口 🏷️模型配置信息 1spring: 2 ai: 3 dashscope: 4 api-key: "API_KEY" # api key 5 chat: 6 options: 7 model: "qwen-plus" # 模型名称: qwen-plus, deepseek-r1 ⭐Api 接口代码 1@RestController 2@RequestMapping("/chat-memory") 3public class ChatMemoryController { 4 5 private final ChatClient chatClient; 6 7 public ChatMemoryController(ChatModel chatModel) { 8 this.chatClient = ChatClient.builder(chatModel) 9 .defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory())) 10 .defaultAdvisors() 11 .build(); 12 } 13 14 /** 15 * 使用 Spring AI 提供的基于内存的 Chat memory方法 16 */ 17 @GetMapping("/in-memory") 18 public Flux<String> chatWithMemory(@RequestParam("prompt") String prompt, 19 @RequestParam("chatId") String chatId, 20 HttpServletResponse response) { 21 response.setCharacterEncoding("UTF-8"); 22 23 return chatClient.prompt(prompt) 24 .advisors(advisorSpec -> advisorSpec 25 .param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId) // 上下文记忆ID,隔离不同的聊天上下文 26 .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100)) // 检索长度 27 .stream() 28 .content(); 29 } 30 31} 多模型切换示例 1@RestController 2@RequestMapping("/multi-model-chat-client") 3public class multiModelChatClientController { 4 5 private final Set<String> modelList = Set.of( 6 "deepseek-r1", 7 "deepseek-v3", 8 "qwen-plus", 9 "qwen-max" 10 ); 11 12 private final ChatClient chatClient; 13 14 public multiModelChatClientController(ChatModel chatModel) { 15 this.chatClient = ChatClient.builder(chatModel) 16 .defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory())) 17 .build(); 18 } 19 20 @GetMapping("/chat") 21 public Flux<String> stream( 22 @RequestParam("prompt") String prompt, 23 @RequestParam("chatId") String chatId, 24 @RequestParam(value = "modelName", required = false) String modelName, 25 HttpServletResponse response 26 ) { 27 response.setCharacterEncoding("UTF-8"); 28 29 if (!modelList.contains(modelName)) { 30 return Flux.just("model not exist!"); 31 } 32 33 return chatClient.prompt(prompt) 34 .options(ChatOptions.builder() // 这里构建聊天选项,指定不同的模型 35 .withModel(modelName) 36 .build()) 37 .advisors(advisorSpec -> advisorSpec // 这里构建顾问参数,指定不同的聊天id和上下文长度 38 .param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId) 39 .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100)) 40 .stream() 41 .content(); 42 } 43} Spring AI Alibaba Image Gen 调用文生图模型示例 ...

April 25, 2025 · 7 min · 1352 words · 👨🏻‍🎓 LongWei

LeetCodeHot100刷题笔记

热题100 哈希 1. 两数之和 给定: 数组nums 和target 要求:找出两元素之和为target的数组下标 暴力: 1for i in range(len(nums)): 2 for j in range(i+1, len(nums)): 3 if ... 4# O(n^2) 题解: 问题分解 => 给定x 寻找 target-x(这是复杂度的主要部分) 用额外结构记录 1hashtable = dict() 2for i in range(len(nums)): 3 if nums[i] in hashtable: 4 return [i, hashtable[nums[i]]] 5 else: 6 hashtable[nums[i]] = i 7# 保存遍历过的元素,参考过去 ⭐思路:target = num1 + num2 => 已知num1 求num2 => 寻找 target - num1 寻找过程:hash存储,这样可以 O(1) 查找 另一个 49. 字母异位词分组 输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"] 输出: [["bat"],["nat","tan"],["ate","eat","tea"]] 思路:condition => […] HashTable, Key=排序后的Str, Value=List<String> 1HashMap<String, List<String>> hashTable = new HashMap<>(); 2for(String str : strs){ 3 char[] charArray = str.toCharArray(); // 将字符串转换为字符数组 4 Arrays.sort(charArray); // 对字符数组进行排序 5 String orderStr = new String(charArray); 6 if (hashTable.containsKey(orderStr)) { 7 hashTable.get(orderStr).add(str); 8 }else { 9 hashTable.put(orderStr, new ArrayList<>()); 10 hashTable.get(orderStr).add(str); 11 } 12} 13 14List<List<String>> res = new ArrayList<>(); 15 16for (Map.Entry<String, List<String>> entry : hashTable.entrySet()) { 17 res.add(entry.getValue()); 18} 19return res; ⭐思路:组合在一起 => eat和tea的共同点 => 均含有aet(顺序的) ...

April 13, 2025 · 36 min · 7504 words · LongWei

消息队列面试题

1. 如何保证消息不丢失 Broker:消息队列的核心组件,负责接收、存储和分发消息; 生产者的消息确认:生产者发送消息,需要通过消息确认机制来确保消息成功到达队列中; 开启confirm模式,在生产者端维护消息的状态,超过一定时间未接收到消息回调,进行消息的重发; 存储消息:Broker收到消息后,将消息持久化到磁盘中,避免宕机或重启导致消息的丢失; 配合生产者的确认机制,当消息持久化到磁盘后,再通知生产者ACK; 消费者的消息确认:消费者处理完消息,需要向MQ发送ACK确认,如果未确认,MQ需要重新投递该消息 手动ACK; 死信队列(兜底):对于多次消费失败的消息,发送到死信队列中,进行处理 => 提醒和记录日志 2. 如何处理重复消息 让消费者的处理逻辑具有幂等性,无论消息被消费多少次,结果都是一样的。 产生原因:消息消费的时候的网络延迟,或者投递的时候的网络延迟; 利用唯一标识ID去重:引入全局唯一ID,例如订单ID,利用Redis进行缓存或者数据库存储,在处理消息时,检查消息是否被处理过; 去重表:利用唯一索引来避免多次插入同一消息的ID (Insert into update on duplicate key ...) 3. 消息的有序性 保证消息的全局有序消费 单一生产者和单一消费者模式:顺序消费消息,但是性能容易出现瓶颈,且无法利用并发优势; 分区与顺序键:在支持分区的消息队列中,可以通过Partition Key将消息发送到特征的分区。分区内部有序。例如:将同一个订单的所有消息路由到同一个分区,确保该订单消息的顺序消费。 **顺序队列:**消息的消费顺序与投递顺序一致。 有序消息之全局有序 单生产者+单消费者 有序消息之部分有序 多组Queue+单消费者 组间消息可以无序;组内消息有序 这样将需要保证有序消息的信息,发送到同一组中,既保证了部分有序性,又提高了并发效率。 通过OrderId的哈希与组数量,将消息路由到不同队列中,相同的OrderId路由到同一队列中。 4. 如何处理消息堆积问题 生产者的生成数据远大于消费者 提高消费者消费速度 增加消费者线程数量:提高消费速度; 增加消费实例:分布式系统中,水平扩展一下消费实例;(多消费者) 优化消费者逻辑:减少IO操作,使用批量处理; 批量消费:一次消费多条消息,减少网络开销; 避免阻塞操作和不必要的计算。 降低生产者生产速度 对生产速度进行限流(令牌桶限流算法)或延迟处理,先消费高优先级的消息。 扩容消息队列 💡临时扩展多个消费者队列:将挤压的消息分发到不同的队列中进行消费。消费完成后再关闭临时队列; 增加消息队列容量,配置更大的内存和磁盘空间 5. 消息队列设计成推消息还是拉消息 Push推模式:MQ主动推送消息给消费者,适合实时性高、需要及时处理的常见。 优点:实时性好 缺点:难以控制消费速度,容易导致消费者过载(高并发下)。 Pull拉模式:消费者主动从MQ拉取消息,适合消费者能力有限,需要自身调整速率的场景 优点:消费者根据自身处理能力控制拉取频率,避免过载,更适合批量处理,一次给我能接受的批量数据。 缺点:实时性差,消息延迟。 6. RabbitMQ中消息什么时候会进入死信交换机 消息被拒绝:消费者明确拒绝消息,不要求重新投递时; 消息过期:TTL(生存时间)到期; **队列长度达到最大长度:**存不下了,将最早放入的消息转到死信交换机中,因为最新的可能正在执行,淘汰最老的消息; 死信交换机用处: 监控分析异常消息 订单的超时处理:处理时检查订单状态,没变化就是没支付,取消返回库存。 高负载场景下的队列限流和空流: 7.AMQP协议 Advanced Message Queuing Protocol ...

April 11, 2025 · 1 min · 92 words · 👨🏻‍🎓 LongWei

拼团营销交易拼团

拼团交易平台系统 **项目背景:**为了盘活沉睡用户,需要适当降低商品价格。但为了达到传播的效果,所以需要引入拼团方式,以客带客,靠用户自身传播的方式进行交易拉新。这样的处理方式对比于 KOL,会让利商品价值到用户自身。 第1-2节 拼团库表设计 业务流程: 运营角度: 1. 给哪些商品配置拼单;2. 拼团商品提供的规则信息:折扣、时间、人数等。 用户角度: 1. 参与拼团,首次发起or参与现有,拼单完成回调通知。 库表设计: 1. 人群设计,将所有符合某个条件的用户ID,全部写入特定Redis记录中。 拼团活动,折扣的多种迭代。 库表设计: 拼团配置表: 拼团活动表:设定了拼团的成团规则,人群标签的使用可以限定哪些人可见,哪些人可参与。 折扣配置表:拆分出拼团优惠到一个新的表进行多条配置。如果折扣还有更多的复杂规则,则可以配置新的折扣规则表进行处理。 人群标签表:专门来做人群设计记录的,这3张表就是为了把符合规则的人群ID,也就是用户ID,全部跑任务到一个记录下进行使用。 比如黑玫瑰人群、高净值人群、拼团履约率90%以上的人群等。 参与拼团表: 拼团账户表:记录用户的拼团参与数据,一个是为了限制用户的参与拼团次数,另外是为了人群标签任务统计数据。 用户拼单表:当有用户发起首次拼单的时候,产生拼单id,并记录所需成团的拼单记录,另外是写上拼团的状态、唯一索引、回调接口等。这样拼团完成就可以回调对接的平台,通知完成了。【微信支付也是这样的设计,回调支付结果,这样的设计可以方便平台化对接】当再有用户参与后,则写入用户拼单明细表。直至达成拼团 回调任务表:当拼团完成后,要做回调处理。但可能会有失败,所以加入任务的方式进行补偿。如果仍然失败,则需要对接的平台,自己查询拼团结果。 第1-3节 研发系统设计 进行研发系统设计。包括:库表设计、用例图、系统建模、工程模型、功能流程、UML时序图。 用例图:用户与系统交互最简表示形式; 流程图:功能节点的串联关系; 时序图:展示了整个拼团过程所涉及的系统模块和流转关系 系统架构: MVC架构 DDD架构 第2-2节 试算模型抽象模板设计 引入设计模式进行解耦和实现,提高工程代码的扩展性 => 设计模式抽象模板的通用结构定义,添加一个 tree规则树抽象模型,在引入到工程中进行使用。这样后续工程中就可以不断的定义通用的设计模式被不同的场景统一使用了。 模型设计 链式的多分支规则树模型结构,由功能节点自行决定后续流程的执行链路。它的设计比责任链的扩展性更好,自由度也更高 首先,定义抽象的通用规则树模型结构 涵盖:StrategyMapper, StrategyHandler /ˈstrætədʒi/、AbstractStrategyRouter<T, D, R>。 通过泛型设计允许使用方可以自定义出入参和动态上下文,让抽象模板模型具有通用性。 之后,由使用方自定义出工厂、功能抽象类和一个个流程流转的节点。 编码实现 项目工程的Types模块中,添加通用设计模式模板 1.1 策略映射器 1public interface StrategyMapper<T, D, R> { 2 3 /** 4 * 获取待执行策略 5 * 6 * @param requestParameter 入参 7 * @param dynamicContext 上下文 8 * @return 返参 9 * @throws Exception 异常 10 */ 11 StrategyHandler<T, D, R> get(T requestParameter, D dynamicContext) throws Exception; 12 13} 用于获取每个执行的节点,责任链加强版 T,D,R:入参,上下文,反参 1.2 策略受理器 ...

March 23, 2025 · 22 min · 4572 words · 👨🏻‍🎓 LongWei

Linux&Docker&Shell命令

docker 1service docker start 2 3docker pull <镜像名称>:<标签> 4 5docker rmi <镜像名称或ID> 6 7docker images 8 9docker run [选项] <镜像名称>:<标签> 10 11docker start <容器名称或ID> 12 13docker ps 14 15docker exec -it <容器名称或ID> /bin/bash # execute -it:Interactive and Teletypewriter 交互式&伪终端 Linux 文件和目录 1ls -l # -l:详细信息 2cd 3pwd # 当前目录 4mkdir/vim 5rm/rmdir 6cp mv 文件权限 1chmod # Change Mode # chmod u+rwx file.txt x:Execute 2chown 系统命令 1ps -ef # e:all -f full info 2ps -u $USER 3ps -ef | grep <info> # global regular expression print 4 5htop # 显示系统资源使用情况 网络管理 ...

March 16, 2025 · 2 min · 372 words · LongWei

集合面试题笔记

6319 HashMap 原理 数据结构:数组 + 链表 (JAVA8之后:数组+链表+红黑树) 数据结构 jdk1.7:数组+链表,数组每个元素是一个链表的表头,当发生冲突,将新元素添加在头部(头插法) hash冲突:头插法,❗扩容时可能造成环形链表 jdk1.8: 引入红黑树,当链表节点超过8个,那么这个链表会转换为红黑树。查询时间由 O(n) 优化为 O(logn)。当节点小于6个,再转换为链表。 hash冲突:尾插法,避免环形链表❗ 扩容机制 jdk1.7 扩容,元素会重新计算hash值,并分配到新的扩容数组中。⭐比较耗时 扩容时,头插法,在多线程情况下,可能造成环形链表 jdk1.8 扩容时,利用了元素哈希值和旧数组容量关系,减少了重新计算的哈希次数 扩容时,尾插法,避免环形链表 使用键的hashcode()计算hash值,然后(n-1) & hash确定数组中的位置。 n-1 => 10000 - 1 = 01111 HashMap的初始默认容量为16,负载因子为0.75。当存储的元素达到75%时,进行扩容,扩容为原来的2倍空间 扩展知识: hashmap的红黑树优化: JAVA8开始,为了优化hash冲突时的查找性能。但链表的长度超过8时,链表会转变为红黑树。红黑树是一种自平衡的二叉搜索树 查找时间 O(n) => O(logn)。 当元素少于6个,切换为链表 hashCode() 和 equals()的重要性: hashCode计算hash值,决定键的存储位置。 而hashCode相同=>冲突。 equals()比较的值 1// HashMap Node class 2static class Entry<K,V> { 3 final K key; // 键 4 V value; // 值 5 final int hash; // 计算后的哈希值 6 Entry<K,V> next; // 指向下一个元素的指针(形成链表) 7} 为什么头插法&多线程&扩容会造成环形链表? ...

March 13, 2025 · 3 min · 608 words · LongWei

项目重难点

黑马点评: 登录相关 使用 JWT 实现无状态认证,登录成功后生成一个包含用户信息的 Token 返回给前端,由前端每次请求时携带在请求头中。后端通过拦截器统一拦截请求,解析并校验 Token 的合法性。 <用户信息只包括:userId,username,role等必要信息 => 安全> 校验通过后,我们会将解析出来的用户信息保存到 ThreadLocal 中,构建一个线程级的用户上下文,在整个请求处理流程中都可以方便地获取当前登录用户,避免层层传参,提升代码整洁度和可维护性。 ThreadLocal原理:每个线程拥有独立的ThreadLocalMap对象。所有线程使用同一个 ThreadLocal 对象作为键,但值互不影响。通过线程级别的变量隔离,避免多线程竞争,实现线程安全。 超卖问题: 需要先判断是否有库存,有则扣减库存并返回成功;没有则返回失败。这包括两步操作 1.如何解决卖超问题 --在sql加上判断防止数据变为负数 =乐观锁 --redis预减库存减少数据库访问 内存标记(过滤掉无库存的访问)减少redis访问 请求先入队列缓冲,异步下单,增强用户体验 异步下单: -- 请求先入队缓冲,异步下单,增强用户体验 -- 请求出队,生成订单,减少库存 -- "客户端"定时轮询检查是否秒杀成功 数据库层面: 悲观锁(FOR UPDATE-行锁) ❌ 别提,浪费时间 乐观锁(版本号法 - SQL加上判断防止数据变成负数) ⭐⭐⭐ 应用层面: 分布式锁(互斥) ❌ 别提,浪费时间 缓存+消息队列 ⭐⭐⭐ 限流+熔断 ❌❌❌1.1 行锁 1BEGIN; 2SELECT stock FROM products WHERE id = 1 FOR UPDATE; 3-- 业务逻辑:检查库存是否足够,执行扣减操作 4UPDATE products SET stock = stock - 1 WHERE id = 1; 5COMMIT; 10000条(10线程循环1000次)请求,吞吐量:791.2/sec 互斥锁,串行访问 ...

March 11, 2025 · 14 min · 2859 words · 👨🏻‍🎓 LongWei

JVM面试题笔记

521. Java如何实现跨平台的? Java编译生成的是字节码文件.class文件,而不是特定于某个操作系统的机器码。 不同操作系统上都有各自实现的JVM,负责将字节码翻译成特定平台的机器代码并执行。使得JAVA文件可以被不同操作系统上的JVM运行。包装了一层。 9807. JVM的组成部分 主要组成部分: 编译好的JAVA字节码(.class文件)准备就绪。 类加载器子系统:将class文件加载到内存中(运行时的数据区)。 运行时数据区。 执行引擎(命令解释器):将字节码文件翻译成机器码,并交给CPU执行; 本地方法接口:过程中会调用不同语言提供的接口,比如驱动和..,调用本地方法接口,例如操作系统级别的功能或者高性能库。 522. 编译执行和解释执行 编译执行:先将源代码编译为机器代码,再在CPU上运行。例如:C,C++; ​ 啊 优点:编译后运行速度快,并且运行时,不需要再进行翻译。 解释执行:运行时,解释器逐行翻译并执行例如Python。 跨平台性好, 每个代码都是在每个平台上通过相应平台的解释器运行。 速度慢,每次执行都需要动态翻译。 => Java采样编译执行和解释执行相结合的方式: 解释执行:JVM将.JAVA文件=>.class字节码。 有助于程序的跨平台性; 即时编译:将经常执行的代码编译为本地机器码,避免反复解释 523. JVM的内存区域如何划分的❗? JVM运行时的数据区分为:1. 方法区 2. 堆 3. 虚拟机栈 4. 本地方法栈 5. 程序计数器。 方法区 - 存储类&共享信息 存储类信息、常量、静态变量 这些信息属于线程共享区域 Java堆 - 与JVM共存亡 存放所有线程共享的对象实例 和 数组 (垃圾回收主要战地) 虚拟机栈 每个线程创建一个栈:用来保存局部变量、操作数栈、方法出口信息。 局部变量:基本数据类型;以及对象引用; 栈与线程共存亡 本地方法栈 为本地方法服务。。。 程序计数器 保存当前线程执行的字节码指令地址或行号。 总结:Java程序与线程在JVM内存中的流程 程序启动:JVM初始化内存区域,加载类信息到方法区。 线程创建:为线程分配程序计数器、Java虚拟机栈和本地方法栈。 方法调用:线程执行方法时,创建栈帧并压入Java虚拟机栈。 对象创建:对象实例存储在Java堆中,元数据存储在方法区。 垃圾回收:JVM清理不再使用的对象和类信息。 线程结束:线程的栈和程序计数器被销毁。 程序结束:JVM释放所有内存区域并退出。 524. JVM中堆和栈的区别是什么? 栈:主要用于存储局部变量(基本类型+对象引用)和方法的调用信息(返回地址、参数等)。线程执行时,会创建该线程的栈帧,被压入Java虚拟机栈中。 执行结束,线程栈帧被弹出(销毁) ...

March 8, 2025 · 1 min · 163 words · LongWei

并发面试题笔记

1 如何创建线程 1) 实现Runnable接口 实现run()方法 1new Thread(MyRunnable()).start(); 2)继承Thread类 重写run()方法 3)Callable接口&&FutureTask 实现Callable call()方法,使用FutureTask包装Callable对象,通过Thread启动 1FutrueTask<ReturnType> task = new FutrueTask<>(MyCallable()); 2Thread(task).start 3 4ResultType res = task.get(); // 这里阻塞 4)使用线程池 通过ExecutorService提交Runnable或者Callable任务 不同方法对比 1Runnable vs Callable 2Callable:可以返回结果,可以抛出异常 3 4线程池的优势:避免重复创建和销毁线程,减少这部分重复带来的开销; 5ThreadPool => (FixedThreadPool, CachedThreadPool, ScheduledThreadPool) 6 7虚拟线程:虚拟线程创建和切换开销更低 8Thread.startVirtualThread() ThreadPool实践 XXXThreadPool.submit(task…) 1FixedThreadPool: 固定池中线程数量 => 适合1.执行较长任务;2.控制并发度 2 3CachedThreadPool:池中线程数量不固定,根据需要动态创建线程,空的被回收,少了多创建; 4⭐适用于任务执行时间短且任务数量不确定的场景; 5 6ScheduledThreadPool:适用于需要定时执行或周期性执行任务的场景。 1// 定时任务线程池 2ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); 3// For 延迟任务 4scheduledThreadPool.schedule(() -> { 5 // 任务逻辑 6}, 10, TimeUnit.SECONDS); // 延迟10秒执行 7 8// For周期执行任务 9scheduledThreadPool.scheduleAtFixedRate(() -> { 10 // 任务逻辑 11}, 0, 1, TimeUnit.SECONDS); // 每隔1秒执行一次 Thread.sleep(0) <= 主动让出CPU控制权 ...

March 5, 2025 · 11 min · 2162 words · LongWei

MySQL面试题笔记

617 MySQL 数据排序 实现? Order By 命中索引(包括索引字段),使用索引排序(⭐有序⭐),效率最高效 否则使用文件排序,文件少=> 内存排序 sort_buffer 文件大=>外部排序,归并排序 内部排序细节: 双路排序(待排序的列数据太大了):使用row_id(回查表) + sort_field ​ 排好序后,使用row_id将完整的记录取出来 单路排序(待排序数据大小能接受) ​ 直接拍,不会查表,直接把拍好的结果返回 外部排序: 拆分小的,外部多路归并排序,小=>大 外部归并排序 => 先分段排序,每一段调入内存执行快排 ​ => 归并阶段,因为每子段都是有序的 => 多路归并排序 589 一条SQL的执行过程 先通过连接器校验权限 利用分析器进行SQL语句词法分析和语法分析,构建解析树 利用优化器选择合适的索引和表连接顺序,最终选择一条最佳的执行计划 利用执行器,调用引擎层查询数据,返回结果集 具体:Select * from user where id = 1; SQL => Server层连接器,权限校验,账号是否有资格获取。无=> Access denied for user。 连接成功后,空闲一段时间会断开 分析器(查询解析) => 语法分析:SQL : Select类型✔️ user表✔️ id列 ✔️拆分成词,再组装为解析树。 语义分析:语法是否有误 => you have an error in your SQL syntax (字段、表|存在?) 分析解析树语法正确性 优化器(查询优化)=> ...

March 4, 2025 · 7 min · 1346 words · LongWei