「Serverless云开发72变」实现一个简单的博客

本文涉及的产品
函数计算FC,每月15万CU 3个月
Serverless 应用引擎免费试用套餐包,4320000 CU,有效期3个月
简介: Serverless 大大降低了开发的成本和上线周期,而且免运维 (服务器运维、容量管理、弹性伸缩等),按资源的使用量付费使得上线后的成本极低。本文将通过云开发与OTS数据库详细介绍如果快速实现一个简单的博客,文末有福利。


写在之前


今年8.03-8.10,我有幸参加了阿里云的云开发校园合伙人创造营,成为了云开发校园合伙人。这篇文章是对之前学习的总结和我自己对阿里云severless云开发的一些经验。水平有限,多多包涵!!


开发前的准备工作


首先你得有一个阿里云账号,之后在谷歌浏览器中输入https://workbench.aliyun.com/ 点击免费云开发登录云开发平台,创建一个新应用。有很多应用场景,根据自己的需求选择即可。我们这里选择实验室,选择midway serverless ots数据库示例。(因为ots数据库基本免费)。


输入应用名称和应用介绍,点击完成。稍等一会,项目就创建成啦。查看环境管理里面依赖的云服务,如果还有未开通的服务,开通即可,都是免费,知道环境管理旁边绿色对勾出现。



创建完成以后点击应用配置,在浏览器输入https://www.aliyun.com/product/ots,点击管理控制台,点击创建实例,输入名称,点击确定。点击创建好的实例,把实例名称和公网分别复制到应用配置中的实例名和endPoint上,点击自己的头像,查看自己的accesskey与secret,并复制自己的accesskey与secret。






点击创建数据表,创建两个表blog和user。设置blog的主键为id,user的主键为username和password。


之后点击创建数据表,创建完成后返回项目页面




点击开发部署


ok,熟悉的味道



安装依赖


npm i


试这运行一下


npm run dev


来看一下demo的页面


至此准备工作就完成啦


Fass能做什么


当前的函数,可以当做一个小容器,原来我们要写一个完整的应用来承载能力,现在只需要写中间的逻辑部分,以及考虑输入和输出的数据。


随着时间的更替,平台的迭代,函数的能力会越来越强,而用户的上手成本,服务器成本则会越来越低。


Midway Serverless


Midway Serverless 是用于构建 Node.js 云函数的 Serverless 框架。帮助你在云原生时代大幅降低维护成本,更专注于产品研发。


基本使用方式就是在f.yml里面配置路由,通过装饰器实现函数的依赖注入


官网介绍https://www.yuque.com/midwayjs/faas/


编写后端接口


编写注册函数


首先在f.yml里functions中配置register函数,注意格式


functions:
  register:
    handler: user.register
    events:
      - apigw:
          path: /api/user/register


之后在src/apis/index.ts里,把默认的几个函数删除


新增一个register函数


@Func('user.register')
asyncregister() {
const { username, password } =this.ctx.request.body;
constparams= {
tableName: "user",
condition: newTableStore.Condition(TableStore.RowExistenceExpectation.IGNORE, null),
primaryKey: [
        { username }, { password }
      ]
    };
returnnewPromise(resolve=> {
this.tb.putRow(params, asyncfunction (err, data) {
if (err) {
resolve({
success: false,
errmsg: err.message          });
        } else {
resolve({
success: true          });
        }
      });
    });
  }


编写登入函数


配置f.yml


  login:
    handler: user.login
    events:
      - apigw:
          path: /api/user/login


编写login函数


@Func('user.login')
asynclogin() {
const { username, password } =this.ctx.request.body;
constparams= {
tableName: 'user',
primaryKey: [{ username }, { password }],
direction: TableStore.Direction.BACKWARD    };
returnnewPromise(resolve=> {
this.tb.getRow(params, async (_, data) => {
awaitformat.row(data.row)
constrow=format.row(data.row)
if (row) {
resolve({
author: row.username,
success: true          });
        } else {
resolve({ success: false });
        }
      });
    })
  }


编写获取博客列表函数


配置f.yml


  list:
    handler: blog.list
    events:
      - apigw:
          path: /api/blog/list


编写list函数


