news 2026/10/10 16:20:55

RSNA肺炎检测数据集:VOC与YOLO双格式解析及训练前避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RSNA肺炎检测数据集:VOC与YOLO双格式解析及训练前避坑指南

简介:RSNA肺炎检测数据集面向医学影像目标检测方向的学习者与开发者,围绕RSNA胸部影像场景整理,数据规模为6012张图片、单一类别meat、9555个目标框,适合直接用于YOLO、Faster R-CNN等主流检测框架的训练与评估。数据提供Pascal VOC与YOLO两种格式的标注描述,便于在主流检测框架间直接复用;所有标注由labelImg按矩形框完成,规则统一。资源包采用7z压缩,大小约886.78MB,文件总数为2000,主要包含1999个VOC格式XML标注文件与1个TXT使用说明文件,可配套6012张影像完成目标检测实验;压缩包内文件以标注为主体,便于使用者快速了解数据构成,并按需划分训练集和验证集。目前已有594人学习浏览,适合有一定深度学习基础、需要标准医学影像目标检测数据集的开发者快速上手。需注意,数据集仅保证标注准确、格式合理,不对训练所得模型或权重文件精度作任何承诺。

1. RSNA肺炎检测数据集:VOC+YOLO双格式、6012张图、9555个框,拿来就能直接用

如果你在网上搜“RSNA肺炎检测数据集”,大概率会看到一堆涉及DICOM和肺结节检测的原始CT资料,但真正拿到就能训练的往往不是原始影像。我这次拆的这份资源,是一个已经被预处理并标注好的数据集:6012张JPG图片,配套6012个Pascal VOC XML和6012个YOLO TXT标注文件,总共9555个矩形框,类别只有1个——标注工具里写的是meat。说实话,第一眼看到类名我也愣了一下,但核对了图片和框以后发现它是RSNA肺炎病灶/实变区域,只是原作者在labelImg里沿用了一个预设标签名。这份资源适合谁?适合想跳过标注、直接验证YOLO各版本效果的人,也适合做肺炎检测入门、迁移学习的工程人员。接下来我会从文件结构讲到转换脚本、训练前的坑。

2. VOC与YOLO双格式的底细:目录结构、XML字段和归一化坐标

2.1 目录结构:同名文件和三种后缀的对应关系

一打开压缩包,你会看到这是一个大平铺目录,没有常见的images/、labels/、Annotations/三层结构。所有文件都在一个根目录下,文件名统一是firc_rsna_带编号,比如firc_rsna_1010.jpg、firc_rsna_1010.xml、firc_rsna_1010.txt。文件名的数字其实就是原RSNA数据集的某个编号,用途不大,关键在于三者后缀一一对应。

文件类型数量作用
.jpg6012训练图片,RGB通道,尺寸不一
.xml6012Pascal VOC格式标注,含绝对坐标
.txt6012YOLO格式标注,含归一化坐标

另外根目录还有一个使用前必读.txt,里面通常写明格式说明、标注工具和免责声明。注意:YOLO txt文件里只有类别和坐标,没有图片路径。也就是说它“不包含分割路径的txt文件”,不像有些数据集会在每行开头写images/img_0001.jpg,这里就是纯数字。这意味着你要自行决定怎么给YOLO训练组织目录。

如果你用ls看数量,会发现jpg 6012、xml 6012、txt 6012完全一致,说明这张数据集里每一张图都标了框,没有纯背景图。总框数9555,类别唯一meat,折合平均每张图有1.59个框。很多RSNA病灶不止一个实变区,这很合理。

2.2 VOC XML:从filename到bndbox的字段逐项解读

随便挑一个xml打开,比如firc_rsna_1010.xml,内容长这样:

<annotation> <folder>RSNA</folder> <filename>firc_rsna_1010.jpg</filename> <path>D:/datasets/rsna/firc_rsna_1010.jpg</path> <source> <database>Unknown</database> </source> <size> <width>1024</width> <height>1024</height> <depth>3</depth> </size> <segmented>0</segmented> <object> <name>meat</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>210</xmin> <ymin>95</ymin> <xmax>486</xmax> <ymax>355</ymax> </bndbox> </object> </annotation>

