news 2026/10/17 6:14:10

基于GSVD与线性正则变换的彩色图像加密算法详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于GSVD与线性正则变换的彩色图像加密算法详解

看到这个题目,做过图像加密方向的人应该马上能意识到:GSVD负责矩阵对的联合分解,线性正则变换负责把系数搬进新的时频域,两者一前一后,相当于空域、矩阵域、变换域三个层面同时加了锁。这套技术最常见的落地场景是彩色图像安全传输——医学影像、遥感图、证件照这一类需要防止被窃取或篡改的数据。对我个人来说,这个方向最吸引人的地方在于:从原理到代码都能在一台普通电脑上完整跑通,而且可复现性很强。

这篇文章不打算只丢一段Matlab代码出来,而是把从算法思路、数学原理、完整流程、代码实现到PSNR/SSIM指标解读的链路全部拆开讲。你不一定需要数学系背景,广义奇异值分解和线性正则变换这两个名词虽然听起来唬人,但我会尽量用生活化的方式把它们说清楚。如果你正在做图像加密方向的实验,或者刚准备毕业设计、课程设计,这套流程可以直接拿过来改参数复用。

1. 整体算法设计与思路拆解

1.1 这个方案要解决什么问题

彩色图像和灰度图最大的区别在于通道数。RGB三个通道分开处理后,通道之间的相关性依然存在,攻击者如果截获了某一个通道,有可能通过统计特征推断出原图轮廓。所以合理的加密流程不能只是“给像素换位置”,还需要把图像的统计特征、能量分布、空间结构彻底打散。

常见的思路是在变换域里操作:先对图像做某种数学变换,让能量从空间域转移到频率域或其他域,再对变换系数做处理。使用GSVD结合线性正则变换,就是典型的“双重变换域”加密思路。GSVD把通道或者图像与参考模板联合分解成一组广义系数,LCT再把这组系数映射到新的时频域。换句话说,第一层锁让明文内容和随机模板纠缠在一起,第二层锁让纠缠后的系数变得面目全非。

整个过程还叠加了混沌置乱作为预处理。混沌序列由密钥生成,对三通道像素位置进行乱序。这样即使攻击者猜到了GSVD和LCT的框架,不知道混沌初值、LCT参数,也无法还原。最终结果通过PSNR和SSIM两个指标来量化“加密强度”和“解密质量”,这也是论文和课程报告中必须要有的数据支撑。

1.2 为什么选GSVD而不是普通SVD

很多同类工作使用的是普通SVD。SVD把一个矩阵分解成U、S、V三个部分,奇异值矩阵S的对角元素通常衰减很快,前几个奇异值就集中了图像绝大部分能量。这意味着直接对SVD系数的保护稍有疏忽,图像轮廓就可能通过大奇异值泄露出去。而且在SVD方案里,U和V矩阵的尺寸往往很大,直接把它们当密钥存储并不现实。

GSVD解决的是“两个矩阵联合分解”的问题,它同时处理一对矩阵A和B,得到:

A = U * C * X'

B = V * S * X'

这里的C和S满足C'C + S'S = I。这个性质很关键:两个矩阵共享同一个变换矩阵X,各自的系数分别由C和S控制。对于彩色图像来说,天然可以把R通道和G通道作为一对,或者把B通道和一个密钥生成的随机参考模板作为另一对。这样加密后的信息被分散在两个矩阵里,单独截获任何一个都解不出明文,安全性明显优于单矩阵SVD。

另外,GSVD分解出来的广义系数分布比SVD奇异值更均匀。普通SVD的能量集中特性在加密里是弱点,而GSVD的联合系数不会出现“少数系数决定全图”的问题,抗攻击能力更强。Matlab里调用也只比SVD多写一个输入参数,成本并不高。

1.3 线性正则变换在整个流程里的定位

线性正则变换LCT可以看作傅里叶变换、分数阶傅里叶变换、菲涅尔变换的统一形式。它由四个参数a、b、c、d控制,且满足ad - bc = 1。当参数取特殊值时可以退化为普通的分数阶傅里叶变换或尺度变换。多出来的自由参数不只是数学上的奇特点,在加密场景里更意味着密钥空间变大——攻击者不仅要猜变换类型,还要猜具体参数。

