【Mind+Python】基于Pyecharts+Flask+Pinpong智能家居数字大屏系统

前言

之前在做项目的时候,遇到了一个问题:就是我想将单片机板子的传感器数据,实时上传到物联网平台,然后进行数字大屏显示。一开始使用的是onenet平台→多协议接入→应用管理来进行设置,奈何里面的一个组件少的可怜!

又看到它家的‘数据可视化View’功能!被他酷炫的数字大屏模板深深吸引了。

心想这不就我要找的模板吗?于是信誓旦旦的准备大概一场的时候:在我们选择一个2D项目模板,准备创建项目的时候,它跳出来一个对话框“提示氪金”(ps:新用户有7天的专业版试用资格,我当时要做项目的时候,已经过了!)

眼看着比赛的时间节点就快到了,于是咬咬牙,就买了两个月!

买了一个企业版的会员,买完回来发现:“里面的有些功能不能用!需要升级专业版会员!(腾讯svip直呼:“内行”)”没有办法了,只能展示用这个了。勉强将项目如期完成!

数字项目大屏项目做完后,我再想能不能自己来写一个数字大屏demo呢?恰好DF的‘’Mind+Python编程与智能设计大赛’正在进行中,于是就开始着手去实现这个想法!本教程将会带领你从零开始搭建一个:基于Pyecharts + Flask + Pinpong 智能家居数字大屏系统的项目!

准备阶段

1、安装pyecharts

概况

Echarts 是一个由百度开源的数据可视化,凭借着良好的交互性,精巧的图表设计,得到了众多开发者的认可。而 Python 是一门富有表达力的语言,很适合用于数据处理。当数据分析遇上数据可视化时,pyecharts 诞生了。

特性

简洁的 API 设计,使用如丝滑般流畅,支持链式调用囊括了 30+ 种常见图表,应有尽有支持主流 Notebook 环境,Jupyter Notebook 和 JupyterLab可轻松集成至 Flask,Django 等主流 Web 框架高度灵活的配置项,可轻松搭配出精美的图表详细的文档和示例,帮助开发者更快的上手项目多达 400+ 地图文件以及原生的百度地图,为地理数据可视化提供强有力的支持 具体开发文档详见pyecharts开发文档

如何安装?

方法一:

在mind+的python模式下:点击“库管理”→"图表"→"pyecharts"点击安装即可!

方法二:pip安裝

在mind+的python模式下:点击“库管理”→"pip模式"下:

输入:pip install pyecharts

(ps:因为我这里已经安装了,界面中提示了欢迎已经存在)

2、安装Flask

简介:

Flask是一个轻量级的可定制框架,使用Python语言编写,较其他同类型框架更为灵活、轻便、安全且容易上手。它可以很好地结合MVC模式进行开发,开发人员分工合作,小型团队在短时间内就可以完成功能丰富的中小型网站或Web服务的实现。另外,Flask还有很强的定制性,用户可以根据自己的需求来添加相应的功能,在保持核心功能简单的同时实现功能的丰富与扩展,其强大的插件库可以让用户实现个性化的网站定制,开发出功能强大的网站。

基本模式:

Flask的基本模式为在程序里将一个视图函数分配给一个URL,每当用户访问这个URL时,系统就会执行给该URL分配好的视图函数,获取函数的返回值并将其显示到浏览器上,其工作过程见图。

特点:

1、简单,Flask的路由以及路由函数由修饰器设定,开发人员不需要借助其他文件匹配;2、配置灵活,有多种方法配置,不同环境的配置也非常方便;环境部署简单,Flask运行不需要借助其他任何软件,只需要安装了Python的IDE,在命令行运行即可。只需要在Python中导入相应包即可满足所有需求;3、入门简单,通过官方指南便可以清楚的了解Flask的运行流程;4、低耦合,Flask可以兼容多种数据库、模板。

更多详情:flask开发文档

如何安装Flask?

在mind+的python模式下:点击“库管理”→"pip模式"下:

输入:pip install Flask

3、安装pinpong

简介:

pinpong库是一套控制开源硬件主控板的Python库,基于Firmata协议并兼容MicroPython语法,5分钟即可让你上手使用Python控制开源硬件。

借助于pinpong库,直接用Python代码就能给各种常见的开源硬件编程。其原理是给开源硬件烧录一个特定的固件,使开源硬件可以通过串口与电脑通讯,执行各种命令。