@Func('blog.list')
asynchandler() {
constparams= {
tableName: 'blog',
direction: TableStore.Direction.BACKWARD,
inclusiveStartPrimaryKey: [{ id: TableStore.INF_MAX }],
exclusiveEndPrimaryKey: [{ id: TableStore.INF_MIN }]
    };
returnnewPromise(resolve=> {
this.tb.getRange(params, (_, data) => {
constrows=format.rows(data, { email: true });
resolve(rows);
      });
    })
  }


编写博客详情页函数


配置f.yml 文件


  detail:
    handler: blog.detail
    events:
      - apigw:
          path: /api/blog/detail


编写detail函数


@Func('blog.detail')
asyncdetail() {
const { id } =this.ctx.query;
constparams= {
tableName: 'blog',
primaryKey: [{ 'id': id }],
direction: TableStore.Direction.BACKWARD,
inclusiveStartPrimaryKey: [{ id: TableStore.INF_MAX }],
exclusiveEndPrimaryKey: [{ id: TableStore.INF_MIN }]
    };
returnnewPromise(resolve=> {
this.tb.getRow(params, (_, data) => {
constrow=format.row(data.row);
resolve(row);
      });
    })
  }


编写删除当前博客函数


配置f.yml


  del:
    handler: blog.del
    events:
      - apigw:
          path: /api/blog/del


编写remove函数


@Func('blog.del')
asyncremove() {
const { id } =this.ctx.query;
constparams= {
tableName: "blog",
condition: newTableStore.Condition(TableStore.RowExistenceExpectation.IGNORE, null),
primaryKey: [{ id }]
    };
returnnewPromise(resolve=> {
this.tb.deleteRow(params, function (err, data) {
if (err) {
resolve({
success: false,
errmsg: err.message          });
        } else {
resolve({
success: true          });
        }
      });
    });
  }


编写新建博客的函数


配置f.yml 文件


  new:
    handler: blog.new
    events:
      - apigw:
          path: /api/blog/new


编写 add 函数


@Func('blog.new')
asyncadd() {
const { content, title, author } =this.ctx.query;
constparams= {
tableName: "blog",
condition: newTableStore.Condition(TableStore.RowExistenceExpectation.IGNORE, null),
primaryKey: [
        { id: `${Date.now()}-${Math.random()}` }
      ],
attributeColumns: [
        { content },
        { title },
        { author }
      ]
    };
returnnewPromise(resolve=> {
this.tb.putRow(params, asyncfunction (err, data) {
if (err) {
resolve({
success: false,
errmsg: err.message          });
        } else {
resolve({
success: true          });
        }
      });
    });
  }


编写更新博客的函数


配置f.yml 文件


  update:
    handler: blog.update
    events:
      - apigw:
          path: /api/blog/update


编写update函数


@Func('blog.update')
asyncupdate() {
const { id, content, title, author } =this.ctx.query;
constparams= {
tableName: "blog",
condition: newTableStore.Condition(TableStore.RowExistenceExpectation.IGNORE, null),
primaryKey: [
        { 'id': id },
      ],
attributeColumns: [
        { content },
        { title },
        { author }
      ]
    };
returnnewPromise((resolve) => {
this.tb.putRow(params, function (err, data) {
if (err) {
resolve(false);
        } else {
resolve(true);
        }
      });
    });
  }


使用react编写前端页面


使用ant degsin 作为ui组件 官方文档看这里 https://ant.design/components/overview-cn/


使用 echarts 作为统计用户博客数量的插件 官方文档看这里 https://echarts.apache.org/zh/tutorial.html


使用 axios 调用后端接口 官方文档看这里 http://www.axios-js.com/docs/


使用react-router编写前端路由 官方文档看这里 http://react-guide.github.io/react-router-cn/


这是所需要的package.json文件


{
"name": "midway-faas-ots-demo",
"version": "0.1.0",
"private": true,
"dependencies": {
"@midwayjs/faas": "^0.3.0",
"@midwayjs/faas-middleware-static-file": "^0.0.4",
"echarts": "^4.9.0",
"echarts-for-react": "^2.0.16",
"koa-session": "^6.0.0",
"otswhere": "^0.0.4",
"tablestore": "^5.0.7",
"todomvc-app-css": "^2.3.0"  },
"midway-integration": {
"tsCodeRoot": "src/apis",
"lifecycle": {
"before:package:cleanup": "npm run build"    }
  },
"scripts": {
"dev": "WORKBENCH_ENV=development npm run local:url & npm run watch",
"watch": "react-scripts start",
"local:url": "node scripts/local.js",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"  },
"eslintConfig": {
"extends": "react-app"  },
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"    ],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"    ]
  },
