Java实战之每日海报

news2024/11/17 1:55:00

前言

使用java生成每日海报。
java

项目起因是巧合下遇到了一篇很棒的文档,说的是用程序来实现每日生成一个海报。如果之后加上自动发布的功能,简直就是太棒了啊!

样例图如下:daily_poster_20240112

每日海报

思路

  1. 访问某词站的API获取网络图片,为的就是他已经裁剪好的大小。
  2. 加载个人品牌二维码,这里我是以个人的微信公众号的二维码。
  3. 获取某词爸的文字内容,此处使用Jsoup解析Html结果后取最新的句子。
  4. 利用 Graphics2D 在海报上打印中英文对照语、绘制个人专属二维码,打印当前日期
  5. 运行项目即可,也可以后期达成Jar包,单独运行一次就能生成一个。

我知道,读者你肯定还是希望能够让项目按时间自动生成,我不仅要求这些,还要能自动发布微信公众号和博客呢。

那些都是后期功能了,本期就关注于生成最关键的图片。

数据来源

因为时间过去很久了,原文中很多的Api已经不能够继续使用了。所以这里简单记录一下我所使用的数据来源。

某词爸

遗憾的是,通过网页访问GET请求只能够得到文字,无法拿到图片了

http://jinjin.online/tools/word_everyday/show.php?page_num=1

某词站

可以通过POST请求访问地址获取。

http://learn.baicizhan.com/api/activity/sentence/list

需要注意的是,要在请求头中加上Cookie,并且其中要加上个人认证的access_token字段才可以获取到数据。

所以如果读者想要复现的话,就需要在自己的手机上登入自己的账户,然后通过抓包的方式获取到指定数据才可以。

具体的抓包方式教程我附在文末了。

图片下载下来之后,可以确认分辨率是1125x1500,对我而言已经足够了。

功能实现

获取网络图片

首先通过抓包拿到自己的手机请求每日图片的Token,为了防止某些不必要的误会,这里不展开讲了。

public String getInfoList() {
    return HttpUtils.doPost(urlConfig.getBczUrl(), "{\"size\":20}", HttpUtils.headersMap);
}

得到的结果是类似于这样的JSON体:

{
    "date":1704902400000,
    "sentence":{
        "en":"What is an ocean but a multitude of drops.",
        "cn":"不积小流,无以成江海。"
    },
    "backgroundImg":"https://7n.bczcdn.com/r/qeb7xm3jjhp9zhmtz8cgv3d254l24xh5.png",
    "dakaNum":7817,
    "sentenceSource":"-《云图》",
    "collected":false,
    "id":597,
    "audio":"https://vol.bczcdn.com/r/us_What_is_20221001152932680_bc72af44e192cb6ee36c.mp3",
    "daka":false
}

而我只需要其中的背景图片地址,将他保存为临时文件。

public File getBczInfoImage(BczDTO bczDTO) {
        String backgroundImg = bczDTO.getBackgroundImg();
        try {
            // 获取输入流对象
            InputStream inputStream = bczService.getBczInfoImage(bczDTO);

            // 存储为临时文件
            return FileUtils.createTmpFile(inputStream,
                    dirsConfig.getImagesDir(),
                    DateUtil.formatDate(new Date(), DateUtil.YYYYMMDD) + "_",
                    backgroundImg.substring(backgroundImg.lastIndexOf(Constant.DOT)));
        } catch (Exception e) {
            log.error("获取某词站临时图片发生错误:{},{}", e.getMessage(), e);
            throw new ServiceException("获取某词站临时图片发生错误");
        }
    }

个人二维码

至于个人二维码我是用的微信公众号的二维码。

直接将图片放到了项目里,也可以通过项目配置文件指定绝对路径来找到你自己的二维码图片。

public File getQcCodeFile(String qcCodeName) {
    return new File(qcCodeName);
}

获取网络文字

