微搭使用笔记(五) 通过数据源API写入数据并展示到页面

一、背景

​今天在学习腾讯云微搭API的时候发现,微搭不仅提供了小程序或者H5调用数据源的API,还支持外部通过http请求+token的方式调用,于是决定体验一把。
​正好结合之前可视化爬虫爬取新闻的一些操作,想着可以把爬好的数据通过数据源API的方式写入到微搭数据源中,同时借用微搭数据模型的能力生成页面展示数据。
本文内容和思路出于突发奇想,可能有不合适的或者使用不到位的地方,仅供参考~

二、思路

结合上面提到的想法,基本思路如下:

	1. 在spiderflow项目中封装调用微搭API写入数据的相关操作
	2. 之前爬取数据后是写入到了数据库中,修改成调用API写入到微搭数据源中

三、spiderflow项目中引入微搭数据源API

微搭官方API提供了通过http请求调用的相关示例代码
官方文档地址: 官方文档地址

结合官方文档及上述思路,集成及开发过程如下:

  • 通过腾讯云控制台获取SecretId和SecretKey,用于后续获取token。
  • 引入jar包,添加相关配置文件
  • 添加获取token的方法以及增删改查相关方法

开发过程

  1. 主pom中需要引入http-client包,具体版本根据实际情况确定,本文以5.2为例。
<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5</artifactId>
    <version>5.2.1</version>
</dependency>
  1. spider-flow-web模块的resources目录下新建application-weda.yml配置文件,加入如下内容:
tencent:
  token:
    secretId: <步骤1中获取到的secretId>
    secretKey: <步骤1中获取到的secretKey>
    baseUrl: https://<你的微搭环境ID>.ap-shanghai.tcb-api.tencentcloudapi.com
    tokenUrl: /auth/v1/token/clientCredential
  weda:
    enabled: true
    schemas: <你的数据模型的标识,可以后面新建完数据模型后回来补充>
    envType: pre
  1. 在spider-flow-web模块的configuration包中编写配置类TencentProperties.java
package org.spiderflow.configuration;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * @author 泽济天下
 * @date 2023年03月16日13:54
 * @description:
 */
@Configuration
@ConfigurationProperties(prefix = "tencent")
public class TencentProperties {

    private TecentTokenProperties token;

    private TecentWedaProperties weda;

    //省略getter和setter...

    public static class TecentTokenProperties {

        private String secretId;

        private String secretKey;

        private String baseUrl;

        private String tokenUrl;

        //省略getter和setter...
    }

    public static class TecentWedaProperties {

        private Boolean enabled;

        private String schemas;

        private String envType;

        //省略getter和setter...
    }
}
  1. 编写获取token及保存数据的方法

在configuration包中新建TencentClient.java类,主要是token获取及数据保存的方法。

package org.spiderflow.configuration;

import com.alibaba.fastjson.JSONObject;
import org.apache.hc.client5.http.ClientProtocolException;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author 泽济天下
 * @date 2023年03月16日14:02
 * @description: 微搭数据源API请求相关方法,主要是token获取及数据保存
 */
@Component
public class WedaClient {

    @Autowired
    private TencentProperties tencentProperties;
	
    //用于保存token
    private static Map<String, Object> map = new ConcurrentHashMap<>();

    //这个方法只是简单实现,供参考
    public String getToken() {
        System.out.println("map = " + map);
        //如果map中不存在token直接调用接口获取
        if (!map.containsKey("token")) {
            return getRemoteToken();
        }
        if (map.containsKey("time")) {
            long time = Long.parseLong(map.get("time").toString());
            int expire = Integer.parseInt(map.get("expire").toString());
            //这里判断如果离过期时间小于2分钟的话就重新获取token
            if (System.currentTimeMillis() / 1000 - time > expire - 2 * 60) {
                return getRemoteToken();
            }
        }
        return map.get("token").toString();
    }

    /**
     * 获取 token
     */
    private synchronized String getRemoteToken() {
        if (map.containsKey("token")) {
            return map.get("token").toString();
        }
        HttpPost httpPost = new HttpPost(tencentProperties.getToken().getTokenUrl());

        String basicKey = tencentProperties.getToken().getSecretId() + ":" + tencentProperties.getToken().getSecretKey();
        String authorizationKey = "Basic " + Base64Utils.encodeToString(basicKey.getBytes());
        httpPost.addHeader("Authorization", authorizationKey);
        httpPost.addHeader("Content-Type", "application/json");

        Map<String, String> body = new HashMap<>();
        body.put("grant_type", "client_credentials");

        StringEntity requestBody = new StringEntity(JSONObject.toJSONString(body), ContentType.parse("UTF-8"));
        httpPost.setEntity(requestBody);
        //这里的handler用法与官方略有不同,主要是适配5.2的语法
        HttpClientResponseHandler<String> responseHandler = response -> {
            int status = response.getCode();
            HttpEntity entity = response.getEntity();
            if (status >= 200 && status < 300) {
                return entity != null ? EntityUtils.toString(entity) : null;
            } else {
                throw new ClientProtocolException("Unexpected response status: " + status + EntityUtils.toString(entity));
            }
        };
        try {
            CloseableHttpClient httpClient = HttpClients.createDefault();
            String responseBody = httpClient.execute(httpPost, responseHandler);
            Map<String, Object> responseMap = JSONObject.parseObject(responseBody, Map.class);
            String accessToken = "Bearer " + responseMap.get("access_token").toString();
			//保存数据到map中
            map.put("token", accessToken);
            long createTime = System.currentTimeMillis() / 1000;
            map.put("time", createTime);
            map.put("expire", Integer.parseInt(responseMap.get("expires_in").toString()));

            return accessToken;
        } catch (Exception e) {
            System.out.println(e.toString());
        }
        return "";
    }