"devDependencies": {
"@midwayjs/faas-cli": "*",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/react": "^16.9.0",
"@types/react-dom": "^16.9.0",
"midway-faas-workbench-dev": "^1.0.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.1",
"typescript": "~3.7.2",
"antd": "^4.5.4",
"axios": "^0.19.2",
"moment": "^2.27.0",
"react-infinite-scroller": "^1.2.4",
"react-router-dom": "^5.2.0"  }
}


覆盖原来的package.json文件后


在命令行输入 npm i 安装依赖


npm i


在src中新建文件index.css


@import'~antd/dist/antd.css';
html,body {  
background-color: #f1f8fd;
height: 100%;
}


编写主菜单组件


先把原来src/components里面的文件清空,


在src/components新建menu.tsx文件


import React, { useState,useEffect } from 'react'
import { Layout, Menu, Input, Button, Row, Col, Card } from 'antd';
import { BrowserRouter, Route, Link} from 'react-router-dom';
import axios from 'axios'
import {InfiniteListExample} from './CardList'
import Tea from './Tea'
import Advise from './Advise'
import Login from './Login'
import Detail from './Detail'
import Update from './Update';
import Register from './Register';
import New from './new'
import {
  HomeOutlined,
  FileTextOutlined,
  CoffeeOutlined,
  AudioOutlined
} from '@ant-design/icons';
const { Search } = Input;
const suffix = (
  <AudioOutlined
    style={{
      fontSize: 16,
      color: '#1890ff',
    }}
  />
);
const { Header, Sider, Content, Footer } = Layout;
export default function SiderDemo() {
  const [collapsed, SetCollapsed] = useState(false);
 const toggle = () => {
     SetCollapsed(!collapsed)
  }
  return (
    <>
    <BrowserRouter>
      <Layout >
        <Sider className='sider' collapsible trigger={null} breakpoint='lg' onBreakpoint={toggle} >
          <Menu className='menu'  mode="inline" defaultSelectedKeys={['1']}>
            <Menu.Item key="1" icon={<HomeOutlined />}>
              <Link to="/">首页</Link>
            </Menu.Item>
            <Menu.Item key="2" icon={<FileTextOutlined />}>
              <Link to="/advise">排行榜</Link>
            </Menu.Item>
            <Menu.Item key="3" icon={ <CoffeeOutlined /> }>
              <Link to="/tea"> 须知 </Link>
            </Menu.Item>
          </Menu>
        </Sider>
        <Layout className="site-layout">
          <Header className="site-layout-background" style={{ padding: 0 }}>
          <Row style={{ background: "white" }}>
            <Col span={6}></Col>
            <Col> 
            <Search
              placeholder="目前还不支持搜索功能"
              style={{
                width: 200,
              }} />
            </Col>
            <Col span={6}></Col>
            <Col><Button type="primary" style={{
            }}><Link to="/login">登入</Link></Button><Button><Link to="/register">注册</Link></Button></Col></Row>
          </Header>
          <Content
            className="site-layout-background"
            style={{
              margin: '24px 16px',
              padding: 24,
              minHeight: 800,
            }}
          >
              <Route path='/' exact render={() =><InfiniteListExample/>}></Route>
            <Route path='/advise' exact render={() => <Advise />}></Route>
            <Route path='/tea' exact render={() => <Tea />}></Route>
            <Route path='/login' exact render={() =>  <Login/>}></Route>
             <Route path='/register' exact render={() =>  <Register/>}></Route>
            <Route path='/detail' exact render={() =>  <Detail/>}></Route>
            <Route path='/update' exact render={() =>  <Update/>}></Route>
            <Route path='/new' exact render={() =>  <New/>}></Route>             
          </Content>
          <Footer style={{ textAlign: 'center' }}>BBBlog ©2020 Created by kunpeng</Footer>
        </Layout>
      </Layout>
    </BrowserRouter>
    </>
  );
}


在src/index.tsx中引入


import React from 'react'
import ReactDOM from 'react-dom';
import './index.css';
import Sider from './components/menu' 
export default function App() {
  return (
    <div>
        <Sider/>
    </div>
  )
}
ReactDOM.render(
    <App />
  ,
  document.getElementById('root')
);