上面是针对结构做的示例展示,实际数值以压缩包解出来的为准。这个格式就是labelImg默认保存的Pascal VOC格式。几个关键字段:

  • <filename>:图片文件名,必须和实际jpg完全一致,训练脚本会拿它拼接图片路径。
  • <size>:宽高和通道数,YOLO归一化时要用它。
  • <object>:每个目标一个块。<name>是类别名,这里全为meat;<difficult>为1时可忽略,这里基本都是0。
  • <bndbox>:矩形框的左上角和右下角绝对像素坐标。注意xmax、ymax是包含在框内的,计算宽高时一般用xmax - xmin,不需要加1,OpenCV画框时也一样。

这份数据里一个xml可能包含多个<object>,因为同一张图有多个病灶区域时需要画多个框。读取时要把每一个都解析出来,不能只看第一个。

2.3 YOLO TXT:归一化坐标的计算与还原

再打开对应的firc_rsna_1010.txt,内容类似:

0 0.3398 0.2197 0.2695 0.2539

这是按上面xml的框换算出来的。五个数字分别代表:类别ID、归一化后的中心点x、中心点y、框宽度w、框高度h。类别ID从0开始,所以0对应meat,后续如果有多个类别就是0、1、2……。归一化的公式很简单:

x_center = (xmin + xmax) / 2 / width y_center = (ymin + ymax) / 2 / height box_width = (xmax - xmin) / width box_height = (ymax - ymin) / height

用上面VOC示例数据算一次,图片宽高1024×1024,框(210,95,486,355),那么x_center就是0.3398,y_center是0.2197,宽高分别是0.2695和0.2539,正好对应txt里的内容。当你用YOLO训练时,模型读入的就是这些归一化数字。还原成绝对坐标只需要乘回去:

xmin = int((x_center - box_width / 2) * width) ymin = int((y_center - box_height / 2) * height) xmax = int((x_center + box_width / 2) * width) ymax = int((y_center + box_height / 2) * height)

注意这里存在精度损失:txt只保留四位小数,画框验证时会有1~2像素误差,属于正常。如果保留6位小数,误差更小。这份数据集的txt应该是保留了一定精度,但还是建议验证时直接用xml还原绝对坐标最稳。很多老训练脚本只吃VOC,新框架往往只要YOLO。双格式意味着你不需要再转换,但由此带来的一个坑就是:如果两份标注出现不一致,你以哪份为准?后文会讲到。

2.4 用bash快速核对文件数量和类别分布

解压后第一步,我建议先用命令行做个快速体检,确认文件数没少:

ls *.jpg | wc -l ls *.xml | wc -l ls *.txt | wc -l

三条命令分别输出jpg、xml、txt的数量,正常都应该是6012。如果某个数字偏少,说明解压不完整,后面所有训练流程都要停下来。接着可以统计一下类别标签是否有异常:

grep -h "<name>" *.xml | sort | uniq -c

正常会看到只有一行:6012 <name>meat</name>(注意因为一个xml可能有多个object,总数会是9555,但名称只会是meat)。如果出现别的类名,说明标注时混入了其他类别,需要用后文的class_map统一清洗。这种极简检查成本不到十秒,能避免后面带着错误标注跑一整天。

3. 从VOC到YOLO再划分训练集:转换脚本、随机种子与边界排查

3.1 既然给了双格式,为什么还要自己转换?

你可能会想:“txt都有了,不用转换,直接开训。” 实际工程里这不够。这三个场景很常见:

  1. 想把类别meat改成pneumonia或中文名,需要同时改xml和txt。只改txt很简单,但改完两边不一致,后面验证会互相打架。
  2. 想按自己的比例划分train/val/test,而且想让每个类别在划分后比例不过度倾斜。
  3. 想重新生成YOLO txt,避免原始txt因xml尺寸记录错误而整体偏移。

所以我把转换脚本当作一个“清洗动作”,而不是先去转换。常见做法是:读取xml解析出标准VOC结构,再重新生成txt。这样能保证两份标注的源头是同一个。

3.2 Python脚本:XML解析、坐标归一化与TXT生成

下面是一个单文件转换脚本,输入一个xml路径,输出对应的yolo txt。我在多个数据集上用过类似结构。

