当前位置: 首页 > news >正文

ImageCombiner设计源码详解

前言

在前面的博客中介绍了一款Java的海报生成器ImageCombiner,原文地址:拿来就用的Java海报生成器ImageCombiner(一),在博文中简单介绍了一下代码以及一个真实的生成案例。但是对源码的介绍不多,本文就针对源码进行深入的讲解,便于用户在使用的过程当中可以知其然,知其所以然,了解它的内部架构,程序设计理念,相关类的具体实现,现有的不足,再此基础上可以扩展出符合自己业务的逻辑,进行二次开发改造。

一、源码分析

1、整体设计

ImageCombiner的源码比较简洁,主要包含以下三个包,element 海报组合要素,enums 海报样式、输出格式枚举,painter 具体的绘制器。

包图说明

整体类图

2、绘制器设计

从上面的类图可以清晰得看到,兑现绘制器和绘制工厂共同完成图像、文本、矩形绘制器三种。

图片绘制器与文本绘制器和矩形绘制器不一样的是多了两个make的方法,一个是用于对图片进行高斯模糊处理的方法。高斯模糊的源码如下:

/*** 高斯模糊(毛玻璃效果)** @param srcImage* @param radius* @return*/private BufferedImage makeBlur(BufferedImage srcImage, int radius) {if (radius < 1) {return srcImage;}int w = srcImage.getWidth();int h = srcImage.getHeight();int[] pix = new int[w * h];srcImage.getRGB(0, 0, w, h, pix, 0, w);int wm = w - 1;int hm = h - 1;int wh = w * h;int div = radius + radius + 1;int r[] = new int[wh];int g[] = new int[wh];int b[] = new int[wh];int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;int vmin[] = new int[Math.max(w, h)];int divsum = (div + 1) >> 1;divsum *= divsum;int dv[] = new int[256 * divsum];for (i = 0; i < 256 * divsum; i++) {dv[i] = (i / divsum);}yw = yi = 0;int[][] stack = new int[div][3];int stackpointer;int stackstart;int[] sir;int rbs;int r1 = radius + 1;int routsum, goutsum, boutsum;int rinsum, ginsum, binsum;for (y = 0; y < h; y++) {rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;for (i = -radius; i <= radius; i++) {p = pix[yi + Math.min(wm, Math.max(i, 0))];sir = stack[i + radius];sir[0] = (p & 0xff0000) >> 16;sir[1] = (p & 0x00ff00) >> 8;sir[2] = (p & 0x0000ff);rbs = r1 - Math.abs(i);rsum += sir[0] * rbs;gsum += sir[1] * rbs;bsum += sir[2] * rbs;if (i > 0) {rinsum += sir[0];ginsum += sir[1];binsum += sir[2];} else {routsum += sir[0];goutsum += sir[1];boutsum += sir[2];}}stackpointer = radius;for (x = 0; x < w; x++) {r[yi] = dv[rsum];g[yi] = dv[gsum];b[yi] = dv[bsum];rsum -= routsum;gsum -= goutsum;bsum -= boutsum;stackstart = stackpointer - radius + div;sir = stack[stackstart % div];routsum -= sir[0];goutsum -= sir[1];boutsum -= sir[2];if (y == 0) {vmin[x] = Math.min(x + radius + 1, wm);}p = pix[yw + vmin[x]];sir[0] = (p & 0xff0000) >> 16;sir[1] = (p & 0x00ff00) >> 8;sir[2] = (p & 0x0000ff);rinsum += sir[0];ginsum += sir[1];binsum += sir[2];rsum += rinsum;gsum += ginsum;bsum += binsum;stackpointer = (stackpointer + 1) % div;sir = stack[(stackpointer) % div];routsum += sir[0];goutsum += sir[1];boutsum += sir[2];rinsum -= sir[0];ginsum -= sir[1];binsum -= sir[2];yi++;}yw += w;}for (x = 0; x < w; x++) {rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;yp = -radius * w;for (i = -radius; i <= radius; i++) {yi = Math.max(0, yp) + x;sir = stack[i + radius];sir[0] = r[yi];sir[1] = g[yi];sir[2] = b[yi];rbs = r1 - Math.abs(i);rsum += r[yi] * rbs;gsum += g[yi] * rbs;bsum += b[yi] * rbs;if (i > 0) {rinsum += sir[0];ginsum += sir[1];binsum += sir[2];} else {routsum += sir[0];goutsum += sir[1];boutsum += sir[2];}if (i < hm) {yp += w;}}yi = x;stackpointer = radius;for (y = 0; y < h; y++) {pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];rsum -= routsum;gsum -= goutsum;bsum -= boutsum;stackstart = stackpointer - radius + div;sir = stack[stackstart % div];routsum -= sir[0];goutsum -= sir[1];boutsum -= sir[2];if (x == 0) {vmin[y] = Math.min(y + r1, hm) * w;}p = x + vmin[y];sir[0] = r[p];sir[1] = g[p];sir[2] = b[p];rinsum += sir[0];ginsum += sir[1];binsum += sir[2];rsum += rinsum;gsum += ginsum;bsum += binsum;stackpointer = (stackpointer + 1) % div;sir = stack[stackpointer];routsum += sir[0];goutsum += sir[1];boutsum += sir[2];rinsum -= sir[0];ginsum -= sir[1];binsum -= sir[2];yi += w;}}srcImage.setRGB(0, 0, w, h, pix, 0, w);return srcImage;}