编写对应的css文件


在src中新建style文件夹,新建menu.css文件


#components-layout-demo-custom-trigger.trigger {
font-size: 18px;
line-height: 64px;
padding: 024px;
cursor: pointer;
transition: color0.3s;
  }
#components-layout-demo-custom-trigger.trigger:hover {
color: #1890ff;
  }
#components-layout-demo-custom-trigger.logo {
height: 32px;
background: rgba(255, 255, 255, 0.2);
margin: 16px;
  }
.site-layout.site-layout-background {
background: #fff;
  }
.ant-layout-sider-children{
background: #fff;
  }
.site-layout{
display:flex;
  }


在index.css引入


@import'./style/menu.css';


编写注册组件


新建register.tsx文件


import React, { useState } from 'react';
import { Form, Input, Button, Checkbox } from 'antd';
import axios from 'axios'
const Register = () => {
    const [username, setUsername] = useState('')
    const [password, setPassword] = useState('')
    const handleRegister = () => {
        axios.post('/api/user/register',{
            username,password
        })
        .then(resp => resp.data)
        .then(resp => {
          if (resp) {
            alert(`注册成功,快去登入吧`)
          } else {
            alert(`注册失败`)
          }
        })
    } 
    return (
        <Form
            name="basic"
            layout='inline'
            initialValues={{
                remember: true,
            }}
        >
            <Form.Item
                label="用户名"
                name="用户名"
                rules={[
                    {
                        required: true,
                        message: '请输入你的用户名!',
                    },
                ]}
            >
                <Input onChange={e => {
                    setUsername(e.target.value)
                }} />
            </Form.Item>
            <Form.Item
                label="密码"
                name="密码"
                rules={[
                    {
                        required: true,
                        message: '请输入你的密码!',
                    },
                ]}
            >
                <Input.Password onChange={e => {
                    setPassword(e.target.value)
                }} />
            </Form.Item>
            <Form.Item >
                <Button  htmlType="submit" onClick={handleRegister}>
                    注册
        </Button>
            </Form.Item>
        </Form>
    );
};
export default Register;


编写登入组件


新建login.tsx 文件


import React, {useState}from 'react';
import { Form, Input, Button} from 'antd';
import axios from 'axios'
const Login = () => {
  const [username, setUsername] = useState('')
  const [password, setPassword] = useState('')
   const handleLogin = () =>{
    axios.post(`/api/user/login`,{
     username,password
    }).then(resp => {
    if (resp.data.success) {
      console.log(resp.data)
           localStorage.setItem('author',resp.data.author)       
          alert(`登录成功`)
       }
       else {
        alert(`登录失败`)
       }
     })}
  return (
    <Form
      name="basic"
      initialValues={{
        remember: true,
      }}
      layout='inline'
    >
      <Form.Item
        label="用户名"
        name="username"
        rules={[
          {
            required: true,
            message: '请输入你的用户名',
          },
        ]}
      >
        <Input  onChange={e => {
                  setUsername(e.target.value)
                }}/>
      </Form.Item>
      <Form.Item
        label="密码"
        name="password"
        rules={[
          {
            required: true,
            message: '请输入你的密码',
          },
        ]}
      >
        <Input.Password onChange={e => {
                  setPassword(e.target.value)
                }}/>
      </Form.Item>
      <Form.Item >
        <Button type="primary" htmlType="submit"onClick={handleLogin}>
          登入
        </Button>
      </Form.Item>
    </Form>
  );
};
export default Login;


编写博客列表组件


新建CardList.tsx文件