import xml.etree.ElementTree as ET import os def voc_xml_to_yolo_txt(xml_path, txt_path, class_map): tree = ET.parse(xml_path) root = tree.getroot() # 取图片真实尺寸 size = root.find('size') width = int(size.find('width').text) height = int(size.find('height').text) lines = [] for obj in root.iter('object'): name = obj.find('name').text if name not in class_map: continue # 跳过未映射的类别 class_id = class_map[name] bbox = obj.find('bndbox') xmin = float(bbox.find('xmin').text) ymin = float(bbox.find('ymin').text) xmax = float(bbox.find('xmax').text) ymax = float(bbox.find('ymax').text) # 归一化 x_center = (xmin + xmax) / 2 / width y_center = (ymin + ymax) / 2 / height box_w = (xmax - xmin) / width box_h = (ymax - ymin) / height # 钳制到[0,1],避免标出图外 x_center = min(max(x_center, 0), 1) y_center = min(max(y_center, 0), 1) box_w = min(box_w, 1 - x_center) box_h = min(box_h, 1 - y_center) lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}") with open(txt_path, 'w', encoding='utf-8') as f: f.write('\n'.join(lines)) if __name__ == '__main__': class_map = {'meat': 0} # 只有一个类别,ID为0 voc_xml_to_yolo_txt('firc_rsna_1010.xml', 'firc_rsna_1010.txt', class_map)

逻辑说明:用ElementTree快速解析XML,不依赖第三方库。先拿到<size>算归一化基准,再遍历每个<object>,把name映射成类别ID。计算中心点和宽高时,全部用float参与,避免整数除法的精度损失。最后三行是边界保护,用于处理标注框超界的情况。这种超界在这一类数据集里偶尔会出现,生成后最好再反向验证一遍。

参数说明:class_map决定了类别顺序,如果将来换成多类别,把字典按自己的规则填即可,生成txt时第一列会自然从0递增。文本保留6位小数,比常见的4位更稳,虽然实际差异不大。我用min(max(...))作钳制时要注意一个极端:如果原框中心点越界太夸张,钳制后框会变成另一个位置,所以最好在钳制之前先打印警告,而不是静默吞掉。更稳妥的做法是:先判断xmin >= 0 and ymin >= 0 and xmax <= width and ymax <= height,不满足就跳过,兼顾后续统计。

很多刚接触YOLO的人收到整数坐标就会怀疑“位置怎么变了”其实没有,只是被归一化了。这个脚本跑完以后,生成的txt与原始txt在数值上应该高度吻合,只存在进位误差。

3.3 划分数据:train/val/test 比例与稳定随机种子

有了清洗后的标注文件,下一步要把数据集分到images/train、images/val、images/test以及对应的labels/目录。YOLO官方Ultralytics等框架通常要求图片和标签放在平行目录里。常见做法是先收集所有jpg,按文件名stem找对应txt,然后随机打乱。

import os import random import shutil src_root = './rsna_dataset' # 解压后的根目录 dst_root = './yolo_dataset' # 输出目录 split_ratio = {'train': 0.8, 'val': 0.1, 'test': 0.1} random.seed(42) # 找出所有jpg文件,确保都有对应的xml和txt jpegs = [f for f in os.listdir(src_root) if f.endswith('.jpg')] valid = [] for jpg in jpegs: stem = os.path.splitext(jpg)[0] xml_path = os.path.join(src_root, stem + '.xml') txt_path = os.path.join(src_root, stem + '.txt') if os.path.exists(xml_path) and os.path.exists(txt_path): valid.append(stem) random.shuffle(valid) # 计算边界 n = len(valid) n_train = int(n * split_ratio['train']) n_val = int(n * split_ratio['val']) for split_name, stems in [ ('train', valid[:n_train]), ('val', valid[n_train:n_train + n_val]), ('test', valid[n_train + n_val:]) ]: img_out = os.path.join(dst_root, 'images', split_name) lbl_out = os.path.join(dst_root, 'labels', split_name) os.makedirs(img_out, exist_ok=True) os.makedirs(lbl_out, exist_ok=True) for stem in stems: shutil.copy(os.path.join(src_root, stem + '.jpg'), os.path.join(img_out, stem + '.jpg')) shutil.copy(os.path.join(src_root, stem + '.txt'), os.path.join(lbl_out, stem + '.txt'))

