SSM框架的学习与应用(Spring + Spring MVC + MyBatis)-Java EE企业级应用开发学习记录(第三天)动态SQL

news2025/1/4 19:35:25

动态SQL—SSM框架的学习与应用(Spring + Spring MVC + MyBatis)-Java EE企业级应用开发学习记录(第三天)Mybatis的动态SQL操作

昨天我们深入学习了Mybatis的核心对象SqlSessionFactoryBuilder掌握MyBatis核心配置文件以及元素的使用,也掌握MyBatis映射文件及其元素的使用。那么今天我们需要掌握的是更加复杂的查询操作。

  1. 学会编写MyBatis中动态SQL
  2. 学会MyBatis的条件查询操作
  3. 学会MyBatis的更新操作
  4. 学会MyBatis的复杂查询操作

一、什么是动态SQL?

​ MyBatis 中的动态 SQL 是一种在 SQL 查询语句中根据不同的条件来动态生成不同的 SQL 片段的技术它允许你根据不同的情况来构建查询条件,避免写大量重复的 SQL 语句


动态SQL常用元素

元素说明
< if >判断语句,用于单条件判断
< choose >(< when >、< otherwise >)相当于Java中的switch…case…default语句,用于多条件判断
< where >简化SQL语句中where的条件判断
< trim >可以灵活地去除多余的关键字
< set >用于SQL语句的动态更新
< foreach >循环语句,常用于in语句等列举条件中

动态 SQL 通常用于构建复杂的查询条件,例如:

  • 根据不同的条件组合进行查询,比如根据姓名、性别、年龄等多个条件进行筛选。
  • 根据不同的条件来动态排序查询结果。
  • 在更新操作中,根据传入的参数来选择性地更新某些字段。
  • 在插入操作中,根据不同的参数情况来选择性地插入某些字段。

MyBatis 提供了以下方式来实现动态 SQL:

  1. 使用 <if> 元素: 可以根据条件来动态生成 SQL 片段。例如:

    <select id="findByCondition" parameterType="map" resultType="User">
        SELECT * FROM users
        <where>
            <if test="name != null">
                AND name = #{name}
            </if>
            <if test="age != null">
                AND age = #{age}
            </if>
        </where>
    </select>
    

    代码解释如下:

    <select> 标签定义了一个查询语句,并指定了它的唯一标识符(id)、输入参数类型(parameterType)和结果类型(resultType)。

    SELECT * FROM users 是实际的 SQL 查询语句,表示从名为 “users” 的表中选取所有列的数据。

    <where> 标签是 MyBatis 提供的一个用于构建动态 SQL 的元素,它会在生成的 SQL 语句中添加 “WHERE” 关键字,并且只会在至少有一个条件满足时添加

    <if> 标签是 <where> 标签中的子元素,用于判断某个条件是否成立。它的 “test” 属性用于指定一个条件表达式。

    如果 test="name != null" 成立,那么会在生成的 SQL 语句中添加一个 “AND name = #{name}” 的条件。

    如果 test="age != null" 成立,那么会在生成的 SQL 语句中添加一个 “AND age = #{age}” 的条件。

    这样,在实际执行查询时,如果传入的参数中有 “name”,那么会根据 “name” 的值添加相应的查询条件;同样地,如果传入的参数中有 “age”,那么会根据 “age” 的值添加相应的查询条件。

  2. 使用 <choose> 元素: 类似于 switch 语句,根据条件选择一个分支进行处理。

    <select id="findByCondition" parameterType="map" resultType="User">
        SELECT * FROM users
        <where>
            <choose>
                <when test="name != null">
                    AND name = #{name}
                </when>
                <when test="age != null">
                    AND age = #{age}
                </when>
                <otherwise>
                    AND 1=1
                </otherwise>
            </choose>
        </where>
    </select>
    

    代码解释:

    这段代码使用了 MyBatis 中的动态 SQL 元素 <choose> 元素,也称为 <choose>/<when>/<otherwise> 构造。这样的构造允许你在 SQL 查询中根据条件动态地生成不同的部分

    <choose> 包含了多个 <when> 元素,每个 <when> 元素对应一个条件判断,如果条件满足,就会生成相应的 SQL 查询条件。如果所有条件都不满足,就会执行 <otherwise> 部分

    简单来说,这个查询语句的意思是:

    • 如果传入的参数 name 不为 null,则添加条件 AND name = #{name} 到查询语句中。
    • 如果传入的参数 age 不为 null,则添加条件 AND age = #{age} 到查询语句中。
    • 如果以上条件都不满足(即都为 null),则添加条件 AND 1=1 到查询语句中。这个条件相当于一个恒为真的条件,因此不会影响查询结果。
  3. 使用 <trim> 元素: 可以根据条件来剪切 SQL 片段的开始或结尾,以防止出现多余的关键字。

    <select id="findByCondition" parameterType="map" resultType="User">
        SELECT * FROM users
        <where>
            <trim prefix="AND (" suffix=")" prefixOverrides="OR">
                <if test="name != null">
                    OR name = #{name}
                </if>
                <if test="age != null">
                    OR age = #{age}
                </if>
            </trim>
        </where>
    </select>
    

    上述代码使用了 MyBatis 的动态 SQL 元素中的 <trim> 元素,用于构建更复杂的条件语句。<trim> 元素用于构建一个以 AND 为前缀和 ) 为后缀的条件片段,同时在生成条件语句时去除可能多余的 OR

    简单来说,这个查询语句的意思是:

    • 如果传入的参数 name 不为 null,则将 OR name = #{name} 添加到条件片段中。
    • 如果传入的参数 age 不为 null,则将 OR age = #{age} 添加到条件片段中。

    <trim> 元素的 prefix 属性指定前缀,suffix 属性指定后缀,prefixOverrides 属性指定需要去除的前缀。在这个例子中,prefixAND (suffix)prefixOverridesOR。这样,如果有至少一个条件满足,就会生成类似 AND ( condition1 OR condition2 ) 的查询语句。如果没有条件满足,就会生成 AND (),这不会影响查询结果。

  4. 使用 <foreach> 元素: 可以用来遍历集合或数组参数,生成多个相同的 SQL 片段。

    <select id="findByIds" parameterType="list" resultType="User">
        SELECT * FROM users
        WHERE id IN
        <foreach item="id" collection="list" open="(" separator="," close=")">
            #{id}
        </foreach>
    </select>
    