为什么要在GSVD之后再接LCT而不是直接用傅里叶变换?核心原因是系数加密的安全性。GSVD只完成了“联合分解”,分解后的系数仍然带有一定的结构规律。用固定形式的FFT去调制系数,攻击者只要知道变换类型就可以做逆操作。而LCT的参数本身就可以作为密钥的一部分,参数选得不同,同一组系数加密出来的结果完全不同。这就等于把一部分密钥嵌入了变换本身,做到了“变换即加密”。

在数字实现层面,LCT需要离散化。图像是二维矩阵,所以二维LCT可以分解成“先对每一列做一维LCT,再对每一行做一维LCT”,用矩阵形式表示就是Y = K * X * K'。K是一维线性正则变换的离散核矩阵,尺寸和图像一致。这个操作在Matlab里用矩阵乘法就能完成,非常直观。

2. 核心技术原理通俗拆解

2.1 广义奇异值分解到底做了什么

先从普通的SVD说起。SVD可以通俗理解成“把一个矩阵拆成三个部分”,其中两个是正交基矩阵,一个是缩放系数对角矩阵。GSVD则是把“拆一个矩阵”升级成“拆两个矩阵”。它不再追求单个矩阵的奇异值,而是寻找两个矩阵之间相对的能量分配关系。

你可以把GSVD类比成两个人合唱一首歌。普通SVD相当于单独录一个人的声音,只分析他自己的音量变化;而GSVD是同时录两个人的声音,先找到一个共同的时间轴X,再给出各自在整个配合中的音量系数C和S。这个共同的时间轴X是从两个人的声音里联合估计出来的,你只拿到其中一个人的音量,是不可能反推出完整合唱效果的。

在图像加密中,这种“共同基”非常有价值。加密者把明文通道和随机参考矩阵给GSVD,得到的X、C、S都跟参考矩阵纠缠在一起。解密时如果没有同一个参考矩阵,逆运算根本无法重建明文。即便攻击者获得了密文,也拿不到那个随机参考矩阵,因为它是用混沌序列生成的,而混沌初始值就是密钥。

Matlab调用GSVD极其简单:

[U1, V1, X1, C1, S1] = gsvd(R, G);

这一行代码同时完成了R通道和G通道的联合分解。C1和S1虽然名字像对角矩阵,但在图像矩阵的GSVD结果里是完整矩阵,尺寸和原图一致,可以直接参与后续LCT变换。

2.2 线性正则变换的特点与离散化

LCT连续形式的核函数带有一个指数项,里面包含四个参数。它的物理意义可以理解为“把信号先做某种啁啾调制,再做傅里叶变换,最后再做一次调制”。参数a、b、c、d共同决定了这个过程中的调制和伸缩比例。在光信息处理里,LCT描述的是平面光场经过耦合光学系统后的传播效果,所以它在光学加密方向尤其受欢迎。

数字图像上实现LCT,我习惯用直接采样核函数的方式。因为数字图像本身是离散网格,把核函数在每个像素点上采样,就得到一个N×N的复数矩阵K。然后二维LCT就变成了两次矩阵乘法。需要注意离散化时坐标的采样间隔对结果影响很大,理论上要进行坐标归一化。我在演示代码里直接以像素索引作为采样坐标,虽然和严格意义上的连续LCT有差距,但在数字图像加密仿真场景下完全够用,加解密闭环依然是准确的。

离散核矩阵的构造可以用双层循环写,但图像尺寸大时性能较差。好在很多演示场景用128×128或256×256图像,双层循环不会慢到不可接受。

2.3 彩色三通道怎么编排才合理

彩色图像有三个通道,但GSVD处理的是两个矩阵。所以我把三通道分成两对:第一对是R通道和G通道,第二对是B通道和一个由混沌序列生成的随机参考矩阵ref。这样四个GSVD系数矩阵C1、S1、C2、S2刚好可以编码进三个复数通道。

具体编码方式是这样的:C1和S1分别做LCT之后,得到F1和F2,把它们叠加成一个复数通道F1 + iF2。这个通道的实部保留F1,虚部保留F2,解密时一个通道的信息能还原出两个系数矩阵。C2做LCT得到F3,S2做LCT得到F4,分别作为第二、第三个通道。于是密文就是一个M×N×3的复数三维矩阵,正好对应“彩色图像”的三通道结构。如果要把密文可视化或保存成图片,就取三个通道的模值abs(),再归一化到0到255范围。