在上述的代码,尤其是绘制器工厂的实现,信心的朋友通过阅读代码可以看到,工厂实现方式还是可以进行改造,由于目前的绘制对象也不多,不是有代码洁癖的也能接受。

package com.freewayso.image.combiner.painter;import com.freewayso.image.combiner.element.CombineElement;
import com.freewayso.image.combiner.element.ImageElement;
import com.freewayso.image.combiner.element.RectangleElement;
import com.freewayso.image.combiner.element.TextElement;/*** @Author zhaoqing.chen* @Date 2020/8/21* @Description*/
public class PainterFactory {private static ImagePainter imagePainter;               //图片绘制器private static TextPainter textPainter;                 //文本绘制器private static RectanglePainter rectanglePainter;       //矩形绘制器public static IPainter createInstance(CombineElement element) throws Exception {//考虑到性能,这里用单件,先不lock了if (element instanceof ImageElement) {if (imagePainter == null) {imagePainter = new ImagePainter();}return imagePainter;} else if (element instanceof TextElement) {if (textPainter == null) {textPainter = new TextPainter();}return textPainter;} else if (element instanceof RectangleElement) {if (rectanglePainter == null) {rectanglePainter = new RectanglePainter();}return rectanglePainter;} else {throw new Exception("不支持的Painter类型");}}
}

在上述的代码中,通过if循环来进行具体的绘制图形实例生成,可以想象一下,针对上述的代码,如果未来要扩展一种新的绘制对象,该怎么进行代码优化。

3、海报元素类设计

海报元素可以分为三种元素,与绘制器一一对应,分别是图片元素、文本元素、矩形元素。这几个类都是CombineElement的子类。后面结合时序图来说明具体的元素绑定与注册及绘制过程。

4、海报生成器

ImageCombiner是最重要的海报生成器类,在ImageCombiner中,定义了一个海报元素类的集合用来保存需要在海报中添加的元素。同时定义了很多的以add开头的添加海报元素的方法,用以往List集合中添加新的元素对象。

/*** 添加图片元素** @param imgUrl   图片rul* @param x        x坐标* @param y        y坐标* @param width    宽度* @param height   高度* @param zoomMode 缩放模式* @return*/public ImageElement addImageElement(String imgUrl, int x, int y, int width, int height, ZoomMode zoomMode) {ImageElement imageElement = new ImageElement(imgUrl, x, y, width, height, zoomMode);this.combineElements.add(imageElement);return imageElement;}

二、生成时序解析

1、海报组装