pinpong库的名称由“Pin”和“Pong”组成,“Pin”指引脚,“PinPong”为“乒乓球”的谐音,指信号的往复。

特点:

pinpong库的设计,是为了让开发者在开发过程中不用被繁杂的硬件型号束缚,而将重点转移到软件的实现。哪怕程序编写初期用Arduino开发,部署时改成了掌控板,只要修改一下硬件的参数就能正常运行,实现了“一次编写处处运行”。

当前PinPong库正在快速更新中,已支持Arduino系列uno、leonardo、mega2560,ESP32系列掌控板(handpy)以及micro:bit板,传感器支持50+,其他主控板及更多扩展库将逐步支持。

更多详情:pinpong开发手册

如何安装pinpong?

方法一:

在mind+的python模式下:点击“库管理”→"推荐库"→"硬件控制"→"pinpong"点击安装即可

4、硬件清单

序列 硬件
1 Romeo 三合一Arduino兼容控制器
2 Gravity: I2C BME280环境传感器 (温度,湿度,气压)

程序设计

step1:新建一个flask项目

在mind+的右侧项目文件:

1、新建一个文件 pyecharts-flask-demo

2、在pyecharts-flask-demo文件夹下 创建 templates文件夹

Step 2: 拷贝 pyecharts 模板

将 pyecharts 模板,位于 “C:\Users\83731\Documents\mindplus-py\environment\Python3.6.5-64\Lib\site-packages\pyecharts\render\templates”拷贝至刚新建的 templates 文件夹

(ps:这里的index.html文件是我自己新建的,后面会讲到)

Flask 前后端分离

Step 3: 新建一个 HTML 文件

新建 HTML 文件保存位于项目根目录的 templates 文件夹,这里以如下 index.html 为例. 主要用到了 jquery 和 pyecharts 管理的 echarts.min.js 依赖!后面用到水球图和词云需要用到echarts-liquidfill.min.js,echarts-wordcloud.min.js

这里你可能需要一点HTML,CSS,javascript等前端的知识,有兴趣的老师可以去w3school了解一下!(PS:本人也是小白一个,所设计的界面很low,希望大神勿喷!)

前端主动向后端进行数据刷新