import React from 'react'
import axios from 'axios'
import { List, message, Avatar, Spin, Button } from 'antd';
import InfiniteScroll from 'react-infinite-scroller';
import { Link } from 'react-router-dom';
export class InfiniteListExample extends React.Component {
  state = {
    data: [],
    loading: false,
    hasMore: true,
    author:localStorage.getItem('author')
  };
  componentDidMount() {
    this.fetchData.then(res => {
      this.setState({
        data: res.list,
      });
    });
  }
  fetchData = axios.get('/api/blog/list').then(res => res.data
  )
  renderRow(item) {
  return (
    <div key={item.id} className="row">
      <div className="image">
      </div>
      <div className="content">
        <div>{item.title}</div>
        <div className='content'>{item.content.substring(0,100).concat('...')}</div>
        <div className='author'>by   {item.author}</div>
        <Button type="dashed"><Link to={`/detail?${item.id}`}>点击查看详情</Link></Button>
      </div>
    </div>
  );
}
  handleInfiniteOnLoad = () => {
    let { data } = this.state;
    this.setState({
      loading: true,
    });
    if (data.length > 14) {
      message.warning('Infinite List loaded all');
      this.setState({
        hasMore: false,
        loading: false,
      });
      return;
    }
    this.fetchData.then(res => {
      data = data.concat(res);
      this.setState({
        data,
        loading: false,
      });
    });
  };
  render() {
    return (
      <div className="demo-infinite-container">
        <InfiniteScroll
          initialLoad={false}
          pageStart={0}
          loadMore={this.handleInfiniteOnLoad}
          hasMore={!this.state.loading && this.state.hasMore}
          useWindow={false}
        >
          <div className="list">
        {this.state.data.map(this.renderRow.bind(this))}
      </div>
        </InfiniteScroll>
        <Button>  {this.state.author ? <Link to='new'>点击新增博客</Link> : '请先登入才能新增博客哦'} </Button>
      </div>
    );
  }
}


编写对应的css文件


在src/style文件夹下新建文件CardList.css


.list {
padding: 10px;
}
.content{
text-overflow:ellipsis;
}
.author{
position:relative;
left:10px;
}
.row { 
border-bottom: 1pxsolid#ebeced;
text-align: left;
margin: 5px0;
display: flex;
align-items: center;
}
/* .image {  margin-right: 10px;} */.content {
padding: 10px;
}


在index.css中新增引入


@import'./style/Cardlist.css';


编写博客详情页组件


新建detail.tsx 文件


import React, { useState } from 'react'
import axios from 'axios'
import {Col,Row, Button, Alert} from 'antd'
import { Link } from 'react-router-dom';
export default function Detail() {
    let list = window.location.search.split('?');
    let id = list[1];
    const  Author = localStorage.getItem('author')
    const [author, setAuthor] = useState('');
    const [content, SetContent] = useState("");
    const [title, setTitle] = useState('')
    axios.get(`/api/blog/detail?id=${id}`).then(
      res => res.data
    ). then(res => {
       setAuthor(res.author)
      setTitle(res.title)
       SetContent(res.content)
   })
    const handleDel =()=>{
                axios(`/api/blog/del?id=${id}`).then(
            res=>res.data
        )
        .then(
               res=>{
                   if(res.success){
                       alert('删除成功')
                   }else{
                       alert('删除失败')
                   }
               }
            )
    }
    return (<div>
       <Row align='middle'justify='center'><h2 className="title">{title}</h2></Row> 
       <Row><div><span style={{
           color:'grey',
           fontSize:'12px'
       }}> write By {author}</span><span style={{
        color:'grey',
        fontSize:'12px'
    }}> </span></div></Row> 
    <br/>
    <br/>
        <div><p>{content}</p></div>
       <Button>{Author===author?<Link to={`/update?${id}`}>更新</Link>:''}</Button> 
        <br/>
         {Author===author?<Button onClick={handleDel}>删除 </Button>:<div></div>}
    </div>
    )
}


编写更新博客组件


新建update组件


import React, { useState, useContext } from 'react'
import { Input,Button } from 'antd';
import axios from 'axios'
export default  function Update(){
    let list = window.location.search.split('?');
    let id = list[1];
    const { TextArea } = Input;
    const [title,SetTitle]=useState('')
    const [content,SetContent]=useState('')
    //  const [author,SetAuthor]=useState('')
     const author = localStorage.getItem('author')
    const HandleUpdate=()=>{
        axios(`/api/blog/update?id=${id}&title=${title}&content=${content}&author=${author}`,).then(res=>res.data
        ).then(res=>{
          // console.log(res)
            if(res){
                alert('更新成功')
            }
            else{
                alert('更新失败')
            }
        }
        )
    }
    return(
        <div> <Input onChange={e=>{SetTitle(e.target.value)}}  placeholder="请输入标题" />
         {/* <Input onChange={e=>{SetAuthor(e.target.value)}}  placeholder="请输入作者姓名" /> */}
        <TextArea onChange={e=>{SetContent(e.target.value)}} rows={4} placeholder='请输入内容'/>
       <Button onClick={HandleUpdate}>提交更新</Button></div>
    )}


