Java实战之每日海报

前言

使用java生成每日海报。

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

样例图如下:

每日海报

思路

  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生成金山词霸的二维码分享海报

安卓手机抓包

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容