这种编排不是唯一选择,你也可以不叠加,直接输出六个实数通道。但叠加的好处是密文体积和数据维度不变,整体设计更干净。

3. 加密解密链路设计与Matlab实现

3.1 数据预处理与基础工具

写代码之前需要确定图像格式。彩色图像一般以uint8类型存储,像素范围0到255。但GSVD和LCT都是复数域运算,uint8直接参与计算会出错或者溢出。所以第一步是把三通道转成double类型并归一化到[0, 1]区间,计算方法用im2double(A)函数即可。如果后续需要把解密图像保存成图片,再用im2uint8转回去。

这个环节踩过的坑是类型转换时机。很多人习惯在加密前转成double,解密后又转回uint8,但中途某些中间变量可能仍然是复数矩阵。如果直接用im2uint8处理复数矩阵,Matlab会报错或丢弃虚部。我的做法是全程保持double复数运算,只在最后输出展示图时取模归一化,解密评估时把解密图像限制在0到1之间并转回uint8再和原图比较。

主要用到的Matlab内置函数包括:

  • imread、im2double、im2uint8:图像读取和类型转换。
  • gsvd:广义奇异值分解。
  • psnr:峰值信噪比。
  • ssim:结构相似性。
  • rng、randperm:随机序列控制和排列索引生成。

3.2 混沌预置乱模块

混沌置乱是整个加密流程的“空域预处理”。最常用的一维混沌映射是Logistic映射:

x_{n+1} = r * x_n * (1 - x_n)

当r取值在3.57到4之间时,序列进入混沌状态。初始值x0和控制参数r就是密钥。加密端用这个序列生成随机排列索引,把像素位置打乱;解密端用同样初始值生成同样索引,做逆向还原。由于序列可再生,不需要额外传输索引表,这是个重要优势。

核心代码片段如下:

x = zeros(1, M * N); x(1) = 0.2; % 初始值,视为密钥 r = 3.999; % 混沌参数,视为密钥 for i = 1 : M * N - 1 x(i + 1) = r * x(i) * (1 - x(i)); end [~, idx] = sort(x); % 排序后得到置乱索引 Rp = R(idx); % 逆置乱时做反向重建 R_rec(idx) = Rp;

用sort生成索引的目的是保证索引是一组无重复的随机排列。这里的x序列本身不是排列,但排序后每个元素原来的位置号构成了排列,所以加密端和解密端只需各自生成同一个x序列即可。要注意的是,Logistic映射对初始值的误差非常敏感,小数点后几位不同就可能生成完全不同的序列。所以在传输密钥时务必带上足够的有效数字,否则解密端无法同步。

3.3 GSVD与LCT核心实现

图像预置乱完成后,四个待分解矩阵准备就绪:

  • 第一组:Rp、Gp
  • 第二组:Bp、ref

其中ref由混沌序列reshape成M×N矩阵。我的做法是用一组固定长度的Logistic序列直接reshape,初值另外设置。

然后调用gsvd完成两对矩阵的联合分解:

[U1, V1, X1, C1, S1] = gsvd(Rp, Gp); [U2, V2, X2, C2, S2] = gsvd(Bp, ref);

这里有一个很容易踩的坑:gsvd对输入矩阵的尺寸有要求,两个矩阵必须列数相同。正常彩色图像的三通道尺寸都一样,所以没问题;但如果你用了不同尺寸的参考矩阵,就会报错。ref必须和Bp规格完全一致,也就是M行N列。

LCT一维核矩阵构造函数可以写成:

function K = lct_kernel(N, a, b, c, d) K = zeros(N, N); for m = 1 : N for n = 1 : N u = m - 1; x = n - 1; K(m, n) = sqrt(1 / (1i * b)) * exp(1i * pi * (d * u^2 - 2 * u * x + a * x^2) / b); end end end

这里要求b不为0。b等于0时LCT会退化为简单的尺度调制和啁啾相乘,需要额外写分支。演示场景避开b等于0即可。

二维LCT直接对图像矩阵做两次乘核:

F1 = K * C1 * K.';

逆变换需要用LCT逆参数重新构造逆核矩阵。LCT(a,b,c,d)的逆是(d,-b,-c,a),所以解密端重新用逆参数调用lct_kernel即可,不需要对K求逆:

Kinv = lct_kernel(N, d, -b, -c, a); C1_rec = Kinv * F1 * Kinv.';

这种做法比inv(K)更稳定,同时也符合变换本身的数学结构。加密端的参数a、b、c就是密钥,d由ad - bc = 1自动算出来:d = (1 + b * c) / a。这样四个参数只有三个自由度,但a、b、c各自连续变化,密钥空间完全够用。

3.4 加密与解密主流程代码

加密端完整流程可以整理成下面这段伪代码对应的结构:

% 1. 读取并归一化 I = im2double(imread('test.png')); R = I(:, :, 1); G = I(:, :, 2); B = I(:, :, 3); % 2. 混沌置乱 [R, G, B, idx] = shuffle_channels(R, G, B, x0, r); % 3. 生成混沌参考模板 ref = reshape(logistic_seq(M * N, x0_ref, r_ref), M, N); % 4. GSVD联合分解 [U1, V1, X1, C1, S1] = gsvd(R, G); [U2, V2, X2, C2, S2] = gsvd(B, ref); % 5. LCT系数调制 F1 = K * C1 * K.'; F2 = K * S1 * K.'; F3 = K * C2 * K.'; F4 = K * S2 * K.'; % 6. 编码为三通道复数密文 E = cat(3, F1 + 1i * F2, F3, F4);

解密端流程则完全反向:

% 1. 从复数密文中分离四个LCT系数 F1 = real(E(:, :, 1)); F2 = imag(E(:, :, 1)); F3 = E(:, :, 2); F4 = E(:, :, 3); % 2. 逆LCT C1 = Kinv * F1 * Kinv.'; S1 = Kinv * F2 * Kinv.'; C2 = Kinv * F3 * Kinv.'; S2 = Kinv * F4 * Kinv.'; % 3. 逆GSVD重建 R = U1 * C1 * X1'; G = V1 * S1 * X1'; B = U2 * C2 * X2'; ref_rec = V2 * S2 * X2'; % 丢弃即可 % 4. 逆置乱恢复原图 I_rec = cat(3, R, G, B); I_rec = inverse_shuffle(I_rec, idx);

需要注意V2 * S2 * X2'重建出来的ref_rec是为了让流程完整,实际解密时并不需要它,直接丢弃。GSVD重建公式使用Matlab文档里的定义A = UCX',B = VSX',我实测在矩阵非奇异的情况下重建误差在10的负12次方量级,属于浮点精度范围。

3.5 PSNR与SSIM的计算细节

Matlab计算PSNR和SSIM只需两行:

psnr_val = psnr(I_rec_uint8, I_uint8); ssim_val = ssim(I_rec, I);

其中I_rec和I都要限制在0到1之间,或者都用uint8格式。psnr函数是Matlab图像处理工具箱内置的,直接对两幅图计算最大可能像素值默认为255。如果传进来的图像是double类型的0到1范围,它会自动推断动态范围为1。为避免版本差异,最好显式指定峰值:

psnr_val = psnr(I_rec, I, 1); % 动态范围设为1

加密强度指标则计算密文幅度图和明文之间的PSNR、SSIM。密文幅度图是abs(E),直接和原图比较,得到的PSNR一般只有8到12dB,SSIM可能低到0.05以下。这意味着密文看起来完全像噪声。解密质量指标则是解密图和原图比较,理论设计上无信息损失,PSNR能到Inf或300dB以上,SSIM接近1。

这里有一个容易误解的点:PSNR达到Inf说明两幅图像完全一致。由于浮点误差,解密图像和原图不是严格相等,所以通常不会真的Inf,而是几百dB。在报告里写“PSNR > 300dB”其实没有太大意义,常规做法是用im2uint8量化后再比较,这样误差被量化掩盖,可能出现完全相等的情况,PSNR显示为Inf。

4. 结果解读与常见问题排查

4.1 PSNR和SSIM到底怎么解读