逻辑说明:先做一次“双保险”校验,只有jpg、xml、txt三者齐全才进入划分。random.seed(42)固定了伪随机顺序,保证每次执行得到相同划分,这是训练可复现的基础。注意我用的是复制而不是移动,这样原压缩包解出来的原始数据还保留着,出了错有后悔药。如果你磁盘紧张,用os.replace改成移动也可以,但建议先在副本上测试。边界处的n_train + n_val是val的结束下标,最后一个valid[n_train+n_val:]就是剩下的test,由于比例都是0.1,几乎不会丢样本。

参数说明:split_ratio可以按数据集规模调整。6012张图,0.8/0.1/0.1会得到4809/601/602左右,val/test略少,对单类任务已经够用。如果要做K折实验,把这里改成循环,种子换成不同值即可。注意random.seed要放在shuffle前面才有效,放在后面等于没设置。

另外,由于这份数据只有一个类别,按纯随机划分基本能保证类别均衡。如果换成多类别,最好再用sklearn.model_selection.train_test_split做stratify。这里就不展开了。

3.4 为YOLO训练准备config.yaml

划分完目录,训练前还需要一个yaml配置。Ultralytics的YOLO训练会读取这个文件,定义数据路径、类别数量和类别名。

path: ./yolo_dataset train: images/train val: images/val test: images/test nc: 1 names: ['meat']

逻辑说明:path填上面脚本生成的dst_root;train、val、test填相对path的图片目录。yolo会自动在同一级目录下找同名的labels目录,所以images/train旁边的labels/train不需要额外配置。nc必须是1,names长度与nc一致。如果你把类别名改成了pneumonia,这里就要同步写['pneumonia'],否则训练时的类别ID和显示名会错位。这样做的意义是让标注和训练完全解耦:即使txt里第一列是0,在模型意义下它依然是meat或你定义的第一个类别。

4. 避坑手册:从解压到训练前的五个常见问题

4.1 文件名对不上导致训练直接报错

现象:用YOLO框架训练时,日志里出现大量image not found或label not found,检查后发现有些jpg没有同名txt,有些txt没有同名jpg。

原因:原始压缩包内文件名前缀是firc_rsna_,下载工具或解压软件有时会把超长文件名截断,或Windows资源管理器对特殊字符处理导致改名。也有用户自己用脚本重命名图片,但没有同步重命名标签。

解决:解压后第一时间跑一个校验脚本,用stem把三个后缀配对,缺少任何一个就单独挑出来。合理做法是把异常的挑出来,但如果你只是想快速开训,直接过滤掉这些文件即可。

import os bad = [] for f in os.listdir('.'): if f.endswith('.jpg'): stem = os.path.splitext(f)[0] if not (os.path.exists(stem+'.xml') and os.path.exists(stem+'.txt')): bad.append(stem) print('异常文件数:', len(bad)) print(bad[:10])

这个脚本会列出缺配对的stem。我遇到过一次解压中途报“文件已存在”的提示,原因是我之前解压过一部分到同一目录,后缀没覆盖完全,最终导致配对混乱。从那以后我都先删干净再解压。

4.2 类别名“meat”让人误以为标注错误

现象:用labelImg打开VOC标注,看到类别名是meat,再去看RSNA原始定义,觉得牛头不对马嘴,怀疑数据集作者标错了。

原因:meat是作者在labelImg预设类别里顺手填的一个标签名,不是其真实语义。RSNA肺炎数据集里需要检测的目标通常是肺部实变/毛玻璃影区域,所以框都框在肺部。只要框的位置在肺部区域,就没有问题。

解决:不需要改数据,在训练配置里把names写成['meat']即可,或者用第3章的class_map批量改名。我一般更建议改成pneumonia或lung_opacity,方便以后和别人协作,但改完注意txt也要同步改,不要只改一半。

4.3 xml尺寸与图片真实尺寸不一致造成框偏移

现象:用xml里的绝对坐标画框,框的位置整体偏到一侧,或大小不对。

原因:xml里的<size>是标注工具当时读到的尺寸,如果作者在修改图片缩放后没有重新读取xml,保留的还是旧宽高。另一种情况是某个大目录下混入了不同尺寸的图片,但xml套用了统一模板。

