Java 环境下用 PDFBox 实现 PDF 文档转换 JPG 图片的功能

PDFBox 是一个用于处理 PDF 文档的开源 Java 工具库。该项目允许创建新的 PDF 文档、操作现有文档以及从文档中提取内容。
我们下面要通过 PDFBox 来演示如何将一个 PDF 文件转换成一个 JPG 文件。代码如下:
package com.sunbloger.pdf2jpg.controller;

import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import javax.imageio.ImageIO;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping(value = "/japi")
public class render {

    @RequestMapping(value = "/convert", method = RequestMethod.GET)
    public @ResponseBody Map<String, Object> convert () throws Exception
    {
        Map<String, Object> responseMap = new HashMap<String, Object>();
        
        PDDocument doc = null;
        InputStream stream = null;
        FileOutputStream fos = null;
        ByteArrayOutputStream baos = null;
        
        try {

            // 读入 PDF 文件
            stream = new FileInputStream("D:\\workspace-springboot\\1.pdf");
            doc = PDDocument.load(stream);

            // 渲染
            PDFRenderer pdfRenderer = new PDFRenderer(doc);
            BufferedImage bufferedImage = pdfRenderer.renderImageWithDPI(0, 300);
            pdfRenderer = null;

            // 写入 JPG 文件
            baos = new ByteArrayOutputStream();
            ImageIO.write(bufferedImage, "jpg", baos);
            byte[] dataList = baos.toByteArray();
            baos = null;
            File file = new File("D:\\workspace-springboot\\1.jpg");
            fos = new FileOutputStream(file);
            fos.write(dataList);
            fos.close();
            fos = null;
            file = null;
            responseMap.put("suc", 1);
            responseMap.put("info", "convert sucess.");
        } catch (Exception e) {
            throw e;
        }
        
        return responseMap;
    }
}
上述代码用 SpringBoot 构建了一个 PDF 转 JPG 的接口,为了方便演示,PDF 和 JPG 文件地址均采用了本地路径,开发者可以自行调整代码,例如改为通过 POST 请求传入 PDF 文件。
pom.xml文件中添加 PDFBox 信息(推荐使用目前 2.0 最新的 2.0.27 版本):
<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0.27</version>
</dependency>

 

详解用 MiniFramework 框架实现对 GET 或 POST 请求参数进行签名校验的方法

在一些特殊场景下,我们可能希望对于 GET 或 POST 进入到接口的数据进行签名和有效期的校验,例如 APP 请求后端接口的场景,我们通常需要考虑两个问题:

问题1:如何避免攻击者在捕获到接口请求后,自行构造请求参数,向接口发送请求,而不通过 APP 的正常界面进行操作。

问题2:在接口请求不可避免能被捕获的情况下,如何确保每一次请求能够过期,不被反复的利用,例如投票刷票的问题。

基于上面两个问题,我们在设计接口时,就需要通过给请求参数进行签名的方式来对数据来源和有效期进行校验。下面将以 MiniFramework 框架为例,演示如何通过 MiniFramework 框架来实现对请求参数进行签名和签名校验的方法。

首先,我们创建一个名为 Index 的控制器,并在控制器中创建名为 sign 和 verifysign 两个动作方法,分别用于生成签名,和校验签名,具体代码如下:

<?php
// 声明控制器命名空间
namespace App\Controller;

// 加载动作类
use Mini\Base\Action;

// 加载签名类
use Mini\Security\Sign;


class Index extends Action
{
    /**
     * 生成签名
     */
    function signAction()
    {
        // 待签名的数据
        $data = [

            // 假设我们要通过GET方式传递参数info=MiniFramework
            'info' => 'MiniFramework',

            // signTime为当前时间戳,且必须随数据一起进行签名
            'signTime' => time()
        ];

        // 实例化签名类
        $signObj = new Sign();

        // 指定用 sha1 来进行加密(默认为:md5)
        $signObj->setEncryptType('sha1');

        // 获得一个签名
        $sign = $signObj->sign($data);

        // 签名随其他数据一起通过GET传递
        $data['sign'] = $sign;
        dump($data);

        // 构造一个GET请求URL
        $dataStr = arrayToUrlParams($data);
        $url = $this->view->baseUrl() . '/index/verifysign?' . $dataStr;
        echo '<a href="' . $url . '" target="_blank">点击这里跳转到签名验证页</a>';

        die();
    }

