Spring Boot单元测试与Mybatis单表增删改查
目录
1. Spring Boot单元测试
1.1 什么是单元测试?
1.2 单元测试有哪些好处?
1.3 Spring Boot 单元测试使用
单元测试的实现步骤
1. 生成单元测试类
2. 添加单元测试代码
简单的断言说明
2. Mybatis 单表增删改查
2.1 单表查询
2.2 参数占位符 ${} 和 #{}
${} 和 #{}的区别
1. 作用不同
2. 安全性: ${} 的SQL注入问题
${} 应用场景
2.3 单表修改操作
2.4 单表删除操作
2.5 单表添加操作
添加返回影响行数
添加返回影响行数和id
2.6 like查询
2.7 标签返回类型使用背景使用
1. Spring Boot单元测试
1.1 什么是单元测试?
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证的过程就叫单元测试。
单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的(代码)功能是否正确。执行单元测试就是为了证明某段代码的执行结果是否符合我们的预期。如果测试结果符合我们的预期,称之为测试通过,否则就是测试未通过 (或者叫测试失败)
1.2 单元测试有哪些好处?
- 可以非常简单、直观、快速的测试某一个功能是否正确。
- 使用单元测试可以帮我们在打包的时候,发现一些问题,因为在打包之前,所有的单元测试必须通过, 否则不能打包成功。

- 使用单元测试,在测试功能的时候,可以不污染连接的数据库,也就是可以不对数据库进行任何改变的情况下,测试功能。
1.3 Spring Boot 单元测试使用
Spring Boot 项目创建时会默认单元测试框架 spring-boot-starter-test,而这个单元测试框架主要是依靠另个著名的测试框架 JUnit 实现的,打开 pom.xml 就可以看到,以下信息是 Spring Boot 项目创建是自动添加的:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
单元测试的实现步骤
1. 生成单元测试类



最终生成的代码:
package com.example.demo.mapper;import org.junit.jupiter.api.Test;class UserMapperTest {@Testvoid getAll() {}
}
这个时候,此方法是不能调用到任何单元测试的方法的,此类只生成了单元测试的框架类,具体的业务代码要自己填充。
2. 添加单元测试代码
- 在测试类上添加Spring Boot 框架测试注解: @SpringBootTest
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest // 表示当前单元测试的类是运行在 Spring Boot 环境中的(一定不能省略)
class UserMapperTest {// ..
}
- 添加单元测试业务逻辑
@Autowiredprivate UserMapper userMapper;@Testvoid getAll() {List<UserEntity> list = userMapper.getAll();System.out.println(list.size());}
简单的断言说明
| 方法 | 说明 |
|---|---|
| assertEquals | 判断两个对象或两个原始类型是否相等 |
| assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
| assertSame | 判断两个对象引用是否指向同一个对象 |
| assertNotSame | 判断两个对象引用是否指向不同的对象 |
| assertTrue | 判断给定的布尔值是否为 true |
| assertFalse | 判断给定的布尔值是否为 false |
| assertNull | 判断给定的对象引用是否为 null |
| assertNotNull | 判断给定的对象引用是否不为 null |
断言: 如果断言失败,则后面的代码都不会执行.
2. Mybatis 单表增删改查
2.1 单表查询
下面我们来实现一下根据用户id查询用户信息的功能.
在UserMapper类中添加接口:
// 根据 id 查询用户对象
UserEntity getUserById(@Param("uid") Integer id); // @Param是给形参起名
<select id="getUserById" resultType="com.example.demo.entity.UserEntity">select * from userinfo where id=${uid}</select>
注: 上面 ${uid} 中的uid对应@Param的uid
使用单元测试的方式去调用它.
@Testvoid getUserById() {UserEntity user = userMapper.getUserById(2);System.out.println(user);}
那么我们的预期结果是能够打印出数据库中"zhangsan"的数据:

执行结果:

可以看到, 预期结果成功执行了.
2.2 参数占位符 ${} 和 #{}
Mybatis获取动态参数有两种实现:
- ${paramName} -> 直接替换
- #{paramName} -> 占位符模式
验证直接替换:
在Spring配置文件中有一个配置, 只需要把这个配置给它配置之后, 那么Mybatis的执行SQL(Mybatis底层是基于JDBC), 最终会生成JDBC的执行SQL和它的执行模式, 那么我们就可以把这个执行的SQL语句打印出来.
需要配置两个配置项, 一个是日志打印的实现, 另一个是设置日志打印的级别 (SQL的打印默认输出的级别的debug级别, 但日志默认级别的info, 默认info要大于debug, 所以并不会显示, 所以要去进行日志级别的设置).
# 打印 Mybatis 执行 SQL
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
logging.level.com.example.demo=debug
配置完成之后再次运行刚才的测试代码, 可以看到SQL的相关信息都被打印了出来, 所以可以知道$是直接替换的模式.