public Sentence getJscbInfo() {
    Sentence sentence = new Sentence();
    String html = jscbService.getInfo();
    //用Jsoup解析html
    Document document = Jsoup.parse(html);

    //像js一样,通过class获取列表下的所有
    Elements postItems = document.getElementsByClass("list-group");
    postItems = postItems.get(postItems.size()-1).getElementsByTag("font");
    // 此处是英文
    sentence.setEn(postItems.get(1).text());

    // 此处是中文
    sentence.setCn(postItems.get(2).text());
    return sentence;
}

解析后将中文和英文读取出来。

打印信息

核心代码如下。

try {
    // 5 利用 Graphics2D 在图片上打印中英文对照语
    log.info("开始在图像上打印中英文对照语");
    Graphics2DPoster graphics2DPoster = Graphics2DUtils.drawImage(ImageIO.read(bczInfoImage));
    graphics2DPoster.setZh(jscbInfo.getCn());
    Graphics2DUtils.drawZhString(graphics2DPoster);
    graphics2DPoster.setEn(jscbInfo.getEn());
    Graphics2DUtils.drawEnString(graphics2DPoster);
    log.info("在图像上打印中英文对照语成功");
    // 6. 打印个人的二维码
    BufferedImage qrcodeImage = ImageIO.read(qcCodeFile);
    graphics2DPoster.setQrcodeImage(qrcodeImage);
    Graphics2DUtils.drawQrcode(graphics2DPoster);
    // 7. 右上角画日期
    String dateString = Graphics2DUtils.drawDate(graphics2DPoster);

    // 释放图形上下文,以及它正在使用的任何系统资源。
    graphics2DPoster.getGraphics2d().dispose();

    // 8. 最后保存成为文件
    File posterFile = new File(dailyPosterDirsConfig.getImagesDir()+"/daily_poster_"+dateString+".png");
    ImageIO.write(graphics2DPoster.getBgImage(), "png",posterFile );
    log.info("绘制好封面图的海报" + posterFile.getAbsolutePath());
}catch (Exception e){
    log.error("发生异常:{},{}",e.getMessage(),e);
    SpringApplication.exit(DailyPosterApplication.applicationContext, () -> 0);
    return;
}

工具类代码如下。

public class Graphics2DUtils {
    /**
     * 留白
     */
    private static final int MARGIN = 25;
    private static final int SUITABLE_WIDTH = 700;


    /**
     * @param bgImage
     * @return {@code Graphics2DPoster}
     * @throws IOException
     */
    public static Graphics2DPoster drawImage(BufferedImage bgImage) throws IOException {
        // 封面图的起始坐标
        int pic_x = MARGIN, pic_y = MARGIN;
        Graphics2D graphics2d = bgImage.createGraphics();
        Graphics2DPoster graphics2dPoster = new Graphics2DPoster(graphics2d);
        // 海报可容纳的宽度
        graphics2dPoster.setSuitableWidth(SUITABLE_WIDTH);
        graphics2dPoster.setBgImage(bgImage);

        // 记录此时的 y 坐标
        graphics2dPoster.setCurrentY(pic_y * 40);

        return graphics2dPoster;
    }

