首页
归档
留言
友链
广告合作
壁纸
更多
美女主播
Search
1
博瑞GE车机升级/降级
5,590 阅读
2
Mac打印机设置黑白打印
4,905 阅读
3
修改elementUI中el-table树形结构图标
4,875 阅读
4
Mac客户端添加腾讯企业邮箱方法
4,654 阅读
5
intelliJ Idea 2022.2.X破解
4,333 阅读
后端开发
HarmonyOS Next
Web前端
微信开发
开发辅助
App开发
数据库
随笔日记
登录
/
注册
Search
标签搜索
Spring Boot
Java
Vue
Spring Cloud
Mac
MyBatis
WordPress
MacOS
asp.net
Element UI
Nacos
.Net
Spring Cloud Alibaba
MySQL
Mybatis-Plus
Typecho
jQuery
Java Script
微信小程序
Oracle
Laughing
累计撰写
621
篇文章
累计收到
1,419
条评论
首页
栏目
后端开发
HarmonyOS Next
Web前端
微信开发
开发辅助
App开发
数据库
随笔日记
页面
归档
留言
友链
广告合作
壁纸
美女主播
搜索到
103
篇与
的结果
2025-04-20
JVisualVM:多合一故障处理工具
VisualVM是功能最强大的虚拟机运行监视和故障处理程序之一,曾经在很长一段时间内是Oracle官方助理发展的虚拟机故障处理工具。VisualVM的性能分析功能,比起JProfiler等专业收费工具也不遑多让,相比于第三方的功能,VisualVM还有一个很大的优点:不需要被监视的程序基于特殊的Agent去运行,因此它的通用性很强,对应用程序实际性能影响也比较小,使得它可以直接应用于生产环境。在这个注重颜值的时代,相较于JProfiler,可能界面比较丑陋是最大的缺点了吧。在强大的插件加持下,VisualVM可以实现包括但不限于以下的功能:显示虚拟机进程以及进程的配置、环境信息显示应用程序的处理器、垃圾收集、堆、方法区及线程信息dump以及分析堆转储快照方法级的应用程序性能分析,找出被调用最多、运行时间最长的方法壹、安装插件VisualVM插件可以直接在线安装,依次点击【tools】- 【plugins】勾选需要安装的插件,点击【install】即可进行安装。贰、生成、浏览堆转储快照生成和浏览堆转储快照可能是日常开发过程中分析性能最常见的操作,下面介绍下在visualVM中如何生成或者分析堆转储快照。一、生成堆转储快照我们运行一个程序后,在visualVM左侧Local下,会自动加载对应的进程信息。比如我这里的JHSDB_TestCae,我们下面都以这个为例进行说明。可以通过以下两种方式之一生成堆转储快照选择应用程序,点击右键,然后选择【Heap Dump】双击应用程序,在打开的页面中,选择【Monitor】,然后选择【Heap Dump】快照存储后会自动打开。二、打开堆转储快照如果是别人发过来的快照文件,可以点击左上角的文件图标打开。Overview页签显示的是一些基本信息,包括pid、Java版本、虚拟机参数、环境参数等信息。Monitor页签是图标信息显示的CPU、堆、元空间、类及线程等信息。Threads页签显示的是线程信息Sampler页签以一定的时间间隔对CPU、内存进行采样,可检查出占用CPU时间较多或占用内存空间较大的线程,有助于性能调优。对CPU采样时,该页提供CPU样例(CPU samples)和线程CPU时间(Thread CPU time)两个子页签,前者可用于分析调用链上的方法耗时,后者可用于比较线程CPU耗时。profiler是性能分析页签,因为性能分析对程序运行性能影响较大,生产环境不建议使用。叁、常见插件介绍上面所过,VisualVM是支持插件的,下面我们介绍几款常用的插件。一、BTrace动态日志跟踪BTrace允许在不中断目标程序的前提下,通过HotSpot虚拟机的Instrument功能动态加入原本并不存在的调试代码。在插件页面,选择BTrace Workbench,然后安装我们创建一段输出固定内容的死循环并运行package cc.lisen.test; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * description: * * @author: lisen * @Blog:<a href="https://lisen.cc">lisen.cc</a> * @DateTime: 2025-04-20 20:34 */ public class BTraceTest { public String sayHello(String name) { return "Hello " + name; } public static void main(String[] args) throws IOException { while (true) { BTraceTest bTraceTest = new BTraceTest(); BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); System.out.println(bTraceTest.sayHello(reader.readLine())); } } }打开VisualVM,找到我们的程序,点击右键选择【Trace Application】,然后输入以下脚本/* BTrace Script Template */ import org.openjdk.btrace.core.annotations.*; import static org.openjdk.btrace.core.BTraceUtils.*; @BTrace(unsafe=true) public class TracingScript { @OnMethod( clazz="cc.lisen.test.BTraceTest", method="sayHello", location=@Location(Kind.RETURN) ) public static void func(@Self cc.lisen.test.BTraceTest instance,String test,@Return String result) { System.out.println("调用堆栈:"); jstack(); } }
2025年04月20日
1 阅读
0 评论
0 点赞
2025-04-18
借助jstack分析CPU占用高及线程死锁问题
jstack是用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内存每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因。一、分析CPU占用高的问题在实际项目开发过程中,CPU占用异常是经常遇到的问题,当遇到CPU异常时,我们可以借助jstack进行问题排查。下面我们通过模拟,刨析一下整个问题分析过程。模拟代码在下面的代码中,我们写了一个死循环,模拟CPU占用过高问题。public class ThreadMain { private static ExecutorService executorService = Executors.newFixedThreadPool(5); public static void main(String[] args) { Task task1 = new Task(); Task task2 = new Task(); executorService.execute(task1); executorService.execute(task2); } public static Object lock = new Object(); static class Task implements Runnable { public void run() { synchronized (lock) { long sum = 0L; while (true) { sum += 1; } } } } }查询CPU占用高的进程在Linux服务器,我们通过top命令,可以查看CPU信息,默认按照由高到低显示。输入top命令,查询进程信息。top可以看到,目前占用最高的进程ID是9420查询对应线程在上面,我们已经查询到9420进程,我们可以进一步分析,9420进程内到底是哪个线程CPU占用最高。输入一下命令,可以查看线程信息top Hp 9420可以看到目前占用CPU最高的线程是9445线程转换我们通过top命令查询到的9445线程ID是10进制的,但是jstack展示的线程是16进制的,因此我们需要将10进制的9445转成16进制,也就是0x24e5通过jstack查询线程快照输入一下命令jstack 9420{alert type="info"}jstack后的vmid就是进程ID{/alert}jstack输出信息的nid就是16进制的线程id,我们找到nid是0x24e5线程,可以定位到具体代码位置 at ThreadMain$Task.run(ThreadMain.java:30),也就是ThreadMain.java的第30行。二、分析线程死锁问题模拟代码我们通过下面的代码,模拟线程死锁问题public class ThreadMain { private final static Object lockA = new Object(); private final static Object lockB = new Object(); public static void main(String[] args) { new Thread(() -> { synchronized (lockA) { System.out.println(Thread.currentThread().getName() + "获得了锁LockA"); try { Thread.sleep(1000); } catch (InterruptedException e) { try { throw new RuntimeException(e); } catch (RuntimeException ex) { throw new RuntimeException(ex); } } synchronized (lockB) { System.out.println(Thread.currentThread().getName() + "获得了锁LockB"); } } }).start(); new Thread(() -> { synchronized (lockB) { System.out.println(Thread.currentThread().getName() + "获得了锁LockB"); try { Thread.sleep(1000); } catch (InterruptedException e) { try { throw new RuntimeException(e); } catch (RuntimeException ex) { throw new RuntimeException(ex); } } synchronized (lockA) { System.out.println(Thread.currentThread().getName() + "获得了锁LockA"); } } }).start(); } }查询VMID我们通过jps命令,可以查询虚拟机进程IDjps -l可以看到,我们当前VMID是18395查询线程信息同样通过jstack命令可以查询进程信息。jstack 18395重点关注状态时BLOCKED的线程,我们可以看到Thread-1跟Thread-1目前都是BLOCKED状态,Thread-1目前锁定了<0x0000000782539600>并且正在等待<0x00000007825395f0>,而Thread-0正好相反,Thread-0锁定了<0x00000007825395f0>并且正在等待<0x0000000782539600>,这就导致两个线程相互锁定。当然,我们也可以看到两个线程异常发生的位置。
2025年04月18日
6 阅读
0 评论
0 点赞
2025-04-16
java获取JVM信息
在Java或者Spring Boot日常开发过程中,因为Java的自动垃圾回收机制,我们可能很少关注JVM的信息,面临性能问题时,我们可能也会更多的借助JProfiler等第三方工具进行问题分析。比如我们需要做一个程序运行健康监控功能,我们可能就需要获取Jvm的信息,下面我们就看一下如何在Java中获取Jvm信息。首先我们定义两个公共方法,分别是用来将毫秒转换成时分秒,另外一个方法是将时间戳转换成时分秒毫秒。/** * 毫秒转换成时分秒 * * @param uptime 毫秒 * @return 时分秒 */ protected static String toDuration(double uptime) { NumberFormat fmtI = new DecimalFormat("###,###", new DecimalFormatSymbols(Locale.ENGLISH)); NumberFormat fmtD = new DecimalFormat("###,##0.000", new DecimalFormatSymbols(Locale.ENGLISH)); uptime /= 1000; if (uptime < 60) { return fmtD.format(uptime) + " 秒"; } uptime /= 60; if (uptime < 60) { long minutes = (long) uptime; return fmtI.format(minutes) + "分"; } uptime /= 60; if (uptime < 24) { long hours = (long) uptime; long minutes = (long)((uptime - hours) * 60); String s = fmtI.format(hours) + "时"; if (minutes != 0) { s += " " + fmtI.format(minutes) + "分"; } return s; } uptime /= 24; long days = (long) uptime; long hours = (long)((uptime - days) * 24); String s = fmtI.format(days) + "天"; if (hours != 0) { s += " " + fmtI.format(hours) + "时"; } return s; } /** * 时间戳钻日期 * * @param timeStamp 时间戳 * @return 日期 */ protected static String timeStamp2Date(long timeStamp) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.ENGLISH); return simpleDateFormat.format(timeStamp); }{mtitle title="获取Jvm基本信息"/}可以获取jvm的名称、jvm版本、jvm制造商、java版本、jvm启动时间、jvm运行时间等信息RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); System.out.printf("jvm名称:%s %n", runtimeMXBean.getVmName()); System.out.printf("jvm版本:%s %n", runtimeMXBean.getVmVersion()); System.out.printf("jvm制造商:%s %n", runtimeMXBean.getVmVendor()); System.out.printf("java版本:%s %n", System.getProperty("java.version")); System.out.printf("jvm启动时间:%s %n", timeStamp2Date(runtimeMXBean.getStartTime())); System.out.printf("jvm运行时间:%s %n", toDuration(runtimeMXBean.getUptime()));{mtitle title="获取编译信息"/}可以获取编译器名称、编译耗时及是否支持及时编译器编译监控。CompilationMXBean compilationMXBean = ManagementFactory.getCompilationMXBean(); System.out.printf("编译器名称:%s %n", compilationMXBean.getName()); System.out.printf("编译耗时:%s %n", toDuration(compilationMXBean.getTotalCompilationTime())); System.out.printf("是否支持及时编译器编译监控:%s %n", compilationMXBean.isCompilationTimeMonitoringSupported());{mtitle title="获取线程信息"/}可以获取总线程数、守护进程线程数、峰值线程数、Java虚拟机启动后创建并启动的线程总数、是否支持测量当前线程的CPU时间等信息。ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); System.out.printf("总线程数(守护+非守护):%s %n", threadMXBean.getThreadCount()); System.out.printf("守护进程线程数:%s %n", threadMXBean.getDaemonThreadCount()); System.out.printf("峰值线程数:%s %n", threadMXBean.getPeakThreadCount()); System.out.printf("Java虚拟机启动后创建并启动的线程总数:%s %n", threadMXBean.getTotalStartedThreadCount()); System.out.printf("是否支持测量当前线程的CPU时间:%s %n", threadMXBean.isCurrentThreadCpuTimeSupported()); System.out.printf("当前线程的总CPU时间:%s %n", threadMXBean.getCurrentThreadCpuTime()); System.out.printf("当前线程在用户模式中执行的CPU时间:%s %n", threadMXBean.getCurrentThreadUserTime()); {mtitle title="堆内存使用情况"/}可以获取初始化堆内存、已使用堆内存、可使用堆内存、最大堆内存等信息MemoryUsage heapMemoryUsage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage(); System.out.printf("初始化堆内存:%sm %n", heapMemoryUsage.getInit() / 1024 / 1024); System.out.printf("已使用堆内存:%sm %n", heapMemoryUsage.getUsed() / 1024 / 1024); System.out.printf("可使用堆内存:%sm %n", heapMemoryUsage.getCommitted() / 1024 / 1024); System.out.printf("最大堆内存:%sm %n", heapMemoryUsage.getMax() / 1024 / 1024); 为了方便查询数据,我们可以手工编译、运行代码javac JvmInfo.java java -Xmx20m JvmInfo{mtitle title="非堆内存使用情况"/}也可以获取非堆内存,也就是永久代的信息。需要注意,JDK1.7之后已经废弃了永久代的概念,使用元空间进行替代MemoryUsage noHeapMemoryUsage = ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage(); System.out.printf("初始化非堆内存:%sm %n", noHeapMemoryUsage.getInit() / 1024 / 1024); System.out.printf("已使用非堆内存:%sm %n", noHeapMemoryUsage.getUsed() / 1024 / 1024); System.out.printf("可使用非堆内存:%sm %n", noHeapMemoryUsage.getCommitted() / 1024 / 1024); System.out.printf("最大非堆内存:%sm %n", noHeapMemoryUsage.getMax() / 1024 / 1024); {mtitle title="系统概况"/}可以获取操作系统名称、操作系统版本、处理器数量、系统平均负载、电脑架构等信息OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean(); System.out.printf("操作系统名称:%s %n", operatingSystemMXBean.getName()); System.out.printf("操作系统版本:%s %n", operatingSystemMXBean.getVersion()); System.out.printf("可用处理器数量%s %n", operatingSystemMXBean.getAvailableProcessors()); System.out.printf("系统平均负载:%f %n", operatingSystemMXBean.getSystemLoadAverage()); System.out.printf("电脑架构:%s %n", operatingSystemMXBean.getArch());{mtitle title="类加载情况"/}可获取当前加载类数量、未加载类数量、总加载类数量ClassLoadingMXBean classLoadingMXBean = ManagementFactory.getClassLoadingMXBean(); System.out.printf("当前加载类数量%s %n", classLoadingMXBean.getLoadedClassCount()); System.out.printf("未加载类数量%s %n", classLoadingMXBean.getUnloadedClassCount()); System.out.printf("总加载类数量%s %n", classLoadingMXBean.getTotalLoadedClassCount()); 也可以获取内存池对象public static void main(String[] args) { List < MemoryPoolMXBean > memoryPoolMXBeanList = ManagementFactory.getMemoryPoolMXBeans(); for (MemoryPoolMXBean memoryMXBean: memoryPoolMXBeanList) { final String kind = memoryMXBean.getType().name(); final MemoryUsage usage = memoryMXBean.getUsage(); System.out.println( "内存模型: " + getKindName(kind) + ", 内存空间名称: " + getPoolName(memoryMXBean.getName()) + ", jvm." + memoryMXBean.getName() + ".init(初始化):" + bytesToMB(usage.getInit())); System.out.println( "内存模型: " + getKindName(kind) + ", 内存空间名称: " + getPoolName(memoryMXBean.getName()) + ", jvm." + memoryMXBean.getName() + ".used(已使用): " + bytesToMB(usage.getUsed())); System.out.println( "内存模型: " + getKindName(kind) + ", 内存空间名称: " + getPoolName(memoryMXBean.getName()) + ", jvm." + memoryMXBean.getName() + ".committed(可使用):" + bytesToMB(usage.getCommitted())); System.out.println( "内存模型: " + getKindName(kind) + ", 内存空间名称: " + getPoolName(memoryMXBean.getName()) + ", jvm." + memoryMXBean.getName() + ".max(最大):" + bytesToMB(usage.getMax())); } } protected static String getKindName(String kind) { if ("NON_HEAP".equals(kind)) { return "NON_HEAP(非堆内存)"; } else { return "HEAP(堆内存)"; } } protected static String getPoolName(String poolName) { switch (poolName) { case "Code Cache": return poolName + "(代码缓存区)"; case "Metaspace": return poolName + "(元空间)"; case "Compressed Class Space": return poolName + "(类指针压缩空间)"; case "PS Eden Space": return poolName + "(伊甸园区)"; case "PS Survivor Space": return poolName + "(幸存者区)"; case "PS Old Gen": return poolName + "(老年代)"; default: return poolName; } } protected static String bytesToMB(long bytes) { return (bytes / 1024 / 1024) + " MB"; }
2025年04月16日
9 阅读
0 评论
0 点赞
2025-04-03
若依前后端分离项目白名单支持携带参数
RuoYi是一个后台管理系统,基于经典技术组合(Spring Boot、Apache Shiro、MyBatis、Thymeleaf)主要目的让开发者注重专注业务,降低技术难度,从而节省人力成本,缩短项目周期,提高软件安全质量。若依前端框架通过permission.js文件中的whiteList进行配置的,比如登录、注册登连接,需要能直接跳转,只需要在whiteList中配置上对应的路由即可。const whiteList = ['/login', '/register']然后在路由打开前,判断路由是否在白名单内。但是这个判断逻辑存在一个问题,就是判断方法使用的whiteList.indexOf(to.path),这样如果我们路由携带参数的话,会无法实现,因为参数是动态的,我们无法配置白名单。为了实现跳转,我们需要替换whiteList.indexOf(to.path)方法,通过正则表达式的方式进行匹配。增加正则表达式校验方法在utils\validate.js文件中,增加正则校验方法/** * 路径匹配器 * @param {string} pattern * @param {string} path * @returns {Boolean} */ export function isPathMatch(pattern, path) { const regexPattern = pattern.replace(/\//g, '\\/').replace(/\*\*/g, '.*').replace(/\*/g, '[^\\/]*') const regex = new RegExp(`^${regexPattern}$`) return regex.test(path) }修改permission.js在permission.js引入新增的方法,并将原来的whiteList.indexOf(to.path)替换成新方法。import { isPathMatch } from '@/utils/validate' const isWhiteList = (path) => { return whiteList.some(pattern => isPathMatch(pattern, path)) }
2025年04月03日
18 阅读
0 评论
0 点赞
2025-03-30
VSCode配置Java(Spring Boot)开发环境
{message type="info" content="VSCode简介"/} 大名鼎鼎的VS Code不用过多介绍吧。VS Code全称Visual Studio Code,是由微软开发的一款免费、开源、跨平台的代码编辑器,支持Windows、macOS和Linux系统。它以其轻量级、高性能和丰富的扩展生态广受开发者欢迎,适用于多种编程语言和开发场景。 如果你也厌倦了一成不变的IntelliJ IDEA开发环境,想换换开发风格,不妨体验一下在VSCode中开发Spring Boot程序。{message type="success" content="前置条件"/} 本文基于Mac OS进行介绍,在Windows及Linux下配置也是类似的,为了顺利完成环境搭建,你需要提前准备好以前软件及配置。JDK并完成环境变量配置。Maven并完成环境变量配置。VSCode软件{message type="warn" content="安装插件"/} 其实所谓的配置环境,不过就是安装一堆Java插件。 打开VS Code,找到【插件】页签,我们需要安装以下插件。Debugger for JavaMaven for JavaLanguage Support for Java(TM) by Red HatExtension Pack for Java{message type="error" content="使用"/} 你可以找一个现有的Spring Boot环境,或者使用脚手架大家一个简单的Spring Boot程序。{alert type="info"} 官方脚手架目前已经不支持JDK1.8,如果使用JDK1.8的话,可以使用阿里云提供的脚手架,地址为: start.aliyun.com {/alert} 使用VS Code打开代码,找到启用程序,如下图 可以点击Run运行程序或者点击Debug调试程序。 也可以打短点调试程序,这里我在启动方法中打一个短点,然后点击Debug调试程序,之后程序就会进入断点。 我们也可以安装依赖,比如下面安装lombok依赖 如果需要打包,可以在控制台输入mvn package命令通过原生Maven的方式进行打包,因为我们上面安装了Maven插件,在VS Code左下角(默认)有个Maven选项,也可以打包,与Idea基本一致。 好了,以上就是在VS Code下搭建Java(Spring Boot)开发环境的完整步骤,感兴趣的童鞋可以去尝试一下。
2025年03月30日
36 阅读
0 评论
0 点赞
2025-03-21
Springboot中使用Undertow替换默认的Tomcat容器
{mtitle title="Undertow介绍"/}Undertow是由JBoss(现为Red Hat)开发的一款轻量级、高性能的Web服务器。它是WildFly应用服务器的默认Web容器,专注于高并发和低延迟的场景。Undertow基于NIO(非阻塞I/O)构建,支持HTTP/1.x、HTTP/2和WebSocket协议。{mtitle title="Undertow优势"/}Tomcat凭借其稳定性、易用性和社区支持成为Springboot默认的容器。虽然Tomcat表现足够优秀,但是Undertow也有其可圈可点的地方。高并发支持:Undertow基于NIO构建,能够高效处理大量并发请求,适合高负载场景。低延迟:由于其非阻塞的设计,Undertow在低延迟场景中表现优异。轻量级:Undertow的核心代码非常精简,启动速度快,资源占用低。在资源有限的情况下,尤其是内存和CPU资源紧张的情况下,Undertow是更好的选择。{mtitle title="Undertow替换Tomcat"/}上面介绍了Undertow的优点,那么我们如何在Springboot项目中使用Undertow作为默认容器呢。先来看一下,默认Tomcat容器,启动成功后控制台输出信息为了使用Undertow容器,我们需要修改项目的pom.xml文件,排除掉Tomcat。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency>然后添加Undertow的依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-undertow</artifactId> </dependency>然后重启项目,查看控制台输出
2025年03月21日
93 阅读
0 评论
0 点赞
2025-03-18
Spring 官宣接入 DeepSeek,太香了!
Spring AI已经支持DeepSeek。今天和大家聊聊如何在Spring Boot项目制使用DeepSeek,还是非常方便的!Spring AI介绍Spring AI是一个用于AI工程的应用程序框架,将Spring生态系统设计原则应用于AI领域。其核心是通过抽象化和模块化设计,简化AI功能的接入步骤,同时保持与Spring生态的无缝兼容。以下是其主要特点与功能:统一的抽象API:支持主流AI服务,如 OpenAI、DeepSeek、Google、和Ollama等,提供了提供标准化的接口。核心功能模块:模型交互、向量处理、检索增强生成(RAG)、函数调用。低代码集成:通过Spring Boot Starter依赖快速接入,在配置文件中配置好AI服务即可使用。结构化输出:将模型响应直接映射为Java对象,简化数据处理。流式响应:支持Flux流式输出,适用于实时聊天等场景。Spring AI集成DeepSeek申请Api Key首先我们需要去DeepSeek官网申请Api Key,地址:https://platform.deepseek.com/api_keys Spring Boot中集成DeepSeek首先在SpringBoot项目中添加Spring AI对应的依赖;<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> <version>1.0.0-M6</version> </dependency>然后在项目的application.yml配置文件中添加调用AI服务相关的配置;spring: ai: openai: # 调用AI接口时表明身份的API KEY api-key: <YOUR_API_KEY> # 调用AI接口时的基础路径,配置的是阿里云百炼的基础路径 base-url: https://api.deepseek.com chat: options: # 调用的模型,DeepSeek的话可以选择deepseek-r1或deepseek-v3 model: deepseek-chat # 用来控制文本生成的随机性(创造力),值越小越严谨 temperature: 0.8创建一个控制器类,用于处理与 DeepSeek 的交互,import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/chat") public class ChatController { @Autowired private DeepSeekClient deepSeekClient; @PostMapping public String chat(@RequestBody String message) { return deepSeekClient.chatCompletion(message).getOutput().getContent(); } @GetMapping(value = "/stream", produces = "text/event-stream") public Flux<String> chatStream(@RequestParam String message) { return deepSeekClient.chatFluxCompletion(message) .map(response -> response.getOutput().getContent()); } }
2025年03月18日
26 阅读
0 评论
0 点赞
2024-07-06
Sprint Boot接入阿里通义千问
阿里通义千问是阿里巴巴推出的大规模语言模型,由达摩院研发。它是基于先进的自然语言处理技术构建的,旨在提供高质量的文本生成和理解能力。通义千问的特点包括:多语言支持:通义千问能够理解和生成多种语言的文本,包括但不限于中文、英文、日文、法文、西班牙文和德文等,这使得它具有全球化的交流能力。训练数据丰富:它的训练数据来自阿里巴巴内部的大量语言和文本资源,涵盖了文学、历史、科学、艺术等各种主题,旨在提供广泛的知识基础。应用场景广泛:通义千问不仅可以用于日常对话和信息查询,还可以为企业和个人用户提供定制化服务,如行业咨询、文档撰写、智能助手等,帮助用户生成内容或解答问题。与阿里巴巴产品整合:2023年4月,阿里巴巴宣布其所有产品将接入通义千问,这意味着用户可以在钉钉、天猫精灵等平台上直接体验到该模型的服务,企业也可以利用阿里云的能力来定制自己的行业专属大模型。合规性:2023年9月13日,通义千问通过了相关备案并正式对公众开放,表明其在遵守法律法规的前提下提供服务。商业价值:张勇(阿里巴巴集团董事会主席兼CEO)强调了通义千问对于提升阿里巴巴产品和服务的智能化水平,以及帮助企业利用人工智能进行创新。如果你是基于Python或Java开发,那么通义千问支持的SDK还是比较完善的,本文已Spring Boot接入阿里通义千问为例进行说明。壹、申请Key进入阿里云官网,定位到【API-KEY管理】,如果已经有Key的话,可以直接使用,如果没有可以创建一个新的。贰、创建Spring Boot工程具体怎么创建工程就不过多介绍了,现在主要说说创建完之后的配置及开发工作。2.1、添加依赖<dependency> <groupId>com.alibaba</groupId> <artifactId>dashscope-sdk-java</artifactId> <version>2.14.0</version> </dependency>2.2、修改配置文件在配置文件application.yml中添加我们申请到的Key注意替换成你实际的keyqwen: ai-api-key: sk-XXXX2.3、创建配置文件,读取配置信息@Component @ConfigurationProperties(prefix = "qwen") @Data public class QWenConfig { private String aiApiKey; }2.4、创建通义千问的配置文件@Configuration public class AliQWenConfig { @Bean public Generation generation() { return new Generation(); } }2.5、创建请求@RestController @RequestMapping("ai") public class QWenController { @Resource private Generation generation; @Resource private QWenConfig qWenConfig; /** * 测试demo * * @param content 用书输入文本内容 */ @PostMapping(value = "qwen") public String send(@RequestBody String content) throws NoApiKeyException, InputRequiredException { //用户与模型的对话历史。list中的每个元素形式为{“role”:角色, “content”: 内容}。 Message userMessage = Message.builder() .role(Role.USER.getValue()) .content(content) .build(); GenerationParam param = GenerationParam.builder() //指定用于对话的通义千问模型名 .model("qwen-turbo") .messages(Collections.singletonList(userMessage)) // .resultFormat(GenerationParam.ResultFormat.MESSAGE) //生成过程中核采样方法概率阈值,例如,取值为0.8时,仅保留概率加起来大于等于0.8的最可能token的最小集合作为候选集。 // 取值范围为(0,1.0),取值越大,生成的随机性越高;取值越低,生成的确定性越高。 .topP(0.8) //阿里云控制台DASHSCOPE获取的api-key .apiKey(qWenConfig.getAiApiKey()) //启用互联网搜索,模型会将搜索结果作为文本生成过程中的参考信息,但模型会基于其内部逻辑“自行判断”是否使用互联网搜索结果。 .enableSearch(true) .build(); GenerationResult generationResult = generation.call(param); return generationResult.getOutput().getChoices().get(0).getMessage().getContent(); } }叁、测试使用ApiFox测试一下
2024年07月06日
1,121 阅读
0 评论
0 点赞
2024-07-04
使用thumbnailator实现图片压缩
Thumbnailator是一个用于Java的强大的图片处理库,主要用于创建、缩放和裁剪图片的缩略图。它设计得既简单又功能强大,提供了一系列丰富的特性:尺寸调整:Thumbnailator能够根据指定的宽度或高度来调整图片大小。裁剪:可以将图片裁剪为特定的尺寸或长宽比。水印添加:可以在图片上添加文本或图片形式的水印。格式支持:支持多种图像格式,如JPEG、PNG、BMP、GIF等。Thumbnailator的使用非常直观。你只需要在你的项目中添加Thumbnailator的依赖(比如在Maven或Gradle的构建文件中),然后就可以在代码中调用其提供的方法了。下面结合项目示例,介绍一下使用Thumbnailator的方法,当然,关于添加水印、裁剪等功能你可以查看其github仓库https://github.com/coobird/thumbnailator壹、添加依赖我这里使用的0.4.20版本。 <!--thumbnailator图片处理--> <dependency> <groupId>net.coobird</groupId> <artifactId>thumbnailator</artifactId> <version>0.4.20</version> </dependency>贰、创建一个公共类这里简单介绍一下,getAccuracy是根据原图片大小,判断压缩的比例,压缩比例介于[0,1],比例越小,压缩的越小,当然相应的图片就会越模糊,所以这个比例可以根据实际情况进行调整,尽量保证压缩比例小的情况下,别造成图片失真。compressPicForScale方法,第一个参数代表源图片字节数组,第二个参数desFileSize代表要压缩到的大小,单位是kb,compressPicForScale方法对输入的图片循环压缩,直至压缩后文件大小<= desFileSizepackage cc.lisen.common.utils.file; import net.coobird.thumbnailator.Thumbnails; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; /** * description: 图片压缩 * * @author: leeframe * DateTime: 2024-07-04 11:54 */ public class PicUtils { //以下是常量 private static final Logger logger = LoggerFactory.getLogger(PicUtils.class); private static final Integer ZERO = 0; private static final Integer ONE_ZERO_TWO_FOUR = 1024; private static final Integer NINE_ZERO_ZERO = 900; private static final Integer THREE_TWO_SEVEN_FIVE = 3275; private static final Integer TWO_ZERO_FOUR_SEVEN = 2047; private static final Double ZERO_EIGHT_FIVE = 0.85; private static final Double ZERO_SEVEN_FIVE = 0.75; private static final Double ZERO_FOUR_FOUR = 0.44; private static final Double ZERO_FOUR = 0.4; /** * 根据指定大小压缩图片 * * @param imageBytes 源图片字节数组 * @param desFileSize 指定图片大小,单位kb * @return 压缩质量后的图片字节数组 */ public static byte[] compressPicForScale(byte[] imageBytes, long desFileSize) { if (imageBytes == null || imageBytes.length <= ZERO || imageBytes.length < desFileSize * ONE_ZERO_TWO_FOUR) { return imageBytes; } long srcSize = imageBytes.length; double accuracy = getAccuracy(srcSize / ONE_ZERO_TWO_FOUR); try { while (imageBytes.length > desFileSize * ONE_ZERO_TWO_FOUR) { ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(imageBytes.length); Thumbnails.of(inputStream) .scale(accuracy) .outputQuality(accuracy) .toOutputStream(outputStream); imageBytes = outputStream.toByteArray(); } logger.info("图片原大小={}kb | 压缩后大小={}kb", srcSize / ONE_ZERO_TWO_FOUR, imageBytes.length / ONE_ZERO_TWO_FOUR); } catch (Exception e) { logger.error("【图片压缩】msg=图片压缩失败!", e); } return imageBytes; } /** * 自动调节精度(经验数值) * * @param size 源图片大小 * @return 图片压缩质量比 */ private static double getAccuracy(long size) { double accuracy; if (size < NINE_ZERO_ZERO) { accuracy = ZERO_EIGHT_FIVE; } else if (size < TWO_ZERO_FOUR_SEVEN) { accuracy = ZERO_SEVEN_FIVE; } else if (size < THREE_TWO_SEVEN_FIVE) { accuracy = ZERO_FOUR_FOUR; } else { accuracy = ZERO_FOUR; } return accuracy; } } 叁、使用我这里是借助阿里云OSS,然后下载的网络图片/** * 阿里云文件上传(中保创) * * @param extension 文件后缀 * @param ownerDirectory 目录 */ public String upload(String extension, String ownerDirectory, String url) throws InvalidExtensionException, IOException { // 填写网络流地址。 try (InputStream inputStream = new URL(url).openStream()) { // 校验格式、大小等 String filePath = getFilePath(extension, ownerDirectory); byte[] bytesOriginal = IOUtils.toByteArray(inputStream); byte[] bytes = PicUtils.compressPicForScale(bytesOriginal, 400); ByteArrayInputStream inputStreamCompress = new ByteArrayInputStream(bytes); // 上传到阿里云 ossClient.putObject(aliyunConfig.getBucketName(), filePath, inputStreamCompress); //this.aliyunConfig.getUrlPrefix() + filePath 文件路径需要保持数据库 return aliyunConfig.getUrlPrefix() + filePath; } }
2024年07月04日
910 阅读
0 评论
0 点赞
2024-03-26
深度解析:如何在若依系统中集成阿里云OSS实现高效文件存储
零、引言随着信息化技术的快速发展,企业级应用对于海量文件存储的需求日益增长。而阿里云对象存储服务(OSS)以其高可用、高可靠、低成本的特点成为众多企业的首选解决方案。本文将以流行的开源后台管理系统——若依系统为例,详细阐述如何将其与阿里云OSS无缝集成,以实现文件资源的安全、高效存储。壹、若依系统上传文件的现状若依系统基于ElementUI的el-upload组件,对于我们的业务来讲,目前存在两个需要改进的地方(1)文件选择后会自动上传,这个在前面的文章有过介绍若依系统上传图片压缩 - 李森的博客 (llisen.cc)(2)若依系统上传文件是上传到应用服务器的,我们需要实现的是上传到阿里云OSS,同时可以将OSS内容,通过内网下载到ECS,方便备份文件,减少OSS存储费用。叁、开通并配置阿里云OSS首先,您需要在阿里云官网注册并登录账号,然后开通OSS服务。在控制台中创建一个新的Bucket,为您的项目设定专属的存储空间,并根据业务需求设置合适的访问权限和地域属性。获取Bucket的相关信息,包括Endpoint、AccessKey ID 和 AccessKey Secret,这是后续与OSS交互的重要凭证。肆、集成阿里云OSS SDK在若依系统的后端开发环境中,通过引入阿里云OSS SDK的依赖包:在根目录的pom.xml的properties配置阿里云OSS的版本 <properties> <aliyun-oss.version>3.17.4</aliyun-oss.version> </properties>在dependencyManagement配置阿里云 OSS依赖 <!--阿里云--> <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>${aliyun-oss.version}</version> </dependency>接着,在项目的配置文件(若依是在admin工程的的resources文件夹中)中添加OSS相关的连接信息:Yaml# application.yml 示例 aliyun: endpoint: 'your-endpoint' endpointInternal: 'your-endpoint-internal' accessKeyId: 'your-access-key-id' accessKeySecret: 'your-access-key-secret' bucketName: 'your-bucket-name' urlPrefix: 'your-domain' urlPrefixInternal: 'https://' + 'your-endpoint-internal'解释一下上面几个配置的含义endpoint创建阿里云Bucket时提供的外网地域节点,使用这个endpoint实现文件的上传endpointInternal创建阿里云Bucket时提供的内网地域节点,为了节约费用,我们ECS跟OSS买的是同一个地域的,这样通过内网下载OSS的文件是不收取费用的,把文件通过内网备份到ECS后,我们可以在空闲的时候,将备份的文件,通过ECS下载到本地accessKeyId、accessKeySecret是您访问阿里云API的密钥,具有该账户完全的权限,这个可以在账户下的AccessKey管理查看bucketName这个是我们创建的Bucket名称伍、配置参数为了方便读取application.yml的配置参数,我们创建一个配置类并完成OSS初始化AliyunConfig.java@Configuration @ConfigurationProperties(prefix = "aliyun") @Data public class AliyunConfig { /** * 外网endpoint */ private String endpoint; /** * 内网endpoint */ private String endpointInternal; /** * key */ private String accessKeyId; /** * 密钥 */ private String accessKeySecret; /** * 空间名称 */ private String bucketName; /** * 外网Url前缀 */ private String urlPrefix; /** * 内网Url前缀 */ private String urlPrefixInternal; @Bean public OSS oSSClient() { return new OSSClient(endpoint, accessKeyId, accessKeySecret); } }陆、编写文件上传类第三步:编写文件上传逻辑在后端服务中创建一个专门处理文件上传的服务类或工具类,利用OSS SDK提供的API实现文件上传功能:AliyunFileUploadService.java@Component @Slf4j public class AliyunFileUploadService { /** * 默认大小 50M */ public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024; @Resource private OSS ossClient; @Resource private AliyunConfig aliyunConfig; /** * 阿里云文件上传 * * @param file 上传的文件 * @param ownerDirectory 目录 */ public String upload(MultipartFile file, String ownerDirectory) throws InvalidExtensionException, IOException { //文件新路径 String originalFilename = file.getOriginalFilename(); // 校验格式、大小等 boolean isLegal = false; assertAllowed(file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); String filePath = getFilePath(file, ownerDirectory); // 上传到阿里云 ossClient.putObject(aliyunConfig.getBucketName(), filePath, new ByteArrayInputStream(file.getBytes())); //this.aliyunConfig.getUrlPrefix() + filePath 文件路径需要保持数据库 return aliyunConfig.getUrlPrefix() + filePath; } /** * 生成文件路径 * * @param file 文件 * @param ownerDirectory 自定义目录 * @return 生成的文件目录 */ private String getFilePath(MultipartFile file, String ownerDirectory) { String fileName; String extension = getExtension(file); if (!StringUtils.isEmpty(ownerDirectory)) { fileName = ownerDirectory + "/" + IdUtils.fastUUID() + "." + extension; } else { fileName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension; } return fileName; } /** * 查看文件列表 * * @return 对象信息 */ public List<OSSObjectSummary> list() { // 设置最大个数。 final int maxKeys = 200; // 列举文件。 ObjectListing objectListing = ossClient.listObjects(new ListObjectsRequest(aliyunConfig.getBucketName()).withMaxKeys(maxKeys)); List<OSSObjectSummary> sums = objectListing.getObjectSummaries(); return sums; } /** * 删除文件 * * @param objectName 文件名 * @return 结果 */ public boolean delete(String objectName) { //如果文件路径是OSS的,截取后删除,否则不处理,直接返回成功 if (objectName != null && objectName.contains(aliyunConfig.getUrlPrefix())) { objectName = objectName.replace(aliyunConfig.getUrlPrefix(), ""); ossClient.deleteObject(aliyunConfig.getBucketName(), objectName); return true; } return true; } /** * 下载文件下载文件 * * @param objectName 数据库存储的文件路径 */ public void exportOssFile(String objectName) throws IOException { // ossObject包含文件所在的存储空间名称、文件名称、文件元信息以及一个输入流。 if (objectName != null && objectName.contains(aliyunConfig.getUrlPrefix())) { objectName = objectName.replace(aliyunConfig.getUrlPrefix(), ""); // 创建OSSClient实例。 OSS ossClientLocal = new OSSClientBuilder().build(aliyunConfig.getEndpointInternal(), aliyunConfig.getAccessKeyId(), aliyunConfig.getAccessKeySecret()); try { File file = getAbsoluteFile(objectName); ossClientLocal.getObject(new GetObjectRequest(aliyunConfig.getBucketName(), objectName), file); }catch (Exception exception){ throw new CustomException(exception.getMessage()); }finally { if (ossClientLocal != null) { ossClientLocal.shutdown(); } } } } /** * 文件大小校验 * * @param file 上传的文件 * @throws FileSizeLimitExceededException 如果超出最大大小 */ public void assertAllowed(MultipartFile file, String[] allowedExtension) throws FileSizeLimitExceededException, InvalidExtensionException { long size = file.getSize(); if (size > DEFAULT_MAX_SIZE) { throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024); } String fileName = file.getOriginalFilename(); String extension = getExtension(file); if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) { if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) { throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension, fileName); } else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) { throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension, fileName); } else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) { throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension, fileName); } else { throw new InvalidExtensionException(allowedExtension, extension, fileName); } } } /** * 获取文件名的后缀 * * @param file 表单文件 * @return 后缀名 */ public String getExtension(MultipartFile file) { String extension = FilenameUtils.getExtension(file.getOriginalFilename()); if (StringUtils.isEmpty(extension)) { extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType())); } return extension; } /** * 判断MIME类型是否是允许的MIME类型 * * @param extension 扩展名 * @param allowedExtension 允许的扩展名 * @return true-允许;false-不允许 */ public boolean isAllowedExtension(String extension, String[] allowedExtension) { for (String str : allowedExtension) { if (str.equalsIgnoreCase(extension)) { return true; } } return false; } /** * 生成本地文件 * * @param fileName 文件名 * @return 文件 * @throws IOException 异常 */ private File getAbsoluteFile(String fileName) throws IOException { String uploadDir = LeeFrameConfig.getProfile(); File desc = new File(uploadDir + File.separator + fileName); if (!desc.getParentFile().exists()) { desc.getParentFile().mkdirs(); } if (!desc.exists()) { desc.createNewFile(); } return desc; } }柒、Controller public AjaxResult add(List<MultipartFile> imageFileList, @RequestParam("form") String form) throws IOException, InvalidExtensionException { }Controller通过MultipartFile接收前端传递的文件,然后调用服务层完成上传。捌、前端交互在若依系统的前端部分,当用户选择文件后,前端需将文件转换为二进制数据并通过Ajax或者其他HTTP请求方式发送给后端。后端接收到请求后,调用OSS服务进行文件上传并将返回的URL反馈至前端展示或保存至数据库。具体可以参考若依系统上传图片压缩 - 李森的博客 (lisen.cc)其他、安全与优化考量为了增强安全性,可以考虑使用STS临时访问凭证进行上传操作,防止关键密钥泄露。另外,如果希望提高文件访问速度,可以为Bucket开启CDN加速,并根据实际场景调整缓存策略。总结起来,通过上述步骤,我们成功实现了若依系统与阿里云OSS的集成,使得整个系统的文件存储和管理能力得到了显著提升。这一过程不仅展示了云存储服务的优势,也展现了若依系统良好的扩展性和兼容性,为企业级应用提供了更加灵活且高效的文件管理方案。
2024年03月26日
1,867 阅读
2 评论
0 点赞
2024-03-24
Spring Boot Controller调用某个方法报Service注入为null
最近为了部署方便,尝试将项目的依赖与配置文件分开进行打包,可以参考Spring Boot分开打包依赖及配置文件 - 李森的博客 (lisen.cc)项目部署之后,试了一下,没有报错,但是后面在用的时候,有一个接口始终报空指针,通过日志分析,是服务层没有注入导致的。接口通过@Resource注入的 @Resource private ICarQuotationPriceHistoryService carQuotationPriceHistoryService;首先,既然别的接口都不存在问题,那么可以断定出现问题不是我们分打开打包依赖导致的。其次,在Idea中直接运行时,接口也不报错,说明方法本身不存在问题(姑且这么说吧),检查了配置、包名等地方,都没有发现问题。既然问题出现在这个方法,那说明肯定是这个方法出现了问题,检查了方法的注解、参数等,也都没发现问题,就在检查方法属性的时候,突然发现问题了,这个方法没有public,其他方法都是有pubic的,方法加上public后,问题解决其实这个地方,只是粗心大意了,忘记写public了。我们都知道,当一个方法没有修饰符时,默认就是default,default通常称为默认访问模式。该模式下,只允许在同一个包中进行访问。这也就为什么我们在不拆分依赖的时候,接口能正常访问,当我们拆分依赖后,因为我们这个是一个单独的模块(依赖),这个接口就无法访问了。通过这件事,得到了两个教训:1.做事不可粗心大意,像controller的方法,记得加public修饰符。2.遇到事情不要被表象迷惑,比如这种注入是null的,我们一般首先想到的是包名、扫描配置、注解上出现问题,往往不会考虑方法修饰符出现问题了。
2024年03月24日
657 阅读
0 评论
0 点赞
2024-03-24
Spring Boot分开打包依赖及配置文件
壹、为何要分开打包依赖Spring Boot默认会将依赖全部打包到一个jar中,这样导致的问题就是我们的一个jar往往很大。加之平时我们分模块开发,往往修改很小的一个部分,就需要打包整个jar包,上传整个jar到服务器。比如我用阿里云服务器,3M的带宽,如果我不拆分开依赖,仅仅是上传jar都需要耗时接近1分钟的时间。当然这样也有一些其他问题,比如我这种多模块的项目,如果我们修改了其他模块(非启动类所在模块),那么我们需要记得将打包的jar要放到依赖对应的文件夹中。贰、为何要分开打包配置文件相对于分开打包依赖,其实配置文件才是更有必要打包的。Spring Boot配置文件默认包裹在jar包中的形式,一方面容易造成配置文件的覆盖,另一方面修改配置文件也相对比较麻烦。叁、如何拆分打包依赖及配置文件Spring Boot分开打包依赖及配置文件的方法也比较简单,我们只需要修改pom.xml文件即可。只需要注意一点就是,如果我们是多模块的项目,需要修改主工程的pom.xml文件。添加一些配置属性,方便修改 <properties> <!--依赖输出目录--> <output.dependence.file.path>../output/lib/</output.dependence.file.path> <!--manifest中lib配置路径--> <manifest.classpath.prefix>lib</manifest.classpath.prefix> <!--jar输出目录--> <output.jar.file.path>../output/</output.jar.file.path> <!--配置文件输出目录--> <output.resource.file.path>../output/config/</output.resource.file.path> </properties>我这里实现的效果是把所有的文件都放到项目顶级的output文件夹中,项目的jar放到output中,依赖放到lib文件夹中,配置文件放到config文件夹中然后我们修改打包插件 <build> <plugins> <!-- 打JAR包,不包含依赖文件;显式剔除配置文件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <outputDirectory>${output.jar.file.path}</outputDirectory> <!-- 将配置文件排除在jar包 --> <excludes> <exclude>*.properties</exclude> <exclude>*.yml</exclude> <exclude>*.xml</exclude> <exclude>*.txt</exclude> </excludes> <archive> <!-- 生成的jar中,包含pom.xml和pom.properties这两个文件 --> <addMavenDescriptor>true</addMavenDescriptor> <!-- 生成MANIFEST.MF的设置 --> <manifest> <!--这个属性特别关键,如果没有这个属性,有时候我们引用的包maven库 下面可能会有多个包,并且只有一个是正确的, 其余的可能是带时间戳的,此时会在classpath下面把那个带时间戳的给添加上去,然后我们 在依赖打包的时候, 打的是正确的,所以两头会对不上,报错。 --> <useUniqueVersions>false</useUniqueVersions> <!-- 为依赖包添加路径, 这些路径会写在MANIFEST文件的Class-Path下 --> <addClasspath>true</addClasspath> <!-- MANIFEST.MF 中 Class-Path 各个依赖加入前缀 --> <!--这个jar所依赖的jar包添加classPath的时候的前缀,需要 下面maven-dependency-plugin插件补充--> <!--一定要找对目录,否则jar找不到依赖lib--> <classpathPrefix>${manifest.classpath.prefix}</classpathPrefix> <!--指定jar启动入口类 --> <mainClass>cc.lisen.LeeFrameApplication</mainClass> </manifest> <manifestEntries> <!-- 假如这个项目可能要引入一些外部资源,但是你打包的时候并不想把 这些资源文件打进包里面,这个时候你必须在 这边额外指定一些这些资源文件的路径,假如你的pom文件里面配置了 <scope>system</scope>,就是你依赖是你本地的 资源,这个时候使用这个插件,classPath里面是不会添加,所以你得手动把这个依赖添加进这个地方 --> <!--MANIFEST.MF 中 Class-Path 加入自定义路径,多个路径用空格隔开 --> <!--此处resources文件夹的内容,需要maven-resources-plugin插件补充上--> <Class-Path>${output.resource.file.path}</Class-Path> </manifestEntries> </archive> </configuration> </plugin> <!-- 复制依赖的jar包到指定的文件夹里 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>copy-dependencies</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <!-- 拷贝项目依赖包到指定目录下 --> <outputDirectory>${output.dependence.file.path}</outputDirectory> <!-- 是否排除间接依赖,间接依赖也要拷贝 --> <excludeTransitive>false</excludeTransitive> <!-- 是否带上版本号 --> <stripVersion>false</stripVersion> </configuration> </execution> </executions> </plugin> <!-- 用于复制指定的文件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <executions> <!-- 复制配置文件 --> <execution> <id>copy-resources</id> <phase>package</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <resources> <resource> <directory>src/main/resources</directory> <includes> <!--将如下格式配置文件拷贝--> <exclude>*.properties</exclude> <exclude>*.yml</exclude> <exclude>*.xml</exclude> <exclude>*.txt</exclude> </includes> </resource> </resources> <!--输出路径--> <outputDirectory>${output.resource.file.path}</outputDirectory> </configuration> </execution> </executions> </plugin> </plugins> <finalName>${project.artifactId}</finalName> </build>
2024年03月24日
1,010 阅读
0 评论
0 点赞
1
2
...
9