将上文的 $ 换成 # , 会看到的是, SQL语句的id变成了?, 也就是变成了占位符的模式.

而占位符的模式是预执行的, 而预执行是比较安全的, 具体来说预执行可以有效的排除SQL注入的问题.
${} 和 #{}的区别
1. 作用不同
${} 所见即所得, 直接替换, #{} 是预处理的.
在进行使用的时候, 如果传的是int这种简单数据类型的时候, 两者是没有区别的, 但是如果更复杂点的使用varchar, 就会有安全的问题出现.
在UserMapper类中添加接口:
// 根据名称查询用户对象
UserEntity getUserByUserName(@Param("username") String username);
<select id="getUserByUserName" resultType="com.example.demo.entity.UserEntity">select * from userinfo where username=#{username}</select>
测试:
@Testvoid getUserByUserName() {UserEntity user = userMapper.getUserByUserName("zhangsan");System.out.println(user);}

测试结果没有问题, 那么再将#换成$.
<select id="getUserByUserName" resultType="com.example.demo.entity.UserEntity">select * from userinfo where username=${username}</select>

这时程序报错没有找到'zhangsan', 并且我们看到SQL语句变成了.
在数据库客户端中执行图中SQL语句也是会报出和上图一样的错.

那么这里的原因就在于刚才我们的代码中, ${}是直接替换的模式, 当加上单引号后再次运行就正常运行了.
<select id="getUserByUserName" resultType="com.example.demo.entity.UserEntity">select * from userinfo where username='${username}'</select>

但是加单引号只能保证不报错, 但是不能保证安全性问题.
所以当我们遇到是int类型的时候, ${} 和 #{} 在执行上没有什么区别, 当出现字符型的时候${} 就有可能会出现问题.
2. 安全性: ${} 的SQL注入问题
${} 的安全性问题出现在登录, 接下来我们以登录为例看一下什么是SQL注入.
首先SQL注入是 用户用了并不是一个真实的用户名和密码, 但是却查询到了数据. 我们通过代码说明.
// 登录方法
UserEntity login(UserEntity user);
<select id="login" resultType="com.example.demo.entity.UserEntity">select * from userinfo where username='${username}' and password='${password}'</select>
注: 当Interface传的是对象时, xml中获取属性时, 也就是{}里面直接写对象的属性名即可, 无需"对象.属性", 这是Mybatis的约定
为了演示效果, 我们在数据库中删掉id=2的zhangsan.

先来看正常的用户行为.
@Testvoid login() {String username = "admin";String password = "admin";UserEntity inputUser = new UserEntity();inputUser.setUsername(username);inputUser.setPassword(password);UserEntity user = userMapper.login(inputUser);System.out.println(user);}

可以看到, 找到了相关信息.
当输入错误密码时, 即:
String password = "admin2";

可以看到, 结果是null, 以上都是正常的行为.
接下来我们来看一个特殊的, 不正常的行为, 输入如下密码:
String password = "' or 1='1";

此时我们可以发现, 输入了一个不正常的密码, 却把admin查出来了, 这就是SQL注入, 对于程序来说是非常危险的.
那么我们可以看到这里的SQL语句是
select * from userinfo where username='admin' and password='' or 1='1'
所以这便是这里出错的原因, 它把字符串误解析成SQL指令去执行了, 使逻辑运行结果与预期不同, 但却正常执行.
当把 ${} 改为 #{} 后, 再次测试, 可以看到结果是null.

由上可见, 使用 ${} 是会安全性问题的, 而使用 #{} 就不会出现安全性问题, 原因在于 #{} 使用了JDBC的占位符的模式, 那么这种模式是预执行的, 是直接当成字符串来执行的.
${} 应用场景
${} 虽然在查询的时候会有安全性问题, 但是它也有具体的应用场景, 比如以下场景:
在淘宝中有时候需要按照某种属性进行排序, 比如价格低到高或者高到低, 这时SQL传递的就是order by后的规则asc或desc.

使用 ${sort} 可以实现排序查询,而使用 #{sort} 就不能实现排序查询了,因为当使用 #{sort} 查询时如果传递的值为 String 则会加单引号,就会导致 sql 错误。
那么对于我们之前的程序, 我们也可以进行类似的应用.
List<UserEntity> getAllByIdOrder(@Param("ord") String order);
<select id="getAllByIdOrder" resultType="com.example.demo.entity.UserEntity">select * from userinfo order by id ${ord}</select>
@Testvoid getAllByIdOrder() {List<UserEntity> list = userMapper.getAllByIdOrder("desc");System.out.println(list.size());}