定时刷新的核心在于 HTML 的 setInterval 方法。

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>智能家居</title><script type="text/javascript" src="https://assets.pyecharts.org/assets/echarts.min.js"></script><script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.0.0/jquery.min.js"></script><script type="text/javascript" src="https://assets.pyecharts.org/assets/echarts-liquidfill.min.js"></script><script type="text/javascript" src="https://assets.pyecharts.org/assets/echarts-wordcloud.min.js"></script><style>h1{font-family:"隶书";color:#9AB3F5;font-size:50px;margin-align:center;line-height:80px;}div{box-shadow:0 10px 20px rgba(191, 216, 184,0.19), 0 6px 6px rgba(191, 216, 184,0.23);border-radius:5px;background:#E8F6EF;position:relative; }#liquid{width:800px; height:400px;margin-left:50px; margin-top:20px;float: left;}#pressure{width:500px; height:400px;margin-left:40px; margin-top:20px; float: left;}#altitude{width:500px; height:400px;margin-left:40px; margin-top:20px;float: left;}#line{width:800px; height:400px;margin-top:20px;margin-left:50px;float: left;}#bar{width:500px; height:400px;margin-left:40px;margin-top:20px;float: left;}#wordcloud{width:500px; height:400px;margin-left:40px;margin-top:20px;float: left;}.ttop{width:100%;height:90px;text-align:center; margin-top:-20px;background:#6F69AC;}
</style>
</head>
<body ><div class="ttop" style=""><h1 > 基于Pyecharts + Flask + Pingpong 智能家居数字大屏系统</h1></div><div id="liquid" ></div><div id="pressure"></div><div id="altitude"></div><div id="line"></div><div id="bar"> </div><div id="wordcloud"> </div><script>var liquid = echarts.init(document.getElementById('liquid'), 'white', {renderer: 'canvas'});$(function () {fetchliquidData(liquid);setInterval(fetchliquidData, 2000);});function fetchliquidData() {$.ajax({type: "GET",url: "http://127.0.0.1:5000/liquidChart",dataType:'json',success: function (result) {var jsonResult = JSON.stringify(result);var jsonResult =  JSON.parse(jsonResult,function(key,value){if(key === "formatter"){console.log(value);return eval('('+value+')');}else{return value;}});console.log(jsonResult);   liquid.setOption(jsonResult);},});}</script><script>var pressure = echarts.init(document.getElementById('pressure'), 'white', {renderer: 'canvas'});$(function () {fetchpressureData(pressure);setInterval(fetchpressureData, 2000);});function fetchpressureData() {$.ajax({type: "GET",url: "http://127.0.0.1:5000/pressureChart",dataType:'json',success: function (result) {pressure.setOption(result);},});}</script><script>var altitude = echarts.init(document.getElementById('altitude'), 'white', {renderer: 'canvas'});$(function () {fetchaltitudeData(altitude);setInterval(fetchaltitudeData, 2000);});function fetchaltitudeData() {$.ajax({type: "GET",url: "http://127.0.0.1:5000/altitudeChart",dataType:'json',success: function (result) {altitude.setOption(result);},});}</script><script>var line = echarts.init(document.getElementById('line'), 'white', {renderer: 'canvas'});var old_data = [];$(function () {fetchlineData(line);setInterval(getDynamicData, 2000);});function fetchlineData() {$.ajax({type: "GET",url: "http://127.0.0.1:5000/lineChart",dataType: "json",success: function (result) {line.setOption(result);old_data = line.getOption().series[0].data;}});}function getDynamicData() {$.ajax({type: "GET",url: "http://127.0.0.1:5000/lineDynamicData",dataType: "json",success: function (result) {old_data.push([result.name, result.value]);line.setOption({series: [{data: old_data}]});}});}</script><script>var bar = echarts.init(document.getElementById('bar'), 'white', {renderer: 'canvas'});$(function () {fetchbarData(bar);setInterval(fetchbarData, 2000);});function fetchbarData() {$.ajax({type: "GET",url: "http://127.0.0.1:5000/barChart",dataType:'json',success: function (result) {bar.setOption(result);},});}</script><script>var wordcloud = echarts.init(document.getElementById('wordcloud'), 'white', {renderer: 'canvas'});$(function () {fetchwordcloudData(wordcloud);setInterval(fetchwordcloudData, 2000);});function fetchwordcloudData() {$.ajax({type: "GET",url: "http://127.0.0.1:5000/wordcloudChart",dataType:'json',success: function (result) {wordcloud.setOption(result);},});}</script>
</body>
</html>

Step 4: 新建app.py文件

请将下面的代码保存为 app.py 文件并移至项目的根目录下。

该程序主要用pingpong来获取传感器的数据,然后将数据放到pyecharts的chart中。然后通过falsk web框架每个两秒将数据发送到前端!浏览器输入:http://127.0.0.1:5000/。进行页面显示。主要用到了get请求来获取数据。