public void test1() throws Exception {try {String bgImageUrl = "https://img.thebeastshop.com/combine_image/funny_topic/resource/bg_3x4.png";                       //背景图(测试url形式)String qrCodeUrl = "http://imgtest.thebeastshop.com/file/combine_image/qrcodef3d132b46b474fe7a9cc6e76a511dfd5.jpg";     //二维码String productImageUrl = "https://img.thebeastshop.com/combine_image/funny_topic/resource/product_3x4.png";             //商品图BufferedImage waterMark = ImageIO.read(new URL("https://img.thebeastshop.com/combine_image/funny_topic/resource/water_mark.png"));  //水印图(测试BufferedImage形式)BufferedImage avatar = ImageIO.read(new URL("https://img.thebeastshop.com/member/privilege/level-icon/level-three.jpg"));           //头像String title = "# 最爱的家居";                                       //标题文本String content = "苏格拉底说:“如果没有那个桌子,可能就没有那个水壶”";  //内容文本//合成器和背景图(整个图片的宽高和相关计算依赖于背景图,所以背景图的大小是个基准)ImageCombiner combiner = new ImageCombiner(bgImageUrl, OutputFormat.PNG);combiner.setBackgroundBlur(30);     //设置背景高斯模糊(毛玻璃效果)combiner.setCanvasRoundCorner(100); //设置整图圆角(输出格式必须为PNG)//商品图(设置坐标、宽高和缩放模式,若按宽度缩放,则高度按比例自动计算)combiner.addImageElement(productImageUrl, 0, 160, 837, 0, ZoomMode.Width).setRoundCorner(46)     //设置圆角.setCenter(true);       //居中绘制,会忽略x坐标参数,改为自动计算//标题(默认字体为“阿里巴巴普惠体”,也可以自己指定字体名称或Font对象)combiner.addTextElement(title, 55, 150, 1400);//内容(设置文本自动换行,需要指定最大宽度(超出则换行)、最大行数(超出则丢弃)、行高)combiner.addTextElement(content, "微软雅黑", 40, 150, 1480).setAutoBreakLine(837, 2, 60);//头像(圆角设置一定的大小,可以把头像变成圆的)combiner.addImageElement(avatar, 200, 1200, 130, 130, ZoomMode.WidthHeight).setRoundCorner(200).setBlur(5);       //高斯模糊,毛玻璃效果//水印(设置透明度,0.0~1.0)combiner.addImageElement(waterMark, 630, 1200).setAlpha(.8f)      //透明度,0.0~1.0.setRotate(15);     //旋转,0~360,按中心点旋转//二维码(强制按指定宽度、高度缩放)combiner.addImageElement(qrCodeUrl, 138, 1707, 186, 186, ZoomMode.WidthHeight);//元素对象也可以直接new,然后手动加入待绘制列表TextElement textPrice = new TextElement("¥1290", 40, 600, 1400);textPrice.setStrikeThrough(true);       //删除线combiner.addElement(textPrice);         //加入待绘制集合//动态计算位置int offsetPrice = textPrice.getX() + textPrice.getWidth() + 10;combiner.addTextElement("¥999", 60, offsetPrice, 1400).setColor(Color.red);//执行图片合并combiner.combine();//保存文件combiner.save("d://fullTest.png");System.out.println(1/0);System.out.println("end...");//或者获取流(并上传oss等)//InputStream is = combiner.getCombinedImageStream();//String url = ossUtil.upload(is);} catch (Exception e) {e.printStackTrace();}}

通过以上的时序图可以清晰的看到系统的调用过程,一共包含了24个步骤。通过时序图和代码,相信您可以很直观的看到相关的调用方式,这里不做过多的赘述。

2、combine海报合成

/*** 合成图片,返回图片对象** @throws Exception*/public BufferedImage combine() throws Exception {combinedImage = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_RGB);Graphics2D g = combinedImage.createGraphics();//PNG要做透明度处理,否则背景图透明部分会变黑if (outputFormat == OutputFormat.PNG) {combinedImage = g.getDeviceConfiguration().createCompatibleImage(canvasWidth, canvasHeight, Transparency.TRANSLUCENT);g = combinedImage.createGraphics();}//抗锯齿g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);g.setColor(Color.white);//循环绘制各元素for (CombineElement element : combineElements) {IPainter painter = PainterFactory.createInstance(element);if (element.isRepeat()) {//平铺绘制painter.drawRepeat(g, element, canvasWidth, canvasHeight);} else {//正常绘制painter.draw(g, element, canvasWidth);}}g.dispose();//处理整图圆角if (roundCorner != null) {combinedImage = this.makeRoundCorner(combinedImage, canvasWidth, canvasHeight, roundCorner);}return combinedImage;}

combine方法是最核心的生成方法,在第6步进入循环,将页面设置的不同的海报元素添加到生成器中,并调用绘制器工程生成相应的绘制器,通过Graphics2D来进行海报的生成。至此,海报生成器的相关类就全部讲解完毕,工具虽小,五脏俱全。

//循环绘制各元素
for (CombineElement element : combineElements) {IPainter painter = PainterFactory.createInstance(element);if (element.isRepeat()) {//平铺绘制painter.drawRepeat(g, element, canvasWidth, canvasHeight);} else {//正常绘制painter.draw(g, element, canvasWidth);}}

三、总结

以上就是本文的主要内容,本文主要从海报生成器的源码和生成时序解析两个方面进行深度解析,使用UML的分析方法对类图、时序图结合源码进行说明,将海报生成器的核心代码做完整的剖析,想研究的朋友可以深入的学习,对于源码中不合理的地方可以进行修改。

相关文章:

ImageCombiner设计源码详解

前言在前面的博客中介绍了一款Java的海报生成器ImageCombiner,原文地址&#xff1a;拿来就用的Java海报生成器ImageCombiner&#xff08;一&#xff09;&#xff0c;在博文中简单介绍了一下代码以及一个真实的生成案例。但是对源码的介绍不多&#xff0c;本文就针对源码进行深入…...

python基础 | python基础语法

文章目录&#x1f4da;基础语法&#x1f407;输入和输出&#x1f955;print()输出&#x1f955;input()输入&#x1f407; 变量的命名&#x1f407;条件判断&#x1f955;单向判断&#x1f955;双向判断&#x1f955;多向判断&#x1f955;if嵌套&#x1f955;三元表达式&#…...

YOLOv6-3.0-目标检测论文解读

文章目录摘要算法2.1网络设计2.2Anchor辅助训练2.3自蒸馏实验消融实验结论论文&#xff1a; 《YOLOv6 v3.0: A Full-Scale Reloading 》github&#xff1a; https://github.com/meituan/YOLOv6上版本参考 YOLOv6摘要 YOLOv6 v3.0中YOLOv6-N达到37.5AP&#xff0c;1187FPS&…...

JAVA集合之Map >>HashMap/Hashtable/TreeMap/LinkedHashMap结构

Map 是一种键-值对&#xff08;key-value&#xff09;集合&#xff0c;键不可以重复&#xff0c;值可以重复。常见的实现类有&#xff1a;HashMap、Hashtable、TreeMap、LinkedHashMap等。 HashMap&Hashtable HashMap&#xff1a;数据结构为哈希表&#xff0c;允许使用 n…...

JavaScript从零开始 学习记录(一)

前言 选择视频课程之前&#xff0c;不仅查阅了资料&#xff0c;还询问了网友&#xff0c;最终敲定了学习黑马前端的视频教程&#xff0c;学了5小节&#xff0c;发现挺对自己口味的且从反响来看&#xff0c;还是相当不错的&#xff0c;便打算利用这个寒假学完 笔记范围 从这节…...

C++项目——高并发内存池(3)--central cache整体设计

1.central cache的介绍 1.1框架思想 1.1.1哈希映射 centralcache其实也是哈希桶结构的&#xff0c;并且central cache和thread cacha的哈希映射关系是一致的。目的为了&#xff0c;当thread cache某一个哈希桶下没有内存块时&#xff0c;可以利用之前编写的SizeClass::Index…...

Spring Boot 整合 MyBatis 配置等案例教程

运行环境&#xff1a;JDK 7 或 8、Maven 3.0 技术栈&#xff1a;SpringBoot 1.5、SpringBoot Mybatis Starter 1.2 、MyBatis 3.4 前言 距离第一篇 Spring Boot 系列的博文 3 个月了。《Springboot 整合 Mybatis 的完整 Web 案例》第一篇出来是 XML 配置 SQL 的形式。虽然 XM…...

比特数据结构与算法(第三章_下)队列的概念和实现(力扣:225+232+622)

一、队列&#xff08;Queue&#xff09;队列的概念&#xff1a;① 队列只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表。② 入队列&#xff0c;进行插入操作的一端称为 队尾。出队列&#xff0c;进行删除操作的一端称为 队头。③ 队列中的元素…...

c++提高篇——STL容器实现打分系统

一、案例说明 有5名选手:选手ABCDE&#xff0c;10个评委分别对每一名选手打分&#xff0c;去除最高分&#xff0c;去除评委中最低分&#xff0c;取平均分。 二、案例实现 在实现这个系统时&#xff0c;我们规划一下实现的步骤以及细节&#xff1a; 1、创建一个选手类&#x…...

【图片上传记录三】element-ui组件详解与封装(自定义上传、限制文件大小、格式以及图片尺寸)

业务上有需求是前端上传 jpg/png/gif 格式, 并且 尺寸为 150px * 150px,300px*300px,428*428px 的图片 同时在上传的同时需要携带用户的个人信息以及其他额外信息 因此在 element-upload 基础之上 实现这个需求需要在上传前检查图片的大小&#xff0c;格式以及尺寸如何上传也成…...

一个golang版本管理工具

GitHub - moqsien/gvc: GVC is a productive tool to manage your dev environment for multi platforms and machines. | GVC 是一个用于快速配置和管理多机器跨平台的开发环境的生产力工具。 目前&#xff0c;gvc拥有以下功能或特点&#xff1a; go编译器自动安装和添加环…...

SpringBoot整合Spring Security过滤器链加载执行流程源码分析

文章目录1.引言2.Spring Security过滤器链加载1.2.注册名为 springSecurityFilterChain的过滤器2、查看 DelegatingFilterProxy类3.查看 FilterChainProxy类3.1 查看 doFilterInternal方法。3.2 查看 getFilters方法。4 查看 SecurityFilterChain接口5 查看 SpringBootWebSecur…...

Jest使用

一、测试到底测什么 提到测试的时候&#xff0c;即使是最简单的一个代码块可能都让初学者不知所措。最常问的问题的是“我怎么知道要测试什么&#xff1f;”。如果你正在写一个 Web 应用&#xff0c;那么你每个页面每个页面的测试用户交互的方式&#xff0c;就是一个很好的开端…...

定位于企业数字化底座,开箱可用(spring cloud+Vue)基础框架,赶紧收藏!

项目介绍&#xff1a;JVS是什么&#xff1f;JVS是企业级应用构建的基础脚手架&#xff0c;提供开箱即用的基础功能集成&#xff0c;其中集成了账户管理、租户管理、用户权限体系、三方登录、环境配置、各种业务日志等功能&#xff0c;还提供了对接低代码、数据中台的能力。JVS能…...

java字符统计

问题描述 给定一个只包含大写字母的字符串 &#xfffd; S, 请你输出其中出现次数最多的字符。 如果有多个字母均出现了最多次, 按字母表顺序依次输出所有这些字母。 输入格式 一个只包含大写字母的字符串 &#xfffd; S. 输出格式 若干个大写字母&#xff0c;代表答案。 …...

C#:Krypton控件使用方法详解(第八讲) ——kryptonBreadCrumb

今天介绍的Krypton控件中的kryptonBreadCrumb&#xff0c;下面开始介绍这个控件的属性&#xff1a;首先要介绍的是RootItem属性和外观属性&#xff1a;RootItem属性组中包含属性如下&#xff1a;image属性&#xff1a;代表在文字对象的前方插入一个图片&#xff0c;属性值如下图…...

2023从0开始学性能(1) —— 性能测试基础【持续更新】

背景 不知道各位大佬有没遇到上面的情况&#xff0c;性能这个东西到底是什么&#xff0c;还是以前的358原则吗&#xff1f;明显并不是适用于现在了。多次想踏入性能测试门槛都以失败告终&#xff0c;这次就以系列的方式来督促自己真正踏进性能测试的门槛。 什么是性能测试 通…...

如何通过一台 iPhone 申请一个 icloud 邮箱账号 后缀为 @icloud.com

总目录 iOS开发笔记目录 从一无所知到入门 文章目录需求关键步骤步骤后续需求 在 iPhone 自带的邮箱软件中添加账号&#xff0c;排第一位的是 iCloud 邮箱&#xff1a; 选 iCloud 之后&#xff1a; 提示信息是exampleicloud.com&#xff0c;也就是说是有icloud.com为域的邮箱…...

SQL89 计算总和

描述OrderItems表代表订单信息&#xff0c;包括字段&#xff1a;订单号order_num和item_price商品售出价格、quantity商品数量。order_numitem_pricequantitya110105a211100a21200a421121a5510a2119a775【问题】编写 SQL 语句&#xff0c;根据订单号聚合&#xff0c;返回订单总…...

Netty高级应用之:编解码器与群聊天室开发

Netty高级应用之&#xff1a;编解码器与群聊天室开发 文章目录Netty高级应用之&#xff1a;编解码器与群聊天室开发Netty编解码器Java的编解码Netty编解码器概念解码器(Decoder)编码器(Encoder)编码解码器CodecNetty案例-群聊天室聊天室服务端编写聊天室客户端编写Netty编解码器…...

Postgresql基础实践教程(九)

⭐️⭐️⭐️⭐️⭐️ 完整数据详见 练习数据免费 ⭐️⭐️⭐️⭐️⭐️ 七十二、WITH查询&#xff08;公用表表达式CTE&#xff09; 1. SELECT 中的 WITH 2. 递归查询 3. 公用表表达式的物化 4. WITH中的数据修改语句 WITH提供了一种在主查询中写辅助语句的方法。这些语…...

保姆级教程:在Windows 10上用QEMU+Kylin搭建可内外网访问的完整开发环境

在Windows 10上构建QEMUKylin全功能开发环境的终极指南当开发者需要在本地快速搭建一个隔离的国产操作系统开发环境时&#xff0c;QEMU虚拟化方案配合银河麒麟系统能提供高度灵活的沙箱体验。本文将手把手带你完成从零配置到内外网联通的完整工作流&#xff0c;涵盖虚拟化环境部…...

【Veo 2提示词SOP白皮书】:从模糊意图到像素级输出的8步标准化工作流(附NASA级测试用例库)

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;Veo 2提示词工程的本质与范式跃迁 Veo 2并非单纯升级的视频生成模型&#xff0c;而是一次提示词工程范式的根本性重构——它将传统“指令式提示”&#xff08;prompt-as-command&#xff09;转向“意图…...

AWS DevOps Agent 完全指南

AWS DevOps Agent 是 AWS 推出的前沿 AI 运维代理,自主调查和解决事件、持续预防故障、提升系统可靠性。本文档覆盖从原理到实战的全生命周期管理。 一、定位与价值 一句话定义 AWS DevOps Agent = AI 驱动的 SRE 队友,724 自主调查告警、定位根因、生成修复方案、预防未来…...

掌握Umi-OCR:5分钟上手开源免费离线文字识别工具

掌握Umi-OCR&#xff1a;5分钟上手开源免费离线文字识别工具 【免费下载链接】Umi-OCR OCR software, free and offline. 开源、免费的离线OCR软件。支持截屏/批量导入图片&#xff0c;PDF文档识别&#xff0c;排除水印/页眉页脚&#xff0c;扫描/生成二维码。内置多国语言库。…...

独立开发者利用taotoken模型广场为不同任务选择性价比最优模型

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 独立开发者利用taotoken模型广场为不同任务选择性价比最优模型 对于独立开发者而言&#xff0c;在有限的预算内高效完成多样化的开…...

收藏|2026年AI大模型就业爆发!岗位暴涨12倍、月薪6W+,小白零基础入门指南

2026年&#xff0c;AI已从“科技热点”彻底变为职场“刚需赛道”&#xff01;脉脉高聘人才智库最新发布的《2026年1-2月中高端人才求职招聘洞察》&#xff0c;用硬核数据揭示行业真相&#xff1a;AI人才成招聘市场顶流&#xff0c;岗位量、薪资双双爆发式增长。尤其对零基础小白…...

京东自动购物终极指南:告别缺货烦恼,智能抢购神器

京东自动购物终极指南&#xff1a;告别缺货烦恼&#xff0c;智能抢购神器 【免费下载链接】Jd-Auto-Shopping 京东商品补货监控及自动下单 项目地址: https://gitcode.com/gh_mirrors/jd/Jd-Auto-Shopping 还在为心仪商品瞬间售罄而苦恼吗&#xff1f;还在熬夜等待补货却…...

Lovable电商网站搭建,为什么92%的初创团队在第3周就遭遇性能雪崩?

更多请点击&#xff1a; https://codechina.net 第一章&#xff1a;Lovable电商网站搭建 Lovable 是一个面向中小商户的轻量级电商解决方案&#xff0c;采用现代 Web 技术栈构建&#xff0c;强调可扩展性、用户体验与快速部署。其核心基于 Vue 3&#xff08;Composition API&a…...

别只盯着主控芯片!拆解STM32最小系统板:电源、时钟、复位三大支柱电路深度解析

STM32最小系统板设计进阶&#xff1a;电源、时钟与复位电路的工程实践 在嵌入式系统开发中&#xff0c;我们常常将注意力集中在主控芯片的功能实现上&#xff0c;却忽略了支撑系统稳定运行的三大基础电路——电源、时钟和复位。这些看似简单的电路模块&#xff0c;实则是整个系…...