这时使用 #{} 就会报错了.
<select id="getAllByIdOrder" resultType="com.example.demo.entity.UserEntity">select * from userinfo order by id #{ord}</select>

既然 ${} 有用, 但是它也极其的危险, 在使用的时候要注意, 要保证它的值必须得被枚举. 所以尽量少用.
2.3 单表修改操作
比如需要修改用户密码.
首先, 在Interface声明方法,
// 修改密码int updatePassword(@Param("id") Integer id,@Param("password") String password,@Param("newPassword") String newPassword);
然后在xml中实现方法, 注意修改操作是使用<update>标签.
<update id="updatePassword">update userinfo set password=#{newPassword}where id=#{id} and password=#{password}</update>
@Testvoid updatePassword() {int result = userMapper.updatePassword(1, "admin", "123456");System.out.println("修改: " + result);}

运行前后查询数据库,

可以看到, password已经成功修改了.
当再次修改newPassword参数的代码时, 即:
int result = userMapper.updatePassword(1, "admin", "666666");

这里说明, 注入参数有问题, 代码没问题.
不过, 这里的测试是把原本数据库污染了, 违背了单元测试的初衷, 那么要想不污染数据库, 需要在测试类前加上@Transactional事务注解.
@Transactional // 事务@Testvoid updatePassword() {int result = userMapper.updatePassword(1, "123456", "666666");System.out.println("修改: " + result);}
当加上注解之后, 测试的代码可以正常执行, 但是就不会污染数据库了.

看到打印了"修改: 1", 就说明成功修改了.
在代码执行的时候不会进行干扰的, 只不过在执行之初, 会开启一个事务, 等全部代码执行完了, 比如这里的"修改: x"已经正常打印了, 然后在它执行完会进行rollback回滚操作, 所以就不会污染数据库了.
验证数据库是否污染:

2.4 单表删除操作
// 删除用户
int delById(@Param("id") Integer id);
<delete id="delById">delete from userinfo where id=#{id}</delete>
@Transactional@Testvoid delById() {int id = 1;int result = userMapper.delById(id);System.out.println("删除结果: " + result);}