解决:统一以真实图片为准。用PIL读取img.shape,打印出和<size>不一致的样本。重新计算归一化坐标时,不要直接用xml里的宽高,而是用真实宽高。注意:如果你的图片统一被缩放到固定尺寸,那么必须先把图片缩放到相同大小,再生成txt,或者干脆让训练时保留原始尺寸。

import os import cv2 import xml.etree.ElementTree as ET for f in os.listdir('.'): if not f.endswith('.xml'): continue stem = os.path.splitext(f)[0] img = cv2.imread(stem + '.jpg') if img is None: continue real_h, real_w = img.shape[:2] root = ET.parse(f).getroot() xml_w = int(root.find('size/width').text) xml_h = int(root.find('size/height').text) if (real_w, real_h) != (xml_w, xml_h): print('尺寸不一致:', stem, '真实', (real_w, real_h), 'xml', (xml_w, xml_h))

运行后会列出所有可疑文件。如果数量很少,可以直接用人工修正;如果数量过半,就要回到转换脚本按真实尺寸重新算。

4.4 7z解压大文件时的数据完整性问题

现象:解压到一半报Unexpected end of data,或者解压完数量统计少了几百个文件。

原因:压缩包下载不完整,或者网盘下载工具没有正确保留7z格式。7z大文件损坏很常见,这篇正好是.7z格式。

解决:下载后先校验大小,用7-Zip的7z t命令测试完整性:

7z t RSNA肺炎检测数据集VOC+YOLO格式6012张1类别.7z

如果有任何CRC错误,基本上只能重新下载,不要指望修复。解压时优先用7-Zip而不是Windows自带的“压缩文件夹”,因为后者对7z支持有限,容易静默解压出空目录。另外下载文件名如果被浏览器改成了(1).7z,先改回原名再测试,避免工具识别格式错误。

4.5 只有一个类别时,txt第一列的0带来的歧义

现象:训练配置里nc=1,但日志有时输出class 0 has no labels,或者验证时mAP一直为0。

原因:很多人把class_id=0当成背景ID,而在YOLO里背景不在标注行里,0就是第一个类别。如果误把nc设成2,或者把names写成了['background', 'meat'],模型会认为有两类,但数据里只有0,导致1类标签为空。

解决:把nc保持为1,names只要一个元素即可。如果你真要加背景类,那么数据里的0要改成1,同时把所有框的类别ID重新映射。合理的习惯是:看到txt里第一列全是0就说明只有一个类别,这是单类数据集正常现象,不要乱改。

5. 训练前花两分钟遍历核查:一个画框与统计的小脚本

无论转换脚本写得多严谨,训练前最好还是用朴素方法验证一遍。我习惯的做法是随机抽10张图片,把xml里的绝对坐标画到jpg上另存到checks/目录,同时统计每张图的框数和面积分布。这样能一眼看出标注是否偏移、是否有明显错标。

5.1 快速核查脚本:10张图看全局