    public static Graphics2DPoster drawZhString(Graphics2DPoster graphics2dPoster) throws IOException {
        // 获取计算机上允许使用的中文字体
        List<String> fontNames = Arrays
                .asList(GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames());
        if (fontNames == null || !fontNames.contains(FontUtil.USE_FONT_NAME)) {
            throw new RuntimeException("计算机上未安装" + FontUtil.USE_FONT_NAME + "的字体");
        }

        // 设置封面图和下方中文之间的距离
        graphics2dPoster.addCurrentY(30);

        Graphics2D graphics2d = graphics2dPoster.getGraphics2d();
        graphics2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

        // Font 的构造参数依次是字体名字,字体式样,字体大小
        Font font = new Font(FontUtil.USE_FONT_NAME, Font.PLAIN, FontUtil.FONT_SIZE);
        graphics2d.setFont(font);
        graphics2d.setColor(new Color(255, 255, 255));

        FontDesignMetrics metrics = FontDesignMetrics.getMetrics(font);
        graphics2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));

        String zhWrap = FontUtil.makeZhLineFeed(graphics2dPoster.getZh(), metrics, graphics2dPoster.getSuitableWidth());
        String[] zhWraps = zhWrap.split("\n");
        for (int i = 0; i < zhWraps.length; i++) {
            graphics2dPoster.addCurrentY(metrics.getHeight());
            graphics2d.drawString(zhWraps[i], MARGIN, graphics2dPoster.getCurrentY());
        }

        return graphics2dPoster;
    }

    public static void drawEnString(Graphics2DPoster graphics2dPoster) throws IOException {
        // 设置封面图和下方中文之间的距离
        graphics2dPoster.addCurrentY(20);

        Graphics2D graphics2d = graphics2dPoster.getGraphics2d();
        graphics2d.setColor(new Color(255, 255, 255));

        FontDesignMetrics metrics = FontDesignMetrics.getMetrics(graphics2d.getFont());
        String enWrap = FontUtil.makeEnLineFeed(graphics2dPoster.getEn(), metrics, graphics2dPoster.getSuitableWidth());
        String[] enWraps = enWrap.split("\n");
        for (int i = 0; i < enWraps.length; i++) {
            graphics2dPoster.addCurrentY(metrics.getHeight());
            graphics2d.drawString(enWraps[i], MARGIN, graphics2dPoster.getCurrentY());
        }

    }

    /**
     * 画二维码
     *
     * @param graphics2dPoster
     */
    public static void drawQrcode(Graphics2DPoster graphics2dPoster) {
        BufferedImage qrcodeImage = graphics2dPoster.getQrcodeImage();
        BufferedImage bgImage = graphics2dPoster.getBgImage();

        // 二维码起始坐标
        int qrcode_x = bgImage.getWidth() - qrcodeImage.getWidth() - MARGIN;
        int qrcode_y = bgImage.getHeight() - qrcodeImage.getHeight() - MARGIN;

        Graphics2D graphics2d = graphics2dPoster.getGraphics2d();
        graphics2d.drawImage(qrcodeImage, qrcode_x, qrcode_y, qrcodeImage.getWidth(), qrcodeImage.getHeight(), null);
    }

    /**
     * 画日期
     *
     * @param graphics2dPoster
     */
    public static String drawDate(Graphics2DPoster graphics2dPoster) {
        BufferedImage bgImage = graphics2dPoster.getBgImage();

        Date date = new Date();
        String dateDay = DateUtil.formatDate(date, DateUtil.DD);
        String dateYearAndMonth = DateUtil.formatDate(date, DateUtil.YYYY_MM);

        Graphics2D graphics2d = graphics2dPoster.getGraphics2d();
        graphics2dPoster.setCurrentY(MARGIN * 5);

        // 先画日期
        graphics2d.setFont(new Font(FontUtil.USE_FONT_NAME, Font.PLAIN, FontUtil.FONT_SIZE + 30));
        graphics2d.drawString(dateDay, bgImage.getWidth() - MARGIN * 6, graphics2dPoster.getCurrentY());
        graphics2dPoster.addCurrentY(66);

        graphics2d.setFont(new Font(FontUtil.USE_FONT_NAME, Font.PLAIN, FontUtil.FONT_SIZE));
        //再画年月
        graphics2d.drawString(dateYearAndMonth, bgImage.getWidth() - MARGIN * 10, graphics2dPoster.getCurrentY());

        return DateUtil.formatDate(date, DateUtil.YYYYMMDD);
    }
}

最后就能够实现生成一个有自己的二维码的每日图片。

总结和感谢

示例文件

gitee:

每日海报模块:

https://gitee.com/JunKuangKuang/keenJavaTest-all/tree/master/keenTest-springBoot-parent/daily-poster

参考的开源项目:

https://github.com/qinggee/poster/tree/jinshanciba

更新记录

2023-01-12 发布初版

2024-01-10 编写本文

感谢

感谢现在的好奇,为了能成为更好的自己。