    // POST: 创建记录
    public void post(Map<String, Object> jsonBody) {
        //这里的StringEntity构造方法的参数也是为了适配5.2.1版本的httpClient
        //需要注意的是第二个参数一定是StandardCharsets.UTF_8,而不是ContentType.parse("UTF-8")
        //否则会出现中文乱码问题
        StringEntity requestBody = new StringEntity(JSONObject.toJSONString(jsonBody), StandardCharsets.UTF_8);
        String url = tencentProperties.getToken().getBaseUrl() + "/weda/odata/v1/"
                + tencentProperties.getWeda().getEnvType() + "/" + tencentProperties.getWeda().getSchemas();
        HttpPost httpPost = new HttpPost(url);
        httpPost.setEntity(requestBody);
        httpPost.addHeader("Authorization", getToken());
        httpPost.addHeader("Content-Type", "application/json");
        HttpClientResponseHandler<String> responseHandler = response -> {
            int status = response.getCode();
            if (status >= 200 && status < 300) {
                HttpEntity entity = response.getEntity();
                return entity != null ? EntityUtils.toString(entity) : null;
            } else {
                throw new ClientProtocolException("Unexpected response status: " + status + EntityUtils.toString(response.getEntity()));
            }
        };
        try {
            CloseableHttpClient httpClient = HttpClients.createDefault();
            httpClient.execute(httpPost, responseHandler);
        } catch (Exception e) {
            System.out.println("e.getMessage() = " + e.getMessage());
        }
    }
}

说明:

  • 这个类主要功能就是提供了获取token和调用数据源API保存数据的方法。
  • 微搭提供的token有效期是43200秒,也就是120小时,5天,这里通过一个map的保存避免了token的重复获取(供参考)
  • HttpClient版本的问题结合代码中的注释查看。
  1. 编写controller及简单测试
    在spider-flow-web模块的controller包中编写测试用的TencentController
package org.spiderflow.controller;

import org.spiderflow.configuration.WedaClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

/**
 * @author 泽济天下
 * @date 2023年03月16日14:50
 * @description:
 */
@RestController
@RequestMapping("tencent")
public class TencentController {

    @Autowired
    private WedaClient wedaClient;

    @PostMapping("save")
    public String save(@RequestParam Map<String, Object> params) {
        wedaClient.post(params);
        return "success";
    }

}

很简单,通过autowired注入了WedaClient这个bean,在save方法中调用了post方法,完事。

到这里接口的准备工作就完成了。

四、spiderflow流程调整

​ 修改之前我们先规划下入微搭数据源的字段:

	- index:  对应数据中的id,页面上的唯一标识
	- title: 新闻标题
	- content: 新闻内容富文本
	- publishDate: 发布日期,最终格式化为yyyy-MM-dd的格式展示。
	- source: 预留,后续可以从别的地方抓取新闻入到这个库,通过这个字段可以区分。

对了,我们还需要添加一个方法,用于时间格式化。

​ 我们通过详情页面抓取到的发布时间格式是xxxx年xx月xx日这样的,我们希望格式化成yyyy-MM-dd的格式。

​ 在DateFunctionExecutor类中添加两个方法备用,如下:

@Comment("格式化日期")
@Example("${date.format('2022年11月22日', 'yyyy年MM月dd日', 'yyyy-MM-dd')}")
public static String format(String source, String sourcePattern, String targetPattern) {
    try {
        Date date = new SimpleDateFormat(sourcePattern).parse(source);
        return new SimpleDateFormat(targetPattern).format(date);
    } catch (ParseException e) {
        return "";
    }
}

@Comment("格式化日期")
@Example("${date.parse('2022年11月22日', 'yyyy年MM月dd日', 'yyyy-MM-dd')}")
public static Date parse(String source, String sourcePattern, String targetPattern) {
    try {
        String format = format(source, sourcePattern, targetPattern);
        return new SimpleDateFormat(targetPattern).parse(format);
    } catch (ParseException e) {
        return null;
    }
}

方法中入参是三个,分别是原始数据,原始数据格式,目标数据格式,完成的功能就是把原始数据格式化成目标格式(上述方法可能比较原始,比较笨拙, 可自行修改)