编写新增博客组件


新建new.tsx文件


import React, { useState, useContext } from 'react'
import { Input,Button } from 'antd';
import axios from 'axios'
export default  function New(){
    const { TextArea } = Input;
    const [title,SetTitle]=useState('')
    const [content,SetContent]=useState('')
    const author = localStorage.getItem('author')
    const HandleUpdate=()=>{
        axios(`/api/blog/new?title=${title}&content=${content}&author=${author}`).then(res=>
          res.data
        )
        .then(res=>{
             if(res.success){
                 alert('新增成功')
             }
             else{
                 alert('新增失败')
             }
         }
         )
    }
    return(
        <div> <Input onChange={e=>{SetTitle(e.target.value)}}  placeholder="请输入标题" />
        {/* <Input onChange={e=>{SetAuthor(e.target.value)}}  placeholder="请输入作者姓名" /> */}
        <TextArea onChange={e=>{SetContent(e.target.value)}} rows={4} placeholder='请输入内容'/>
       <Button onClick={HandleUpdate}>提交</Button></div>
    )}


编写说明组件


新建文件Tea.tsx


import React, { useState } from 'react'
import { Alert } from 'antd'
export default  function Tea(){
    return(
     <div>
       <Alert
      message="请注意"
      description="不要发不良的信息呦!"
      type="info"
      showIcon
    />
    <br/>
        <h1>这个blog有很多不足</h1>
          <h1>但俺才快大二,有时间去升级和维护</h1>
        <h1>求大家点赞^ ^</h1></div>
    )}


编写统计博客数量的组件


新建文件Advise.tsx


importReact, { useState,useRef,useEffect } from'react'importBarfrom'../echarts/bar'exportdefaultfunctionAdvise(){ 
return(
<div><Bar/></div>    )}



在src下新建echarts文件夹,新建文件bar.jsx


importReact, { useState } from'react'import {Card} from'antd'importaxiosfrom'axios'importechartsfrom'echarts'importReactEchartsfrom'echarts-for-react'import { useEffect } from'react'exportdefaultfunctionBar (){
const  [keys,setKeys] =useState([]);
const  [ values ,setValues] =useState([]); 
echarts.registerTheme('my_theme', {
backgroundColor: '#f0ffff'});
useEffect(()=>{
axios.get('/api/blog/list').then(res=>res.data.list).then(res=>res.map(item=>item.author)).then(res=>res.reduce(function (allNames, name) { 
if (nameinallNames) {
allNames[name]++;
  }
else {
allNames[name] =1;
  }
returnallNames;
}, {})).then(res=>{
setKeys(Object.keys(res)) 
setValues(Object.values(res))
})
},[])
functiongetOption(){
letoption= {
title: {
text: '发布博客文章数量'            },
tooltip: {},
xAxis: {
data: keys            },
yAxis: {},
series: [{
name: '数量',
type: 'bar',
data: values            }]
        };
returnoption}
return(
<div><Cardtitle='来看看发布文章的数量吧'><ReactEchartsoption={getOption()} theme={"theme_name"}/></Card></div>  )
}


ok,至此我们开发完毕了


终端中输入


npm run dev


来看看效果吧


部署上线


注意部署之前先把文件克隆到本地,以防丢失


点左侧第一个部署按钮,首先选择日常环境,点击与文件同步,自动拉取f.yml的配置,如果不行,手动配置一下~,之后点击部署。



之后预发环境与线上环境与之一样,按顺序即可。部署成功后,会给出一个免费的临时测试域名用于访问部署到线上的效果。


如果你要用自己的域名长期访问,可以参见以下文档继续在线上环境进行部署和发布上线。https://help.aliyun.com/document_detail/176711.html


总结


参加训练营,让我受益良多,感受到serverless的强大之处。serverless 大大降低了开发的成本和上线周期,而且免运维 (服务器运维、容量管理、弹性伸缩等),按资源的使用量付费使得上线后的成本极低。

上线地址 http://bk.ckpbk.top/

项目github地址   https://github.com/JokerChen-peng/BBBlog_midway