这段代码是一个 MyBatis 的动态 SQL 查询语句,用于根据传入的一组 id 值查询对应的用户信息。这个查询语句利用了 <foreach> 元素,允许在 SQL 查询中动态地生成 IN 子句。

具体来说,这个查询语句的意思是:

  • 查询 users 表中的所有列(SELECT *)。
  • 使用 WHERE 子句进行过滤,条件是 id 值在传入的 list 参数中。
  • <foreach> 元素用于循环遍历传入的 list 参数,将每个 id 值包装在 () 之间,并使用 , 作为分隔符。

在这个查询语句中,<foreach> 元素的属性含义如下:

  • item:指定在每次循环中使用的变量名,这里是 id
  • collection:指定要遍历的集合参数的名字,这里是 list
  • open:指定循环的开始字符,这里是 (
  • separator:指定循环每个元素之间的分隔符,这里是 ,
  • close:指定循环的结束字符,这里是 )

这样,如果传入一个列表参数 list,比如 [1, 2, 3],就会生成类似 SELECT * FROM users WHERE id IN (1, 2, 3) 的查询语句,从而查询对应的用户信息。

这些动态 SQL 的特性使得 MyBatis 在构建灵活和动态的查询条件时非常方便,避免了硬编码大量重复的 SQL 语句。


二、MyBatis的条件查询操作

​ 在学习了上面的几个动态SQL的常用元素,现在我们来尝试一下编写一下我们自己项目的动态SQL。

1.编写一个使用<if>的动态SQl查询语句,若有账号名称Account则按账号名称Account查询,若有网站名WebsiteName则用WebsiteName查询,若俩个都有则俩个都匹配的才查询。

PasswordMSMapper.xml中编写动态sql语句:

在这里插入图片描述

①首先进行单条件查询测试,若Account不为空:

在这里插入图片描述

②也是单条件查询测试,若WebsiteName不为空:

在这里插入图片描述

③若是俩个条件都满足呢?Account和WebsiteName都不为空:

在这里插入图片描述

