找3gipv6去下载个就行了.
本回答由网友推荐
你的API需要在淘宝开发平台审核通过以后,再使用TOPAPI技术,才能调用API商品的。
NO.1巨蟹座
天生恋家的巨蟹座,在婚后的生活中,更让人觉得她是一位天生的贤妻良母,在生活起居上,巨蟹座的她们会体贴老公工作上的辛劳,从来不让老公分担、担心家中的一切事务,而且她们还会照顾老公到无微不至的程度,让老公体会到她们对他的关心和疼爱,能娶到这种老婆真的是上辈子修来的福气!
NO.2白羊座
白羊座的女生对于婚姻的观点,一旦下定决心步入婚姻,她们就会全心全意的投入、奉献于家庭中,她们对另一半的付出不求回报,就算再辛劳看到老公脸上幸福、欣喜的表情,就会觉得一切都值得这么做。团体5566中的王仁甫及其它团员前些日子为新专辑奔走活动,白羊座的季芹意外现身造势,并送上一张以“家人”名义的传情卡片,让王仁甫笑颜逐开,害羞的感动不已!
NO.3双子座
鬼灵精怪的双子座,思考模式与一般女人不同,喜欢有变化的生活方式,在婚姻中求新求变,常常会带给老公极大的惊喜和欢乐,把老公逗的乐不开支的,而且在工作上,双子座的她们更是老公的得力助手,她们会分享自己所有的资源给老公,在众人面前大力推荐自己老公的优秀事迹、表现,不仅做足了老公的面子,还让老公的事业飞黄腾达、知名度大增,更加让老公爱她!
保健ptisys.com/list.php?catid=50012472
本回答由网友推荐
如何快速为wordpress网站开发对外的数据接口
随着移动互联网的发展,越来越的传统站点都在为移动化做努力,如开发一套手机专用的网站主题,甚至专门为其开发app!但是开发app(非嵌入移动版网页的PhoneGap),就会涉及到接口调用!如何才能在不影响网站原来内容、模板的情况下快速开发出可供第三方调用的数据接口呢?
也许有同学会说,wordpress具有强大的主题切换能力,通过一个简单的主题预览插件就可以实现不同主题显示不同的内容,接口就在另一个主题下实现!
没错,这种方式确实可以,但是由于wordpress目前cms功能越来越强大,很多网站都使用了自定义模板、register_post_type、register_nav_menus等功能,wordpress的主题切换不能很好的兼容这些地方(即使register_post_type代码照搬)。即使克服了以上的困难,如果以后网站增加版块或者新功能,要维护就得同时改动网站主题和api主题模板,工作量巨大。
那么有没有什么简单的方法呢?
当然有,不然我在这里罗里吧嗦开头那些屁话干啥!我的这个方法可以在原有的主题基础上进行方便的接口开发。
如何做呢?
首先在网站所用主题的functions.php文件中增加以下代码:
1: if(isset($_GET@['json'])){
2:add_filter('template_include','wp_my_api');
3: }
4: function wp_my_api($template){
5:return preg_replace('#([^/]+.php)#','api/$1',$template);
6: }
然后在网站当前所用主题目录下新建一个文件夹,名字为“api”。然后把主题下的模板文件复制进api文件夹里,就可以对这些模板进行接口数据转化了。比如输出json或者xml等。
这种方法原理就是将wordpress的模板重新定义到api文件下寻找同名模板文件。如果你愿意做更多的探寻,还可以实现更加智能的接口模板匹配功能出来。我这里就展示了最简单的方法。
这种方法完全不影响网站原有的模板文件,不需要改动任何原有模板,接口的实现在api文件夹下的同名文件里实现。
使用示例
要将你的主题下的page.php模板进行json化输出,只需要在api文件下新建一个叫page.php的模板(或者复制page.php过来),里面写入:
1: <?php //这里偷懒了,直接将wordpress的$wp_query对象json化输出,实际中最好提取自己需要的内容输出,以减少http传输量
2: header("Content-type:application/json");
3: if(isset($_GET@['debug'])){
4:print_r($wp_query);
5: }else echo json_encode($wp_query);
比如网站有个页面,地址是,其对应的接口地址就是?json。如果要调试,查看json接口,可以加上debug参数,如?json&debug。
是不是很简单呢?如果你的网站不需要这个接口了,只需要直接删除api文件夹就行了。
API:Application Programming Interface,应用程序开发接口。
就是说,只是提供了一个功能接入的接口。但是具体的功能实现,页面的布局,以及其他的网站实现技术还得找人来做。
【CSDN 编者按】大家都知道Web和API服务器在互联网中的重要性,在计算机网络方面提供了最基本的界面。本文主要介绍了怎样利用Scala实现实时聊天网站和API服务器,通过本篇文章,你定将受益匪浅。
作者 | Haoyi
译者 | 弯月,责编 | 刘静
出品 | CSDN(ID:CSDNnews)
以下为译文:
Web和API服务器是互联网系统的骨干,它们为计算机通过网络交互提供了基本的界面,特别是在不同公司和组织之间。这篇指南将向你介绍如何利用Scala简单的HTTP服务器,来提供Web内容和API。本文还会介绍一个完整的例子,告诉你如何构建简单的实时聊天网站,同时支持HTML网页和JSON API端点。
这篇文及章的目的是介绍怎样用Scala实现简单的HTTP服务器,从而提供网页服务,以响应API请求。我们会建立一个简单的聊天网站,可以让用户发表聊天信息,其他访问网站的用户都可以看见这些信息。为简单起见,我们将忽略认证、性能、用户挂历、数据库持久存储等问题。但是,这篇文章应该足够你开始用Scala构建网站和API服务器了,并为你学习并构建更多产品级项目打下基础。
我们将使用Cask web框架:
Cask是一个Scala的HTTP为框架,可以用来架设简单的网站并迅速运行。
开始
要开始使用Cask,只需下载并解压示例程序:
$ curl -L > cask.zip
$ unzip cask.zip
$ cd minimalApplication-0.3.0
运行find来看看有哪些文件:
$ find . -type f
./build.sc
./app/test/src/ExampleTests.scala
./app/src/MinimalApplication.scala
./mill
我们感兴趣的大部分代码都位于app/src/MinimalApplication.scala中。
package app
object MinimalApplication extends cask.MainRoutes{
@cask.get("/")
def hello= {
"Hello World!"
}
@cask.post("/do-thing")
def doThing(request: cask.Request)= {
new String(request.readAllBytes).reverse
initialize
用build.sc进行构建:
import mill._, scalalib._
object app extends ScalaModule{
def scalaVersion = "2.13.0"
def ivyDeps = Agg(
ivy"com.lihaoyi::cask:0.3.0"
)
object test extends Tests{
def testFrameworks = Seq("utest.runner.Framework")
ivy"com.lihaoyi::utest::0.7.1",
ivy"com.lihaoyi::requests::0.2.0",
如果你使用Intellij,那么可以运行如下命令来设置Intellij项目配置:
$ ./mill mill.scalalib.GenIdea/idea
现在你可以在Intellij中打开minimalApplication-0.3.0/目录,查看项目的目录,也可以进行编辑。
可以利用Mill构建工具运行该程序,只需执行./mill:
$ ./mill -w app.runBackground
该命令将在后台运行Cask Web服务器,同时监视文件系统,如果文件发生了变化,则重启服务器。然后我们可以使用浏览器浏览服务器,默认网址是localhost:8080:
在/do-thing上还有个POST端点,可以在另一个终端上使用curl来访问:
$ curl -X POST --data hello :8080/do-thingolleh
可见,它接受数据hello,然后将反转的字符串返回给客户端。
然后可以运行app/test/src/ExampleTests.scala中的自动化测试:
$ ./mill clean app.runBackground # stop the webserver running in the background
$ ./mill app.test
[50/56] app.test.compile
[info] Compiling 1 Scala source to /Users/lihaoyi/test/minimalApplication-0.3.0/out/app/test/compile/dest/classes ...
[info] Done compiling.
[56/56] app.test.test
-------------------------------- Running Tests --------------------------------
+ app.ExampleTests.MinimalApplication 629ms
现在基本的东西已经运行起来了,我们来重新运行Web服务器:
然后开始实现我们的聊天网站!
提供HTML服务
第一件事就是将纯文本的"Hello, World!"转换成HTML网页。最简单的方式就是利用Scalatags这个HTML生成库。要在项目中使用Scalatags,只需将其作为依赖项加入到build.sc文件即可:
def ivyDeps = Agg(
+ ivy"com.lihaoyi::scalatags:0.7.0",
如果使用Intellij,那么还需要重新运行./mill mill.scalalib.GenIdea/idea命令,来发现依赖项的变动,然后重新运行./mill -w app.runBackground让Web服务器重新监听改动。
然后,我们可以在MinimalApplication.scala中导入Scalatags:
+import scalatags.Text.all._
然后用一段最简单的Scalatags HTML模板替换"Hello, World!"。
def hello = {
- "Hello World!"
+ html(
+ head,
+ body(
+ h1("Hello!"),
+ p("World")
+ )
+ ).render
我们应该可以看到./mill -w app.runBackground命令重新编译了代码并重启了服务器。然后刷新网页额,就会看到纯文本已经被替换成HTML页面了。
Bootstrap
为了让页面更好看一些,我们使用Bootstrap这个CSS框架。只需按照它的指南,使用link标签引入bootstrap:
head(
+ link(
+ rel := "stylesheet",
+ href := ""
),
body(
- h1("Hello!"),
- p("World")
+ div(cls := "container")(
现在字体不太一样了:
虽然还不是最漂亮的网站,但现在已经足够了。
在本节的末尾,我们修改一下Scalatags的HTML模板,加上硬编码的聊天文本和假的输入框,让它看起来更像一个聊天应用程序。
div(cls := "container")(
+ h1("Scala Chat!"),
+ hr,
+ div(
+ p(b("alice"), " ", "Hello World!"),
+ p(b("bob"), " ", "I am cow, hear me moo"),
+ p(b("charlie"), " ", "I weigh twice as much as you")
+ ),
+ input(`type` := "text", placeholder := "User name", width := "20%"),
+ input(`type` := "text", placeholder := "Please write a message!", width := "80%")
现在我们有了一个简单的静态网站,其利用Cask web框架和Scalatags HTML库提供HTML网页服务。现在的服务器代码如下所示:
import scalatags.Text.all._
def hello = {
html(
head(
link(
rel := "stylesheet",
href := ""
body(
h1("Scala Chat!"),
hr,
div(
p(b("alice"), " ", "Hello World!"),
p(b("bob"), " ", "I am cow, hear me moo"),
p(b("charlie"), " ", "I weigh twice as much as you")
input(`type` := "text", placeholder := "User name", width := "20%"),
input(`type` := "text", placeholder := "Please write a message!", width := "80%")
).render
接下来,我们来看看怎样让它支持交互!
表单和数据
为网站添加交互的第一次尝试是使用HTML表单。首先我们要删掉硬编码的消息列表,转而根据数据来输出HTML网页:
object MinimalApplication extends cask.MainRoutes{
+ var messages = Vector(
+ ("alice", "Hello World!"),
+ ("bob", "I am cow, hear me moo"),
+ ("charlie", "I weigh twice as much as you"),
div(
- p(b("alice"), " ", "Hello World!"),
- p(b("bob"), " ", "I am cow, hear me moo"),
- p(b("charlie"), " ", "I weight twice as much as you")
+ for((name, msg) <- messages)
+ yield p(b(name), " ", msg)
这里我们简单地使用了内存上的mssages存储。关于如何将消息持久存储到数据库中,我将在以后的文章中介绍。
接下来,我们需要让页面底部的两个input支持交互。为实现这一点,我们需要将它们包裹在form元素中:
hr,
- div(
- input(`type` := "text", placeholder := "User name", width := "20%"),
- input(`type` := "text", placeholder := "Please write a message!", width := "80%")
+ form(action := "/", method := "post")(
+ input(`type` := "text", name := "name", placeholder := "User name", width := "20%"),
+ input(`type` := "text", name := "msg", placeholder := "Please write a message!", width := "60%"),
+ input(`type` := "submit", width := "20%")
这样我们就有了一个可以交互的表单,外观跟之前的差不多。但是,提交表单会导致Error 404: Not Found错误。这是因为我们还没有将表单与服务器连接起来,来处理表单提交并获取新的聊天信息。我们可以这样做:
- )
+
+ @cask.postForm("/")
+ def postHello(name: String, msg: String)= {
+ messages = messages :+ (name -> msg)
+ hello
+ }
@cast.postForm定义为根URL(即 / )添加了另一个处理函数,但该处理函数处理POST请求,而不处理GET请求。Cask文档()中还有关于@cask.*注释的其他例子,你可以利用它们来定义处理函数。
验证
现在,用户能够以任何名字提交任何评论。但是,并非所有的评论和名字都是有效的:最低限度,我们希望保证评论和名字字段非空,同时我们还需要限制最大长度。
实现这一点很简单:
@cask.postForm("/")
def postHello(name: String, msg: String)= {
- messages = messages :+ (name -> msg)
+ if (name != "" && name.length < 10 && msg != "" && msg.length < 160){
hello
这样就可以阻止用户输入非法的name和msg,但出现了另一个问题:用户输入了非法的名字或信息并提交,那么这些信息就会消失,而且不会为错误产生任何反馈。解决方法是,给hello页面渲染一个可选的错误信息,用它来告诉用户出现了什么问题:
- if (name != "" && name.length < 10 && msg != "" && msg.length < 160){
- }
- hello
+ if (name == "") hello(Some("Name cannot be empty"))
+ else if (name.length >= 10) hello(Some("Name cannot be longer than 10 characters"))
+ else if (msg == "") hello(Some("Message cannot be empty"))
+ else if (msg.length >= 160) hello(Some("Message cannot be longer than 160 characters"))
+ else {
@cask.get("/")
- def hello= {
+ def hello(errorOpt: Option[String] = None)= {
+ for(error <- errorOpt)
+ yield i(color.red)(error),
form(action := "/", method := "post")(
现在,当名字或信息非法时,就可以正确地显示出错误信息了。
下一次提交时错误信息就会消失。
记住名字和消息
现在比较烦人的是,每次向聊天室中输入消息时,都要重新输入用户名。此外,如果用户名或信息非法,那消息就会被清除,只能重新输入并提交。可以让hello页面处理函数来填充这些字段,这样就可以解决:
- def hello(errorOpt: Option[String] = None) = {
+ def hello(errorOpt: Option[String] = None,
+ userName: Option[String] = None,
+ msg: Option[String] = None) = {
form(action := "/", method := "post")(
- input(`type` := "text", name := "name", placeholder := "User name", width := "20%", userName.map(value := _)),
- input(`type` := "text", name := "msg", placeholder := "Please write a message!", width := "60%"),
+ input(
+ `type` := "text",
+ name := "name",
+ placeholder := "User name",
+ width := "20%",
+ userName.map(value := _)
+ `type` := "text",
+ name := "msg",
+ placeholder := "Please write a message!",
+ width := "60%",
+ msg.map(value := _)
input(`type` := "submit", width := "20%")
这里我们使用了可选的userName和msg查询参数,如果它们存在,则将其作为HTML input标签的value的默认值。
接下来在postHello的处理函数中渲染页面时,填充userName和msg,再发送给用户:
def postHello(name: String, msg: String)= {
- if (name == "") hello(Some("Name cannot be empty"))
- else if (name.length >= 10) hello(Some("Name cannot be longer than 10 characters"))
- else if (msg == "") hello(Some("Message cannot be empty"))
- else if (msg.length >= 160) hello(Some("Message cannot be longer than 160 characters"))
+ if (name == "") hello(Some("Name cannot be empty"), Some(name), Some(msg))
+ else if (name.length >= 10) hello(Some("Name cannot be longer than 10 characters"), Some(name), Some(msg))
+ else if (msg == "") hello(Some("Message cannot be empty"), Some(name), Some(msg))
+ else if (msg.length >= 160) hello(Some("Message cannot be longer than 160 characters"), Some(name), Some(msg))
else {
messages = messages :+ (name -> msg)
+ hello(None, Some(name), None)
注意任何情况下我们都保留name,但只有错误的情况才保留msg。这样做是正确的,因为我们只希望用户在出错时才进行编辑并重新提交。
完整的代码MinimalApplication.scala如下所示:
var messages = Vector(
("alice", "Hello World!"),
("bob", "I am cow, hear me moo"),
("charlie", "I weigh twice as you"),
@cask.postForm("/")
def postHello(name: String, msg: String) = {
if (name == "") hello(Some("Name cannot be empty"), Some(name), Some(msg))
else if (name.length >= 10) hello(Some("Name cannot be longer than 10 characters"), Some(name), Some(msg))
else if (msg == "") hello(Some("Message cannot be empty"), Some(name), Some(msg))
else if (msg.length >= 160) hello(Some("Message cannot be longer than 160 characters"), Some(name), Some(msg))
hello(None, Some(name), None)
def hello(errorOpt: Option[String] = None,
userName: Option[String] = None,
msg: Option[String] = None) = {
for((name, msg) <- messages)
yield p(b(name), " ", msg)
for(error <- errorOpt)
yield i(color.red)(error),
input(
`type` := "text",
name := "name",
placeholder := "User name",
width := "20%",
userName.map(value := _)
name := "msg",
placeholder := "Please write a message!",
width := "60%",
msg.map(value := _)
利用Ajax实现动态页面更新
现在有了一个简单的、基于表单的聊天网站,用户可以发表消息,其他用户加载页面即可看到已发表的消息。下一步就是让网站变成动态的,这样用户不需要刷新页面就能发表消息了。
为实现这一点,我们需要做两件事情:
允许HTTP服务器发送网页的一部分,例如接收消息并渲染消息列表,而不是渲染整个页面
添加一些Javascript来手动提交表单数据。
渲染页面的一部分
要想只渲染需要更新的那部分页面,我们可以重构下代码,从hello页面处理函数中提取出messageList辅助函数:
)
+
+ def messageList = {
+ frag(
- for((name, msg) <- messages)
- yield p(b(name), " ", msg)
+ div(id := "messageList")(
+ messageList
接下来,我们可以修改postHello处理函数,从而仅渲染可能发生了变化的messageList,而不是渲染整个页面:
- @cask.postForm("/")
- def postHello(name: String, msg: String)= {
- if (name == "") hello(Some("Name cannot be empty"), Some(name), Some(msg))
- else if (name.length >= 10) hello(Some("Name cannot be longer than 10 characters"), Some(name), Some(msg))
- else if (msg == "") hello(Some("Message cannot be empty"), Some(name), Some(msg))
- else if (msg.length >= 160) hello(Some("Message cannot be longer than 160 characters"), Some(name), Some(msg))
- else {
- hello(None, Some(name), None)
+ @cask.postJson("/")
+ if (name == "") ujson.Obj("success" -> false, "txt" -> "Name cannot be empty")
+ else if (name.length >= 10) ujson.Obj("success" -> false, "txt" -> "Name cannot be longer than 10 characters")
+ else if (msg == "") ujson.Obj("success" -> false, "txt" -> "Message cannot be empty")
+ else if (msg.length >= 160) ujson.Obj("success" -> false, "txt" -> "Message cannot be longer than 160 characters")
+ ujson.Obj("success" -> true, "txt" -> messageList.render)
注意我们这里用@cask.postJson替换了@cask.postForm,此外不再调用hello来重新渲染整个页面,而是仅返回一个很小的JSON结构ujson.Obj,这样浏览器可以利用它更新HTML页面。ujson.Obj数据类型由uJson库提供。
利用Javascript更新页面
现在我们写好了服务器端代码,接下来我们编写相关的客户端代码,从服务器接收JSON响应,并利用它来更新HTML界面
要处理客户端逻辑,我们需要给一些关键的HTML元素添加ID,这样才能在Javascript中访问它们:
- for(error <- errorOpt)
- yield i(color.red)(error),
+ div(id := "errorDiv", color.red),
input(
- name := "name",
+ id := "nameInput",
width := "20%"
- name := "msg",
+ id := "msgInput",
width := "60%"
接下来,在页面头部引入一系列Javascript:
- ),
+ script(raw("""
+ function submitForm{
+ fetch(
+ "/",
+ {
+ method: "POST",
+ body: JSON.stringify({name: nameInput.value, msg: msgInput.value})
+ ).then(response => response.json)
+ .then(json => {

+ if (json.success) {
+ messageList.innerHTML = json.txt
+ msgInput.value = ""
+ errorDiv.innerText = ""
+ } else {
+ errorDiv.innerText = json.txt
+ })
+ """))
从表单的onsubmit处理函数中调用该Javascript函数:
- form(action := "/", method := "post")(+ form(onsubmit := "submitForm; return false")(
这样就可以了。现在向网站添加聊天文本,文本就会立即出现在网页上,之后加载页面的其他人也能看见。
最终的代码如下:
def messageList= {
frag(
@cask.postJson("/")
if (name == "") ujson.Obj("success" -> false, "txt" -> "Name cannot be empty")
else if (name.length >= 10) ujson.Obj("success" -> false, "txt" -> "Name cannot be longer than 10 characters")
else if (msg == "") ujson.Obj("success" -> false, "txt" -> "Message cannot be empty")
else if (msg.length >= 160) ujson.Obj("success" -> false, "txt" -> "Message cannot be longer than 160 characters")
ujson.Obj("success" -> true, "txt" -> messageList.render)
script(raw("""
function submitForm{
fetch(
"/",
{
method: "POST",
body: JSON.stringify({name: nameInput.value, msg: msgInput.value})
).then(response => response.json)
.then(json => {
if (json.success) {
messageList.innerHTML = json.txt
msgInput.value = ""
errorDiv.innerText = ""
} else {
errorDiv.innerText = json.txt
})
"""))
div(id := "messageList")(
messageList
div(id := "errorDiv", color.red),
form(onsubmit := "submitForm; return false")(
id := "nameInput",
id := "msgInput",
注意尽管你输入的消息你自己可以立即看到,但其他人只有刷新页面,或者输入自己的消息迫使messageList重新加载,才能看到你的消息。本文的最后一节将介绍怎样让所有人立即看到你的消息,而不需要手动刷新。
利用Websockets实时更新页面
推送更新的概念和简单:每次提交新消息后,就将消息”推送"到所有监听中的浏览器上,而不是等待浏览器刷新并“拉取”更新后的数据。实现这一目的有多种方法。本文我们使用Websockets。
Websockets可以让浏览器和服务器在正常的HTTP请求-响应流之外互相发送消息。连接一旦建立,任何一方都可以在任何时间发送消息,消息可以包含任意字符串或任意字节。
我们要实现的流程如下:
网站加载后,浏览器建立到服务器的websocket连接
连接建立后,浏览器将发送消息"0"到服务器,表明它已准备好接收更新
服务器将响应初始的txt,其中包含所有已经渲染的消息,以及一个index,表示当前的消息计数
每当收到消息时,浏览器就会将最后看到的index发送给服务器,然后等待新消息出现,再按照步骤3进行响应
在服务器上实现这一点的关键就是保持所有已打开的连接的集合:
var openConnections = Set.empty[cask.WsChannelActor]
该集合包含当前等待更新的浏览器的列表。每当新消息出现时,我们就会向这个列表进行广播。
接下来,定义@cask.websocket处理函数,接收进入的websocket连接并处理:
@cask.websocket("/subscribe")
def subscribe = {
cask.WsHandler { connection =>
cask.WsActor {
case cask.Ws.Text(msg) =>
if (msg.toInt < messages.length){
connection.send(
cask.Ws.Text(
ujson.Obj("index" -> messages.length, "txt" -> messageList.render).render
}else{
openConnections += connection
case cask.Ws.Close(_, _) => openConnections -= connection
该处理函数接收来自浏览器的msg,检查其内容是否应该立即响应,还是应该利用openConnections注册一个连接再稍后响应。
我们需要在postHello处理函数中做类似的修改:
messages = messages :+ (name -> msg)
+ val notification = cask.Ws.Text(
+ ujson.Obj("index" -> messages.length, "txt" -> messageList.render).render
+ for(conn <- openConnections) conn.send(notification)
+ openConnections = Set.empty
这样,每当新的聊天消息提交时,就会将它发送给所有打开的连接,以通知它们。
最后,我们需要在浏览器的script标签中添加一点Javascript代码,来打开Websocket连接,并处理消息的交换:
var socket = new WebSocket("ws://" + location.host + "/subscribe");
var eventIndex = 0
socket.onopen = function(ev){ socket.send("" + eventIndex) }
socket.onmessage = function(ev){
var json = JSON.parse(ev.data)
eventIndex = json.index
socket.send("" + eventIndex)
这里,我们首先打开一个连接,发送第一条"0"消息来启动整个流程,然后每次收到更新后,就将json.txt渲染到messageList中,然后将json.index发送回服务器,来订阅下一次更新。
现在,同时打开两个浏览器,就会看到一个窗口中发送的聊天消息立即出现在另一个窗口中:
本节的完整代码如下:
var openConnections = Set.empty[cask.WsChannelActor]
val notification = cask.Ws.Text(
for(conn <- openConnections) conn.send(notification)
openConnections = Set.empty
var socket = new WebSocket("ws://" + location.host + "/subscribe");
socket.onopen = function(ev){ socket.send("0") }
socket.send("" + json.index)
@cask.websocket("/subscribe")
def subscribe= {
总结
本文我们介绍了怎样利用Scala实现实时聊天网站和API服务器。我们从静态网站开始,添加基于表单的交互,再利用Ajax访问JSON API实现动态页面,最后利用websocket实现推送通知。我们使用了Cask web框架,Scalatags HTML库,以及uJson序列化库,代码大约125行。
这里展示的聊天网站非常简单。我们故意忽略了将消息保存到持久数据库、认证、用户账号、多聊天室、使用量限制以及许多其他的功能。这里仅使用了内存上的messages列表和openConnections集合,从并发更新的角度来看,它们并非线程安全的。但无论如何,希望这篇文章能够让你体会到怎样使用Scala实现简单的网站和API服务器,进而用它构建更大、更宏伟的应用程序。
英文:Simple Web and Api Servers with Scala
原文链接:
【END】
CSDN 博客诚邀入驻啦!
本着共享、协作、开源、技术之路我们共同进步的准则
只要你技术够干货,内容够扎实,分享够积极
欢迎加入 CSDN 大家庭!
一、前言网站主题最初是在开发数据api的时候看到的,当时在学习Mybatis-Plus,最初也不知道这是个主题,就觉得Mybatis-Plus官网UI真的不错,直到后面发现另外一个类似的网站,我意识到这是个模板,于是有了自己也搭建一个的想法,不过拖延症相当严重,最初是在宝塔上修改的,效率极低,一度放弃部署,直到最近换成本地开发才好了很多,也就是最近网站搭建初步完成,接下来就是往里面增加文章等等,这里带大家从零开始搭建Vdoing主题网站,算是对过去一个月的知识总结。下图就是mybatis-plus官网,是我们年轻人喜欢的风格(这里随便吐槽一下xx园吧,风格还是20年前的,不知道他们的UI设计师是不是还是兼职的)。
二、准备工作1.github注册what?你居然没有github账号,建议你放弃网站部署(开玩笑)。作为全球最大的代码托管平台,基本上每个稍微接触过开发的应该都用过,如果你还没注册的话,赶紧注册一个,没听过github太丢人了,注册也很简单(有手就行)。
点击进入github官网,这里需要注意一点,如果是国内访问的话,移动是可以的,其他运营商貌似有屏蔽,如果访问不了,自行百度解决。
2.申请属于自己的域名这里推荐去国外的Godaddy平台注册一个域名
3.申请注册vercel平台vercel平台是一个可以免费托管个人网站的自动化部署平台,只要你的代码托管平台(类似github,gitlab,gitee等)commit发生变化,它就会自动更新和重新部署,解放生产力。
点击vercel官网sign up注册一个账号,当然也可以选择github直接登录,会更方便一点,可以直接导入自己的仓库,包括私有仓库。
4.下载node和yarnnode进入node的官网,选择LTS即长期稳定版本下载。会得到一个msi后缀的文件,即开箱即用的版本,不用自己手动配置环境变量啥的,会配置好的,注意安装位置改一下就行,放在除c盘外的盘即可,其他就一步步点击确认即可,最后cmd中输入node有welcome提示即为成功,不成功按照相应提示浏览器搜索即可。
yarn这个是个包管理工具,和npm差不多,听说更快一点,这里安装是由于主题更新采用的是这个命令,所以这里安装一下。
yarn官网,如果不想配置环境变量的话,还是可以采用msi后缀安装。
5.图床配置CDN + COS配置图床有了图床,你就可以随时随地采用URL引用你的图片,非常方便,可以参考我写的这篇搭建博客CDN+COS搭建图床超详细步骤,有个缺点就是需要备案才能使用自定义域名和CDN服务,不过如果想要白嫖的话,搜索国外的图床也是可以的,国内也有一些提供图床配置的,使用它们的域名上传图片也可以。这里采用的是pic-go图床管理工具,强烈安利,真的太方便了。
路径引用如果你没有自己的图床也没关系,主题博客还提供本地引用的功能,不过这个更改图片的时候比较麻烦,虽然有时候它可能更快一点点。
6.开发IDE下载我是用习惯了idea,所以这里推荐idea进行开发,安装node.js插件,然后就可以本地开发了,没有的话安装一下即可。
idea官网
安装node.js插件
至此,准备工作已经完成,接下来就进入今天的主题。
三、本地开发1.github fork项目Vdoing主题项目地址,这里建议fork整个仓库,然后自己做一些修改即可,作者大大真的太强了。
2.基础网站配置主页配置打开以下目录的index.md,你可能会问咋是md格式的,不应该是html格式的吗?别着急,这个是开发中的,打包之后是index.html
以下字段对应相关的图,可以自行体会一下,对应导航栏下面的几个字段,其中涉及使用URL引入的,可以替换成路径引用,具体字段帮助请自行查看fork过来的仓库介绍,也可以到Vdoing官方文档查阅.
home: trueheroImage: # heroImage:/img/web.png# 这个路径在public下的img目录中heroText: IT七剑客tagline: 互联网中的持剑者actionText: 立刻进入 →actionLink: /wresource/bannerBg: none
通过路径引用的图片目录,把你想要的图片全放进去然后就可以引用了。
config.ts配置进入config.ts界面
首先我们先找到head位置进行配置,打开网站的小图标,同样也可以使用URL或者本地引用
找到location开头的这段代码,网站的标题和描述写一下,这个会在百度搜索的快照中展示。
locales: {'/': {//代表baseurl,即网站所有的引用的根路径,默认是没有的,如果文件夹有需要可以修改lang: 'zh-CN',title: "IT七剑客",description: '互联网中的持剑者,七剑客带你斩断一切bug',}},
下面这些代码主要是配置导航栏和其他全局功能,其中logo是最左侧显示的图片,repro是自己的github名,用于直接跳转github主页,其他的取默认即可。
sidebarDepth: 2, // 侧边栏显示深度,默认1,最大2(显示到h3标题)logo: '', // 导航栏logorepo: 'xyh-fu', // 导航栏右侧生成Github链接searchMaxSuggestions: 10, // 搜索结果显示最大数lastUpdated: '上次更新', // 开启更新时间,并配置前缀文字string | boolean (取值为git提交时间)docsDir: 'docs', // 编辑的文件夹// docsBranch: 'master', // 编辑的文件所在分支,默认master。 注意:如果你的分支是main则修改为maineditLinks: true, // 启用编辑editLinkText: '编辑',
footer:{ // 页脚信息createYear: 2022, // 博客创建年份//博客版权信息,支持a标签copyrightInfo:'IT七剑客 | MIT License<li><img src="" style="zoom:30%;"><a href = "" target="_blank" >闽ICP备2021006579号-4</a></li><li><img src="" style="zoom:30%;"> <a target="_blank" rel="noopener" href="?recordcode=35012102500470">闽公网安备 35012102500470号</a></li>'},
至此基本配置已经大功告成,接下来我们谈谈文章是怎么配置和主页路由的规则。
文章誊写和路由设置首先配置目录页,这个是文章的导航界面,可以分别查看各个目录下的文章列表
---pageComponent:name: Catalogue data:path: 01.wresource 安卓imgUrl:description: Android,Kotlin,Jetpack等技术title: wresource 安卓date: 2020-03-11 21:50:53permalink: /wresourcesidebar: falsearticle: falsecomment: falseeditLink: falseauthor: name: wresource link:
在这里设置目录的路由以及一些基础的配置,放图自行体会,这里需要注意一点,path要和docs下面的相应文章目录路径名一致,这里仅仅只是配置一下界面,真正的文章是放在docs下面的目录中。
关于文章誊写和命名规则具体查看作者的这篇文章关于目录和文章名称约定(这里偷懒一波)。
接下来再次回到之前得index.md中,可以配置主界面相关信息了
features: # 可选的 - title: wresource 安卓details: Android,Kotlin,Jetpack等技术link: /wresource/ # 可选imgUrl: # 可选
配置效果如下图所示
3.插件配置这部分就属于网站的内部配置了,与外观没有多大关系,如果觉得可以了,就不用配置了
站点信息页面主要用于统计网站相关信息的功能同时也增加文章字数统计以及阅读量统计等等,这个配置参考kbt大大的博客,这里提醒一下readfile文件在博客中失效,建议去kbt大大的github项目主页获取最新的代码。站点信息搭建
这里给出当时踩坑的那个readfile文件代码,如果有不同,以作者的为主。
import fs from 'fs'; // 文件模块import path from 'path'; // 路径模块import matter from 'gray-matter'; // FrontMatter解析器 chalk from 'chalk' // 命令行打印美化const log = console.logconst docsRoot = path.join(__dirname, '..', '..', '..', 'docs'); // docs文件路径/** * 获取本站的文章数据 * 获取所有的 md 文档,可以排除指定目录下的文档 */function readFileList(excludeFiles: Array<string> = [''], dir: string = docsRoot, filesList: Array<Object> = []) { const files = fs.readdirSync(dir); files.forEach((item, index) => {let filePath = path.join(dir, item);const stat = fs.statSync(filePath);if (!(excludeFiles instanceof Array)) {log(chalk.yellow(`error: 传入的参数不是一个数组。`))}excludeFiles.forEach((excludeFile) => {if (stat.isDirectory() && item !== '.vuepress' && item !== '@pages' && item !== excludeFile) {readFileList(excludeFiles, path.join(dir, item), filesList); //递归读取文件} else {if (path.basename(dir) !== 'docs') { // 过滤 docs目录级下的文件const fileNameArr = path.basename(filePath).split('.')let name = null, type = null;if (fileNameArr.length === 2) { // 没有序号的文件name = fileNameArr[0]type = fileNameArr[1]} else if (fileNameArr.length === 3) { // 有序号的文件name = fileNameArr[1]type = fileNameArr[2]} else { // 超过两个‘.’的log(chalk.yellow(`warning: 该文件 "${filePath}" 没有按照约定命名,将忽略生成相应数据。`))return}if (type === 'md') { // 过滤非 md 文件filesList.push({name,filePath});}}}}); }); return filesList;}/** * 获取本站的文章总字数 * 可以排除某个目录下的 md 文档字数 */function readTotalFileWords(excludeFiles = ['']) { const filesList = readFileList(excludeFiles); let wordCount = 0; filesList.forEach((item: any) => {const content = getContent(item.filePath);let len = counter(content);wordCount += len[0] + len[1]; }); if (wordCount < 1000) {return wordCount; } return Math.round(wordCount / 100) / 10 + 'k';}/** * 获取每一个文章的字数 * 可以排除某个目录下的 md 文档字数 */function readEachFileWords(excludeFiles: Array<string> = [''], cn: number, en: number) { const filesListWords = []; const filesList = readFileList(excludeFiles); filesList.forEach((item: any) => {const content = getContent(item.filePath);let len = counter(content);// 计算预计的阅读时间let readingTime = readTime(len, cn, en);let wordsCount: any = 0;wordsCount = len[0] + len[1];if (wordsCount >= 1000) {wordsCount = Math.round(wordsCount / 100) / 10 + 'k';}// fileMatterObj => {content:'剔除frontmatter后的文件内容字符串', data:{<frontmatter对象>}, ...}const fileMatterObj = matter(content, {});const matterData = fileMatterObj.data;filesListWords.push({ ...item, wordsCount, readingTime, ...matterData }); }); return filesListWords;}/** * 计算预计的阅读时间 */function readTime(len: Array<number>, cn: number = 300, en: number = 160) { let readingTime = len[0] / cn + len[1] / en; if (readingTime > 60 && readingTime < 60 * 24) {// 大于一个小时,小于一天let hour = Math.trunc(readingTime / 60);let minute = Math.trunc(readingTime - hour * 60);if (minute === 0) {return hour + 'h';}return hour + 'h' + minute + 'm'; } else if (readingTime > 60 * 24) {// 大于一天let day = Math.trunc(readingTime / (60 * 24));let hour = Math.trunc((readingTime - day * 24 * 60) / 60);if (hour === 0) {return day + 'd';}return day + 'd' + hour + 'h'; } return readingTime < 1 ? '1' : Math.trunc(readingTime * 10) / 10 + 'm';// 取一位小数}/** * 读取文件内容 */function getContent(filePath: string) { return fs.readFileSync(filePath, 'utf8');}/** * 获取文件内容的字数 * cn:中文 * en:一整句英文(没有空格隔开的英文为 1 个) */function counter(content: string) { const cn = (content.match(/[u4E00-u9FA5]/g) || []).length; const en = (content.replace(/[u4E00-u9FA5]/g, '').match(/[a-zA-Z0-9_u0392-u03c9u0400-u04FF]+|[u4E00-u9FFFu3400-u4dbfuf900-ufaffu3040-u309fuac00-ud7afu0400-u04FF]+|[u00E4u00C4u00E5u00C5u00F6u00D6]+|w+/g) || []).length; return [cn, en];}export { readFileList, readTotalFileWords, readEachFileWords,}
下面给出效果图
这里有一点比较坑,就是当时这个模块默认挂载到热门标签下面,然而我没有设置标签。最后给挂在了页面底部。请务必仔细阅读作者的博客。
广告配置首先在config.ts的head中引入下列代码,这里是谷歌广告配置。
['script',{'data-ad-client': '自己的广告id',async: 'async',src: '',},], // 网站关联Google AdSense 与 html格式广告支持(你可以去掉)
然后在themeConfig中添加下列字段
在下列目录中完成html广告的配置
我的配置,在全局右下角添加广告。
windowRB:` <script async src=""></script><ins class="adsbygoogle"style="display:inline-block;width:200px;height:200px"data-ad-client="ca-pub-广告id"data-ad-slot="用户名"></ins><script>(adsbygoogle = window.adsbygoogle || []).push({});</script>`,
复制
百度统计这部分比较简单,在head中加入以下字段即可(谁说比较简单的,这个我鼓捣了一周,插件用不了,不知道是啥原因,最后采用这个比较简单的方法配置),至于百度统计码,百度一下百度统计即可。它最重要的作用就是分析网站的来访数量以及搜索量等等,站长必备工具之一。
['script',{src: '?百度统计码',},],
复制
评论配置这个是最近配置成功的,踩了好多坑,也是参考kbt大大的博客,我用的也是twikoo评论,之前使用gittalk的,后面发现国内根本不能访问,所以换成了目前这个评论系统。kbt大大写的搭建评论的博客,说明几个坑。
部署时注意是部署云函数,不是该评论系统项目,被这个坑了好久好久。点击自动部署会自动进入dev分支下的vercel配置,不是整个仓库
关于mongo db,感觉应该是香港节点会更快一点,不知道为啥会推荐us节点关于环境id这里应该是最近vercel的默认域名被禁的缘故,vercel.app之类的域名国内均不能访问,所以在部署环境id时需要添加自己的域名进行部署,类似xx.de7v.com。
关于管理员的评论管理最开始是使用代理才可以进行配置的,之前都失败了,这一步如果没有默认的密码账号输入的话,建议开代理。
四、站点部署1.COS+CDN部署温馨提示,这部分部署需要国内备案域名,如果没有备案域名,请使用其他两种方式部署。
首先先运行vuepress项目,进入项目路径,终端输入以下命令进行构建项目
npm run build
复制
生成静态文件成功之后可以在dist文件夹找到打包好的静态文件
先创建一个存储桶上传文件直接将文件复制一下拖拽到cos的以下界面,注意是dist里面的文件,不要传dist整个文件夹,反正最后是存储桶中有如下的内容,即dist里面的文件。
开启静态网站设置此时还访问不了,如果想直接访问的话,权限管理改成公共读就可以访问了,使用访问节点的url即可,不过我们要介绍使用cdn进行访问,更快。
cdn配置访问利用cdn进行私有读写,cdn提供给外部访问,配置相关可以查看这篇博客里面的配置,几乎是一样的,不多赘述。CDN+COS搭建图床超详细步骤,最后记得开启https访问。
至此我们已经完成了cos+cdn部署静态网站。下面是部署成功的站点,访问速度还可以。
2.vercel部署vercel部署可以说是最简单的部署,还带自动化部署的功能,强烈推荐,目前主站是部署在vercel上的。
进入主界面,选择添加新项目:Add New,选择Project
之后就是选择导入github中的项目,私有仓库也可以,gitpage中这个功能貌似收费,只能public。
配置运行参数,这部分是当初最坑的那个,当时不知道输出填啥,一直构建失败
先选择vue.js构建
接下来就是build和Output settings,按照以下设置即可
其中output directory是很坑,之前不知道这个是输出文件,一直部署dev模式的网站,速度感人。
点击deploy即部署的意思
接下来就是稍微有点长的构建时间,大概10分钟吧,主要是Vdoing作者的文章比较多,构建需要花一定时间。
部署完毕是这样的,不过目前有个小尴尬的事情,自动提供的这几个域名国内访问不了,所以只能自己添加域名了。
在domains中添加自己的域名
先添加,然后根据提示进行添加dns解析
按照提示添加完解析即可
添加完会自动帮你添加ssl证书
大功告成
至此部署已经完成,之后只需要远程提交自己的修改到github上面即可完成修改界面。
创建静态网站记得填入自己的域名并添加dns解析
将里面的文件删去,最后一个.user.ini没删除问题不大,最主要是为了防止冲突
上传完解压一下就行
将解压后的文件复制到网站最开始的目录下,并删去原来的dist和dist.rar文件
最后申请一下免费的证书和开启强制https就结束了(声明,没有hhh.com域名,只是随便输一个)
浏览器键入自己的域名就可以访问了,我的宝塔部署的站点IT七剑客,挺快的,比之前dev模式好多了
五、常用命令安装node依赖自动安装缺少的依赖
npm install项目调试
在测试功能时使用,该模式下,所有改动均可以快速看到效果,无需重新构建
npm run dev项目打包
用于生成环境的打包,项目成功之后可以打包,然后部署到各个地方
npm run build六、写在最后
这次网站的部署大概经历了前前后后一个月,都是凌晨肝一会,书读的少,做事就非常费劲。希望大家能够通过我的这篇文章少走弯路,那这篇文章就是有价值的,有问题评论区留言。