开发者社区> 倚贤> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

用函数计算搭建微服务——云客服访客名片

简介:
+关注继续查看

云客服可以方便快捷集成到用户的官网、APP、公众号等任意位置;依托阿里云智能算法,机器人能准确的理解用户的意图,并准确的回答用户的问题;提供完整的热线、在线服务功能,并能轻松连接企业的其他系统,如 CRM 等;动态管理客户和坐席使用的统一知识库、知识文章;实时汇总、实时分析服务中心的数据,帮助业务决策者从全局视角了解热门问题和当前的服务瓶颈;云客服是一套完整的智能服务体系。

访客名片是云客服上一个功能,用于关联云客服和客户 CRM 系统之间的用户,方便客服人员了解提问用户的基本信息,更好的支持用户。

目前云客服提供的访客名片集成指南是一个基于 Spring MVC 实现的 Web 项目,nodejs 语言背景的用户提出希望将 java 实现移植到函数计算服务,以服务的形式提供给 nodejs 实现的核心业务调用。

困难点

用户自己尝试过移植,如下几个技术点让其受挫:

  1. 云客服提供的 jar 是 maven 私有仓库,外网无法访问,所以需要以拷贝 jar 的方式集成进 maven
  2. 如何正确的配置 maven 插件打包成函数计算支持的 jar 包。

依赖本地 Jar 包

fccsplatform-common-crypto-1.0.0.20161108.jar 是一个阿里内网 maven 仓库的包,外网客户需要问云客服索要。下面 xml片段是 maven 依赖本地 jar 包的通用方法,但是由于 maven 依赖本地 jar 并不常见,需要查些资料。

<dependency>
    <groupId>com.alipay.fc.csplatform</groupId>
    <artifactId>fccsplatform-common-crypto</artifactId>
    <version>1.0.0.20161108</version>
    <scope>system</scope>
    <systemPath>${project.basedir}/lib/fccsplatform-common-crypto-1.0.0.20161108.jar</systemPath>
</dependency>

<scope>system</scope>表示从系统路径查找 jar 包,而不是仓库,要配合 <systemPath/> 一起使用。<systemPath/> 指定 jar 的真实路径,一般需要配合${project.basedir}一起使用,表示相对于项目目录。

封装 Handler 函数

Spring MVC 以 Controller 的形式对外暴露服务,而对应于函数计算实现 Handler 接口即可。

/**
 * This is the interface for any none event based function handler
 */
public interface StreamRequestHandler {

    /**
     * The interface to handle a function compute invoke request
     *
     * @param input The function compute input data wrapped in a stream
     * @param output The function compute output data wrapped in a stream
     * @param context The function compute execution environment context object.
     * @throws IOException IOException during I/O handling
     */
    void handleRequest(InputStream input, OutputStream output, Context context) throws IOException;
}

访客名片的业务较为简单只要实现加密和解码两个接口,不涉及到第三方服务调用

public class Encryptor implements StreamRequestHandler {

    private CustomerInfo customerInfo = new CustomerInfo();

    public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException {
        String cInfo = CharStreams.toString(new InputStreamReader(
                input, Charset.defaultCharset()));
        try {
            output.write(customerInfo.encrypt(cInfo).getBytes(Charset.defaultCharset()));
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
        }
    }
}
public class Decryptor implements StreamRequestHandler {

    private CustomerInfo customerInfo = new CustomerInfo();

    @Override
    public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException {
        String jsonString = CharStreams.toString(new InputStreamReader(
                input, Charset.defaultCharset()));

        JSONObject jsonObject = JSON.parseObject(jsonString);
        try {
            String cInfo = customerInfo.decrypt(jsonObject.getString("params"), jsonObject.getString("key"));
            output.write(cInfo.getBytes(Charset.defaultCharset()));

        } catch (GeneralSecurityException e) {
            e.printStackTrace();
        }

    }
}

上面 Encryptor.java 和 Decryptor.java 负责实现 StreamRequestHandler 接口,而 CustomerInfo.java 类封装了真正的业务逻辑。

public class CustomerInfo {

    # 问云客服要 PUB_KEY
    private static String PUB_KEY = "your PUB_KEY";


    public String encrypt(String cInfo) throws GeneralSecurityException, UnsupportedEncodingException {
        PublicKey publicKey = getPubKey(PUB_KEY);
        Map<String, String> res = CustomerInfoCryptoUtil.encryptByPublicKey(cInfo, publicKey);

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("cinfo", res.get("text"));
        jsonObject.put("key", res.get("key"));

        return jsonObject.toJSONString();
    }

    public String decrypt(String params, String key) throws UnsupportedEncodingException, GeneralSecurityException {
        PublicKey publicKey = getPubKey(PUB_KEY);
        return CustomerInfoCryptoUtil.decryptByPublicKey(params, key, publicKey);
    }

    private PublicKey getPubKey(String pubKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64Util.decode(pubKey));
        KeyFactory keyFactory;
        keyFactory = KeyFactory.getInstance("RSA");
        PublicKey key = keyFactory.generatePublic(keySpec);
        return key;
    }

打包

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <executions>
        <execution>
            <id>copy-dependencies</id>
            <phase>prepare-package</phase>
            <goals>
                <goal>copy-dependencies</goal>
            </goals>
            <configuration>
                <outputDirectory>${project.build.directory}/classes/lib</outputDirectory>
                <includeScope>compile</includeScope>
            </configuration>
        </execution>
    </executions>
</plugin>