2.5 单表添加操作
添加返回影响行数
// 添加用户
int addUser(UserEntity user);
<insert id="addUser">insert into userinfo(username,password) values(#{username},#{password})</insert>
@Testvoid addUser() {UserEntity user = new UserEntity();user.setUsername("lisi");user.setPassword("123456");int result = userMapper.addUser(user);System.out.println("添加: " + result);}


添加返回影响行数和id
int addUserGetId(UserEntity user);
<insert id="addUserGetId" useGeneratedKeys="true" keyProperty="id">insert into userinfo(username,password) values(#{username},#{password})</insert>
@Testvoid addUserGetId() {UserEntity user = new UserEntity();user.setUsername("lili");user.setPassword("123456");int result = userMapper.addUserGetId(user);System.out.println("添加结果: " + result);System.out.println("ID: " + user.getId());}


2.6 like查询
like使用 #{} 会报错
// 根据用户名模糊查询List<UserEntity> getListByName(@Param("username") String username);
<select id="getListByName" resultType="com.example.demo.entity.UserEntity">select * from userinfo where username like '%#{username}%'</select>
@Testvoid getListByName() {String username = "zhang";List<UserEntity> list = userMapper.getListByName(username);list.stream().forEach(System.out::println);}

可以看到, 当我们使用#{}的方式进行like查询的时候, 它是有问题的, 因为它会把#{username}作为占位符, 看到传过来的参数是String后, 相当于又加了一个单引号, 也就是'%'zhang'%', 所以它是报错的.
在数据库中演示一下.

那么可以通过MySQL内置方法concat解决这个问题, concat能实现字符的拼接.
<select id="getListByName" resultType="com.example.demo.entity.UserEntity">select * from userinfo where username like concat('%',#{username},'%')</select>

可以看到, 我们可以查到zhangsan的信息了, 说明此时查询是没有问题的.
2.7 <select>标签返回类型
我们知道, <select>查询标签至少需要设置两个属性:
id属性: 用于标识实现接口中的哪个方法;
结果映射(即返回)属性: 结果映射有两种实现标签: <resultType>和<resultMap>
<resultType>
绝大多数场景可以使用resultType返回, 如下所示:
<select id="getAll" resultType="com.example.demo.entity.UserEntity">select * from userinfo</select>
它的优点是使用方便,直接定义到某个实体类即可.
<resultMap>
<resultMap>使用场景:
- 字段名称和程序中的属性名不同的情况,可使用 resultMap 配置映射
- 一对一和一对多关系可以使用 resultMap 映射并查询数据
使用背景
我们以数据库中的字段为例,

比如当项目实体类中密码的属性为pwd, 而数据库中对应的字段为password, 这个时候Mybatis就匹配不到了, 原本实体类中为password, Mybatis因为做了映射, 而映射规则是根据名称进行匹配, 所以能够拿到数据库中的信息(Mybatis是一个ORM框架). 那么问题在哪里呢?
我们来看前文的getListByName(), 原来使用zhang把信息查询到了,

但是我们此时将实体类中的password改为pwd, 再次测试,

我们可以会发现, 执行没有报错, JDBC可以正常拿到password, 但是在映射的时候给pwd没有映射上.
这里就是因为对比的时候pwd和password是不相同的, Mybatis在属性中没有找到pwd.
所以这时我们<resultType>使用实体类已经和数据库的表对应不上了, 那么就使用<resultMap>.
使用

<resultMap id="BaseMap" type="com.example.demo.entity.UserEntity"><id property="id" column="id"></id><result property="username" column="username"></result><result property="pwd" column="password"></result><result property="createtime" column="createtime"></result><result property="updatetime" column="updatetime"></result></resultMap><select id="getListByName" resultMap="BaseMap">select * from userinfo where username like concat('%',#{username},'%')</select>

可以看到, 此时pwd是有值的, 数据库中为password, 程序中是pwd, 依然能够拿到值.
注: 如果依然要用resultType就需要对SQL语句修改.
<select id="getListByName" resultType="com.example.demo.entity.UserEntity">select id,username,password as pwd from userinfo where username like concat('%',#{username},'%')</select>

可以看到成功的拿到了password, 并且在SQL中是实现了重命名.
相关文章:
Spring Boot单元测试与Mybatis单表增删改查
目录 1. Spring Boot单元测试 1.1 什么是单元测试? 1.2 单元测试有哪些好处? 1.3 Spring Boot 单元测试使用 单元测试的实现步骤 1. 生成单元测试类 2. 添加单元测试代码 简单的断言说明 2. Mybatis 单表增删改查 2.1 单表查询 2.2 参数占位符 ${} 和 #{} ${} 和 …...
机器学习样本数据划分的典型Python方法
机器学习样本数据划分的典型Python方法 DateAuthorVersionNote2023.08.16Dog TaoV1.0完成文档撰写。 文章目录 机器学习样本数据划分的典型Python方法样本数据的分类Training DataValidation DataTest Data numpy.ndarray类型数据直接划分交叉验证基于KFold基于RepeatedKFold基…...
重建与突破,探讨全链游戏的现在与未来
全链游戏(On-Chain Game)是指将游戏内资产通过虚拟货币或 NFT 形式记录上链的游戏类型。除此以外,游戏的状态存储、计算与执行等皆被部署在链上,目的是为用户打造沉浸式、全方位的游戏体验,超越传统游戏玩家被动控制的…...
[C++] 模板template
目录 1、函数模板 1.1 函数模板概念 1.2 函数模板格式 1.3 函数模板的原理 1.4 函数模板的实例化 1.4.1 隐式实例化 1.4.2 显式实例化 1.5 模板参数的匹配原则 2、类模板 2.1 类模板的定义格式 2.2 类模板的实例化 讲模板之前呢,我们先来谈谈泛型编程&am…...
[vite] 项目打包后页面空白,配置了base后也不生效
记录下解决问题的过程和思路 首先打开看打包后的 dist/index.html 文件,和页面上的报错 这里就发现了第一个问题 报错的意思是 index.html中引用的 css文件 和 js文件 找不到 为了解决这个问题,在vite.config.js配置中,增加一项 base:./ …...
springboot整合kafka-笔记
springboot整合kafka-笔记 配置pom.xml 这里我的springboot版本是2.3.8.RELEASE,使用的kafka-mq的版本是2.12 <dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>s…...
Rust软件外包开发语言的特点
Rust 是一种系统级编程语言,强调性能、安全性和并发性的编程语言,适用于广泛的应用领域,特别是那些需要高度可靠性和高性能的场景。下面和大家分享 Rust 语言的一些主要特点以及适用的场合,希望对大家有所帮助。北京木奇移动技术有…...
Spring Boot业务代码中使用@Transactional事务失效踩坑点总结
1.概述 接着之前我们对Spring AOP以及基于AOP实现事务控制的上文,今天我们来看看平时在项目业务开发中使用声明式事务Transactional的失效场景,并分析其失效原因,从而帮助开发人员尽量避免踩坑。 我们知道 Spring 声明式事务功能提供了极其…...
知识体系总结(九)设计原则、设计模式、分布式、高性能、高可用
文章目录 架构设计为什么要进行技术框架的设计 六大设计原则一、单一职责原则二、开闭原则三、依赖倒置原则四、接口分离原则五、迪米特法则(又称最小知道原则)六、里氏替换原则案例诠释 常见设计模式构造型单例模式工厂模式简单工厂工厂方法 生成器模式…...
Springboot 集成Beetl模板
一、在启动类下的pom.xml中导入依赖: <!--beetl模板引擎--><dependency><groupId>com.ibeetl</groupId><artifactId>beetl</artifactId><version>2.9.8</version></dependency> 二、 配置 beetl需要的Beetl…...
RabbitMQ查询队列使用情况和消费者详情实现
spring-boot-starter-amqp spring-boot-starter-amqp是Spring Boot框架中与AMQP(高级消息队列协议)相关的自动配置启动器。它提供了使用AMQP进行消息传递和异步通信的功能。 以下是spring-boot-starter-amqp的主要特性和功能: 自动配置:spring-boot-starter-amqp通过自动…...
Spark第二课RDD的详解
1.前言 RDD JAVA中的IO 1.小知识点穿插 1. 装饰者设计模式 装饰者设计模式:本身功能不变,扩展功能. 举例: 数据流的读取 一层一层的包装,进而将功能进行进一步的扩展 2.sleep和wait的区别 本质区别是字体不一样,sleep斜体,wait正常 斜体是静态方法…...
人工智能学习框架—飞桨Paddle人工智能
1.人工智能框架 机器学习的三要素:模型、学习策略、优化算法。 当我们用机器学习来解决一些模式识别任务时,一般的流程包含以下几个步骤: 1.1.浅层学习和深度学习 浅层学习(Shallow Learning):不涉及特征学习,其特征…...
SElinux 导致 Keepalived 检测脚本无法执行
哈喽大家好,我是咸鱼 今天我们来看一个关于 Keepalived 检测脚本无法执行的问题 一位粉丝后台私信我,说他部署的 keepalived 集群 vrrp_script 模块中的脚本执行失败了,但是手动执行这个脚本却没有任何问题 这个问题也是咸鱼第一次遇到&…...
2022年电赛C题——小车跟随行驶系统——做题记录以及经验分享
前言 自己打算将做过的电赛真题,主要包含控制组的,近几年出现的小车控制题目,自己做过的真题以及在准备电赛期间刷真题出现的问题以及经验分享给大家 这次带来的是22年电赛C题——小车跟随行驶系统,这道题目指定使用的是TI的单片…...
vscode + python
序 参考链接: 【教程】VScode中配置Python运行环境_哔哩哔哩_bilibili Python部分 Python Releases for Windows | Python.org vscode部分 Visual Studio Code - Code Editing. Redefined 一路next,全部勾上: 就可以了: 安装插…...
badgerdb里面的事务
事务的ACID A 原子性(Atomicity) 多步骤操作,只能是两种状态,要么所有的步骤都成功执行,要么所有的步骤都不执行,举例说明就是小明向小红转账30元的场景,拆分成两个步骤,步骤1&#…...
C# this.Invoke(new Action(() => { /* some code */ }))用法说明
在 C# 中,this.Invoke(new Action(() > { /* some code */ })) 是一种用于在 UI 线程上执行代码的方法,通常用于在后台线程中更新 UI 控件的值或执行其他需要在 UI 线程上执行的操作。 在 Windows Forms 或 WPF 等图形界面应用程序中,UI …...
MongoDB:MySQL,Redis,ES,MongoDB的应用场景
简单明了说明MySQL,ES,MongoDB的各自特点,应用场景,以及MongoDB如何使用的第一章节. 一. SQL与NoSQL SQL被称为结构化查询语言.是传统意义上的数据库,数据之间存在很明确的关联关系,例如主外键关联,这种结构可以确保数据的完整性(数据没有缺失并且正确).但是正因为这种严密的结…...
leetcode每日一题_2682.找出转圈游戏输家
2682.找出转圈游戏输家 题目: n 个朋友在玩游戏。这些朋友坐成一个圈,按 顺时针方向 从 1 到 n 编号。从第 i 个朋友的位置开始顺时针移动 1 步会到达第 (i 1) 个朋友的位置(1 < i < n),而从第 n 个朋友的位置开始顺时针移…...
Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...
如何更改默认 Crontab 编辑器 ?
在 Linux 领域中,crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用,用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益,允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...