    /**
     * 验证签名
     */
    function verifysignAction()
    {
        // 实例化
        $signObj = new Sign();

        // 验证时需要使用相同的加密方式
        $signObj->setEncryptType('sha1');

        // 设定签名过期时间为30秒(默认为:300秒)
        $signObj->setExpireTime(30);

        // 获得签名校验结果(传入参数get代表对GET请求进行签名校验)
        $res = $signObj->verifySign('get');

        if ($res === true) {
            echo '签名有效';
        } else {
            echo '签名无效';
        }

        die();
    }
}

 

完成上述代码编写后,我们可以通过浏览器访问这个控制器的 sign 动作方法,生成签名并构造一个跳转链接,通过跳转链接进入 verifysign 动作方法完成对请求的签名校验。

我们从代码中可以看到签名使用的是 sha1 加密算法,生成签名和校验签名要使用相关的加密算法,否则将无法正确进行校验。

签名的有效期在 verifysign 动作方法中通过 setExpireTime() 设定为30秒,那么从签名被生成开始,有效期为30秒,过期后的签名将无法通过校验。

上述针对签名校验的特性在 MiniFramework 的 2.4.0 版本中加入,示例代码可以在项目的 Example 控制器中找到。

获取 MiniFramework 源代码请移步至码云Gitee仓库:https://gitee.com/jasonwei/miniframework

 

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=3m34f5dknekg8

MiniFramework 2.9.0 已经发布,超轻量级的 PHP 框架

MiniFramework 2.9.0 已经发布,超轻量级的 PHP 框架

此版本更新内容包括:

版本变化

  • 新增 Mini\Base\Header 类,用于处理 Request 和 Response 的 Header 信息。
  • 新增 Mini\Base\Response 类,用于响应客户端,控制请求结果的输出。
  • 新增 Mini\Base\App::setAction () 方法,用于设置动作。
  • 新增 Mini\Base\App::setController () 方法,用于设置控制器。
  • 新增 Mini\Base\Action::forward () 替代原 _forward () 方法,旧方法暂时保留,新旧两个方法功能完全一致。
  • 新增 Mini\Security\Sign 类的 setEncryptType () 方法,用于指定加密方式。
  • 改进 Controller 和 Action 的设置由 Mini\Base\App 类负责处理。
  • 改进在部分核心类库中用 Mini\Base\Response 替代 Mini\Base\Http 以规范响应输出。
  • 改进并优化框架异常报错的特性。
  • 修复 Action 中使用 $this->_forward () 跳转相同的 Action 时出现死循环的 Bug。
  • 修复 Mini\Base\Http 在被继承的场景中可能出现的实例获取 Bug。
  • 修复全局函数 isTimestamp () 校验时间戳的 Bug。

升级说明

  • 兼容 PHP 最低版本为 7.2.0,PHP 8.0.0 已测试可正常运行。
  • 当前版本向前兼容至 2.4.0 版本,使用 2.4.0 及后续版本的开发者可直接升级至 2.9.0 版本。
  • 文档已同步更新,地址:http://www.miniframework.com/docv2/guide/

详情查看:https://gitee.com/jasonwei/miniframework/releases/2.9.0

转载:Redis 的 KEYS 命令引起 RDS 数据库雪崩

最近的互联网线上事故发生比较频繁,9月19日网上爆料出顺丰近期发生了一起线上删库事件,在这里就不介绍了。

在这里讲述一下最近发生在我公司的事故,以及如何避免,并且如何处理优化。 该宕机的直接原因是使用 Redis 的 keys * 命令引起的,一共造成了某个服务化项目的两次宕机。

间接原因还有很多,技术跟不上业务的发展,由每日百万量到千万级是一个大的跨进,公司对于系统优化的处理优先级不高,技术开发人手的短缺。
第一次宕机

2018年9月13日的某个点,公司某服务化项目的 RDS 实例连接飙升,CPU 升到 100%,拒绝了其他应用的所有请求服务。