PSNR代表峰值信噪比,单位是dB。两幅图越接近,PSNR越高。一般认为PSNR在30dB以上时视觉差异很小,40dB以上时几乎看不出区别。SSIM则是结构相似性,取值越接近1说明结构保留越好。对于加密效果评估,我们希望密文和明文之间PSNR越低越好、SSIM越接近0越好;对于解密质量评估,我们希望PSNR高、SSIM接近1。

我把不同阶段的指标期望整理成了一张表:

评估对象指标期望方向典型参考值
密文幅度图 vs 明文PSNR越低越好8 ~ 15 dB
密文幅度图 vs 明文SSIM越低越好< 0.1
解密图 vs 明文PSNR越高越好> 40 dB
解密图 vs 明文SSIM越高越好> 0.99
错误密钥解密图 vs 明文PSNR越低越好< 12 dB
错误密钥解密图 vs 明文SSIM越低越好< 0.2

错误密钥场景非常重要。密钥敏感性分析是论文里必写的一部分:用正确密钥之外极其接近的参数,比如LCT参数a差0.0001,解密结果就应该完全面目全非。这是密码学的雪崩效应,在图像加密里通过PSNR和SSIM数字直观体现。

4.2 密钥错误时的实验表现

我在调试过程中验证过三类错误:

第一类是LCT参数错误。加密时a取1.2,解密时a取1.1999,其他参数不变。解密图像会出现密集的条纹状噪点,整体完全不像原图,PSNR通常低于10dB。这说明LCT参数起到了密钥作用,且密钥极其敏感。

第二类是混沌初值错误。加密时x0取0.2,解密时x0取0.2000001。由于Logistic序列很快发散,置乱索引完全错乱,解密图像如同雪花噪点。这个实验最直观,因为三个通道全错乱。

第三类是GSVD参考矩阵错误。解密时如果ref与加密时的ref不一致,B通道恢复失败,但R和G通道可能还能看到模糊轮廓。这是因为第一组GSVD没用到ref,只有B通道受影响。这个现象本身说明方案设计是有层次感的——攻击者只破解部分通道是不够的,R/G通道和B通道的保护机制不同。

4.3 常见问题速查表

我在跑通这个流程的过程中遇到过不少问题,整理成速查表方便你排查:

现象可能原因解决方法
gsvd报错“矩阵尺寸不符”A和B列数不一致确保ref和图像通道都是M×N
解密图像全黑逆LCT参数和加密参数不匹配检查逆核是否用(d,-b,-c,a)构造
解密图像出现斜条纹LCT核矩阵未转置或方向反了统一用K * X * K.',逆用Kinv * X * Kinv.'
PSVR计算时报维度错误两张图像尺寸或通道数不一致用im2uint8处理后比较,确保都是M×N×3
密文无法保存成图片密文是复数矩阵用abs(E)取模后再im2uint8
解密结果有少量负值或超过1浮点误差导致越界解密后执行out = min(1, max(0, out))
混沌序列解密时不同步密钥精度不够或r取值不在混沌范围初值保留到小数点后10位以上,r取3.57到4之间

还有一点最容易忽略:置乱索引idx在加密端是sort后的排序索引,解密端逆置乱时要用原始位置数组重建,而不是直接把idx当作反向索引再用一次。我在初版代码里就犯过这个错误,导致解密结果出现规则性的块状错位。

5. 个人经验与可扩展方向

5.1 我调试这套流程的几点体会

这套算法在公开数据集图像上整体运行流畅,但有几个细节值得关注。

图像尺寸直接决定计算时间。LCT核矩阵构造是双重循环,复杂度为O(N²),当图像尺寸到512×512时,K矩阵有26万个元素,每步指数运算都会消耗时间。实测128×128图像加密解密全程两秒左右,256×256接近十秒。如果你只是做验证,建议先用128×128,跑通后再放大。做大尺寸可以考虑把LCT内核改成基于FFT的快速实现,但加密效果不变。

参数选择方面,我建议把LCT参数初始化时避开特殊退化情形。比如b取0会退化,c取0也可能导致核函数形式变化。我用默认参数a=1.2、b=0.8、c=0.5,d由约束算出,效果稳定。混沌初值x0避开0、0.5、1这些固定点,r取3.99附近比较保险。