函数计算官方文档推荐了两种打包方式 maven-assembly-plugin 和 maven-dependency-plugin。

  • maven-assembly-plugin 会提取所有被依赖的 class 文件,重新打包成一个 jar 包,体积更小,但可能会产生一些额外的问题
  • maven-dependency-plugin 将所有依赖 jar 包都打包那一个内部的 lib/ 目录下,然后打包成嵌套 jar 包,函数计算运行时会解压顶层 jar 包。

关于 java 打包更多的细节可以参考个人的另外一篇文章《Java 打包 FatJar 方法小结》 。本例中实测使用 maven-assembly-plugin 插件打包是不行的,因为 systemPath 指定依赖的传递依赖没法被打包进去,导致运行时缺少类,所以选用了 maven-dependency-plugin 的方式。

fun 部署

最后通过一个 fun 的 template.yml 文件进行描述, 然后执行 mvn package && fun deploy

ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
  YunkefuSign:
    Type: 'Aliyun::Serverless::Service'
    Properties:
      Description: 'yun ke fu Sign'
    Encryptor:
      Type: 'Aliyun::Serverless::Function'
      Properties:
        Handler: com.aliyun.fc.Encryptor::handleRequest
        Runtime: java8
        CodeUri: './target/yunkefu-sign-1.0-SNAPSHOT.jar'
        Timeout: 60
    Decryptor:
      Type: 'Aliyun::Serverless::Function'
      Properties:
        Handler: com.aliyun.fc.Decryptor::handleRequest
        Runtime: java8
        CodeUri: './target/yunkefu-sign-1.0-SNAPSHOT.jar'
        Timeout: 60

小结

将语言作为服务的边界是服务架构常见的划分边界方式之一。函数计算支持多语言环境,具备可扩展和高可用特性,使其适于以服务的形式支持多语言的架构。通常情况下,开发商在熟悉语言领域有足够的积累,构建了一整套完备的架构和部署运维体系,而为了扩大业务的边界,有时候会面临被迫选择熟悉语言领域之外的编程语言和架构,对于这一挑战,函数计算只需要用语言简单地写业务,无需关注架构和运维,可以减轻开发商的压力。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
基于阿里云 Serverless 函数计算搭建按量付费的 WordPress 网站
Serverless 经过几年的发展,已经和传统服务器一样好用了。我最初接触 Serverless 是在 2020 年的云开发校园合伙人创造营上,当时我基于阿里云云开发平台部署了一个在线工具箱。
0 0
使用Serverless 函数计算搭建一个Vue3的框架
使用Serverless 函数计算搭建一个Vue3的框架
0 0
基于Serverless函数计算搭建一个属于自己的博客系统
本次场景使用阿里云提供的serverless函数服务来搭建一个属于自己的博客系统
0 0
基于阿里云 Severless 函数计算(FC)搭建按量付费的研发工具包
在日常研发工作中,我们总会遇到一些奇奇怪怪的事情,然后我们会收集一下「检测方法」、「解决方法」等等方法来辅助我们解决问题。 但是这样的服务并不是每时每刻都会使用,甚至有可能一两个月才会使用一次。而且有一些方法需要多地部署。也有可能会有一些任务是定时执行。 对于上述遇到的问题「使用频次低」、「多地部署」、「定时执行」等等,我们搭建普通的云服务(ECS)是比较昂贵的做法,临时的云主机又比较复杂。 这篇文章带着大家看看如何基于阿里云 Severless 函数计算(FC),来减低成本,提交效率。
0 0
场景体验:基于函数计算快速搭建Vuepress博客
本场景介绍使用函数计算服务搭建一个Vuepress博客。
0 0
基于阿里云Severless函数计算搭建按量付费的WordPress网站
基于阿里云Severless函数计算搭建按量付费的WordPress网站
0 0
场景实践:基于函数计算快速搭建Wordpress博客系统
本场景介绍使用函数计算服务搭建一个Wordpress博客。使用Serverless Devs命令行工具可以将很多框架、应用、案例一键部署到函数计算平台中。点击获取所需云产品资源
0 0
函数计算 HTTP 触发器支持异步,解放双手搭建 Web 服务
函数计算 HTTP 触发器支持异步,解放双手搭建 Web 服务
0 0
函数计算 HTTP 触发器支持异步,解放双手搭建 Web 服务
HTTP 触发支持异步调用,为用户搭建 WEB 服务使用函数计算作为全托管平台打通了最后一公里,让 HTTP 触发器的使用者也可以一样感受到异步调用开箱即用的便捷体验。
0 0
函数计算 HTTP 触发器支持异步,解放双手搭建 Web 服务
函数计算 HTTP 触发器支持异步调用,为用户搭建 WEB 服务使用函数计算作为全托管平台打通了最后一公里,以后 HTTP 触发器的使用者,也可以感受到异步调用”开箱即用“的便捷体验。
0 0
+关注
倚贤
全栈工程师,从事了 12 年以 Java 语⾔为主的软件开发工作,热衷于整合框架与开发工具,关注 交互设计,喜欢写技术博客(http://codelife.me/),Linux拥趸,问题终结者。近期开始学习和关注 Elixir 函数语言,合作翻译了《Elixir 程序设计》。
文章
问答
来源圈子
更多
专注 Serverless、微服务、函数计算、Serverless 应用引擎、云原生技术
+ 订阅
文章排行榜
最热
最新
相关电子书
更多
函数计算集团规模化落地实践(3).ppt
立即下载
函数计算事件驱动的serverless计算平台
立即下载
基于函数计算的serverless应用开发
立即下载