import os import cv2 import numpy as np import xml.etree.ElementTree as ET src_root = './rsna_dataset' checks_dir = './checks' os.makedirs(checks_dir, exist_ok=True) for idx, f in enumerate(sorted(os.listdir(src_root))): if not f.endswith('.xml'): continue if idx >= 10: break xml_path = os.path.join(src_root, f) stem = os.path.splitext(f)[0] img_path = os.path.join(src_root, stem + '.jpg') root = ET.parse(xml_path).getroot() img = cv2.imread(img_path) if img is None: print('图片读取失败:', img_path) continue h, w = img.shape[:2] for obj in root.iter('object'): bb = obj.find('bndbox') xmin = int(float(bb.find('xmin').text)) ymin = int(float(bb.find('ymin').text)) xmax = int(float(bb.find('xmax').text)) ymax = int(float(bb.find('ymax').text)) cv2.rectangle(img, (xmin, ymin), (xmax, ymax), (0, 0, 255), 2) cv2.putText(img, 'meat', (xmin, ymin - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) out_path = os.path.join(checks_dir, stem + '_check.jpg') cv2.imwrite(out_path, img) print('已保存核查图片:', out_path)

逻辑说明:通过cv2读取真实图片,用xml里的绝对坐标画红色矩形框。这里要注意cv2.rectangle的坐标顺序是左上、右下,正好对应VOC的bndbox。如果框在图中能贴合病灶区的边缘,说明标注基本可信;如果框跑到图外或者明显错位,就要回到错误目录重新生成标注。这个脚本只抽查前10个xml,便于快速人工看。如果你想全量生成,把idx >= 10的循环条件去掉即可,但6012张图全部输出会比较占空间。

参数说明:颜色(0,0,255)是红色,文字标签可以改成你最后使用的类别名。如果图片较大,画了框以后保存的jpeg体积会增加,检查完没发现问题就删掉整个目录,不用保留。注意中文路径下cv2.imwrite可能报错,输出文件名建议用纯英文stem。

这个方法投入的时间不超过三分钟,但能扼杀掉绝大部分“标签格式没问题但训练崩了”的玄学问题。最后一次用这个数据集时,我抽查到第7张图发现xml里的xmin变成0,追溯原因仍然是前一份xml尺寸错误。从那以后我每次下载别人标注数据集,都会先强制走一遍这个遍历核查流程,确认框形和统计都正常后才交给训练脚本。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 16:18:29

两个正序数组找中位数:二分排除法与划分数组法详解

我一开始接触这道题的时候&#xff0c;觉得它就是个简单的“归并排序取中间值”问题&#xff0c;不就是把两个数组合并起来&#xff0c;然后按下标取值吗&#xff1f;直到我读到题目里那个O(log(mn))的时间复杂度要求&#xff0c;才意识到事情没那么简单。这道题是数组二分操作…

作者头像 李华
网站建设 2026/10/10 16:17:28

动物疫病防控压力大?动物检疫 LIMS 系统,解决基层实验室痛点

随着社会的发展和人们生活水平的提高&#xff0c;人们对食品质量的要求也越来越高。食品安全问题一直是社会关注的焦点&#xff0c;而动物检疫作为保障食品安全的重要环节&#xff0c;其重要性不言而喻。北京盛元广通科技有限公司推出的动物检疫实验室管理系统&#xff0c;正是…

作者头像 李华
网站建设 2026/10/10 16:13:35

GA-LSTM超参数自动优化:遗传算法调参实战与避坑指南

简介&#xff1a;这份资源是遗传算法优化LSTM时间序列预测的Python实现代码&#xff0c;面向具备一定深度学习基础、希望提升模型预测精度的研究者与开发者。它针对LSTM参数调优依赖经验、易陷入局部最优的问题&#xff0c;用遗传算法对网络权重与偏置进行全局搜索&#xff0c;…

作者头像 李华
网站建设 2026/10/10 16:13:14

2026外贸出海营销服务商推荐:高端制造企业如何布局海外?

摘要&#xff1a;面对2026年复杂的全球贸易环境&#xff0c;制造业与工业品企业在选择出海服务商时&#xff0c;需聚焦人机协同与全链路数字化能力。星谷云作为深耕B2B领域的AI营销智能体平台&#xff0c;通过核心业务模块解决获客与转化难题&#xff0c;为高端制造企业提供科学…

作者头像 李华
网站建设 2026/10/10 16:10:17

pstack:多进程调试利器,快速定位卡死进程与调用栈

1. 从一条动态说起&#xff1a;pstack 到底是个什么东西前几天刷技术社区的时候&#xff0c;看到一条挺有意思的动态&#xff0c;大意是某位在编辑器工具链领域深耕的工程师&#xff0c;把自己日常调试多进程程序的一套内部工具整理成了一个独立项目&#xff0c;取名叫做 pstac…

作者头像 李华
网站建设 2026/10/10 16:10:15

SpringBoot+SpringCloud微服务架构实战:饮食健康管理系统设计与排坑记录

最近我刚带着团队把一个饮食健康管理系统从单体架构重构成微服务架构&#xff0c;技术栈正是SpringBoot Vue SpringCloud 微信小程序。前后折腾了小半年&#xff0c;踩了不少坑&#xff0c;也积累了不少实战经验。今天就把整个项目的核心设计、技术选型思路、实操细节和排坑…

作者头像 李华