当前位置:   article > 正文

520来袭之前和我用serverless和FaaS实现一个表白小程序_编写表白小程序

编写表白小程序

学习目标

  • 掌握什么是serverless和FaaS

  • 学习使用阿里云函数计算(FC)构建多语言的后台服务

  • 使用Spring Boot + 阿里云函数计算 + OSS打造极低成本的表白小程序

一、520表白小程序介绍

1.1 需求说明

距离一年一度的520全民表白日,只!有!一!个!月!了!!!空气中处处弥漫着恋爱的甜(suan)蜜(chou)味!作为程序员,这一次我们来玩点不一样的!自己设计一款520表白小程序,来对他(她)表白吧!

在这里插入图片描述

二、什么是Serverless?

说起当前最火的技术,不得不提的一个概念就是 Serverless。近两年几乎所有人都在说 Serverless,Serverless 作为一种新型的互联网架构,直接或间接推动了云计算的发展,从 AWS Lambda 到阿里云函数计算,Serverless 一路高歌,同时基于 Serverless 的轻量计算开始登录云计算的舞台。

从Serverless名字上可以看出来,其实Serverless这个单词是两个单词的组合Serverless = server + less。Server指的是服务端,而less指的是较少关心,所以组合在一起就是较少关心服务端。

2.1 Serverfull的工作模式

要理解Serverless,我们先来看看Serverfull这种模式下,工作模式到底是怎么样的。

我们以服务端开发中两个角色为例:

在这里插入图片描述

做研发的小程,他是个精通JAVA WEB后端技术的程序员,主要负责与产品经理对接,将产品经理提出的产品需求转化成代码,当然他还得负责项目的版本管理以及后续线上bug的修复。

做运维的小魏,他只关心应用的服务端运维事务。他负责部署上线小程的 Web 应用以及日志监控。在用户访问量大的时候,他要给这个应用扩容(多加几台服务器);在用户访问量小的时候,他要给这个应用缩容(把服务器作他用节省成本);在服务器挂了的时候,他还要重启或者换一台服务器。

最开始小魏承诺将运维的事情全包了,小程不用关心任何部署运维相关的事情。小程每次发布新的应用,都会打电话给小魏,让小魏部署上线最新的代码。小魏要管理好迭代版本的发布,分支合并,将应用上线,遇到问题回滚。如果线上出了故障,还要抓取线上的日志发给小程解决。

在这里插入图片描述

这个时候小魏就觉得自己像一个工具人,每天都在重复的做一些琐碎的工作,特别是每次出现bug的时候,还要自己登陆到服务器上去查询日志,然后发给小程进行bug的修复。

这个时期研发和运维隔离,服务端运维都交给小服一个人,纯人力处理,也就是 Serverfull。

2.3 DevOps的工作模式

后来,小魏渐渐发现日常其实有很多事情都是重复性的工作,尤其是发布新版本的时候,与其每次都等小程电话,线上出故障了还要自己抓日志发过去,效率很低,不如干脆自己做一套运维平台,将部署上线和日志抓取的工作让小程自己处理。

在这里插入图片描述

运维平台上线后,小魏稍微轻松了一些,但是对于应用的扩容和缩容,还是需要小魏自己定期审查。而小程除了开发的任务,每次发布新版本或解决线上故障,都要自己到运维平台平台上去处理。这个时代就是研发兼运维 DevOps,小程兼任了小魏的部分工作。小魏将部分服务端运维的工作工具化了,自己可以去做更加专业的事情。相对ServerFull时代,看上去小程负责的内容更多了,但实际这些事情本身就应该是小程负责的。本来版本控制、线上故障就应该是小程自己处理的。而且小魏将这部分人力的工作工具化了,更加高效。其实已经有变少(less)的趋势了。那么还能不能再进一步优化呢?

2.4 Serverless的工作模式

这时,小魏发现资源优化和扩缩容方案也可以利用性能监控 + 流量估算解决。小魏又基于小程的开发流程,运维平台再进一步升级,帮小程做了一套代码自动化发布的流水线:从代码扫描 到测试再到上线。现在的小程连运维平台都不用登陆操作,只要将代码进行提交,剩下的就都由流水线自动化处理发布上线了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zfX660vh-1651140767936)(pic\114.jpg)]

这个时候研发不需要运维了。小魏的服务端运维工作全部自动化了。小程也变回到最初,只需要关心自己的应用业务就可以了。我们不难看出,在服务端运维的发展历史中,对于小程来说,小魏的角色存在感越来越弱,需要小魏参与的事情越来越少,都由自动化工具替代了。这就是“Serverless”。

那么我们怎么来实现serverless呢?这时候我们就需要使用到Faas了,FaaSFunctions as a Service的缩写,可以广义的理解为功能服务化,也可以解释为函数服务化。使用FaaS只需要关注业务代码逻辑,无需关注服务器资源,所以FaaS也跟开发者无需关注服务器Serverless密切相关。可以说FaaS提供了一个更加细分和抽象的服务化能力。其实很多的云平台已经提供了Faas相关的功能,今天我们使用的就是阿里云提供的函数计算FC。函数计算FC,它可以让我们随时随地创建、使用、销毁一个函数。函数计算FC需要加载代码进行实例化,然后被触发器 Trigger 或者被其他的函数调用。而我们要做的就是购买函数计算FC的服务,然后上传代码,函数计算服务会自动编译我们的代码并将其发布到服务器上运行。然后我们就可以使用触发器访问服务了。

在这里插入图片描述

Serverless 对 Web 服务开发的革命之一,就是极度简化了服务端运维模型,使一个零经验的新手,也能快速搭建一套低成本、高性能的服务端应用。