由于笔者才疏学浅,这个项目的代码肯定很多优化的空间,欢迎大家来帮我找bug和重构O(∩_∩)O哈哈~


还没有使用过Serverless云开发?


现在花3分钟体验新手任务即领10元阿里云无门槛代金券。


本文参加Serverless云开发的有奖征文活动,已经获得作者授权

相关实践学习
【文生图】一键部署Stable Diffusion基于函数计算
本实验教你如何在函数计算FC上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。函数计算提供一定的免费额度供用户使用。本实验答疑钉钉群:29290019867
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
相关文章
|
22天前
|
监控 安全 Serverless
"揭秘D2终端大会热点技术:Serverless架构最佳实践全解析,让你的开发效率翻倍,迈向技术新高峰!"
【10月更文挑战第23天】D2终端大会汇聚了众多前沿技术,其中Serverless架构备受瞩目。它让开发者无需关注服务器管理,专注于业务逻辑,提高开发效率。本文介绍了选择合适平台、设计合理函数架构、优化性能及安全监控的最佳实践,助力开发者充分挖掘Serverless潜力,推动技术发展。
51 1
|
1月前
|
监控 Serverless 云计算
探索Serverless架构:开发的未来趋势
【10月更文挑战第5天】Serverless架构,即无服务器架构,正逐渐成为云计算领域的热点。它允许开发者构建和运行应用程序而无需管理底层服务器。本文介绍了Serverless架构的基本概念、核心优势及挑战,并展示了其在事件驱动编程、微服务架构和数据流处理等场景中的应用。通过优化冷启动、使用外部存储等实战技巧,开发者可以更好地利用Serverless架构提升开发效率和应用性能。随着技术的成熟,Serverless将在未来软件开发中扮演重要角色。
|
3月前
|
前端开发 大数据 数据库
🔥大数据洪流下的决战:JSF 表格组件如何做到毫秒级响应?揭秘背后的性能魔法!💪
【8月更文挑战第31天】在 Web 应用中,表格组件常用于展示和操作数据,但在大数据量下性能会成瓶颈。本文介绍在 JavaServer Faces(JSF)中优化表格组件的方法,包括数据处理、分页及懒加载等技术。通过后端分页或懒加载按需加载数据,减少不必要的数据加载和优化数据库查询,并利用缓存机制减少数据库访问次数,从而提高表格组件的响应速度和整体性能。掌握这些最佳实践对开发高性能 JSF 应用至关重要。
70 0
|
3月前
|
存储 设计模式 运维
Angular遇上Azure Functions:探索无服务器架构下的开发实践——从在线投票系统案例深入分析前端与后端的协同工作
【8月更文挑战第31天】在现代软件开发中,无服务器架构因可扩展性和成本效益而备受青睐。本文通过构建一个在线投票应用,介绍如何结合Angular前端框架与Azure Functions后端服务,快速搭建高效、可扩展的应用系统。Angular提供响应式编程和组件化能力,适合构建动态用户界面;Azure Functions则简化了后端逻辑处理与数据存储。通过具体示例代码,详细展示了从设置Azure Functions到整合Angular前端的全过程,帮助开发者轻松上手无服务器应用开发。
29 0
|
4月前
|
运维 Serverless API
Serverless 应用引擎使用问题之如何开发HTTP服务
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
|
3月前
|
监控 Serverless Go
Golang 开发函数计算问题之Go 语言中切片扩容时需要拷贝原数组中的数据如何解决
Golang 开发函数计算问题之Go 语言中切片扩容时需要拷贝原数组中的数据如何解决
|
3月前
|
Java Serverless Go
Golang 开发函数计算问题之在 Golang 中避免 "concurrent map writes" 异常如何解决
Golang 开发函数计算问题之在 Golang 中避免 "concurrent map writes" 异常如何解决
|
3月前
|
Serverless Go
Golang 开发函数计算问题之defer 中的 recover() 没有捕获到 如何解决
Golang 开发函数计算问题之defer 中的 recover() 没有捕获到 如何解决
|
4月前
|
监控 IDE Java
函数计算产品使用问题之如何不使用FC的IDE进行开发,并将开发好的应用部署到FC上
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
5月前
|
运维 前端开发 Serverless
云开发如何解决serverless对端的最后一公里问题
云开发如何解决serverless对端的最后一公里问题

热门文章

最新文章

相关产品

  • 函数计算