整个过程如下:

阅读更多

详解用 MiniFramework 计算程序运行时间的方法

我们在项目调优过程中,通常会对代码的运行时间进行统计,以便了解程序运行的性能和效率,这些统计结果将作为代码优化时的重要指标,帮助开发者有针对性的进行调优工作。

MiniFramework 在 1.3.0 版本中,新增了 Debug 类,其中包含有时间统计功能的若干方法,可以非常便捷地帮助开发者实现上述统计需求,下面我们来通过示例代码介绍具体实现方法。

首先,假设我们有一个名为 Index 的 Controller,并且其中包含有一个名为 index 的 Action(MiniFramework下载包中已经包含),我们将代码写在这个 Action 中,如下:

阅读更多

Golang 开发 Socket 通信时常用的 TCP 封包和解包协议

在开发 Socket 通信时,由于 TCP 协议的特性,在网络状况不佳的情况下,数据传输过程中经常会出现半包或粘包。为解决这一问题,通常我们需要自定义一个通信协议,增加一个 HEADER 部分,并在其中对数据包的长度进行声明,下面分享一段封包和解包的示例代码,可用于 Golang 开发 Socket 时处理数据传输,具体代码如下:

package protocol
 
import (
    "bytes"
    "encoding/binary"
)
 
const (
    TCP_HEADER     = "TCPHEADER"
    TCP_HEADER_LEN = 9
    TCP_DATA_LEN   = 4
)
 
// 封包
func Enpack(msg []byte) []byte {
    return append(append([]byte(TCP_HEADER), IntToBytes(len(msg))...), msg...)
}
 
// 解包
func Depack(buffer []byte, readerChannel chan []byte) []byte {
    length := len(buffer)
 
    var i int
    for i = 0; i < length; i++ {
        if length < i + TCP_HEADER_LEN + TCP_DATA_LEN {
            break
        }
        if string(buffer[i:i + TCP_HEADER_LEN]) == TCP_HEADER {
            msgLen := BytesToInt(buffer[i + TCP_HEADER_LEN : i + TCP_HEADER_LEN + TCP_DATA_LEN])
            if length < i + TCP_HEADER_LEN + TCP_DATA_LEN + msgLen {
                break
            }
            data := buffer[i + TCP_HEADER_LEN + TCP_DATA_LEN : i + TCP_HEADER_LEN + TCP_DATA_LEN + msgLen]
            readerChannel <- data
 
            i += TCP_HEADER_LEN + TCP_DATA_LEN + msgLen - 1
        }
    }
 
    if i == length {
        return make([]byte, 0)
    }
    
    return buffer[i:]
}
 
// 整形转换成字节
func IntToBytes(n int) []byte {
	x := int32(n)
    bytesBuffer := bytes.NewBuffer([]byte{})
    binary.Write(bytesBuffer, binary.BigEndian, x)
    
    return bytesBuffer.Bytes()
}
 
// 字节转换成整形
func BytesToInt(b []byte) int {
    bytesBuffer := bytes.NewBuffer(b)
    var x int32
    binary.Read(bytesBuffer, binary.BigEndian, &x)
    
    return int(x)
}

 

深入研究 PHP 的 SESSION 阻塞问题

最近在一个基于 Web 的 IM 项目中,我采用异步向服务器发起请求拉取最新的聊天内容,服务器端通过 PHP 处理拉取请求,拉取过程是用 10 次循环查询数据库是否有最新的聊天内容。如发现新内容,则立即向浏览器输出,并结束掉本次请求的进程。在这 10 次的循环中,每次查询数据库后,均通过 Sleep 函数让进程暂停 1 秒,那么这个 PHP 进程可能会在服务器端保持 10 秒。

在测试过程中,我发现当这个拉取请求运行期间,其他向服务器端 PHP 发起的请求,均受到影响,响应变的非常慢。

经过一系列的排查,问题始终得不到解决,但当把代码中涉及到 SESSION 的部分全部跳过时,情况发生了变化,所有 PHP 进程都恢复正常的响应速度了。由此,联想到问题可能出在了 SESSION 阻塞机制上了。

阅读更多