Java生成金山词霸的二维码分享海报

安卓手机抓包

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

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

相关文章

HTML--CSS--超链接样式以及鼠标样式自定义

超链接伪类 再复习一下,超链接的定义方式如下&#xff1a; <!DOCTYPE html> <html> <head> <title>这是一个标题</title><meta charset"utf-8"/><style></style> </head> <body><a href"http…

c语言题目之斐波那契数列

文章目录 题目一、什么叫斐波那契数列1&#xff0c;由来2&#xff0c;定义 二、代码编写总结 题目 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、什么叫斐波那契数列 1&#xff0c;由来 在数学历史上&#xff0c;欧洲黑暗时期过后&#xff0c;第…

服务器机房上架交付流程

服务器上架交付 服务器到货验收后&#xff0c;会进行机房机房上架&#xff0c;完成重装系统、网络配置后交付使用 1、到货验收 采购服务器到货后&#xff0c;会联合多部门进行SN、配置、数量等多方面验收&#xff0c;如数量是否匹配&#xff0c;配置是否相符等也会拆开机箱看看…

高精度磁导航传感器MGS系列RS232|RS485|CANBUS通讯连线方法

高精度磁导航传感器MGS系列&#xff0c;包含&#xff1a;CNS-MGS-080N、CNS-MGS-160N等&#xff0c;具有1mm的检测精度&#xff0c;特别适应于⾼精度磁条导航。利⽤检测磁场相对位置来进⾏AGV的辅助定位对接&#xff0c;获得更⾼的导航、定位、驻⻋精度。 MGS系列磁导航传感器⽀…

Linux进程【2】进程地址空间(+页表详解哦)

fork 引言&#xff08;程序地址空间&#xff09;进程地址空间进程地址空间mm_struct 虚拟地址到物理地址的转化总结 引言&#xff08;程序地址空间&#xff09; 在之前的学习过程中&#xff0c;我们认识了内存与地址&#xff0c;并且了解了在程序地址空间中的基本分区&#xf…

AI语音机器人的发展

第一代AI语音机器人具体投入研发的开始时间不太清楚&#xff0c;只记得2017年的下半年就已经开始接触到成型的AI语音机器人&#xff0c;并且正式商用。语音识别效果还不多&#xff0c;大多都是接入的科大讯飞或者百度的ASR。 2018年算是AI语音机器人的“青春期”吧&#xff0c;…

如何在网上赚取零花钱?真实靠谱的六个方法

现如今&#xff0c;时代的不断进步和发展。网赚这个词对我们来说已经不再陌生&#xff0c;随着互联网的发展&#xff0c;许多朋友都希望在空闲时间利用上网多赚一份收入&#xff0c;但因为不懂又经常有人被骗&#xff0c;造成大部分对很多可以赚钱的项目都不敢相信了&#xff0…

二叉树简介

二叉树 二叉树是每个节点最多有两个子树的树结构&#xff0c;通常子树被称作“左子树”和“右子树”。 二叉树的遍历 二叉树的遍历主要有三种方式&#xff1a;前序遍历、中序遍历和后序遍历。 前序遍历&#xff1a;访问根节点 --> 遍历左子树 --> 遍历右子树中序遍历&…

OS进程管理

进程 文章目录 进程概念组成特征状态与转换组织方式链接方式索引方式 进程控制实现进程控制如何实现原语的“原子性” 进程通信(IPC)共享存储基于存储区共享基于数据结构的共享 消息传递直接通信方式间接通信方式 管道通信 线程实现方式用户级线程内核级线程 多线程模式状态与转…

RT-Thread: eeprom存储芯片 at24cxx软件包使用流程

说明&#xff1a;介绍 i2c 通讯接口的 eeprom at24cxx 读写测、试代码&#xff0c;代码基于 at24cxx 软件包实现。 使用步骤&#xff1a; * 1&#xff1a;在 RT-Thread Settings 中开启 【软件模拟I2C】 * 2&#xff1a;在 RT-Thread Settings 软件包中搜索 at24cxx 添加软件…