from random import randrange
# 导入Flask  相关库文件
from flask import Flask, render_template
from flask.json import jsonify# 导入pyecharts 相关库文件
from pyecharts import options as opts
from pyecharts.charts import Grid, Liquid,Gauge,Line,Pie,Bar,WordCloud
from pyecharts.faker import Faker
from pyecharts.commons.utils import JsCode
from pyecharts.globals import ThemeType
# 导入pinpong库相关文件
from pinpong.board import Board,Pin
from pinpong.libs.dfrobot_bme280 import BME280import timeBoard("uno").begin() # BME280 初始化
bme = BME280()app = Flask(__name__, static_folder="templates")date=[0,0,0,0]# 创建水球图
def create_liquid_charts(temp,humi) ->Liquid:l1 = (Liquid().add("湿度", # 系列名称[humi], # 系列数值center=["48%", "50%"], # 设置x,y坐标值label_opts=opts.LabelOpts( # 标签配置项font_size=35,  # 标签大小position="inside",)).set_global_opts(title_opts=opts.TitleOpts(title="温湿度电量水球图")) # 提示框组件配置项)l2 = (Liquid().add("温度",[temp],center=["18%", "50%"],color=["#FFA500"],label_opts=opts.LabelOpts(font_size=35,formatter=JsCode("""function (param) {return (Math.floor(param.value * 10000) / 100) + '℃';}"""),position="inside",),))l3=(Liquid().add("电量", [0.8], center=["78%", "50%"],color=["#00FA9A"], # 水球图波浪颜色label_opts=opts.LabelOpts(font_size=35,position="inside",)))# 通过grid 并行多图将三个水球合并在一张图上grid = Grid().add(l1, grid_opts=opts.GridOpts()).add(l2, grid_opts=opts.GridOpts()).add(l3, grid_opts=opts.GridOpts())return grid
# 创建仪表盘def creat_pressure_chart(press) -> Gauge:c = (Gauge().add("", [("压强", press)],min_=['0'], # 最小值max_=['2000'], # 最大值split_number=5,axisline_opts=opts.AxisLineOpts( # 设置仪表盘颜色及宽度linestyle_opts=opts.LineStyleOpts(color=[(0.3, "#67e0e3"), (0.7, "#37a2da"), (1, "#fd666d")], width=30)),detail_label_opts=opts.LabelOpts(formatter="{value}  Pa"),) # 格式.set_global_opts(title_opts=opts.TitleOpts(title="压强仪表盘")))return c
# 创建海拔仪表盘
def creat_altitude_chart(alti) -> Gauge:c1 =(Gauge().add("", [("海拔", alti)],min_=['0'], # 最小值max_=['1000'], # 最大值split_number=5,axisline_opts=opts.AxisLineOpts( # 设置仪表盘颜色及宽度linestyle_opts=opts.LineStyleOpts(color=[(0.3, "#F3EAC2"), (0.7, "#F5B461"), (1, "#EC524B")], width=30)),detail_label_opts=opts.LabelOpts(formatter="{value}  M"),) # 格式.set_global_opts(title_opts=opts.TitleOpts(title="海拔仪表盘")))return c1
# 创建折现图
def line_base() -> Line:line = (Line().add_xaxis(["{}".format(i) for i in range(10)]).add_yaxis(series_name="",y_axis=[randrange(20, 35) for _ in range(10)],is_smooth=True,label_opts=opts.LabelOpts(is_show=False),).set_global_opts(title_opts=opts.TitleOpts(title="温度动态数据显示"),xaxis_opts=opts.AxisOpts(type_="value"),yaxis_opts=opts.AxisOpts(type_="value"),))return line
# 创建主创图
def creat_bar_chart(temp,humi,press,alti) -> Bar:c = (Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT)).add_xaxis(["温度","湿度","压强","海拔"]).add_yaxis("小明家", [temp,humi,press,alti]).set_global_opts(title_opts=opts.TitleOpts(title="BME280数据显示")))return c # 词条
data = [("hockel", "2004"),("智能家居", "1500"),("少儿编程", "777"),("人工智能", "666"),("python", "200"),("mind+", "555"),("Erised", "1204"),("温度", "50"),("湿度", "40"),("压强", "21"),("海拔", "30"),("BME280", "21"),("pyecharts", "99"),("arduino", "50"),("flask", "80"),("html", "60"),("css", "20"),("js", "70"),("pinpong", "120"),]
# 创建词云
def creat_wordcloud_chart() -> WordCloud:c = (WordCloud().add(series_name="所用技术", data_pair=data, word_size_range=[6, 66]).set_global_opts(title_opts=opts.TitleOpts(title="所用技术", title_textstyle_opts=opts.TextStyleOpts(font_size=23)),tooltip_opts=opts.TooltipOpts(is_show=True),))return c @app.route("/")
def index():return render_template("index.html")@app.route("/liquidChart")
def get_liquid_chart():date[0] =  bme.temp_c()date[1] =  bme.humidity()date[2] =  bme.press_pa()date[3] =  bme.cal_altitudu()temp = date[0] / 100humi = date[1] / 100c = create_liquid_charts(temp,humi)return c.dump_options_with_quotes()# 压强仪表盘
@app.route("/pressureChart")
def get_pressure_chart():press = date[2] / 100c = creat_pressure_chart(press)return c.dump_options_with_quotes()# 海拔仪表盘
@app.route("/altitudeChart")
def get_altitude_chart():alti =  date[3]c = creat_altitude_chart(alti)return c.dump_options_with_quotes()
# 动态图
@app.route("/lineChart")
def get_line_chart():c = line_base()return c.dump_options_with_quotes()idx = 9
@app.route("/lineDynamicData")
def update_line_data():global idxidx = idx + 1return jsonify({"name": idx, "value": date[0]})# 柱状图
@app.route("/barChart")
def get_bar_chart():c = creat_bar_chart(date[0],date[1],date[2]/100,date[3])return c.dump_options_with_quotes()# 词云
@app.route("/wordcloudChart")
def get_wordcloud_chart():c = creat_wordcloud_chart()return c.dump_options_with_quotes()if __name__ == "__main__":app.run()

演示效果

基于Pyecharts + Flask + Pinpong 智能家居数字大屏系统

FAQ:

1、Gauge 仪表板显示图例与数据重叠!

这个是pyecharts V1.9.0的BUG!在V2.0.0将被修复!

解决方法:

找到pyecharts的安装目录(右击右侧项目:打开文件所在位置即可找到):

C:\Users\83731\Documents\mindplus-py\environment\Python3.6.5-64\Lib\site-packages\

然后修改pyecharts\charts\basic_charts\chart.py文件

2、pyecharts中的水球图不显示问题:

在遇到这个问题之后,了解一下echarts,才知道echarts-liquidfill是第三方插件,需要单独导入

需要在index.html文件中标签内加入:

<script type="text/javascript" src="https://assets.pyecharts.org/assets/echarts-liquidfill.min.js"></script>

3、水球图在进行数据精度显示是时:单位不能显示!

在看了pyecharts文档时有一处写到:

注: 目前由于 json 数据类型的问题,无法将 pyecharts 中的 JSCode 类型的数据转换成 json 数据格式返回到前端页面中使用。因此在使用前后端分离的情况下尽量避免使用 JSCode 进行画图。

l2 = (Liquid().add("温度",[0.3254],center=["18%", "50%"],color=["#FFA500"],label_opts=opts.LabelOpts(font_size=35,formatter=JsCode("""function (param) {return (Math.floor(param.value * 10000) / 100) + '℃';}"""),position="inside",),))

我这这里jscode返回的对象是str:str! 然而我们需要调用jscode的函数!我们需要在html的文档中进行数据处理:将str:str 转换成 str:function().找到我们调用的liquid的script标签进行如下更改!

 function fetchliquidData() {$.ajax({type: "GET",url: "http://127.0.0.1:5000/liquidChart",dataType:'json',success: function (result) {var jsonResult = JSON.stringify(result);jsonResult =  JSON.parse(jsonResult,function(key,value){if(key === "formatter"){console.log(value);return eval('('+value+')');}else{return value;}});console.log(jsonResult);    liquid.setOption(jsonResult);},}

4.用grid组件无法将liquid和gauge并列合并在同一图上!

使用grid时,一般情况下,永远不要先给grid加入一个没有x y轴的图,比如饼图、仪表盘、地图等等。

如果你想加的,可以参考这篇文章:pyecharts丨页面布局工具——grid注意事项和overlap的使用

总结

整个项目做下来!被Flask的前后端分离数据显示问题搞得崩溃!主要还是因为自己的前端知识有点薄弱。为此自己恶补了一晚的前端的html,css,js等知识!对falsk web框架和pyecharts有了进一步的了解!屏幕前的你,有没有学吐了呢?反正我是秃了!

更多教程欢迎关注个人博客:www.hockel.club

项目源码请关注微信公众号:跟着hockel玩科创

​ 回复:“pyecharts”即可获得

【Mind+Python】基于Pyecharts+Flask+Pinpong智能家居数字大屏系统相关推荐

  1. ChatGPT直出1.5w字论文查重率才30% - 基于物联网技术的智能家居控制系统设计与实现

    文章目录 ChatGPT直出1.5w字论文查重率才30% - 基于物联网技术的智能家居控制系统设计与实现 一.绪论 1.1 研究背景与意义 1.2 国内外研究现状分析 1.3 研究内容与目标 1.4 ...

  2. 基于python的智能家居系统_基于Python Django的可扩展智能家居系统

    基于 Python Django 的可扩展智能家居系统 龚 鸣,余杨志,邓宏涛 * [摘 要] 针对现阶段智能家居系统智能化迭代开发的需求,分析当前智能家居 系统主控的相关实现技术,提出了基于 Pyt ...

  3. 基于物联网的新型智能家居控制系统设计

    来自http://www.tiaozhanbei.net/project/8633/ 简介: 本系统是基于物联网的新型智能家居控制系统,系统以提升家居的安全性.便利性.舒适性.艺术性为目的,以智能化. ...

  4. 基于ZigBee技术的智能家居系统实施方案

    智能家居:通常意义上的智能家居是指使用计算机技术.网络技术.综合布线技术.传感器技术和设备自动控制技术建立家庭或楼宇智能化管理平台,通过信息管理平台将与家居生活相关的各种设施管理起来.智能家居信息系统 ...

  5. 基于51单片机简易智能家居

    基于51单片机的智能家居的设计 前言 一.项目简介 二.开发环境/工具: 1.Keil 5/Keil 4 2.STC-ISP 3.蓝牙串口助手 4.手机app 三.硬件设计: 1.单片机与蓝牙模块连接 ...

  6. 基于语音控制的智能家居实现

    前言:因为大三的时候需要交一个物联网通信以及Zigbee的俩门课设,所以花了半天的时间做了一个基于语音控制的智能家居,在这里记录一下以及分享给一些在校学生作为分享,因为那段时间忙着比赛所以花了很短的时 ...

  7. 一、基于wifi控制的智能家居系统之项目简介和设计方案(硬件基于arduino+esp8266,软件Android+Web端+scoket服务器,实现语音控制)

    由于是物联网工程的学生,会一点硬件,会一点Android开发,会一点Web开发,于是乎决定毕设的时候做一个简单一点的毕设,但是能够把所有的知识都应用,串联起来,将所学的知识实践. 一.项目功能介绍 项 ...

  8. android客户端显示拓扑结构,基于WIFI构建的智能家居系统综合接入及控制装置(Android客户端及综合测控).doc...

    基于WIFI构建的智能家居系统综合接入及控制装置(Android客户端及综合测控) 毕 业 论 文(设计) 中文题目: 基于WIFI构建的智能家居系统综合接入 及控制装置(安卓及综合测控) 英文题目: ...

  9. 基于STM32物联网WiFi智能家居控制系统设计(原理图+源代码+系统资料)

    基于STM32物联网WiFi智能家居控制系统设计(原理图+源代码+系统资料) 原理图:Altium Designer 程序编译器:keil 5 编程语言:C语言 设计编号:C0053 主要功能: 1. ...

最新文章

  1. 支持的网卡列表_Windows 10的5G网卡折腾笔记(含采购链接)
  2. 毫秒级的时间处理上G的图片(生成缩略图)
  3. Java程序中AB类可调用_19年【石油大学】《Java语言程序设计》二次在线作业(100分)...
  4. Enterprise Library 2.0 Hands On Lab 翻译(14):加密应用程序块(一)
  5. service注入为null_如何解决quartz调度时候,job中的service为null的问题?
  6. hibernate 复合主键 根据主键删除_Python 之 MySql“未解之谜”11--主键 id 那些事
  7. 任天堂的好日子還會繼續嗎﹖
  8. 【网络安全入门大总结】—Java语言中常用的渗透漏洞大汇总
  9. ajax传图片的方法
  10. VS错误的解决办法:error LNK2019: 无法解析的外部符号
  11. mongodb python 大于_菜鸟成长记--如何根据关键词爬取微博内容?(scrapy+mongodb)
  12. C#如何调用阿里云短信接口
  13. CSS度量单位px/pt/em/in/pc/mm/cm
  14. ORCAL计算司龄是否满一年
  15. qq机器人插件之奥运奖牌获得数量
  16. Windows系统的电脑有可以删除的文件夹(个人笔记)
  17. 【BZOJ4370】【IOI2015】horses 数据结构 平衡树+线段树
  18. linux 程序开发
  19. torch.flatten、np.flatten 详解
  20. 有关华为的七大猜想:或在国内屈居老二

热门文章

  1. Python模块之Pandas 格式化数据
  2. Win7、Win8、Win10系统USB-Blaster驱动程序无法安装的解决办法
  3. 浅析Windows2000服务与后门技术
  4. Elasticsearch 中文IK分词器
  5. h5发送短信以及判别用户浏览器版本
  6. 零基础入门 自学 JAVA SE 基础篇(九)instanceof final 开闭原则 多态 抽象(abstract)方法与抽象类 接口(interface)
  7. 多多自走棋改动_多多自走棋:20日更新,刺客、光羽修改,装备小幅调整
  8. Elasticsearch索引别名alias操作
  9. 软件实施工程师的岗位职责和要求
  10. julia的Unicode 字符输入