首页
留言
导航
统计
Search
1
追番推荐!免费看动漫的网站 - 支持在线观看和磁力下载
2,524 阅读
2
推荐31个docker应用,每一个都很实用
1,315 阅读
3
PVE自动启动 虚拟机 | 容器 顺序设置及参数说明
935 阅读
4
一条命令,永久激活!Office 2024!
619 阅读
5
优选 Cloudflare 官方 / 中转 IP
491 阅读
默认分类
服务器
宝塔
VPS
Docker
OpenWRT
Nginx
群晖
前端编程
Vue
React
Angular
NodeJS
uni-app
后端编程
Java
Python
SpringBoot
SpringCloud
流程引擎
检索引擎
Linux
CentOS
Ubuntu
Debian
数据库
Redis
MySQL
Oracle
虚拟机
VMware
VirtualBox
PVE
Hyper-V
计算机
网络技术
网站源码
主题模板
登录
Search
标签搜索
Java
小程序
Redis
SpringBoot
docker
Typecho
Cloudflare
docker部署
虚拟机
WordPress
群晖
uni-app
CentOS
Vue
Java类库
Linux命令
防火墙配置
Mysql
脚本
Nginx
微醺
累计撰写
264
篇文章
累计收到
11
条评论
首页
栏目
默认分类
服务器
宝塔
VPS
Docker
OpenWRT
Nginx
群晖
前端编程
Vue
React
Angular
NodeJS
uni-app
后端编程
Java
Python
SpringBoot
SpringCloud
流程引擎
检索引擎
Linux
CentOS
Ubuntu
Debian
数据库
Redis
MySQL
Oracle
虚拟机
VMware
VirtualBox
PVE
Hyper-V
计算机
网络技术
网站源码
主题模板
页面
留言
导航
统计
搜索到
264
篇与
的结果
2023-07-07
再见了 Xshell、iTerm2、FinalShell,这款开源的终端工具真香
现目前的的远程终端工具有很多,功能齐全好用的收费,免费的功能外观又不怎么满意。XShell 收费而且感觉用起来也一般,putty 免费但很不方便,我们的 FinalShell 好用、功能齐全可以说是一款非常好的终端工具但外观总是觉得不尽人意。直到我发现这款「Tabby」。引言Tabby 是一个高度可配置的终端模拟器、SSH 和串行客户端,适用于 Windows、macOS 和 Linux。特性集成的 SSH 和 Telnet 客户端和连接管理器集成串口终端主题和配色方案完全可配置的快捷键和多和弦快捷键拆分窗格记住你的标签PowerShell(和 PS Core)、WSL、Git-Bash、Cygwin、MSYS2、Cmder 和 CMD 支持通过 Zmodem 从/到 SSH 会话的直接文件传输完整的 Unicode 支持,包括双角字符不会因快速流动的输出而窒息Windows 上的正确 shell 体验,包括选项卡完成(通过 Clink)用于 SSH 机密和配置的集成加密容器SSH、SFTP 和 Telnet 客户端可用作 Web 应用程序 (也可自托管)。地址GitHub:https://github.com/Eugeny/tabby官网:https://tabby.sh/在线体验:https://tabby.sh/app 大家可以先在线体验,这个还挺厉害的功能介绍默认打开是本地终端,如果对本地终端工具不满意也可以试试它创建远程连接点击右上角齿轮点击左侧菜单的 Profiles&connections点击蓝色按钮 New profile 添加新的远程连接选择第一个,创建 SSH 连接填入服务器信息等,保存连接点击窗口按钮选中你创建的远程连接支持FTP 没错他直接支持文件的上传和下载总结Tabby 市面上少见的既好用又好看还免费开源的远程终端工具。
2023年07月07日
57 阅读
0 评论
0 点赞
2023-07-06
利用docker搭建home-assistant打造全屋智能
home-assistant 安装打开注册表搜索 homeassistant/home-assistant 下载 docker映像 下载完成后启动网络类型一定 不要选择bridge ,选择 bridge-host 或者 host 否则未在同一局域网 homekit 无法绑定选择高级-添加环境变量-保存TZ:Asia/Shanghai端口本地 8321 - 容器 8321,如果选择的 host 则不需要设置端口添加一个文件夹并装载到 /config,完成并运行容器Home Assistant 安装 HACS什么是 HACS?HACS 可以理解为 Home Assistant 上的一个第三方应用商店打开容器详情页面-选择终端-新建命令右键粘贴并运行以下命令wget -O - https://get.hacs.xyz | bash -运行完成后重启容器浏览器打开并输入 群晖ip + 端口8123 并访问根据向导完成并创建自己的账号点击「配置」,点击「集成」,点击右下角的「添加集成」,在搜索框里输入「HACS」并点击,接受协议。需要全选,无法不选。点击「提交」,设备注册。需要先拥有Github账号,没有的话注册一个并在浏览器上登录。打开 HACS商店 浏览并下载存储库:Xiaomi Miot Auto,下载完成后重启容器重新登录后打开 配置-设备与服务-添加集成-搜索xiaomi-安装Xiaomi Miot Auto-选择账号集成模式配置 HomeKit 后打开通知手机扫描二维码后即可使用iphone控制小米设备
2023年07月06日
26 阅读
0 评论
0 点赞
2023-07-06
解决WordPress开启CDN后评论IP不正确的方法
前言最近启用CDN内容分发后发现评论评论人的IP都是CDN的节点IP(也有可能是服务器IP),由于使用CDN后访问用户服务器的IP都变成CDN服务器的IP。百度一下,发现了一个好的方法。只要添加一段代码,IP地址就可以正常了,其实CDN在头部应该有把访问者IP发送的,那段代码就是把 wordpress 的 ip 中变量换成 $_SERVER 获取的。这个可以用在任意CDN和反代里面,IP地址可以获取正确的。在 WordPress 安装根目录 下面 wp-config.php 文件,打开后,在其内添加下面代码:if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $list = explode(',',$_SERVER['HTTP_X_FORWARDED_FOR']); $_SERVER['REMOTE_ADDR'] = $list[0]; }
2023年07月06日
46 阅读
0 评论
0 点赞
2023-07-03
SpringBoot 服务接口限流,搞定!
前言在开发高并发系统时有三把利器用来保护系统: 缓存 、降级 和 限流 。限流 可以认为服务降级的一种,限流通过限制请求的流量以达到保护系统的目的。一般来说,系统的吞吐量是可以计算出一个阈值的,为了保证系统的稳定运行,一旦达到这个阈值,就需要限制流量并采取一些措施以完成限制流量的目的。比如:延迟处理,拒绝处理,或者部分拒绝处理等等。否则,很容易导致服务器的宕机。常见限流算法计数器限流计数器限流算法 是最为简单粗暴的解决方案,主要用来限制总并发数,比如数据库连接池大小、线程池大小、接口访问并发数等都是使用计数器算法。如:使用 AomicInteger 来进行统计当前正在并发执行的次数,如果超过域值就直接拒绝请求,提示系统繁忙。漏桶算法漏桶算法 思路很简单,我们把水比作是 请求,漏桶比作是系统处理能力极限,水先进入到漏桶里,漏桶里的水按一定速率流出,当流出的速率小于流入的速率时,由于漏桶容量有限,后续进入的水直接溢出(拒绝请求),以此实现限流。令牌桶算法令牌桶算法 的原理也比较简单,我们可以理解成医院的挂号看病,只有拿到号以后才可以进行诊病。系统会维护一个令牌(token)桶,以一个恒定的速度往桶里放入令牌(token),这时如果有请求进来想要被处理,则需要先从桶里获取一个令牌(token),当桶里没有令牌(token)可取时,则该请求将被拒绝服务。令牌桶算法通过控制桶的容量、发放令牌的速率,来达到对请求的限制。单机模式Google 开源工具包 Guava 提供了限流工具类 RateLimiter ,该类基于 令牌桶算法 实现流量限制,使用十分方便,而且十分高效引入依赖到 pom.xml<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>30.1-jre</version> </dependency>创建注解 Limit package com.example.demo.common.annotation; import java.lang.annotation.*; import java.util.concurrent.TimeUnit; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Documented public @interface Limit { // 资源key String key() default ""; // 最多访问次数 double permitsPerSecond(); // 时间 long timeout(); // 时间类型 TimeUnit timeunit() default TimeUnit.MILLISECONDS; // 提示信息 String msg() default "系统繁忙,请稍后再试"; }注解 AOP 实现package com.example.demo.common.aspect; import com.example.demo.common.annotation.Limit; import com.example.demo.common.dto.R; import com.example.demo.common.exception.LimitException; import com.google.common.collect.Maps; import com.google.common.util.concurrent.RateLimiter; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.Map; @Slf4j @Aspect @Component public class LimitAspect { private final Map<String, RateLimiter> limitMap = Maps.newConcurrentMap(); @Around("@annotation(com.example.demo.common.annotation.Limit)") public Object around(ProceedingJoinPoint pjp) throws Throwable { MethodSignature signature = (MethodSignature)pjp.getSignature(); Method method = signature.getMethod(); //拿limit的注解 Limit limit = method.getAnnotation(Limit.class); if (limit != null) { //key作用:不同的接口,不同的流量控制 String key=limit.key(); RateLimiter rateLimiter; //验证缓存是否有命中key if (!limitMap.containsKey(key)) { // 创建令牌桶 rateLimiter = RateLimiter.create(limit.permitsPerSecond()); limitMap.put(key, rateLimiter); log.info("新建了令牌桶={},容量={}",key,limit.permitsPerSecond()); } rateLimiter = limitMap.get(key); // 拿令牌 boolean acquire = rateLimiter.tryAcquire(limit.timeout(), limit.timeunit()); // 拿不到命令,直接返回异常提示 if (!acquire) { log.debug("令牌桶={},获取令牌失败",key); throw new LimitException(limit.msg()); } } return pjp.proceed(); } }注解使用permitsPerSecond 代表请求总数量timeout 代表限制时间即 timeout 时间内,只允许有 permitsPerSecond 个请求总数量访问,超过的将被限制不能访问package com.example.demo.module.test; import com.example.demo.common.annotation.Limit; import com.example.demo.common.dto.R; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; @Slf4j @RestController public class TestController { @Limit(key = "cachingTest", permitsPerSecond = 1, timeout = 500, msg = "当前排队人数较多,请稍后再试!") @GetMapping("cachingTest") public R cachingTest(){ log.info("------读取本地------"); List<String> list = new ArrayList<>(); list.add("蜡笔小新"); list.add("哆啦A梦"); list.add("四驱兄弟"); return R.ok(list); } }测试启动项目,快读刷新访问 /cachingTest 请求可以看到访问已经有被成功限制该种方式属于 应用级限流 ,假设将应用部署到多台机器,应用级限流方式只是单应用内的请求限流,不能进行全局限流。因此我们需要分布式限流和接入层限流来解决这个问题。分布式模式基于 redis + lua 脚本的 分布式限流 分布式限流 最关键的是要将限流服务做成原子化,而解决方案可以使用 redis + lua 或者 nginx + lua 技术进行实现,通过这两种技术可以实现的 高并发 和 高性能 。首先我们来使用 redis + lua 实现时间窗内某个接口的请求数限流,实现了该功能后可以改造为限流总并发/请求数和限制总资源数。lua 本身就是一种编程语言,也可以使用它实现复杂的令牌桶或漏桶算法。 因操作是在一个 lua 脚本中(相当于原子操作),又因 redis 是单线程模型,因此是线程安全的。相比 redis 事务来说,lua 脚本有以下优点减少网络开销 :不使用 lua 的代码需要向 redis 发送多次请求,而脚本只需一次即可,减少网络传输;原子操作 :redis 将整个脚本作为一个原子执行,无需担心并发,也就无需事务;复用 :脚本会永久保存 redis 中,其他客户端可继续使用。创建注解 RedisLimitpackage com.example.demo.common.annotation; import com.example.demo.common.enums.LimitType; import java.lang.annotation.*; @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface RedisLimit { // 资源名称 String name() default ""; // 资源key String key() default ""; // 前缀 String prefix() default ""; // 时间 int period(); // 最多访问次数 int count(); // 类型 LimitType limitType() default LimitType.CUSTOMER; // 提示信息 String msg() default "系统繁忙,请稍后再试"; }注解 AOP 实现package com.example.demo.common.aspect; import com.example.demo.common.annotation.RedisLimit; import com.example.demo.common.enums.LimitType; import com.example.demo.common.exception.LimitException; import com.google.common.collect.ImmutableList; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; import java.util.Objects; @Slf4j @Aspect @Configuration public class RedisLimitAspect { private final RedisTemplate<String, Object> redisTemplate; public RedisLimitAspect(RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = redisTemplate; } @Around("@annotation(com.example.demo.common.annotation.RedisLimit)") public Object around(ProceedingJoinPoint pjp){ MethodSignature methodSignature = (MethodSignature)pjp.getSignature(); Method method = methodSignature.getMethod(); RedisLimit annotation = method.getAnnotation(RedisLimit.class); LimitType limitType = annotation.limitType(); String name = annotation.name(); String key; int period = annotation.period(); int count = annotation.count(); switch (limitType){ case IP: key = getIpAddress(); break; case CUSTOMER: key = annotation.key(); break; default: key = StringUtils.upperCase(method.getName()); } ImmutableList<String> keys = ImmutableList.of(StringUtils.join(annotation.prefix(), key)); try { String luaScript = buildLuaScript(); DefaultRedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class); Number number = redisTemplate.execute(redisScript, keys, count, period); log.info("Access try count is {} for name = {} and key = {}", number, name, key); if(number != null && number.intValue() == 1){ return pjp.proceed(); } throw new LimitException(annotation.msg()); }catch (Throwable e){ if(e instanceof LimitException){ log.debug("令牌桶={},获取令牌失败",key); throw new LimitException(e.getLocalizedMessage()); } e.printStackTrace(); throw new RuntimeException("服务器异常"); } } public String buildLuaScript(){ return "redis.replicate_commands(); local listLen,time" + "\nlistLen = redis.call('LLEN', KEYS[1])" + // 不超过最大值,则直接写入时间 "\nif listLen and tonumber(listLen) < tonumber(ARGV[1]) then" + "\nlocal a = redis.call('TIME');" + "\nredis.call('LPUSH', KEYS[1], a[1]*1000000+a[2])" + "\nelse" + // 取出现存的最早的那个时间,和当前时间比较,看是小于时间间隔 "\ntime = redis.call('LINDEX', KEYS[1], -1)" + "\nlocal a = redis.call('TIME');" + "\nif a[1]*1000000+a[2] - time < tonumber(ARGV[2])*1000000 then" + // 访问频率超过了限制,返回0表示失败 "\nreturn 0;" + "\nelse" + "\nredis.call('LPUSH', KEYS[1], a[1]*1000000+a[2])" + "\nredis.call('LTRIM', KEYS[1], 0, tonumber(ARGV[1])-1)" + "\nend" + "\nend" + "\nreturn 1;"; } public String getIpAddress(){ HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); String ip = request.getHeader("x-forwarded-for"); if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){ ip = request.getHeader("Proxy-Client-IP"); } if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){ ip = request.getHeader("WL-Client-IP"); } if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){ ip = request.getRemoteAddr(); } return ip; } }注解使用count 代表请求总数量period 代表限制时间即 period 时间内,只允许有 count 个请求总数量访问,超过的将被限制不能访问package com.example.demo.module.test; import com.example.demo.common.annotation.Limit; import com.example.demo.common.annotation.RedisLimit; import com.example.demo.common.dto.R; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; @Slf4j @RestController public class TestController { @RedisLimit(key = "cachingTest", count = 2, period = 2, msg = "当前排队人数较多,请稍后再试!") // @Limit(key = "cachingTest", permitsPerSecond = 1, timeout = 500, msg = "当前排队人数较多,请稍后再试!") @GetMapping("cachingTest") public R cachingTest(){ log.info("------读取本地------"); List<String> list = new ArrayList<>(); list.add("蜡笔小新"); list.add("哆啦A梦"); list.add("四驱兄弟"); return R.ok(list); } }测试启动项目,快读刷新访问 /cachingTest 请求可以看到访问已经有被成功限制这只是其中一种实现方式,尚有许多实现方案,经供参考。
2023年07月03日
17 阅读
0 评论
0 点赞
2023-07-03
SpringBoot 实现 PDF 添加水印有哪些方案?
简介PDF(Portable Document Format,便携式文档格式) 是一种流行的文件格式,它可以在多个操作系统和应用程序中进行查看和打印。在某些情况下,我们需要对 PDF 文件添加水印,以使其更具有辨识度或者保护其版权。本文将介绍如何使用 Spring Boot 来实现 PDF 添加水印的方式。方式一:使用 Apache PDFBox 库PDFBox 是一个流行的、免费的、用 Java 编写的库,它可以用来创建、修改和提取 PDF内容。 PDFBox 提供了许多 API,包括添加文本水印的功能。{mtitle title="添加 PDFBox "/}首先,在 pom.xml 文件中添加 PDFBox 的依赖:<dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</artifactId> <version>2.0.24</version> </dependency>{mtitle title="添加水印"/}在添加水印之前,需要读取 原始PDF 文件:PDDocument document = PDDocument.load(new File("original.pdf"));然后,遍历 PDF 中的所有页面,并使用 PDPageContentStream 添加水印:// 遍历 PDF 中的所有页面 for (int i = 0; i < document.getNumberOfPages(); i++) { PDPage page = document.getPage(i); PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true, true); // 设置字体和字号 contentStream.setFont(PDType1Font.HELVETICA_BOLD, 36); // 设置透明度 contentStream.setNonStrokingColor(200, 200, 200); // 添加文本水印 contentStream.beginText(); contentStream.newLineAtOffset(100, 100); // 设置水印位置 contentStream.showText("Watermark"); // 设置水印内容 contentStream.endText(); contentStream.close(); }最后,需要保存修改后的 PDF 文件:document.save(new File("output.pdf")); document.close();{mtitle title="完整代码"/}下面是使用 PDFBox 来实现 PDF 添加水印的完整代码:import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.font.PDType1Font; import java.io.File; import java.io.IOException; public class PdfBoxWatermark { public static void main(String[] args) throws IOException { // 读取原始 PDF 文件 PDDocument document = PDDocument.load(new File("original.pdf")); // 遍历 PDF 中的所有页面 for (int i = 0; i < document.getNumberOfPages(); i++) { PDPage page = document.getPage(i); PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true, true); // 设置字体和字号 contentStream.setFont(PDType1Font.HELVETICA_BOLD, 36); // 设置透明度 contentStream.setNonStrokingColor(200, 200, 200); // 添加文本水印 contentStream.beginText(); contentStream.newLineAtOffset(100, 100); // 设置水印位置 contentStream.showText("Watermark"); // 设置水印内容 contentStream.endText(); contentStream.close(); } // 保存修改后的 PDF 文件 document.save(new File("output.pdf")); document.close(); } }方式二:使用 iText 库iText 是一款流行的 Java PDF 库,它可以用来创建、读取、修改和提取 PDF 内容。iText提供了许多API,包括添加文本水印的功能。{mtitle title="添加iText依赖"/}在 pom.xml 文件中添加 iText 的依赖:<dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.5.13</version> </dependency>{mtitle title="添加水印"/}在添加水印之前,需要读取原始 PDF 文件:PdfReader reader = new PdfReader("original.pdf"); PdfStamper stamper = new PdfStamper(reader, new FileOutputStream("output.pdf"));然后,遍历 PDF 中的所有页面,并使用 PdfContentByte 添加水印:// 获取 PDF 中的页数 int pageCount = reader.getNumberOfPages(); // 添加水印 for (int i = 1; i <= pageCount; i++) { PdfContentByte contentByte = stamper.getUnderContent(i); // 或者 getOverContent() contentByte.beginText(); contentByte.setFontAndSize(BaseFont.createFont(), 36f); contentByte.setColorFill(BaseColor.LIGHT_GRAY); contentByte.showTextAligned(Element.ALIGN_CENTER, "Watermark", 300, 400, 45); contentByte.endText(); }最后,需要保存修改后的 PDF 文件并关闭文件流:stamper.close(); reader.close();{mtitle title="完整代码"/}下面是使用 iText 来实现 PDF 添加水印的完整代码:import com.itextpdf.text.*; import com.itextpdf.text.pdf.*; import java.io.FileOutputStream; import java.io.IOException; public class ItextWatermark { public static void main(String[] args) throws IOException, DocumentException { // 读取原始 PDF 文件 PdfReader reader = new PdfReader("original.pdf"); PdfStamper stamper = new PdfStamper(reader, new FileOutputStream("output.pdf")); // 获取 PDF 中的页数 int pageCount = reader.getNumberOfPages(); // 添加水印 for (int i = 1; i <= pageCount; i++) { PdfContentByte contentByte = stamper.getUnderContent(i); // 或者 getOverContent() contentByte.beginText(); contentByte.setFontAndSize(BaseFont.createFont(), 36f); contentByte.setColorFill(BaseColor.LIGHT_GRAY); contentByte.showTextAligned(Element.ALIGN_CENTER, "Watermark", 300, 400, 45); contentByte.endText(); } // 保存修改后的 PDF 文件并关闭文件流 stamper.close(); reader.close(); } }方式三:用 Ghostscript 命令行Ghostscript 是一款流行的、免费的、开源的 PDF 处理程序,它可以用来创建、读取、修改和提取 PDF 内容。 Ghostscript 中提供了命令行参数来添加水印。{mtitle title="Ghostscrip"/}首先需要在本地安装 Ghostscript 程序。可通过以下链接下载安装包:{mtitle title="添加水印"/}可以在终端中使用 Ghostscript 的命令行工具执行以下命令来实现:gs -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=output.pdf -c "newpath /Helvetica-Bold findfont 36 scalefont setfont 0.5 setgray 200 200 moveto (Watermark) show showpage" original.pdf上述命令中,-sDEVICE=pdfwrite 表示输出为 PDF 文件;-sOutputFile=output.pdf 表示输出文件名为 output.pdf ;最后一个参数 original.pdf 则表示原始 PDF 文件的路径;中间的字符串则表示添加的水印内容。{mtitle title="注意事项"/}使用 Ghostscript 命令行添加水印时,会直接修改原始 PDF 文件,因此建议先备份原始文件。方式四:Free Spire.PDF for Java下面介绍一下使用 Free Spire.PDF for Java 实现 PDF 添加水印的方式。Free Spire.PDF for Java 是一款免费的 Java PDF 库,它提供了一个简单易用的 API,用于创建、读取、修改和提取 PDF 内容。Free Spire.PDF for Java 也支持添加 文本水印 以及 图片水印。{mtitle title="添加 Free Spire.PDF for Java 依赖"/}首先,在 pom.xml 文件中添加 Free Spire.PDF for Java 的依赖:<dependency> <groupId>e-iceblue</groupId> <artifactId>free-spire-pdf-for-java</artifactId> <version>1.9.6</version> </dependency>{mtitle title="添加文本水印"/}在 添加水印 之前,需要读取原始 PDF 文件:PdfDocument pdf = new PdfDocument(); pdf.loadFromFile("original.pdf");然后,遍历 PDF 中的所有页面,并使用 PdfPageBase 添加水印:// 遍历 PDF 中的所有页面 for (int i = 0; i < pdf.getPages().getCount(); i++) { PdfPageBase page = pdf.getPages().get(i); // 添加文本水印 PdfWatermark watermark = new PdfWatermark("Watermark"); watermark.setFont(new PdfFont(PdfFontFamily.Helvetica, 36)); watermark.setOpacity(0.5f); page.getWatermarks().add(watermark); }最后,需要保存修改后的 PDF 文件:pdf.saveToFile("output.pdf"); pdf.close();{mtitle title="添加图片水印"/}添加 图片水印 与 添加文本水印 类似,只需要将 PdfWatermark 的参数修改为图片路径即可。// 添加图片水印 PdfWatermark watermark = new PdfWatermark("watermark.png"); watermark.setOpacity(0.5f); page.getWatermarks().add(watermark);{mtitle title="完整代码"/}下面是使用 Free Spire.PDF for Java 来实现 PDF 添加水印的完整代码:import com.spire.pdf.*; public class FreeSpirePdfWatermark { public static void main(String[] args) { // 读取原始 PDF 文件 PdfDocument pdf = new PdfDocument(); pdf.loadFromFile("original.pdf"); // 遍历 PDF 中的所有页面 for (int i = 0; i < pdf.getPages().getCount(); i++) { PdfPageBase page = pdf.getPages().get(i); // 添加文本水印 PdfWatermark watermark = new PdfWatermark("Watermark"); watermark.setFont(new PdfFont(PdfFontFamily.Helvetica, 36)); watermark.setOpacity(0.5f); page.getWatermarks().add(watermark); // 添加图片水印 // PdfWatermark watermark = new PdfWatermark("watermark.png"); // watermark.setOpacity(0.5f); // page.getWatermarks().add(watermark); } // 保存修改后的 PDF 文件 pdf.saveToFile("output.pdf"); pdf.close(); } }方式五:Aspose.PDF for JavaAspose.PDF for Java 是一个强大的 PDF 处理库,提供了添加水印的功能。结合 Spring Boot 使用 Aspose.PDF for Java 库添加 PDF 水印的方式如下:首先,在 pom.xml 文件中添加 Aspose.PDF for Java 的依赖:<dependency> <groupId>com.aspose</groupId> <artifactId>aspose-pdf</artifactId> <version>21.4</version> </dependency>在 Spring Boot 应用程序中调用 Aspose.PDF for Java 的 API 设置 PDF 水印。{mtitle title="添加文本水印"/}@PostMapping("/addTextWatermark") public ResponseEntity<byte[]> addTextWatermark(@RequestParam("file") MultipartFile file) throws IOException { // 加载 PDF 文件 Document pdfDocument = new Document(file.getInputStream()); TextStamp textStamp = new TextStamp("Watermark"); textStamp.setWordWrap(true); textStamp.setVerticalAlignment(VerticalAlignment.Center); textStamp.setHorizontalAlignment(HorizontalAlignment.Center); pdfDocument.getPages().get_Item(1).addStamp(textStamp); // 保存 PDF 文件 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); pdfDocument.save(outputStream); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"watermarked.pdf\"") .contentType(MediaType.APPLICATION_PDF) .body(outputStream.toByteArray()); }{mtitle title="添加图片水印"/}@PostMapping("/addImageWatermark") public ResponseEntity<byte[]> addImageWatermark(@RequestParam("file") MultipartFile file) throws IOException { // 加载 PDF 文件 Document pdfDocument = new Document(file.getInputStream()); ImageStamp imageStamp = new ImageStamp("watermark.png"); imageStamp.setWidth(100); imageStamp.setHeight(100); imageStamp.setVerticalAlignment(VerticalAlignment.Center); imageStamp.setHorizontalAlignment(HorizontalAlignment.Center); pdfDocument.getPages().get_Item(1).addStamp(imageStamp); // 保存 PDF 文件 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); pdfDocument.save(outputStream); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"watermarked.pdf\"") .contentType(MediaType.APPLICATION_PDF) .body(outputStream.toByteArray()); }注意,以上代码中的文件名、宽度、高度等参数需要根据实际情况进行调整。{mtitle title="完整代码"/}完整的 Spring Boot 控制器类代码如下:import com.aspose.pdf.*; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.ByteArrayOutputStream; import java.io.IOException; @RestController @RequestMapping("/api/pdf") public class PdfController { @PostMapping("/addTextWatermark") public ResponseEntity<byte[]> addTextWatermark(@RequestParam("file") MultipartFile file) throws IOException { // 加载 PDF 文件 Document pdfDocument = new Document(file.getInputStream()); TextStamp textStamp = new TextStamp("Watermark"); textStamp.setWordWrap(true); textStamp.setVerticalAlignment(VerticalAlignment.Center); textStamp.setHorizontalAlignment(HorizontalAlignment.Center); pdfDocument.getPages().get_Item(1).addStamp(textStamp); // 保存 PDF 文件 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); pdfDocument.save(outputStream); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"watermarked.pdf\"") .contentType(MediaType.APPLICATION_PDF) .body(outputStream.toByteArray()); } @PostMapping("/addImageWatermark") public ResponseEntity<byte[]> addImageWatermark(@RequestParam("file") MultipartFile file) throws IOException { // 加载 PDF 文件 Document pdfDocument = new Document(file.getInputStream()); ImageStamp imageStamp = new ImageStamp("watermark.png"); imageStamp.setWidth(100); imageStamp.setHeight(100); imageStamp.setVerticalAlignment(VerticalAlignment.Center); imageStamp.setHorizontalAlignment(HorizontalAlignment.Center); pdfDocument.getPages().get_Item(1).addStamp(imageStamp); // 保存 PDF 文件 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); pdfDocument.save(outputStream); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"watermarked.pdf\"") .contentType(MediaType.APPLICATION_PDF) .body(outputStream.toByteArray()); } }这里使用了两个 RESTful API :/addTextWatermark 和 /addImageWatermark ,分别用于添加 文本水印 和 图片水印 。在请求中通过 file 参数传递 PDF 文件。下面介绍如何使用 Postman 来测试 Spring Boot 应用程序的 API。下载并安装 Postman 。打开 Postman ,选择 POST 请求方法。在 URL 地址栏中输入 http://localhost:8080/api/pdf/addTextWatermark 。在 Headers 标签页中设置 Content-Type 为 multipart/form-data 。在 Body 标签页中选择 form-data 类型,然后设置 key 为 file ,value 选择本地的 PDF 文件。点击 Send 按钮发送请求,等待应答结果。处理结果将会在响应的 Body 中返回,也可以选择浏览器下载或保存到本地磁盘。以上就是使用 Aspose.PDF for Java 库结合 Spring Boot 添加 PDF 水印的方式。结论本文介绍了几种使用 Spring Boot 实现 PDF 添加水印的方式,分别是使用 Apache PDFBox 库、 iText 库以及 Ghostscript 命令行等。选择哪种方式,可以根据项目需求和个人偏好来决定。无论采用哪种方式,都需要注意保护原始 PDF 文件,不要在不必要的情况下直接修改原始文件。欢迎点赞收藏,在你老板安排你干这时,希望你能够及时找到相关的Java工具库,实现这项功能。
2023年07月03日
36 阅读
0 评论
0 点赞
2023-07-03
一款开源的匿名聊天工具,太牛了
今天推荐的这个项目是 「anonymous-chat-room 」 ,一个基于 livekit 和 Next.js 的匿名聊天室,可以进行文字、语音聊天,并支持语音录屏。特点部署简单:前端支持直接部署到 vercel ,后端可以直接使用 livekit cloud 的免费服务,也可以按照官方文档自建支持视频,语音聊天(默认只允许语音),无需登录支持文本聊天,也可以使用 emoji支持emoji表情搜索支持更多的消息类型,如图片、视频(开发中)支持浏览器直接录制麦克风,扬声器和屏幕(Chrome、 Edge 可以完全支持,safari 不支持扬声器录制)延迟测试(当前版本实现很简陋)设置房间密码前端可以选择使用多个 apikey ,通过轮询的方式选择可用的入口本地部署1、克隆或下载本仓库git clone git@github.com:velor2012/anonymous-chat-room.git cd anonymous-chat-room yarn install2、在 http://cloud.livekit.io 上创建一个新的 Project ,然后生成 apikey project settings 3、按照提示,修改 env.example 中的环境变量,重命名为 env.local 4、运行以下命令npm run dev之后就可以在 http://localhost:3000 打开。在线体验地址: https://chat.cwy666.eu.org/开源项目地址: https://github.com/velor2012/anonymous-chat-room
2023年07月03日
83 阅读
0 评论
0 点赞
2023-06-29
群晖 DMS7.0 Video Station 支持 DTS 和 eac3 解决方案
Video Station 一直是我必装并且使用频繁的套件。但一直有个问题比较困扰,如果下载的是高清视频,经常会提示不支持当前所选音频的文件格式,因此无法播放视频。请尝试其它音轨。 具体原因我不过多赘述,在网上找了一些方案基本都是搬运,所以我翻阅了很多资料,经过尝试之后成功解决。在这里把过程记录下来,各位如果有相同问题的可以用来参考。下面开始操作。首先安装社区版FFMPEG社区地址:http://packages.synocommunity.com套件中心 > 社群 > ffmpeg开启SSH可设置自定义端口或使用默认端口使用终端连接群晖依次输入以下命令:# 切换到root权限,回车后输入管理员密码即可 sudo -i # 保存 Video Station 使用的 ffmpeg mv -n /var/packages/VideoStation/target/bin/ffmpeg /var/packages/VideoStation/target/bin/ffmpeg.orig # 注入脚本 wget -O - https://gist.githubusercontent.com/BenjaminPoncet/bbef9edc1d0800528813e75c1669e57e/raw/ffmpeg-wrapper > /var/packages/VideoStation/target/bin/ffmpeg # 更改脚本的所有权和模式 chown root:VideoStation /var/packages/VideoStation/target/bin/ffmpeg chmod 750 /var/packages/VideoStation/target/bin/ffmpeg chmod u+s /var/packages/VideoStation/target/bin/ffmpeg # 保存 Video Station 的 libsynovte.so cp -n /var/packages/VideoStation/target/lib/libsynovte.so /var/packages/VideoStation/target/lib/libsynovte.so.orig chown VideoStation:VideoStation /var/packages/VideoStation/target/lib/libsynovte.so.orig # 使libsynovte.so 支持 DTS, EAC3, TrueHD sed -i -e 's/eac3/3cae/' -e 's/dts/std/' -e 's/truehd/dheurt/' /var/packages/VideoStation/target/lib/libsynovte.so替换群晖使用的 FFmpeg命令 调用的相关文件(注意DMS6.0和DMS7.0不同):DMS6.0mv /var/packages/CodecPack/target/bin/ffmpeg33 /var/packages/CodecPack/target/bin/ffmpeg33.orig cp /var/packages/VideoStation/target/bin/ffmpeg /var/packages/CodecPack/target/bin/ffmpeg33DMS7.0mv /var/packages/CodecPack/target/bin/ffmpeg41 /var/packages/CodecPack/target/bin/ffmpeg41.orig cp /var/packages/VideoStation/target/bin/ffmpeg /var/packages/CodecPack/target/bin/ffmpeg41重启Video Station套件通过套件中心进行Video Station的重启后续的更新更新只需要执行以下命令即可:wget -O - https://gist.githubusercontent.com/BenjaminPoncet/bbef9edc1d0800528813e75c1669e57e/raw/ffmpeg-wrapper > /var/packages/VideoStation/target/bin/ffmpeg还原与卸载如果出现了问题,或者不想用了。执行以下命令即可还原:# 还原 Video Station 官方使用的 ffmpeg 与 libsynovte.so mv -f /var/packages/VideoStation/target/bin/ffmpeg.orig /var/packages/VideoStation/target/bin/ffmpeg mv -f /var/packages/VideoStation/target/lib/libsynovte.so.orig /var/packages/VideoStation/target/lib/libsynovte.so mv -f /var/packages/CodecPack/target/bin/ffmpeg41.orig /var/packages/CodecPack/target/bin/ffmpeg41当然,你也可以在套件中心卸载重装 Video Station 套件,来达到还原的效果。操作完之后,一切正常,Synology Photos也都正常有缩略图和播放。又能愉快的通过Video Station套件看片了。
2023年06月29日
43 阅读
0 评论
0 点赞
2023-06-29
520表白html页 实现3D动态相册
前言:明天就是5月20号了赶紧做好给你女朋友给你喜欢的人表白3D动态相册,换成女朋友照片,顺畅丝滑,单身狗的吃狗粮吧,哈哈哈赶紧行动起来吧。预览:源码:下载上传即可,可二级目录。里面的照片在 images 文件夹修改就行,记得改名。另外,手机端看不到效果哦。{cloud title="520 3D相册源码" type="lz" url="https://wwua.lanzoup.com/isK0110q7prc" password=""/}
2023年06月29日
25 阅读
0 评论
0 点赞
2023-06-29
拿来就能用的网页炫酷特效代码
前言:网上收集能美化网页的代码,比如给网页加个背景,给鼠标加个特效,来来回回也收集到了一些“使用简单”,“效果爆炸” 的页面,给大家分享出来。{collapse}{collapse-item label="鼠标点击弹出爱心"}<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 网页鼠标点击特效(爱心) --> <script type="text/javascript"> ! function (e, t, a) { function r() { for (var e = 0; e < s.length; e++) s[e].alpha <= 0 ? (t.body.removeChild(s[e].el), s.splice(e, 1)) : (s[ e].y--, s[e].scale += .004, s[e].alpha -= .013, s[e].el.style.cssText = "left:" + s[e].x + "px;top:" + s[e].y + "px;opacity:" + s[e].alpha + ";transform:scale(" + s[e].scale + "," + s[e] .scale + ") rotate(45deg);background:" + s[e].color + ";z-index:99999"); requestAnimationFrame(r) } function n() { var t = "function" == typeof e.onclick && e.onclick; e.onclick = function (e) { t && t(), o(e) } } function o(e) { var a = t.createElement("div"); a.className = "heart", s.push({ el: a, x: e.clientX - 5, y: e.clientY - 5, scale: 1, alpha: 1, color: c() }), t.body.appendChild(a) } function i(e) { var a = t.createElement("style"); a.type = "text/css"; try { a.appendChild(t.createTextNode(e)) } catch (t) { a.styleSheet.cssText = e } t.getElementsByTagName("head")[0].appendChild(a) } function c() { return "rgb(" + ~~(255 * Math.random()) + "," + ~~(255 * Math.random()) + "," + ~~(255 * Math .random()) + ")" } var s = []; e.requestAnimationFrame = e.requestAnimationFrame || e.webkitRequestAnimationFrame || e .mozRequestAnimationFrame || e.oRequestAnimationFrame || e.msRequestAnimationFrame || function (e) { setTimeout(e, 1e3 / 60) }, i( ".heart{width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);}.heart:after,.heart:before{content: '';width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 50%;-moz-border-radius: 50%;position: fixed;}.heart:after{top: -5px;}.heart:before{left: -5px;}" ), n(), r() }(window, document); </script> </body> </html>{/collapse-item}{/collapse}{dotted startColor="#ff6c6c" endColor="#1989fa"/}{collapse}{collapse-item label="鼠标点击弹出文字"}<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <script> (function () { var a_idx = 0; window.onclick = function (event) { var a = new Array("❤富强❤", "❤民主❤", "❤文明❤", "❤和谐❤", "❤自由❤", "❤平等❤", "❤公正❤", "❤法治❤", "❤爱国❤", "❤敬业❤", "❤诚信❤", "❤友善❤"); var heart = document.createElement("b"); //创建b元素 heart.onselectstart = new Function('event.returnValue=false'); //防止拖动 document.body.appendChild(heart).innerHTML = a[a_idx]; //将b元素添加到页面上 a_idx = (a_idx + 1) % a.length; heart.style.cssText = "position: fixed;left:-100%;"; //给p元素设置样式 var f = 16, // 字体大小 x = event.clientX - f / 2, // 横坐标 y = event.clientY - f, // 纵坐标 c = randomColor(), // 随机颜色 a = 1, // 透明度 s = 1.2; // 放大缩小 var timer = setInterval(function () { //添加定时器 if (a <= 0) { document.body.removeChild(heart); clearInterval(timer); } else { heart.style.cssText = "font-size:16px;cursor: default;position: fixed;color:" + c + ";left:" + x + "px;top:" + y + "px;opacity:" + a + ";transform:scale(" + s + ");"; y--; a -= 0.016; s += 0.002; } }, 15) } // 随机颜色 function randomColor() { return "rgb(" + (~~(Math.random() * 255)) + "," + (~~(Math.random() * 255)) + "," + (~~(Math .random() * 255)) + ")"; } }()); </script> </body> </html>{/collapse-item}{/collapse}{dotted startColor="#ff6c6c" endColor="#1989fa"/}{collapse}{collapse-item label="鼠标小星星拖尾跟随"}<!DOCTYPE html> <html lang="en"> <head> </head> <body> <span class="js-cursor-container"></span> <script> (function fairyDustCursor() { var possibleColors = ["#D61C59", "#E7D84B", "#1B8798"] var width = window.innerWidth; var height = window.innerHeight; var cursor = { x: width / 2, y: width / 2 }; var particles = []; function init() { bindEvents(); loop(); } // Bind events that are needed function bindEvents() { document.addEventListener('mousemove', onMouseMove); window.addEventListener('resize', onWindowResize); } function onWindowResize(e) { width = window.innerWidth; height = window.innerHeight; } function onMouseMove(e) { cursor.x = e.clientX; cursor.y = e.clientY; addParticle(cursor.x, cursor.y, possibleColors[Math.floor(Math.random() * possibleColors.length)]); } function addParticle(x, y, color) { var particle = new Particle(); particle.init(x, y, color); particles.push(particle); } function updateParticles() { // Updated for (var i = 0; i < particles.length; i++) { particles[i].update(); } // Remove dead particles for (var i = particles.length - 1; i >= 0; i--) { if (particles[i].lifeSpan < 0) { particles[i].die(); particles.splice(i, 1); } } } function loop() { requestAnimationFrame(loop); updateParticles(); } /** * Particles */ function Particle() { this.character = "*"; this.lifeSpan = 120; //ms this.initialStyles = { "position": "fixed", "display": "inline-block", "top": "0px", "left": "0px", "pointerEvents": "none", "touch-action": "none", "z-index": "10000000", "fontSize": "25px", "will-change": "transform" }; // Init, and set properties this.init = function (x, y, color) { this.velocity = { x: (Math.random() < 0.5 ? -1 : 1) * (Math.random() / 2), y: 1 }; this.position = { x: x + 10, y: y + 10 }; this.initialStyles.color = color; this.element = document.createElement('span'); this.element.innerHTML = this.character; applyProperties(this.element, this.initialStyles); this.update(); document.querySelector('.js-cursor-container').appendChild(this.element); }; this.update = function () { this.position.x += this.velocity.x; this.position.y += this.velocity.y; this.lifeSpan--; this.element.style.transform = "translate3d(" + this.position.x + "px," + this.position.y + "px, 0) scale(" + (this.lifeSpan / 120) + ")"; } this.die = function () { this.element.parentNode.removeChild(this.element); } } /** * Utils */ // Applies css `properties` to an element. function applyProperties(target, properties) { for (var key in properties) { target.style[key] = properties[key]; } } if (!('ontouchstart' in window || navigator.msMaxTouchPoints)) init(); })(); </script> </body> </html>{/collapse-item}{/collapse}{dotted startColor="#ff6c6c" endColor="#1989fa"/}{collapse}{collapse-item label="鼠标笑脸跟随+仙女棒+泡泡+雪花+点击烟花效果(自由组合)"}<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!--光标特效仙女棒--> <script src="https://blog-static.cnblogs.com/files/axqa/fairyDustCursor.js"></script> <!--光标之泡泡--> <script src="https://blog-static.cnblogs.com/files/axqa/bubbleCursor.js"></script> <!--笑脸--> <script src="https://blog-static.cnblogs.com/files/axqa/emojiCursor.js"></script> <!--雪花--> <script src="https://blog-static.cnblogs.com/files/axqa/snowflakeCursor.js"></script> <!-- 点击后出现烟花效果 --> <script src="https://blog-static.cnblogs.com/files/axqa/cursor-effects.js"></script> </body> </html>{/collapse-item}{/collapse}
2023年06月29日
30 阅读
0 评论
0 点赞
2023-06-29
SQL注入介绍看这一篇就够了
SQL注入会引发什么问题?SQL注入 是一种 对数据库的恶意攻击 ,注入进去的恶意指令就会被误认为是正常的SQL指令而执行,因此遭到破坏或是入侵。什么是SQL注入?SQL注入(英语:SQL injection) ,也称SQL注入或SQL注码,是发生于应用程序与数据库层的安全漏洞。简而言之,是在输入的字符串之中注入SQL指令,在设计不良的程序当中忽略了字符检查,那么这些注入进去的恶意指令就会被数据库服务器误认为是正常的SQL指令而执行,因此遭到破坏或是入侵。为什么会发生SQL注入?在设计不良的应用程序中,对用户输入数据的合法性并没有判断或过滤不严导致。如图中所示,没有对客户端用户输入的数据合法性进行检查或过滤,导致客户端用户可以任意构造自己想要的参数,达成SQL注入条件,最终引发严重的后果,如果在账号登录成功SQL注入,那么就可以成功登录他人的账号,使用对他人的账号进行一系列破环手段,比如黑客通过SQL注入成功登录你的微信,可以使用你微信里面的余额,给你的家人朋友发钓鱼链接等等。当然SQL注入的危害远不止可以成功登录他人的账号,还有可能造成的伤害如下资料表中的资料外泄,例如企业及个人 机密资料,账户资料,密码 等;数据结构被黑客探知,得以做进一步攻击(例如 SELECT * FROM sys.tables );数据库服务器被攻击,系统管理员账户被窜改(例如 ALTER LOGIN sa WITH PASSWORD='xxxxxx' );获取系统较高权限后,有可能得以在网页加入恶意链接、恶意代码以及 Phishing 等;经由数据库服务器提供的操作系统支持,让黑客得以修改或控制操作系统(例如 xp_cmdshell "net stop iisadmin" 可停止服务器的 IIS服务 );攻击者利用数据库提供的各种功能操纵文件系统,写入Webshell,最终导致攻击者攻陷系统;破坏硬盘资料,瘫痪全系统(例如 xp_cmdshell "FORMAT C:" );获取系统最高权限后,可针对企业内部的任一管理系统做大规模破坏,甚至让其企业倒闭;网站主页被窜改,导致声誉受到损害。总之作为程序设计者,需要保证程序的健壮性避免被 SQL注入攻击 。如何避免SQL注入?所有的查询语句都使用数据库提供的 参数化查询(Parameterized Query) 接口,参数化的语句使用参数而不是将用户输入变量嵌入到SQL语句中,当前几乎所有的数据库系统都提供了 参数化SQL语句 执行接口,使用此接口可以非常有效的防止 SQL注入攻击 ;set @name := xxx; set @pwd := xxx; select id from users where name = @name and pwd = @pwd在组合SQL字符串时,先针对所传入的参数加入其他字符,对进入数据库的特殊字符('<>&*;)等等进行转义处理;确认每种数据的类型,比如数字型的数据就必须是数字,数据库中的存储字段必须对应为int型;try: pwd = int(param.get("pwd")) except (TypeError, ValueError): return "pwd type must be int"数据长度应该严格规定,能在一定程度上防止比较长的SQL注入语句无法正确执行;name_max_length = 12 if len(param.get("name", "")) > name_max_length: return "name length cannot be greater than 12"网站每个数据层的编码统一,建议全部使用 UTF-8编码 ,上下层编码不一致有可能导致一些过滤模型被绕过;严格限制网站用户的数据库的操作权限,给此用户提供仅仅能够满足其工作的权限,从而最大限度的减少注入攻击对数据库的危害;避免网站显示SQL错误信息,比如类型错误、字段不匹配等,防止攻击者利用这些错误信息进行一些判断。案例这里分析一个案例1.数据库中先创建用户表及数据-- 创建一张用户表 CREATE TABLE `users` ( `id` INT(11) NOT NULL AUTO INCREMENT, `username` VARCHAR(20), `password` VARCHAR(50), PRIMARY KEY (`id`) ) ENGINE=INNODB DEFAULT CHARSET=utf8; -- 插入数据 INSERT INTO users(username,`password`) VALUES('张三','456123'),('李 四','qqatfv'),('王五','Qwe123'); INSERT INTO users(username,`password`) VALUES('小张','987456'),('小 王','ngjplg'),('小李','!@#$%^'); -- 查看数据 SELECT * FROM users; +----+----------+----------+ | id | username | password | +----+----------+----------+ | 1 | 张三 | 456123 | | 2 | 李四 | qqatfv | | 3 | 王五 | Qwe123 | | 4 | 小张 | 987456 | | 5 | 小王 | ngjplg | | 6 | 小李 | !@#$%^ | +----+----------+----------+ 6 rows in set (0.00 sec)2.编写一个登录程序import pymysql def login(): # 打开数据库连接 db = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='12345', db='test', charset='utf8') # 使用 cursor() 方法创建一个游标对象 cursor cursor = db.cursor() username = input('请输入用户名:') password = input('请输入密码:') sql = "select * from users where username = '%s' and password = '%s'" % (username, password) print(sql) # 执行SQL语句 cursor.execute(sql) results = cursor.fetchone() if results: print('登录成功') else: print('登录失败') # 关闭数据库连接 db.close()2.1.正常登录>>> login() 请输入用户名:>? 张三 请输入密码:>? 456123 select * from users where username = '张三' and password = '456123' 登录成功, 你好:张三2.2.登录失败>>> login() 请输入用户名:>? 张三 请输入密码:>? 123456 select * from users where username = '张三' and password = '123456' 用户名或密码错误,请重新输入2.3.模拟注入此处我们给SQL注入了一个 or '1' = '1' 的条件,此时不管密码是否正确都可以成功登录login() 请输入用户名:>? 张三 请输入密码:>? 123456' or '1' = '1' select * from users where username = '张三' and password = '123456' or '1' = '1' 登录成功, 你好:张三3.解决方法,采用参数化查询import pymysql def login(): # 打开数据库连接 db = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='12345', db='test', charset='utf8') # 使用 cursor() 方法创建一个游标对象 cursor cursor = db.cursor() username = input('请输入用户名:') password = input('请输入密码:') sql = "select * from users where username = %s and password = %s" # 执行SQL语句 cursor.execute(sql, (username, password)) results = cursor.fetchone() if results: print('登录成功, 你好:', username) else: print('用户名或密码错误,请重新输入') # 关闭数据库连接 db.close()3.1.正常登录>>> login() 请输入用户名:>? 张三 请输入密码:>? 456123 登录成功, 你好:张三3.2.登录失败>>> login() 请输入用户名:>? 张三 请输入密码:>? 123456 用户名或密码错误,请重新输入3.3.继续模拟注入此处我们给SQL注入了一个 or '1' = '1' 的条件,此时我们使用的是 参数化查询 方式有效的防止了SQL注入login() 请输入用户名:>? 张三 请输入密码:>? 123456' or '1' = '1' 用户名或密码错误,请重新输入
2023年06月29日
23 阅读
0 评论
0 点赞
1
...
12
13
14
...
27