三、阿里云函数计算FC入门

3.1 什么是函数计算

3.1.1 简介

函数计算是事件驱动的全托管计算服务。使用函数计算,您无需采购与管理服务器等基础设施,只需编写并上传代码。函数计算为您准备好计算资源,弹性地、可靠地运行任务,并提供日志查询、性能监控和报警等功能,借助函数计算,您可以快速构建任何类型的应用和服务,并且只需为任务实际消耗的资源付费。在众多Serverless产品中阿里云函数计算FC更是其中的佼佼者。

3月25日消息,日前权威咨询机构 Forrester 发布 2021 年第一季度 FaaS 平台(Function-As-A-Service Platforms)评估报告,阿里云凭借产品能力全球第一的优势脱颖而出,在八个评测维度中拿到最高分,成为比肩亚马逊的全球 FaaS 领导者。这也是首次有国内科技公司进入 FaaS 领导者象限。

在这里插入图片描述

3.1.2 工作流程

流程说明如下:

  1. 开发者使用编程语言编写应用和服务。函数计算支持的开发语言请参见开发语言列表

  2. 开发者上传应用到函数计算。

    上传途径包括:

    • (推荐)通过函数计算控制台(https://fc.console.aliyun.com/?spm=a2c4g.11186623.2.9.6f33398eERpRzW)上传。
    • (推荐)通过命令行工具Funcraft上传更多信息。
    • 通过API或SDK上传。
    • 通过命令行工具fcli上传。
  3. 触发函数执行。触发方式包括OSS、API网关、日志服务、表格存储以及函数计算API、SDK等。

  4. 动态扩容以响应请求。函数计算可以根据用户请求量自动扩容,该过程对您和您的用户均透明无感知。

  5. 根据函数的实际执行时间按量计费。函数执行结束后,可以通过账单来查看执行费用,收费粒度精确到1毫秒。

流程图如下所示:

在这里插入图片描述

3.1.3 计费模式

函数计算每月为您提供一定的免费额度。您的阿里云账户与RAM用户共享每月免费的调用次数和执行时间额度。免费额度不会按月累积,在下一自然月的起始时刻,即1号零点,会清零然后重新计算。具体免费额度如下:

注意 免费额度只能在弹性实例的后付费模式下使用。

  • 调用次数:每月前100万次函数调用免费。
  • 函数实例资源使用量(内存大小):每月前400,000(GB-秒)函数实例资源使用量免费。

以下是以后付费单价为例计算月度费用。

函数执行内存调用次数执行时长网络带宽月度费用
512 MB300万次1秒/次0124.31元
128 MB3000万次200毫秒/次077.28元
128 MB2500万次200毫秒/次056.80元
448 MB500万次500毫秒/次082.04元
1024 MB250万次1秒/次0234.24元

3.2 快速入门

3.2.1 环境搭建流程

函数计算使用流程图如下所示。

在这里插入图片描述

流程说明如下:

  1. 创建服务。
  2. 创建函数,编写代码,将应用部署到函数中。
  3. 以事件源触发函数。
  4. 查看执行日志。
  5. 查看服务的监控。

3.2.2 服务的基本操作

创建服务
  1. 登录函数计算控制台

  2. 在顶部菜单栏,选择地域。

  3. 在左侧导航栏中,单击服务及函数。在服务列表区域右上角,单击新增服务

在这里插入图片描述

  1. 新建服务页面,设置服务参数,单击提交

在这里插入图片描述

参数说明如下。

参数说明
服务名称设置服务名称。
功能描述设置服务描述信息,便于区分服务,非必选。
绑定日志选择是否绑定日志。绑定日志后,您可以查看函数执行日志,方便您进行函数开发及调试。
开启链路追踪选择是否开启链路追踪功能。更多信息,请参见链路追踪简介

服务及函数页面的服务列表中可以查看已创建的服务。

在这里插入图片描述

更新服务
  1. 登录函数计算控制台

  2. 在顶部菜单栏,选择地域。

  3. 在左侧导航栏,单击服务及函数

  4. 服务及函数页面,单击目标服务。然后单击服务配置,在服务配置页签,单击修改配置

在这里插入图片描述

  1. 配置服务页面根据需要修改相应的参数,然后单击提交

在这里插入图片描述

删除服务

注意 删除服务前,请确保您的服务中没有函数、预留的函数实例、版本及别名,否则会导致删除失败。

  1. 登录函数计算控制台

  2. 在顶部菜单栏,选择地域。

  3. 在左侧导航栏,单击服务及函数

  4. 服务及函数页面,单击目标服务。然后单击服务配置,在服务配置页签,单击页面右上角的删除服务

在这里插入图片描述

  1. 在弹出的对话框中单击确认

3.2.3 创建函数

  1. 登录函数计算控制台

  2. 在顶部菜单栏,选择地域。

  3. 在左侧导航栏中,单击服务及函数。在服务及函数页面,单击目标服务,然后单击页面右上角的新增函数

在这里插入图片描述

  1. 新建函数页面选择创建的函数类型或函数模板,然后单击配置部署

    本文以创建HTTP函数为例。

在这里插入图片描述

  1. 新建函数页面,设置相关参数,然后单击新建,以NodeJS为例(FC函数计算中,Java语言不支持在线编辑)。

在这里插入图片描述

参数说明如下所示。

参数是否必填操作示例值
函数类型您选择的函数类型,选择后,无法更改。事件函数
所在服务若已创建服务:在列表中选择已存在的服务。若未创建服务:填写自定义的服务名称,系统将自动为您创建服务。Service
函数名称填写自定义的函数名称。Function
运行环境选择您熟悉的语言,例如Python、Java、PHP、Node.js等。函数计算支持的运行环境,请参见函数简介。选择运行环境后,您可以通过以下方式上传您的函数代码:代码包上传:选择后,单击上传代码,上传您的函数代码。文件夹上传:选择后,单击选择文件夹,选择您需要上传的文件夹。OSS上传:选择后,配置Bucket名称Object名称,即可上传您OSS中的函数代码。使用示例代码:选择后,即可使用函数计算的示例代码。Node.JS 12.x
函数入口填写函数入口。格式为[文件名].[函数名]。index.handler
高级设置
函数实例类型选择适合您的实例类型。弹性实例****性能实例更多信息,请参见实例规格及使用模式弹性实例
函数执行内存设置函数执行内存。选择输入:单击函数执行内存,在下拉列表中选择所需内存。手动输入:单击手动输入,可自定义函数执行内存。输入的内存必须为64 MB的倍数。512 MB
超时时间设置超时时间。默认超时时间为60秒,最长为600秒。说明 超过设置的超时时间,函数将以执行失败结束。60
单实例并发度单个实例能够并发处理的请求数。更多信息,请参见单实例多并发简介Python语言不支持设置实例并发度。
选择您需要加载的层。更多信息,请参见层概述NodeJS
修改代码
  1. 登录函数计算控制台

  2. 在顶部菜单栏,选择地域。

  3. 在左侧导航栏,单击服务及函数

  4. 点击函数名称,进入函数详情页面:

在这里插入图片描述

5.选择代码执行标签,编辑代码:

在这里插入图片描述

node代码修改如下:

var getRawBody = require('raw-body');
var getFormBody = require('body/form');
var body = require('body');


/*
To enable the initializer feature (https://help.aliyun.com/document_detail/156876.html)
please implement the initializer function as below:
exports.initializer = (context, callback) => {
  console.log('initializing');
  callback(null, '');
};
*/

exports.handler = (req, resp, context) => {
   
 		resp.send('hello world');
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

6.在代码执行标签页的最后,可以对函数进行测试:

在这里插入图片描述

3.2.4 Funcraft快速搭建Spring Boot环境

安装Funcraft

详见https://help.aliyun.com/document_detail/161136.htm?spm=a2c4g.11186623.2.6.6f756fcf9hheNP#section-jni-z2b-zrf

配置Funcraft
  1. 执行以下命令初始化Funcraft工具,配置账号信息。

    fun config
    
    • 1
  2. 根据提示依次配置Account ID(阿里云账号ID)、AccessKey ID、AccessKey Secret、Default Region Name。

    如果您的账号是RAM用户,Account ID需要配置为阿里云账号的ID,AccessKey ID、AccessKey Secret为RAM用户的密钥。

    完成配置后,Funcraft会将配置保存到用户目录下的.fcli/config.yaml文件中。

创建初始化模板
  1. 执行以下命令初始化项目模板。

    fun init -n demo
    
    • 1
  2. 根据提示选择一个项目模板。

在这里插入图片描述

项目模板类型如下:

  • event-为前缀的模板是普通的事件函数。
  • http-trigger为前缀的模板会默认为您创建HTTP触发器。HTTP触发器以Request、Response为入参,帮助您快速搭建Web应用。

本示例中,选择http-trigger-spring-boot的模板,使用Idea加载项目,项目结构如下:

在这里插入图片描述

本地编译

通过 fun build 可以对项目进行编译构建:

fun build
  • 1

执行效果如下:

img

部署到云端
  1. 执行以下命令将函数部署到云端。

    fun deploy
    
    • 1
  2. 部署过程中,输入Y确认需要创建的资源。

    deploy

    创建完成后,提示service demo deploy success代表您的资源部署成功。

3.2.5 迁移Spring Boot到函数计算

  1. 创建一个Spring Boot项目,详情请参见Spring Quickstart Guide

    在IDEA中选择File-New Project创建新项目,选择Spring Initializr,在右侧的服务URL中输入:https//start.aliyun.com

在这里插入图片描述

选择lombok和spring web两个组件:

在这里插入图片描述

删除多余的文件,项目结构如下图所示:

在这里插入图片描述

(重要)修改pom文件:

​ 删除pom文件中如下这段

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

添加parent的继承关系:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.7.RELEASE</version>
    </parent>
  • 1
  • 2
  • 3
  • 4
  • 5

添加测试接口:

package com.itheima.love520demo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author itheima
 * @version 1.0
 * @date 2021/5/13 18:25
 */
@RestController
public class TestController {

    @RequestMapping("/test")
    public String test(){
        return "itheima";
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  1. 执行以下命令进入刚创建的示例项目或您已有的项目。

    cd <project-name>
    
    • 1
  2. 在项目的根目录下执行mvn package -Dmaven.test.skip=true命令打包。

    编译输出结果与以下示例类似。

    mvn package -Dmaven.test.skip=true
    
    • 1
    [INFO] Scanning for projects...
    [INFO] 
    [INFO] ----------------------< com.example:Spring-Boot >-----------------------
    [INFO] Building Spring-Boot 0.0.1-SNAPSHOT
    [INFO] --------------------------------[ jar ]---------------------------------
    [INFO] 
    [INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ Spring-Boot ---
    ... ... ...
    [INFO] 
    [INFO] Results:
    [INFO] 
    [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
    [INFO] 
    [INFO] 
    [INFO] --- maven-jar-plugin:3.1.2:jar (default-jar) @ Spring-Boot ---
    [INFO] Building jar: /Users/txd123/Desktop/Spring-Boot/target/Spring-Boot-0.0.1-SNAPSHOT.jar
    [INFO] 
    [INFO] --- spring-boot-maven-plugin:2.2.6.RELEASE:repackage (repackage) @ Spring-Boot ---
    [INFO] Replacing main artifact with repackaged archive
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time:  38.850 s
    [INFO] Finished at: 2020-03-31T15:09:34+08:00
    [INFO] ------------------------------------------------------------------------
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
  3. 执行fun deploy -y命令将项目部署至函数计算。

    Funcraft会自动进入部署流程。

    fun deploy -y
    
    • 1
    current folder is not a fun project.
    Generating template.yml...
    Generate Fun project successfully!
    ========= Fun will use 'fun deploy' to deploy your application to Function Compute! =========
    using region: cn-qingdao
    using accountId: ***********3743
    using accessKeyId: ***********Ptgk
    using timeout: 60
    
    Collecting your services information, in order to caculate devlopment changes...
    
    Resources Changes(Beta version! Only FC resources changes will be displayed):
    
                   trigger httpTrigger deploy success
           function Spring-Boot deploy success
    service Spring-Boot deploy success
    
    Detect 'DomainName:Auto' of custom domain 'Domain'
    Request a new temporary domain ...
    The assigned temporary domain is 15639196-XXX.test.functioncompute.com,expired at 2020-04-10 15:19:56, limited by 1000 per day.
    Waiting for custom domain Domain to be deployed...
    custom domain Domain deploy success
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在控制台找到URL路径中的Path,将其复制下来:

在这里插入图片描述

修改application.properties文件,添加访问路径:

server.servlet.context-path=/2016-08-15/proxy/love520demo/love520demo
  • 1

重新执行打包和部署:

mvn package -Dmaven.test.skip=true
fun deploy -y
  • 1
  • 2

如果在页面上调用测试不成功,可能是因为URL中版本被添加了.LATEST的关系,可以使用POSTMAN进行测试

postman测试结果如下:

在这里插入图片描述

四、阿里云对象存储OSS入门

阿里云对象存储OSS(Object Storage Service)是阿里云提供的海量、安全、低成本、高持久的云存储服务。其数据设计持久性不低于99.9999999999%(12个9),服务可用性(或业务连续性)不低于99.995%。OSS具有与平台无关的RESTful API接口,可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。表白小程序将使用OSS上传二维码和用户自定义的图片。

4.1 创建存储空间

  1. 登录OSS管理控制台

  2. 单击Bucket列表,然后单击创建Bucket

    您也可以单击概览,然后单击右上角的创建Bucket

  3. 创建Bucket面板,按如下说明配置必要参数。其他参数均可保持默认配置,也可以在Bucket创建完成后单独配置。

    参数描述
    Bucket名称Bucket的名称。Bucket一旦创建,则无法更改其名称。命名规则如下:Bucket名称必须全局唯一。只能包括小写字母、数字和短划线(-)。必须以小写字母或者数字开头和结尾。长度必须在3~63字节之间。
    地域Bucket的数据中心。Bucket一旦创建,则无法更改其所在地域。如需通过ECS内网访问OSS,请选择与ECS相同的地域。更多信息,请参见OSS访问域名使用规则
    同城冗余存储OSS同城冗余存储采用多可用区(AZ)机制,将用户的数据以冗余的方式存放在同一地域(Region)的3个可用区。当某个可用区不可用时,仍然能够保障数据的正常访问。启用:开启同城冗余存储,则Bucket内的Object将以同城冗余的方式进行存储。例如,Bucket存储类型为标准存储,则该Bucket内的Object默认为标准存储(同城冗余)。详情请参见同城冗余存储注意仅华南1(深圳)、华北2(北京)、华东1(杭州)、华东2(上海)、中国(香港)、新加坡地域支持开启同城冗余存储。仅允许创建Bucket时开启同城冗余存储。开启后不支持关闭,请谨慎操作。关闭:默认不开启同城冗余存储,则Bucket内的Object将以本地冗余的方式进行存储。例如,Bucket存储类型为标准存储,则该Bucket内的Object默认为标准存储(本地冗余)。

在这里插入图片描述

在这里插入图片描述

  1. 单击确定

4.2 上传文件

  1. 登录OSS管理控制台

  2. 单击左侧导航栏的Bucket列表,然后单击目标Bucket名称。

  3. 文件管理页签,单击上传文件

  4. 上传文件面板,按如下说明配置各项参数。

在这里插入图片描述

参数说明
上传到设置文件上传到OSS后的存储路径。当前目录:将文件上传到当前目录。指定目录:将文件上传到指定目录,您需要输入目录名称。若输入的目录不存在,OSS将自动创建对应的文件夹并将文件上传到该文件夹中。
文件ACL选择文件的读写权限。继承Bucket:以Bucket读写权限为准。私有(推荐):只有文件Owner拥有该文件的读写权限,其他用户没有权限操作该文件。公共读:文件Owner拥有该文件的读写权限,其他用户(包括匿名访问者)都可以对文件进行访问,这有可能造成您数据的外泄以及费用激增,请谨慎操作。公共读写:任何用户(包括匿名访问者)都可以对文件进行访问,并且向该文件写入数据。这有可能造成您数据的外泄以及费用激增,若被人恶意写入违法信息还可能会侵害您的合法权益。除特殊场景外,不建议您配置公共读写权限。有关文件ACL的更多信息,请参见Object ACL
待上传文件选择您需要上传的文件或文件夹。您可以单击扫描文件扫描文件夹选择本地文件或文件夹,或者直接拖拽目标文件或文件夹到待上传文件区域。如果上传文件夹中包含了无需上传的文件,请单击目标文件右侧的移除将其移出文件列表。注意如果上传的文件与存储空间中已有的文件重名,则会覆盖已有文件。使用拖拽方式上传文件夹时,OSS会保留文件夹内的所有文件和子文件夹。文件上传过程中,请勿刷新或关闭页面,否则上传任务会被中断且列表会被清空。
  1. 单击上传文件

    此时,您可以在上传列表页签查看各个文件的上传进度。上传完成后,您可以在目标路径下查看上传文件的文件名、文件大小以及存储类型等信息。

在这里插入图片描述

4.3 下载文件

  1. 登录OSS管理控制台

  2. 单击左侧导航栏的Bucket列表,然后单击目标Bucket名称。

  3. 单击左侧导航栏的文件管理,下载单个或多个文件。

在这里插入图片描述

  • 下载单个文件

    方式一:单击目标文件右侧的更多> 下载

    方式二:单击目标文件的文件名或其右侧的详情,在弹出的详情面板中单击下载

  • 下载多个文件

    选中多个文件,选择批量操作 > 下载。通过OSS控制台可一次批量下载最多100个文件。

五、表白小程序服务端开发

5.1 系统设计

系统设计核心要点:

  • 成本极低
  • 部署难度低
  • 支持动态扩容

为了保证服务部署难度和成本较低,使用了FC函数计算服务(Serverless)。存储层将所有的LOGO图片和表白信息都以文件的方式保存到阿里云OSS对象存储上,既能保证对大量文件保存的支持,也减少了部署数据库的成本。

注:由于小程序以个人身份不支持页面直接跳转,所以扫描二维码跳转小程序无法实现,所以需要单独部署表白信息的展示服务(tomcat),所以需要一台低成本的服务器。如以企业申请小程序,可直接在小程序中开发界面展示表白信息,真正做到访问量小的时候0成本。

在这里插入图片描述

5.2 小程序uni-app开发框架介绍

本小程序使用uni-app开发,uni-app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到iOS、Android、Web(响应式)、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉/淘宝)、快应用等多个平台。

uni-app在手,做啥都不愁。即使不跨端,uni-app也是更好的小程序开发框架、更好的App跨平台框架、更方便的H5开发框架。不管领导安排什么样的项目,你都可以快速交付,不需要转换开发思维、不需要更改开发习惯。

在这里插入图片描述

5.3 环境搭建

5.3.1 创建服务端程序

请按照3.2.4的方式搭建初始环境

5.3.2 集成工具模块

1.添加依赖:

    	<!--阿里云OSS依赖-->
		<dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>3.10.2</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
            <version>4.1.1</version>
        </dependency>
		<!--二维码依赖-->
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
            <version>3.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

2.将资料中的工具类OssUtil和QRCodeKit添加到utils包下。

OSSUtil工具类:

package com.itheima.love520.utils;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.GetObjectRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;

/**
 * @author brianxia
 * @version 1.0
 * @date 2021/4/18 23:45
 */

@Component
public class OssUtil {

    @Value("${oss.bucketName}")
    private String bucketName;
    @Value("${oss.accessKeyId}")
    private String accessKeyId;
    @Value("${oss.accessKeySecret}")
    private String accessKeySecret;
    @Value("${qr.image.path}")
    private String qrPath;

    public OSS createClient() {
        // Endpoint以杭州为例,其它Region请按实际情况填写。
        String endpoint = "https://oss-cn-beijing.aliyuncs.com";
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        return ossClient;
    }

    /**
     * 以流的方式保存,用于保存文件
     * @param objectName 文件名
     * @param stream 流对象
     */
    public void save(String objectName, InputStream stream) {
        OSS client = createClient();
        // 上传文件到指定的存储空间(bucketName)并将其保存为指定的文件名称(objectName)。
        client.putObject(bucketName, objectName, stream);
        // 关闭OSSClient。
        client.shutdown();
    }

    /**
     * 保存字符串
     * @param objectName 文件名
     * @param content   字符串
     */
    public void save(String objectName, String content) {
        OSS client = createClient();
        // 上传文件到指定的存储空间(bucketName)并将其保存为指定的文件名称(objectName)。
        client.putObject(bucketName, objectName, new ByteArrayInputStream(content.getBytes()));
        // 关闭OSSClient。
        client.shutdown();
    }

    /**
     * 保存二维码
     * @param objectName 文件名
     * @param context   二维码内容
     * @param logo      中间的logo图
     */
    public void saveQr(String objectName, String context,String logo) throws IOException {
        BufferedImage image = null;
        if(StringUtils.isEmpty(logo)){
            image = QRCodeKit.createQRCode(context);
        }else{
            String path = loadFile(logo);
            File file = new File(path);
            image = QRCodeKit.createQRCodeWithLogo(context,file);
            if(file.exists()){
                file.delete();
            }
        }

        if(image == null){
            return;
        }

        ByteArrayOutputStream os = new ByteArrayOutputStream();
        ImageIO.write(image, "png", os);
        InputStream qrCode = new ByteArrayInputStream(os.toByteArray());

        try{
            OSS client = createClient();
            // 上传文件到指定的存储空间(bucketName)并将其保存为指定的文件名称(objectName)。
            client.putObject(bucketName, objectName, qrCode);
            // 关闭OSSClient。
            client.shutdown();
        }finally {
            if(qrCode != null){
                try {
                    qrCode.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    /**
     * 加载文件
     * @param objectName 文件名
     * @return 文件内容字符串
     */
    public String loadFile(String objectName) {
        String path = qrPath + objectName;
        OSS ossClient = createClient();
        // 下载Object到本地文件,并保存到指定的本地路径中。如果指定的本地文件存在会覆盖,不存在则新建。
        // 如果未指定本地路径,则下载后的文件默认保存到示例程序所属项目对应本地路径中。
        ossClient.getObject(new GetObjectRequest(bucketName, objectName), new File(path));
        // 关闭OSSClient。
        ossClient.shutdown();

        return path;
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129

二维码工具类:

package com.itheima.love520.utils;

import com.google.zxing.*;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Base64OutputStream;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.HashMap;
import java.util.Map;

public class QRCodeKit {

    public static final String QRCODE_DEFAULT_CHARSET = "UTF-8";

    public static final int QRCODE_DEFAULT_HEIGHT = 400;

    public static final int QRCODE_DEFAULT_WIDTH = 400;

    public static final int LOGO_DEFAULT_HEIGHT = 100;
    public static final int LOGO_DEFAULT_WIDTH = 100;

    private static final int BLACK = 0xFF000000;
    private static final int WHITE = 0xFFFFFFFF;

    public static void main(String[] args) throws IOException, NotFoundException{
        String data = "http://www.baidu.com";
        File logoFile = new File("logo.png");
        BufferedImage image = QRCodeKit.createQRCodeWithLogo(data, logoFile);
        ImageIO.write(image, "png", new File("result7.png"));
        System.out.println("done");
    }

    /**
     * Create qrcode with default settings
     *
     * @param data
     * @return
     */
    public static BufferedImage createQRCode(String data) {
        return createQRCode(data, QRCODE_DEFAULT_WIDTH, QRCODE_DEFAULT_HEIGHT);
    }

    /**
     * Create qrcode with default charset
     *
     * @param data
     * @param width
     * @param height
     * @return
     */
    public static BufferedImage createQRCode(String data, int width, int height) {
        return createQRCode(data, QRCODE_DEFAULT_CHARSET, width, height);
    }

    /**
     * Create qrcode with specified charset
     *
     * @param data
     * @param charset
     * @param width
     * @param height
     * @return
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static BufferedImage createQRCode(String data, String charset, int width, int height) {
        Map hint = new HashMap();
        hint.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        hint.put(EncodeHintType.CHARACTER_SET, charset);

        return createQRCode(data, charset, hint, width, height);
    }

    /**
     * Create qrcode with specified hint
     *
     * @param data
     * @param charset
     * @param hint
     * @param width
     * @param height
     * @return
     */
    public static BufferedImage createQRCode(String data, String charset, Map<EncodeHintType, ?> hint, int width,
                                             int height) {
        BitMatrix matrix;
        try {
            matrix = new MultiFormatWriter().encode(new String(data.getBytes(charset), charset), BarcodeFormat.QR_CODE,
                    width, height, hint);
            return toBufferedImage(matrix);
        } catch (WriterException e) {
            throw new RuntimeException(e.getMessage(), e);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }
    public static BufferedImage toBufferedImage(BitMatrix matrix) {
        int width = matrix.getWidth();
        int height = matrix.getHeight();
        BufferedImage image = new BufferedImage(width, height,
                BufferedImage.TYPE_INT_RGB);
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                image.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE);
            }
        }
        return image;
    }
    /**
     * Create qrcode with default settings and logo
     *
     * @param data
     * @param logoFile
     * @return
     */
    public static BufferedImage createQRCodeWithLogo(String data, File logoFile) {
        return createQRCodeWithLogo(data, QRCODE_DEFAULT_WIDTH, QRCODE_DEFAULT_HEIGHT, logoFile);
    }

    /**
     * Create qrcode with default charset and logo
     *
     * @param data
     * @param width
     * @param height
     * @param logoFile
     * @return
     */
    public static BufferedImage createQRCodeWithLogo(String data, int width, int height, File logoFile) {
        return createQRCodeWithLogo(data, QRCODE_DEFAULT_CHARSET, width, height, logoFile);
    }

    /**
     * Create qrcode with specified charset and logo
     *
     * @param data
     * @param charset
     * @param width
     * @param height
     * @param logoFile
     * @return
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static BufferedImage createQRCodeWithLogo(String data, String charset, int width, int height, File logoFile) {
        Map hint = new HashMap();
        hint.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        hint.put(EncodeHintType.CHARACTER_SET, charset);

        return createQRCodeWithLogo(data, charset, hint, width, height, logoFile);
    }

    /**
     * Create qrcode with specified hint and logo
     *
     * @param data
     * @param charset
     * @param hint
     * @param width
     * @param height
     * @param logoFile
     * @return
     */
    public static BufferedImage createQRCodeWithLogo(String data, String charset, Map<EncodeHintType, ?> hint,
                                                     int width, int height, File logoFile) {
        try {
            BufferedImage qrcode = createQRCode(data, charset, hint, width, height);
            BufferedImage logo2 = ImageIO.read(logoFile);

            BufferedImage logo =new BufferedImage(LOGO_DEFAULT_WIDTH,LOGO_DEFAULT_HEIGHT,BufferedImage.TYPE_INT_RGB);
            Graphics graphics=logo.getGraphics();
            //将原始位图缩小后绘制到bufferedImage对象中
            graphics.drawImage(logo2,0,0,LOGO_DEFAULT_WIDTH,LOGO_DEFAULT_HEIGHT,null);

            int deltaHeight = height - logo.getHeight();
            int deltaWidth = width - logo.getWidth();

            BufferedImage combined = new BufferedImage(height, width, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g = (Graphics2D) combined.getGraphics();
            g.drawImage(qrcode, 0, 0, null);
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f));
            g.drawImage(logo, (int) Math.round(deltaWidth / 2), (int) Math.round(deltaHeight / 2), null);

            return combined;
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage(), e);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * Return base64 for image
     *
     * @param image
     * @return
     */
    public static String getImageBase64String(BufferedImage image) {
        String result = null;
        try {
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            OutputStream b64 = new Base64OutputStream(os);
            ImageIO.write(image, "png", b64);
            result = os.toString("UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e.getMessage(), e);
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
        return result;
    }

    /**
     * Decode the base64Image data to image
     *
     * @param base64ImageString
     * @param file
     */
    public static void convertBase64StringToImage(String base64ImageString, File file) {
        FileOutputStream os;
        try {
            Base64 d = new Base64();
            byte[] bs = d.decode(base64ImageString);
            os = new FileOutputStream(file.getAbsolutePath());
            os.write(bs);
            os.close();
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e.getMessage(), e);
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage(), e);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240

3.创建resources文件夹,并添加配置文件application.properties:

oss.bucketName=OSS仓库名
oss.accessKeyId=OSS的ak
oss.accessKeySecret=OSS的SK
qr.url=http://www.cloudprogrammer.cn:8090/show/confess
qr.image.path=/tmp/
  • 1
  • 2
  • 3
  • 4
  • 5

5.4 接口开发

5.4.1 接口文档

流程设计

整体的流程分为两个阶段:

1.表白人通过小程序进行操作,生成二维码。

2.表白对象扫描二维码,展示表白页面。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DsVW8QgW-1651140767946)(pic\520表白小程序业务逻辑流程 .jpg)]

1.小程序的用户点击上传LOGO按钮,将LOGO图片进行上传。后台系统会将此图片存储。

2.小程序的用户编写表白对象、表白内容、选择模板,完成之后点击生成二维码的按钮。

3.后台系统将上述所有的数据保存,并生成最终的访问链接,以二维码的方式返回。

在这里插入图片描述

1.表白人将二维码发给表白对象,表白对象扫描二维码。

2.向后端发起请求,后端获取之前保存的表白数据,生成页面。

3.表白对象看到表白的内容。

上传LOGO图片

接口地址:/2016-08-15/proxy/love520demo/love520demo/confess/upload

请求方式:POST

请求数据类型:multipart/form-data

响应数据类型:*/*

接口描述:

生成随机文件名,将文件保存到OSS中,返回文件名。

参数名称参数说明in是否必须数据类型schema
file图片文件formDatatruefile

响应参数:

文件名,是一个随机字符串

响应示例:

5609a150-22cf-4d8f-a8a4-7d673444b617
  • 1
保存表白内容

接口地址:/2016-08-15/proxy/love520demo/love520demo/confess/save

请求方式:POST

请求数据类型:application/json

响应数据类型:*/*

接口描述:

小程序点击生成二维码时,向后端发送请求,保存表白内容

请求示例:

{
	"content": "",
	"logo": "",
	"template": "",
	"to": ""
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

请求参数:

参数名称参数说明in是否必须数据类型schema
confessconfessbodytrue表白内容实体定义表白内容实体定义
  content表白内容falsestring
  logo二维码中间的logo图片falsestring
  template用户选择的模板falsestring
  to表白对象falsestring

响应参数:

文件名,是一个随机字符串

响应示例:

5609a150-22cf-4d8f-a8a4-7d673444b617
  • 1
表白展示接口

接口地址:/show/confess

请求方式:GET

请求数据类型:*

响应数据类型:*/*

接口描述:

请求参数:

参数名称参数说明是否必须数据类型schema
id保存表白内容返回的IDtrueString

响应参数:

展示页面

5.4.2 代码编写

创建表白实体类(也可以使用工具自动生成 https://www.bejson.com/json2javapojo/new/):

package com.itheima.love520.entity;

import lombok.Data;

/**
 * 表白内容实体定义
 * @author itheima
 * @version 1.0
 * @date 2021/4/18 23:35
 */
@Data
public class Confess {
    //表白对象
    private String to;
    //表白内容
    private String content;
    //二维码中间的logo图片
    private String logo;
    //用户选择的模板
    private String template;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

创建接口Controller:

package com.itheima.love520demo.controller;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.itheima.love520demo.entity.Confess;
import com.itheima.love520demo.utils.OssUtil;
import lombok.extern.java.Log;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;
import java.util.UUID;

/**
 * @author itheima
 * @version 1.0
 * @date 2021/4/18 23:34
 */
@RestController
@CrossOrigin("*")
@RequestMapping("/confess")
@Slf4j
public class ConfessController {
    /**
     * JSON转换器
     */
    @Autowired
    private ObjectMapper objectMapper;
    /**
     * OSS上传下载工具类
     */
    @Autowired
    private OssUtil ossUtil;
    /**
     * 表白的展示页面,已提供
     */
    @Value("${qr.url}")
    private String qrUrl;

    /**
     * 上传图片接口
     * @param file 文件对象
     * @return 文件名,是一个随机字符串
     */
    @PostMapping("/upload")
    public String uploadFile(MultipartFile file) throws Exception {

        //生成随机字符串作为文件名
        String uuid = UUID.randomUUID().toString();
        try {
            //存储图片
            ossUtil.save(uuid,file.getInputStream());
        } catch (Exception e) {
            log.error("二维码图片保存失败",e);
        }
        return uuid;
    }


    /**
     * 保存表白内容
     * @param confess 表白内容
     * @return 文件名,是一个随机字符串
     */
    @PostMapping("/save")
    public String saveConfess(@RequestBody Confess confess) throws Exception {

        //生成随机文件名
        String uuid = UUID.randomUUID().toString();
        String qrRealUrl = qrUrl + "?id=" + uuid;
        try {
            //存储数据
            ossUtil.save(uuid,objectMapper.writeValueAsString(confess));
            //存储二维码
            ossUtil.saveQr(uuid + "qr.jpg",qrRealUrl,confess.getLogo());
        } catch (IOException e) {
            log.error("表白保存失败",e);
        }
        return uuid;
    }


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88

查询接口请复制资料/初始工程中的love520-show到本地工作目录中,并使用idea打开工程:

在这里插入图片描述

在controller下编写接口:

package com.itheima.love520show.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.itheima.love520show.entity.Confess;
import com.itheima.love520show.util.OssUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * @author itheima
 * @version 1.0
 * @date 2021/4/20 23:58
 */
@Controller
@RequestMapping("/show")
public class ShowController {
    @Autowired
    OssUtil ossUtil;
    @Autowired
    ObjectMapper mapper;

    @RequestMapping("/confess")
    public String confess(HttpServletRequest request,Model model) throws IOException {
        String id = request.getParameter("id");
        String load = ossUtil.load(id);
        Confess confess = mapper.readValue(load, Confess.class);
        model.addAttribute("to",confess.getTo());
        model.addAttribute("content",confess.getContent());
        return "template" + confess.getTemplate();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

5.5 接口测试

使用POSTMAN测试接口,上传图片接口:

在这里插入图片描述

表白内容保存接口:

在这里插入图片描述

运行love520-show项目,访问地址:

http://localhost:8090/show/confess?id=45eaa681-c252-4dce-98b6-c71aa6082db8

显示如下内容,代码编写成功:

在这里插入图片描述

5.6 前后端联调测试

5.6.1 部署函数服务

将项目中application.properties文件中的展示地址修改为最终服务器的ip地址或者域名:

qr.url=http://www.cloudprogrammer.cn:8090/show/confess
  • 1

双击maven命令中的package,对当前项目进行打包:

在这里插入图片描述

成功之后,在当前目录下执行命令

fun deploy -y
  • 1

如下图所示,已经显示成功

在这里插入图片描述

5.6.2 部署展示服务

同5.4.1中,将项目先进行打包,然后将项目上传到服务器中:

在这里插入图片描述

执行命令:

 nohup java -jar love520-show.jar &
  • 1

5.6.3 前端调试

使用HbuilderX打开前端代码,HbuilderX下载地址:https://www.dcloud.io/hbuilderx.html

在这里插入图片描述

修改接口地址和OSS地址(129行):

        // 修改为程序真实地址
        action: 'https://1847961572749689.cn-hangzhou.fc.aliyuncs.com/2016-08-15/proxy/love520-1/love520-1/confess/upload',
        saveUrl : 'https://1847961572749689.cn-hangzhou.fc.aliyuncs.com/2016-08-15/proxy/love520-1/love520-1/confess/save',
		ossUrl : 'https://itheima-test20210509.oss-cn-beijing.aliyuncs.com/',
  • 1
  • 2
  • 3
  • 4

点击真机调试,就可以看到画面并运行程序了:

在这里插入图片描述

在这里插入图片描述

六、总结

本次课程我们通过使用阿里云的FC函数计算和OSS对象存储打造了一款超低成本的520表白小程序,大家也感受到了Serverless带给我们的独特魅力。

Serverless的优势:

1.超低成本,每个月表白小程序的调用前100万次免费,OSS也有很大的免费使用量。

2.弹性扩容,当用户量增大时阿里云会自动增加服务器以提供支持。

3.降低运维成本,不需要运维人员参与,开发人员上传代码即可发布最新版本。

典型应用场景:

发布小程序注意事项

发布小程序注意事项

如果需要将小程序进行发布,还需要在上传及生成二维码时对文本和图片进行内容安全审核,可以调用小程序相应的内容安全代码(详见https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/sec-check/security.imgSecCheck.html)。

以下给出示例代码,图片审核:

        Map map1 = restTemplate.getForObject("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=小程序ID&secret=小程序secret", Map.class);
        String token = (String) map1.get("access_token");
        Boolean aBoolean = PickCheckUtil.checkPic(file,token);
        if(!aBoolean){
            return "error";
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

文本审核:

        String content = confess.getTo() + "," + confess.getContent();
        Map map1 = restTemplate.getForObject("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=小程序ID&secret=小程序secret", Map.class);
        String token = (String) map1.get("access_token");
        HashMap<Object, Object> param = new HashMap<>();
        param.put("content",content);
        Map map = restTemplate.postForObject("https://api.weixin.qq.com/wxa/msg_sec_check?access_token=" + token, param, Map.class);
        Integer errcode = (Integer) map.get("errcode");
        if(errcode != 0){
            return "error";
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

(https://help.aliyun.com/document_detail/148417.html)

发布小程序注意事项

发布小程序注意事项

如果需要将小程序进行发布,还需要在上传及生成二维码时对文本和图片进行内容安全审核,可以调用小程序相应的内容安全代码(详见https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/sec-check/security.imgSecCheck.html)。

以下给出示例代码,图片审核:

        Map map1 = restTemplate.getForObject("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=小程序ID&secret=小程序secret", Map.class);
        String token = (String) map1.get("access_token");
        Boolean aBoolean = PickCheckUtil.checkPic(file,token);
        if(!aBoolean){
            return "error";
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

文本审核:

        String content = confess.getTo() + "," + confess.getContent();
        Map map1 = restTemplate.getForObject("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=小程序ID&secret=小程序secret", Map.class);
        String token = (String) map1.get("access_token");
        HashMap<Object, Object> param = new HashMap<>();
        param.put("content",content);
        Map map = restTemplate.postForObject("https://api.weixin.qq.com/wxa/msg_sec_check?access_token=" + token, param, Map.class);
        Integer errcode = (Integer) map.get("errcode");
        if(errcode != 0){
            return "error";
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/194584?site
推荐阅读
相关标签
  

闽ICP备14008679号