普通视图

Received before yesterday

一个解析Excel2007的POI工具类

2025年5月19日 00:00

通过apache-poi解析读取excel2007表格中的文字和图片,数字按照字符形式读取,表格中的图片和文字都按照行和列顺序读取到二维数组中相应的位置上。

package com.util;import org.apache.poi.hssf.usermodel.HSSFSheet;import org.apache.poi.hssf.usermodel.HSSFWorkbook;import org.apache.poi.ooxml.POIXMLDocumentPart;import org.apache.poi.ss.usermodel.*;import org.apache.poi.xssf.usermodel.*;import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;import java.io.ByteArrayInputStream;import java.io.IOException;import java.io.InputStream;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;public class POIUtil {    /**     * 读入excel2007文件     *     * @param file     * @throws IOException     */    public static List<String[]> readExcel(String fileName, byte[] bytes, int sheetNum) throws IOException {        // 获取excel文件的io流        InputStream is = new ByteArrayInputStream(bytes);        // 根据文件后缀名不同(xls和xlsx)获得不同的Workbook实现类对象        Workbook workbook =  new XSSFWorkbook(is);;        // 创建返回对象,把每行中的值作为一个数组,所有行作为一个集合返回        List<String[]> list = new ArrayList<String[]>();        if (workbook != null) {            // for (int sheetNum = 0; sheetNum < workbook.getNumberOfSheets(); sheetNum++) {            // 获得当前sheet工作表            Sheet sheet = workbook.getSheetAt(sheetNum);            // if (sheet == null) {            // continue;            // }            // 获得当前sheet的开始行            int firstRowNum = sheet.getFirstRowNum();            // 获得当前sheet的结束行            int lastRowNum = sheet.getLastRowNum();            // 循环除了第一行的所有行            for (int rowNum = firstRowNum + 0; rowNum <= lastRowNum; rowNum++) {                // 获得当前行                Row row = sheet.getRow(rowNum);                if (row == null || row.getPhysicalNumberOfCells()==0) {                    continue;                }                // 获得当前行的开始列                int firstCellNum = row.getFirstCellNum();                // 获得当前行的列数                int lastCellNum = row.getPhysicalNumberOfCells();                String[] cells = new String[row.getPhysicalNumberOfCells()];                // 循环当前行                for (int cellNum = firstCellNum; cellNum < lastCellNum; cellNum++) {                    Cell cell = row.getCell(cellNum);                    cells[cellNum] = getCellValue(cell);                }                list.add(cells);            }            // }            workbook.close();        }        return list;    }    private static String getCellValue(Cell cell) {        String cellValue = "";        if (cell == null) {            return cellValue;        }        // 把数字当成String来读,避免出现1读成1.0的情况        if (cell.getCellType() == CellType.NUMERIC) {            cell.setCellType(CellType.STRING);        }        // 判断数据的类型        switch (cell.getCellType()) {            case NUMERIC: // 数字                cellValue = String.valueOf(cell.getNumericCellValue());                break;            case STRING: // 字符串                cellValue = String.valueOf(cell.getStringCellValue());                break;            case BOOLEAN: // Boolean                cellValue = String.valueOf(cell.getBooleanCellValue());                break;            case FORMULA: // 公式                cellValue = String.valueOf(cell.getCellFormula());                break;            case BLANK: // 空值                cellValue = "";                break;            case ERROR: // 故障                cellValue = "非法字符";                break;            default:                cellValue = "未知类型";                break;        }        return cellValue;    }    public static Map<String, byte[]> getExcelPictures(String fileName, byte[] bytes, int sheetNum) throws IOException {        Map<String, byte[]> map = new HashMap<String, byte[]>();        // 获取excel文件的io流        InputStream is = new ByteArrayInputStream(bytes);        // 获得Workbook工作薄对象        Workbook workbook =  new XSSFWorkbook(is);;        XSSFSheet sheet = (XSSFSheet) workbook.getSheetAt(sheetNum);        List<POIXMLDocumentPart> list = sheet.getRelations();        for (POIXMLDocumentPart part : list) {            if (part instanceof XSSFDrawing) {                XSSFDrawing drawing = (XSSFDrawing) part;                List<XSSFShape> shapes = drawing.getShapes();                for (XSSFShape shape : shapes) {                    XSSFPicture picture = (XSSFPicture) shape;                    XSSFClientAnchor anchor = picture.getPreferredSize();                    CTMarker marker = anchor.getFrom();                    String key = marker.getRow() + "-" + marker.getCol();                    byte[] data = picture.getPictureData().getData();                    map.put(key, data);                }            }        }        return map;    }}

利用Python实现Hexo站点的持续集成

2024年10月15日 20:00

引言

Hexo博客编写完文章需要从头构建并重新上传生成的静态文件到服务器,不能增量更新十分不便,每次上传的文件也越来越大,于是一个比较好的解决办法是在提交文章、资源和配置后,让服务器自动去从git远程仓库拉取最新的提交到服务器上的本地git仓库中,并部署。使用Jenkins等持续集成工具比较繁琐也消耗资源,于是我选择用python编写一个基于flask的webhook脚本来实现。

具体实现流程是:文章提交到git代码托管平台的远程仓库后,远程仓库发送HTTP回调请求给服务器上的webhook脚本,脚本程序根据回调请求的参数,先调用git拉取远程仓库最新提交内容,再去调用npm构建和部署最新版hexo博客到nginx下。

这里的git代码托管平台我选择Gitee:https://gitee.com/

1. 安装pip和python

首先,确保你已经安装了python。如果没有安装,可以使用以下命令来安装python和pip:

1.1 检查python版本

python3 --version

如果你已经安装了python 3.x版本,可以跳过安装python的步骤。否则,继续安装:

1.2 安装python3

sudo yum install python3 -y  # 适用于 CentOS 或其他 RHEL 系统

1.3 安装pip

安装pip的方法:

sudo yum install python3-pip -y  # CentOS/RHEL 系统

安装完成后,确认pip是否已经成功安装:

pip3 --version

2. 使用pip安装依赖

一旦 pip 安装好,你可以使用以下命令来安装需要的库:

pip3 install flask gitpython

3.编写脚本

vim webhook.py
import osimport subprocessfrom flask import Flask, request, jsonifyimport gitapp = Flask(__name__)# 配置你的本地仓库路径和构建命令REPO_PATH = "/path/to/your/hexo/blog"PUBLIC_PATH = os.path.join(REPO_PATH, 'public')# 拉取代码的函数def pull_code():    try:        repo = git.Repo(REPO_PATH)        origin = repo.remotes.origin        origin.pull()        return True    except Exception as e:        print(f"Failed to pull code: {e}")        return False# 构建 Hexo 站点的函数def build_hexo():    try:        # 执行 Hexo 命令        subprocess.run(["npm", "run", "build"], cwd=REPO_PATH, check=True)                return True    except subprocess.CalledProcessError as e:        print(f"Failed to build Hexo: {e}")        return False@app.route("/webhook", methods=["POST"])def webhook():    # 验证请求是否来自 Gitee    if request.headers.get("X-Gitee-Token") != "": #这里改成你设置的密码        return jsonify({"message": "Unauthorized"}), 401    # 获取事件类型,确保是 push 事件    event = request.headers.get("X-Gitee-Event")    if event != "Push Hook":        return jsonify({"message": "Not a push event"}), 400    # 拉取代码并构建    if pull_code() and build_hexo():        return jsonify({"message": "Hexo build success"}), 200    else:        return jsonify({"message": "Failed to pull or build"}), 500if __name__ == "__main__":    app.run(host="0.0.0.0", port=5000)

代码优化,加入线程控制,防止webhook链接被并发调用后,两个hook任务线程同时执行出现安全问题。

import osimport subprocessfrom flask import Flask, request, jsonifyimport gitimport threadingapp = Flask(__name__)# 配置你的本地仓库路径和构建命令REPO_PATH = "/blog"PUBLIC_PATH = os.path.join(REPO_PATH, 'public')lock = threading.Lock()is_building = False  # 标志位,用于指示是否有任务正在进行# 拉取代码的函数def pull_code():    try:        repo = git.Repo(REPO_PATH)        origin = repo.remotes.origin        origin.pull()        return True    except Exception as e:        print(f"Failed to pull code: {e}")        return False# 构建 Hexo 站点的函数def build_hexo():    try:        # 执行 Hexo 的清理和生成命令        subprocess.run(["npm", "run", "build"], cwd=REPO_PATH, check=True)        #subprocess.run(["hexo", "generate"], cwd=REPO_PATH, check=True)        return True    except subprocess.CalledProcessError as e:        print(f"Failed to build Hexo: {e}")        return False@app.route("/webhook", methods=["POST"])def webhook():    global is_building    # 验证请求是否来自 Gitee    if request.headers.get("X-Gitee-Token") != "":        return jsonify({"message": "Unauthorized"}), 401    # 获取事件类型,确保是 push 事件    event = request.headers.get("X-Gitee-Event")    if event != "Push Hook":        return jsonify({"message": "Not a push event"}), 400    if is_building:        return jsonify({"message": "Build in progress, try again later"}), 429    with lock:        is_building = True  # 设置标志位为 True,表示任务开始        try:            # 拉取代码并构建            if pull_code() and build_hexo():                return jsonify({"message": "Hexo build success"}), 200            else:                return jsonify({"message": "Failed to pull or build"}), 500        finally:            is_building = False  # 重置标志位,表示任务结束            if __name__ == "__main__":    app.run(host="0.0.0.0", port=5000)

代码还可以进一步优化,只对master分支提交推送进行触发构建。

import osimport subprocessfrom flask import Flask, request, jsonifyimport gitimport threadingapp = Flask(__name__)# 配置你的本地仓库路径和构建命令REPO_PATH = "/blog"PUBLIC_PATH = os.path.join(REPO_PATH, 'public')lock = threading.Lock()is_building = False  # 标志位,用于指示是否有任务正在进行# 拉取代码的函数def pull_code():    try:        repo = git.Repo(REPO_PATH)        origin = repo.remotes.origin        origin.pull()        return True    except Exception as e:        print(f"Failed to pull code: {e}")        return False# 构建 Hexo 站点的函数def build_hexo():    try:        # 执行 Hexo 的清理和生成命令        subprocess.run(["npm", "run", "build"], cwd=REPO_PATH, check=True)        #subprocess.run(["hexo", "generate"], cwd=REPO_PATH, check=True)        return True    except subprocess.CalledProcessError as e:        print(f"Failed to build Hexo: {e}")        return False@app.route("/webhook", methods=["POST"])def webhook():    global is_building    # 验证请求是否来自 Gitee    if request.headers.get("X-Gitee-Token") != "":        return jsonify({"message": "Unauthorized"}), 401    # 获取事件类型,确保是 push 事件    event = request.headers.get("X-Gitee-Event")    if event != "Push Hook":        return jsonify({"message": "Not a push event"}), 400    # 解析推送数据    payload = request.get_json()    if not payload:        return jsonify({"message": "Invalid payload"}), 400    # 检查是否是 master 分支的推送    ref = payload.get("ref")    if ref != "refs/heads/master":        return jsonify({"message": "Not a master branch push, ignored"}), 200    if is_building:        return jsonify({"message": "Build in progress, try again later"}), 429    with lock:        is_building = True  # 设置标志位为 True,表示任务开始        try:            # 拉取代码并构建            if pull_code() and build_hexo():                return jsonify({"message": "Hexo build success"}), 200            else:                return jsonify({"message": "Failed to pull or build"}), 500        finally:            is_building = False  # 重置标志位,表示任务结束            if __name__ == "__main__":    app.run(host="0.0.0.0", port=5000)

4.执行脚本

执行前,服务器还需要安装好node、npm、git并配置好环境变量

nohup python3 webhook.py &

5.配置hook到gitee

设置好签名(密码),设置回调地址,勾选两项

除此外,还要将自己gitee账户相匹配的ssh密钥设置在服务器的上用于拉取我们私有仓库的内容。

一个通用的CloseableHttpClient工厂类

2023年1月1日 00:00

一个CloseableHttpClient工厂,基于java知名开源库apache-httpclient,能够忽略SSL,并且超时和状态异常时可以重试

<dependencies>    <dependency>        <groupId>org.apache.httpcomponents</groupId>        <artifactId>httpclient</artifactId>        <version>4.5.9</version>    </dependency>    <dependency>        <groupId>org.apache.httpcomponents</groupId>        <artifactId>httpmime</artifactId>        <version>4.5.9</version>    </dependency></dependencies>
package util;import lombok.extern.slf4j.Slf4j;import org.apache.http.ConnectionClosedException;import org.apache.http.HttpResponse;import org.apache.http.NoHttpResponseException;import org.apache.http.client.CookieStore;import org.apache.http.client.ServiceUnavailableRetryStrategy;import org.apache.http.conn.ConnectTimeoutException;import org.apache.http.conn.ssl.NoopHostnameVerifier;import org.apache.http.conn.ssl.SSLConnectionSocketFactory;import org.apache.http.impl.client.BasicCookieStore;import org.apache.http.impl.client.CloseableHttpClient;import org.apache.http.impl.client.HttpClients;import org.apache.http.protocol.HttpContext;import org.apache.http.ssl.SSLContextBuilder;import javax.net.ssl.SSLContext;import javax.net.ssl.SSLHandshakeException;import java.net.SocketTimeoutException;@Slf4jpublic class HttpClientUtil {    public static CloseableHttpClient createSSLClientDefault() {        try {            SSLContextBuilder sslContextBuilder = new SSLContextBuilder();            SSLContext sslContext = sslContextBuilder.loadTrustMaterial(null, (chain, authType) -> true).build();            SSLConnectionSocketFactory ssl = new SSLConnectionSocketFactory(                    sslContext,                    new String[]{"TLSv1", "TLSv1.1", "TLSv1.2"},                    null,                    SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);            return HttpClients.custom()                    .setSSLSocketFactory(ssl)                    .setRetryHandler((e, executionCount, context) -> {                        if (executionCount <= 20) {                            if (e instanceof NoHttpResponseException                                    || e instanceof ConnectTimeoutException                                    || e instanceof SocketTimeoutException                                    || e instanceof SSLHandshakeException) {                                log.info("{} 异常, 重试 {}", e.getMessage(), executionCount);                                return true;                            }                        }                        return false;                    })                    .setServiceUnavailableRetryStrategy(new ServiceUnavailableRetryStrategy() {                        @Override                        public boolean retryRequest(HttpResponse response, int executionCount, HttpContext context) {                            int statusCode = response.getStatusLine().getStatusCode();                            if (statusCode != 200) {                                if (executionCount <= 20) {                                    log.info("{} 状态码异常, 重试 {}", statusCode, executionCount);                                    return true;                                }                            }                            return false;                        }                        @Override                        public long getRetryInterval() {                            return 0;                        }                    })                    .build();        }        catch (Exception e) {            throw new RuntimeException(e);        }    }}
❌