④若是俩个条件都不满足呢?Account和WebsiteName都为空:

在这里插入图片描述

可以看到Mybatis是会根据我们输入的查询条件进行拼接生成动态的SQL。


2.那么我们继续尝试编写<choose>的动态sql语句,情景时:若有账号名Account,则优先使用账号名进行查询。若无账户名,有网站名WebsiteName,那么就按网站名搜索。若账号名,网站名都无,那么就采用默认查询passwordms表中的所有信息。

​ 在使用<if>元素时,只要test属性中的表达式为true,就会执行元素中的条件语句,但是在实际应用中,有时只需要从多个选项中选择一个去执行。这时候使用<choose>就是非常合适的,如果使用<if>则是不合适的


PasswordMSMapper.xml中编写动态sql语句:

在这里插入图片描述

那么去编写我们的测试类,根据不同的输入情况,查看是否满足我们的需求?
①Account不为空,WebsiteName为空的情况:

在这里插入图片描述

②Account不为空,WebsiteName不为空的情况(结果和上面的一样):

在这里插入图片描述

③Account为空,WebsiteName不为空的情况:

在这里插入图片描述

④Account为空,WebsiteName也为空的情况:

在这里插入图片描述

可以看到Mybatis是会根据我们输入的查询条件进行拼接生成动态的SQL。


3.那么我们继续尝试编写<where><trim>的动态sql语句,情景时:在sql中where 后面直接跟and 或者 or之类的关键字都是错误的,会导致我们的SQL语句运行不成功

在这里插入图片描述