修改最后入库的步骤为请求本地接口,配置如下:
流程配置修改1
流程配置修改2
到这里,流程配置的调整也完成了,接下来我们对微搭做一些配置就可以测试了。

五、微搭页面处理

  1. 新增数据模型及配置

    数据模型的介绍之前有文章专门写过,这里就不赘述了,附上文章链接: 数据模型介绍和初步使用

    数据模型配置如下:
    微搭数据源配置
    配置完成后点击保存,在基本信息标签里点击立即发布即可。

    TIPS:

    ​ 这里新建了数据模型后会有个数据模型的唯一标识,需要复制到spider-flow项目的配置文件的对应位置。

  2. 通过数据模型生成列表页面及详情页面

    • 新建列表页面
      新建页面
    • 添加数据列表组件并做相应配置

    数据列表字段配置
    ​ 可以看到如果表里有数据这里配置完后是可以直接预览的,还是很方便的,数据模型以及数据模型应用也是微搭官方主推的应用类型。

    • 注意到上述页面上默认展示的对应信息的id,我们修改成发布日期,同时调整字体样式等。

    数据列表字段配置
    如果需要添加图片在对应位置添加图片展示组件即可,不做赘述。

    • 新建详情页面,包含一个标题文本以及一个用于展示内容的富文本,如下图
      详情页面布局配置

    • 列表页面添加点击跳转事件,点击后跳转到详情页面并传参
      页面跳转配置2
      打开对应的详情页面,新建URL参数并传入当前记录的id参数.如上图所示。

    • 详情页面配置

    详情页面添加数据详情组件,并配置数据源为之前新建的新闻对应的数据源,选中后会根据字段类型生成对应组件及页面,可以看到默认生成的页面布局字段都是平铺的,这里我们手动调整下布局样式。

    其实要展示的字段就标题、发布日期和富文本,很简单的三个组件,配置好对应的展示字段即可。

详情页面配置1 详情页面配置2 详情页面配置3
其他的字段类似绑定即可。

新版配置截图@20231113:
新版配置跟老版配置略有区别,这里也截图展示下。
新版详情页数据配置

六、整体联调测试

  1. 数据爬取与入库测试

    要求application.yml配置文件中关于微搭的配置正确,流程配置正确即可在微搭的数据管理看到对应数据。

微搭数据源入库数据展示
2. 页面生成与数据展示测试

数据入库没问题后,这里要求对应页面展示字段配置正确即可。最终效果如下图:

最终效果1
最终效果2

七、问题与总结

​ 本文结合了可视化爬虫spiderflow及微搭对于数据源操作提供的API实现了爬虫数据入库,并利用微搭的数据模型的能力完成了新闻列表页面的生成以及数据的展示和调整。

​ 目前遗留问题如下:

  1. 后续新闻增量更新的问题,目前的方式是每天爬取第一页数据然后入库,入库失败说明有重复数据,不处理即可。

  2. 微搭提供的数据源API没有saveOrUpdate的操作,如果需要该操作的话需要先根据条件查询出对应记录,再根据查询结果调用新增或者更新的接口。

  3. 本文对于多条数据的处理目前是一条一条写入的,按说应该是考虑批量操作的,而且微搭官方也提供了批量操作的相关接口,待优化。

  4. 对于token的保存文中使用的方式比较一般,大家如果有好的想法欢迎留言。

以上就是本文所有内容,希望对大家关于微搭的使用能有所帮助,有任何疑问或者建议欢迎留言或者私信~

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
数据结构学习资料分享 内容概览: 本次分享包涵了大学计算机相关专业必学的“数据结构”课程的一系列学习资料。主要包括: 算法代码:我们提供了多种数据结构的实现代码,包括数组、链表、栈、队列、树、图等。这些代码不仅能帮助你理解数据结构的基本概念,而且能让你明白如何在实际情况中应用这些数据结构。 笔记:详细且系统的笔记,涵盖了数据结构的各个方面,从基础概念到复杂的数据结构如堆、B树等。这些笔记有助于你系统地复习和学习数据结构。 相关书籍推荐:为了更深入地理解数据结构,我们推荐了几本经典的教材和参考书籍。这些书籍将帮助你建立完整的数据结构知识体系。 适用人群: 这份学习资料适用于所有大学计算机相关专业的学生,无论你是初学者还是已经有一定的数据结构基础。同时,对于对数据结构感兴趣的非专业人士,这份资料也是一个很好的起点。 使用建议: 结合理论和实践:在学习的过程中,请结合算法代码和理论知识。尝试自己编写代码实现数据结构,并在遇到问题时参考提供的代码。 由浅入深:建议先从基础的数据结构开始学习,如数组和链表,然后再学习更复杂的数据结构如树和图。 多做练习:数据结构是实践性很强的学科。通过多做练习,你可以更好地理解数据结构的基本概念和原理,并提高编程能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

泽济天下

你的鼓励是我最大的动力。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值