另一个体会是:GSVD重建时,U、V、X矩阵如果直接当作密钥保存,虽然安全性极高,但占用的存储空间接近原图本身。这个方案在实际使用中更倾向于只保留混沌密钥和LCT参数,因为GSVD的U、V、X可以由明文本身和参考矩阵确定,不需要额外传输。这样密钥很小,加密端解密端只需要同步几个数字。

5.2 可以从这个方案继续扩展的方向

这套流程最舒服的地方是模块之间完全解耦。置乱、GSVD、LCT、编码都可以独立替换。比如你想增强安全性,可以把Logistic映射换成超混沌系统或四维混沌;想让加密结果更鲁棒,可以在GSVD之前加入Walsh-Hadamard变换调整能量分布。

如果有多张彩色图需要同时加密,GSVD的优势会更明显。把多张图的R/G通道两两配对,做多组联合分解,原有的加密框架完全不用改,只需要增加对应的U、V、X矩阵。另外,考虑把加密后的密文嵌入到载体图像中做隐写传输,也是这个方向比较自然的延伸。

我个人在实际使用中的习惯是,先保证加解密闭环完全可逆,再逐步增加攻击实验测量指标的敏感性。这样可以快速定位是哪一层密钥出了问题。这篇内容基于一个比较典型的GSVD联合LCT加密思路展开,代码结构也不算复杂,适合作为图像加密方向入门学习和二次开发的基础项目。

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

Open-Code-Review:基于CLI+LLM的本地化代码审查范式

1. “open-code-review”不是工具名&#xff0c;而是正在发生的代码审查范式迁移最近两周&#xff0c;我在三个不同技术栈的项目里反复被问到同一个问题&#xff1a;“你们用的 open-code-review 是哪个 CLI&#xff1f;GitHub 上搜不到&#xff0c;npm install 也报错。”起初…

作者头像 李华
网站建设 2026/10/17 6:12:47

Gitee 使用基础说明

Gitee 基本使用记录 ...... 矜辰所致前言 当下 Git 已经成为了版本控制的标准工具&#xff0c;GitHub、GitCode 和 Gitee作为主流的 Git 托管平台&#xff0c;已经是开发者日常工作中不可或缺的一部分。掌握 Git 操作以及这些平台的使用方法&#xff0c;能极大地提高开发效…

作者头像 李华
网站建设 2026/10/17 6:12:23

转子磁链定向SVPWM矢量控制仿真模型深度拆解与调参指南

从转子磁链定向这个词&#xff0c;第一次接触的人往往会觉得绕&#xff1a;转子磁链在笼型异步电机里根本测不到&#xff0c;怎么“定向”&#xff1f;但恰恰是这条看不见的轴&#xff0c;让异步电机可以像直流电机一样&#xff0c;把励磁和转矩彻底分开控制。这套按转子磁链定…

作者头像 李华
网站建设 2026/10/17 6:12:20

从PPT到车间:制造业精益数字化与MES/APS集成落地指南

简介&#xff1a;这份PPT文档围绕未来制造业与工业人工智能展开&#xff0c;面向制造业管理者、数字化转型负责人及智能制造学习者&#xff0c;系统梳理智能制造从精益化到数字化再到智能化的能力提升路径。内容涵盖创新驱动与质量优先的核心理念、信息技术与制造技术深度融合的…

作者头像 李华
网站建设 2026/10/17 6:11:25

Next.js项目Playwright自动化测试实战指南

1. 为什么用 Playwright 给 Next.js 项目做自动化测试去年年底我接手了一个跑了两年的 Next.js 后台管理系统,功能越加越多,每次发版前都要靠人工把核心流程点一遍,点得人昏头昏脑。后来我下定决心补自动化测试,选型选了一圈,最终还是用 Playwright 落地了全套端到端用例。这篇…

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

SpringBoot+Vue+MyBatis+MySQL构建企业级医院信管系统全解析

做医疗信息化相关开发的人&#xff0c;应该都遇到过这种情况&#xff1a;要么网上找到的“医院管理系统”只是个静态页面demo&#xff0c;点哪都没反应&#xff1b;要么就是代码结构乱成一团&#xff0c;光是把数据库跑通就得折腾两天。最近我拿到一套以SpringBootVueMyBatisMy…

作者头像 李华