在这个示例中我们写了个
<trim> prefix="AND (" suffix=")" prefixOverride="AND",
<if>条件中Account满足时,程序会自动在AND Account=#{Account}前面、后面,添加上前后缀生成新的语句:
AND (AND Account=#{Account})
若是WebsiteName也满足的话,新的sql语句为:
AND (AND Account=#{Account} AND WebsiteName = #{WebsiteName} )

我们写的prefixOverrides=“AND”,会自动将()里面的多余的前缀AND去除,即生成:
AND ( Account=#{Account} AND WebsiteName = #{WebsiteName} )

看到这里是不是觉得那外面还有一个where呢?where AND (Account=#{Account} AND WebsiteName = #{WebsiteName})也是错的对不?实际上<where> 会自动帮我们去除()外多余的AND和OR防止SQL语句出错。

因此这个流程下来我们的语句就变成了:
where ( Account=#{Account} AND WebsiteName = #{WebsiteName} )
我们去输入测试类看看就知道是不是了

①Account不为空,WebsiteName也不为空:

在这里插入图片描述

②Account为空,WebsiteName不为空:

根据我们上面讲的流程:那么推测生成的动态sql语句应该是:

where ( WebsiteName=#{WebsiteName} )

在这里插入图片描述

③Account、WebsiteName都为空:

根据我们上面讲的流程:那么推测生成的动态sql语句应该是

Select * from passwordms,因为if里面的条件每一个成立,所以where也不会添加进去,但是会自动删除语句中多余的AND或OR

在这里插入图片描述

可以看到Mybatis是会根据我们输入的查询条件进行拼接生成动态的SQL,并且正确无误。


三、Mybatis的更新操作

4.那么我们继续尝试编写<set><trim>的动态sql语句,情景时:如果想要更新某一个对象,就需要发送所有的字段给持久化对象,然而在实际应用中,大多数情况下都是更新某一个或几个字段。如果更新的每一条数据都要将其所有的属性都更新一遍,那么执行效率是非常差的。为了解决更新数据的效率问题,MyBatis提供了<set>元素。

​ 除了可以用<trim>元素来进行更新操作外,也使用<set>元素来进行更新操作, 在映射文件中使用<set>元素和<if>元素组合进行update语句动态SQL组装时,如果<set>元素内包含的内容都为空,则会出现SQL语法错误。因此,在使用<set>元素进行字段信息更新时,要确保传入的更新字段不能都为空

​ 除此以外还可以跟前面的<trim>元素联合起来一起使用。其中, <trim>元素,suffixOverrides属性指定去除的<trim>元素所包含内容的后缀为逗号

好那么我们来编写我们的Mapper文件:

在这里插入图片描述

接下来进行测试类测试:

①:更新多个字段的情况:

在这里插入图片描述

​ 可以看到嗷,正常数据更新了,但是呢,其实是有问题的,不管数据是不是更原来的一样,都会对数据库进行一次更新的操作。

因此我们还需要再改改,还需要加一个数据库对比的判断,详细代码如下:

在这里插入图片描述

isDataEqual()方法如下:
// 判断两个对象的数据是否相等
    private boolean isDataEqual(PasswordMS oldData, PasswordMS newData) {
        return Objects.equals(oldData.getAccount(), newData.getAccount())
                && Objects.equals(oldData.getWebsiteName(), newData.getWebsiteName());
    }

在这里插入图片描述

那么保持参数条件不变的情况下继续进行测试:

在这里插入图片描述

可以看到测试成功,正确提示!


②尝试看看单参数字段修改的情况:

在这里插入图片描述

③若是无参数那么就会报错,所以一定要确保更新的参数必须要有,不能全为空。

在这里插入图片描述

好的那么更新操作就学到这里了。


四、使用Mybatis的动态SQL进行复杂操作

使用 <foreach> 元素可以在 MyBatis 中对集合进行迭代操作,通常用于构建 IN 条件的查询语句

<foreach>元素的属性
属性说明
item表示集合中每一个元素进行迭代时的别名。该属性为必选。
index在List和数组中,index是元素的序号,在Map中,index是元素的key。该属性可选。
open表示foreach语句代码的开始符号,一般和close=“)”合用。常用在in条件语句中。该属性可选。
separator表示元素之间的分隔符,例如,在条件语句中,separator=“,”会自动在元素中间用“,”隔开,避免手动输入逗号导致SQL错误,错误示例如in(1,2,)。该属性可选。
close表示foreach语句代码的关闭符号,一般和open="("合用。常用在in条件语句中。该属性可选。
collection用于指定遍历参数的类型。注意,该属性必须指定,不同情况下,该属性的值是不一样的。

<collection>属性的取值

在遍历参数时<collection>属性的值是必须指定的。不同情况下,该属性的取值也是不一样的,主要有以下三种情况:List类型、数值类型、Map类型

  • 若入参为单参数且参数类型是一个List,collection属性值为list。
  • 若入参为单参数且参数类型是一个数组,collection属性值为array。
  • 若传入参数为多参数,就需要把参数封装为一个Map进行处理,collection属性值为Map。若传入参数为多参数,就需要把参数封装为一个Map进行处理,collection属性值为Map。

5.那么我们继续尝试编写<foreach>的动态sql语句,情景时:要从数据表passwordms中查询出id为1、2、3的账号信息,就可以利用数组作为参数,存储id的属性值1、2、3,并通过<foreach>元素迭代数组完成账号信息的批量查询操作。

好那么我们来编写我们的Mapper文件

 <select id="findByIds" parameterType="list" resultType="PasswordMS">
        SELECT * FROM passwordms
        WHERE id IN
        <foreach item="id" collection="list" open="(" separator="," close=")">
            #{id}
        </foreach>
    </select>

在这里插入图片描述

在我们的代码中,findByIds 是查询的 ID,parameterType 设置为 list 表示传入的参数是一个 List,resultType 设置为 PasswordMS 表示返回结果是 PasswordMS 类型的对象。

编写测试类:

输入的参数类型为同一种,查询1-3的账号信息
@Test
void findByIds() {
    SqlSession session = MyBatisUtil.createSqlSession();

    List<Integer> idList = Arrays.asList(1, 2, 3); // 要查询的 id 列表

    // 传入参数查询,返回结果
    List<PasswordMS> passwordMSList = session.selectList("findByIds", idList);

    for (PasswordMS s : passwordMSList) {
        logger.info("id:" + s.getId() + ",账号:" + s.getAccount() + ",密码:" + s.getPassword() + ",网站名:" + s.getWebsiteName() + ",网站网址:" + s.getWebsiteURL() + ",网站缩略图:" + s.getWebsiteImage() + ",账号描述:" + s.getAccountDescription());
    }

    // 关闭 session
    session.close();
}

在这里插入图片描述

可以看出,通过使用 <foreach> 元素就能够方便地在 MyBatis 中对集合进行迭代操作,构建需要的查询条件。


总结

这是第三天对SSM框架的学习,深入学习了MyBatis的动态SQL它允许我们根据不同的条件在 SQL 查询中动态地构建、拼接和执行 SQL 语句。动态 SQL 可以帮助你编写更加灵活、可复用和动态的数据库查询语句,适用于各种复杂的业务场景
​ 想要跟着学习的可以去我的资源里面找对应的文件下载,我的md文件也会发上去,项目文件会上传可以自己跟着学习一下。

PS:sql语句自己编写┗|`O′|┛ 嗷~~

作者:Stevedash

发表于:2023年8月23日 16点28分

注:本文内容基于个人学习理解,如有错误或疏漏,欢迎指正。感谢阅读!如果觉得有帮助,请点赞和分享。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/918398.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《Zookeeper》源码分析(十九)之 LearnerHandler

目录 LearnerCnxAcceptorrun() LearnerCnxAcceptorHandlerrun() LearnerHandlerrun()syncFollower()SNAP全量同步startSendingPackets() LearnerCnxAcceptor 在Leader.lead()方法中创建并启动LearnerCnxAcceptor线程&#xff0c;该线程主要是建立LearnerCnxAcceptorHandler并将…

介绍两个js补环境项目

1. v-jstools 这个项目是一个浏览器插件&#xff0c;用来补环境的话&#xff0c;是非常好的一个插件。项目地址是&#xff1a;GitHub - cilame/v_jstools: https://github.com/cilame/v_jstools 这里是我的配置 这个是使用后的效果 可以看到&#xff0c;里面调用的环境都被检…

【ARM AMBA AXI 入门 10 - AXI 总线 DATA信号与 STRB 信号之间的关系 】

文章目录 AXI STRB 信号 AXI STRB 信号 AXI总线是ARM公司设计的高性能处理器接口&#xff0c;其中STRB和DATA信号在AXI协议中有特殊的含义和关系。 DATA信号&#xff1a;在AXI中&#xff0c;DATA信号用于在读写操作中传输实际的数据。数据的大小可以根据AXI接口的位宽来变化&…

Redis(缓存预热,缓存雪崩,缓存击穿,缓存穿透)

目录 一、缓存预热 二、缓存雪崩 三、缓存击穿 四、缓存穿透 一、缓存预热 开过车的都知道&#xff0c;冬天的时候启动我们的小汽车之后不要直接驾驶&#xff0c;先让车子发动机预热一段时间再启动。缓存预热是一样的道理。 缓存预热就是系统启动前&#xff0c;提前将相关的…

I2C读写eeprom的问题

接线 在配置I2C的时候要把IO的口设置为开漏模式&#xff0c;为什么要设置开漏模式呢&#xff1f; 答&#xff1a;I2C协议支持多个主设备与多个从设备在一条总线上&#xff0c;如果不用开漏输出&#xff0c;而用推挽输出&#xff0c;会出现主设备之间短路的情况所以总线一般会…

基于闪电搜索算法优化的BP神经网络(预测应用) - 附代码

基于闪电搜索算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于闪电搜索算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.闪电搜索优化BP神经网络2.1 BP神经网络参数设置2.2 闪电搜索算法应用 4.测试结果&#xff1a;5…

使用Linux本地快速搭建web网站,并内网穿透发布上线「内网穿透」

文章目录 前言1. 本地搭建web站点2. 测试局域网访问3. 公开本地web网站3.1 安装cpolar内网穿透3.2 创建http隧道&#xff0c;指向本地80端口3.3 配置后台服务 4. 配置固定二级子域名5. 测试使用固定二级子域名访问本地web站点 前言 在web项目中,部署的web站点需要被外部访问,则…

最小二乘法,残差,线性模型-线性回归

目录 什么是最小二乘法 残差是什么意思 线性模型 线性回归 方法一&#xff1a;解析解法 代码实战&#xff1a; 方法二&#xff1a;数值解法 代码实战&#xff1a; 解析法&#xff08;最小二乘&#xff09;还是数值法&#xff08;梯度下降&#xff09;&#xff0c;如何…

笔记:自注意力机制

1、和其他网络的比较 自注意力机制适合处理长文本&#xff0c;并行度好&#xff0c;在GPU上&#xff0c;CNN和Self-attention性能差不多&#xff0c;在TPU&#xff08;Tensor Processing Uni&#xff09;效果更好。 2、输入特点 原生的Transformer中nn.embeding输入需要非负整…

住宅IP代理与数据中心IP代理的区别,最详解

跨境业务中常见到浏览器指纹防关联&#xff0c;但说到底&#xff0c;最重要的指纹是您的IP地址。在多个账号使用相同的IP地址简直触犯了大忌&#xff0c;这样做往往会导致账号惨遭暂停。 现在越来越多的跨境业务场景需要用到IP代理&#xff0c;那么我们常见的数据中心代理与住…

Instagram最新防封教程,看这一篇就够了

Instagram一直以来都是海外社媒巨头&#xff0c;也是跨境外贸引流推广的必争之地。在庞大的用户量中&#xff0c;真正了解平台规则的却并不多。它有一系列的社区准则和使用条款&#xff0c;稍有不慎违反规定就会造成限流&#xff0c;甚至导致账号被封禁&#xff0c;进而造成客户…

C++学习--函数实现

##MakeFileMAIN :Examples/main.cpp#主文件目录MAIN.o:objs/main.o##目标文件目录cpp_srcs :$(shell find src -name "*.cpp")cpp_objs :$(patsubst src/%.cpp,objs/%.o,$(cpp_srcs))#加头文件的编译选项 include_dirs :/home/shenlan/Group/00.lsa/project/include …

无涯教程-PHP - eregi()函数

eregi() - 语法 int eregi(string pattern, string string, [array regs]); eregi()函数在pattern指定的整个字符串中搜索string指定的字符串,。搜索不区分大小写。 Eregi()在检查字符串的有效性时特别有用。 可选的输入参数regs包含一个由正则表达式中的括号分组的所有匹配…

制作酒店预订小程序的秘诀揭秘

如今&#xff0c;小程序已经成为各行各业的必备工具。酒店业也不例外&#xff0c;拥有一个能够进行酒店预订的小程序&#xff0c;不仅可以提供更加便捷的预订服务&#xff0c;还能够提升酒店的品牌形象和用户体验。而今天&#xff0c;我将教你如何在零基础的情况下&#xff0c;…

基于哈里斯鹰算法优化的BP神经网络(预测应用) - 附代码

基于哈里斯鹰算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于哈里斯鹰算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.哈里斯鹰优化BP神经网络2.1 BP神经网络参数设置2.2 哈里斯鹰算法应用 4.测试结果&#xff1a;5…

oracle数据库总结

文章适合熟悉mysql&#xff0c;想学习oracle的选手&#xff0c;基本的语法都相差不大&#xff0c;但还是有区别的 一、oracle高水位线问题 1、什么是高水位线   简单来说&#xff0c;oracle存储数据的时候会分配空间&#xff0c;但是删除数据的时候并不会回收空间。这样的话…

行业追踪,2023-08-23

自动复盘 2023-08-23 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

DFX概述 | Design For X | Design For Excellent

Design for X (DFX) Methods 什么是“Design for X”&#xff1f; Design for eXcellence是一种在设计和制造领域中的不断发展的原则哲学。它采用了全面和系统的设计方法&#xff0c;关注产品的各个方面——从概念生成到最终交付。 它提供了良好的实践和设计指南&#xff0c…

ESD门禁管理系统的主要功能和优势

ESD门禁管理系统是一种用于控制和管理人员进出特定区域的系统。它通常由门禁控制器、门禁读卡器、门禁管理软件等组成。 ESD门禁管理系统的主要功能包括&#xff1a; 1. 门禁控制&#xff1a;通过门禁控制器实现对门禁设备的控制&#xff0c;如开关门、锁定门等。 2. 门禁验…

视频批量剪辑矩阵分发系统源码开源分享----基于PHP语言

批量剪辑视频矩阵分发&#xff1a; 短视频seo主要基于抖音短视频平台&#xff0c;为企业实现多账号管理&#xff0c;视频分发&#xff0c;视频批量剪辑&#xff0c;抖音小程序搭建&#xff0c;企业私域转化等&#xff0c;本文主要介绍短视频矩阵系统抖音小程序开发详细及注意事…