数据结构学习 jz38 字符串的排列

关键词&#xff1a;字典序排列 dfs 回溯 哈希 这种全排列题目可以考虑用下一个排列的方法做&#xff0c;这是最优解&#xff08;方法四&#xff09; 题目&#xff1a;套餐内商品的排列顺序 我的&#xff1a;[ 用时: 21 m 11 s ] 回溯 dfs 哈希表 方法一&#xff1a;我写的 …

Maven 基础安装配置及使用

大家好我是苏麟 , 今天聊聊Maven . Maven Maven , 是Apache公司下基于Java开发的开源项目 . 我们构建一个项目需要用到很多第三方的类库&#xff0c;需要引入大量的jar包。一个项目Jar包的数量之多往往让我们瞠目结舌&#xff0c;并且Jar包之间的关系错综复杂&#xff0c;一…

海外云手机助力企业拓展海外市场

在当前全球化的商业环境中&#xff0c;由于政策限制&#xff0c;许多企业面临着无法顺利将产品推广到国外的困境&#xff0c;使得海外市场的机遇白白流失。而随着科技的不断创新&#xff0c;一种解决企业海外拓展困境的工具应运而生&#xff0c;那就是海外云手机。本文将深入探…

手撕乘积(**Multiplication** **Product**): 穷举和图示(2) 点积的几何意义

手撕乘积(Multiplication & Product): 穷举和图示(2) 点积的几何意义 点乘 x 3 y 5 xNda np.arange(x) >>> array([0, 1, 2]) x2Nda xNda*21 >>> array([1, 3, 5]) yNda np.arange(1, y) >>> array([1, 2, 3, 4]) xyNda np.meshgrid(xN…

Linux系统使用docker部署Geoserver(简单粗暴,复制即用)

1、拉取镜像 docker pull kartoza/geoserver:2.20.32、创建数据挂载目录 # 统一管理Docker容器的数据文件,geoserver mkdir -p /mydata/geoserver# 创建geoserver的挂载数据目录 mkdir -p /mydata/geoserver/data_dir# 创建geoserver的挂载数据目录&#xff0c;存放shp数据 m…

01.15

#include "widget.h" #include <QApplication>int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();VideoCapture mv;mv.open("D:\\opencv\\heads\\01.mp4");//定义一个存放视频里读取到的一帧图像Mat src;//定义一个存…

配置华为设备NQA UDP Jitter检测VoIP业务抖动

组网需求 如图1所示&#xff0c;总部和子公司之间需要跨越外部网络进行通信&#xff0c;DeviceA和DeviceD为总部和子公司的网络出口设备&#xff0c;DeviceB和DeviceC为外部网络提供商的边缘设备。 总部和子公司之间经常要通过VoIP进行电话会议&#xff0c;要求双向时延小于2…

Java内置锁:深度解析ReentrantReadWriteLock并发类

ReentrantLock和ReentrantReadWriteLock是Java中用于线程同步的重要工具。ReentrantLock提供独占访问&#xff0c;适合需要保护共享资源不被并发修改的场景&#xff0c;同时支持可重入性&#xff0c;适用于递归操作。而ReentrantReadWriteLock则通过读写分离&#xff0c;允许多…

XTuner 微调 课程学习

大语言模型于海量的文本内容上&#xff0c;以无监督和半监督的方式进行训练的 模型微调的目的&#xff1a;使其在具体的使用场景或领域中输出更好的回答 增量预训练——给模型喂新的领域知识&#xff1b; 指令跟随或指令微调—— 基于海量的预训练数据训练出来的模型通常叫做…

[易语言]易语言部署yolox的onnx模型

【官方框架地址】 https://github.com/Megvii-BaseDetection/YOLOX 【算法介绍】 YOLOX是YOLO系列目标检测算法的进一步演变和优化。它由Megvii Technology的研究团队开发&#xff0c;是一个高性能、可扩展的对象检测器。YOLOX在保留快速处理速度的同时&#xff0c;通过引入一…