├── .gitignore
├── Jenkinsfile
├── README.md
├── _lib
└── sbt-launch.jar
├── akka-rest-api
├── README.md
├── build.sbt
├── docs
│ └── scripts.txt
├── project
│ ├── build.properties
│ ├── plugins.sbt
│ └── sbt-launch.jar
├── sbt
└── src
│ └── main
│ ├── resources
│ ├── application.conf
│ └── logback.xml
│ └── scala
│ └── me
│ └── yangbajing
│ └── akkarestapi
│ ├── Main.scala
│ ├── SystemUtils.scala
│ ├── model
│ ├── NewsResult.scala
│ ├── NewsStatus.scala
│ └── ResponseMessage.scala
│ ├── repo
│ ├── MongoRepo.scala
│ ├── MongoSettings.scala
│ └── NewsRepo.scala
│ ├── routes
│ ├── ApiRoute.scala
│ ├── MongoApiRoute.scala
│ └── NewsApiRoute.scala
│ ├── service
│ ├── ContextProps.scala
│ └── NewsService.scala
│ └── utils
│ ├── DocumentSupport.scala
│ ├── JsonSupport.scala
│ └── Utils.scala
├── combine-request
├── README.md
├── build.sbt
├── project
│ └── build.properties
├── sbt
└── src
│ └── main
│ └── scala
│ └── combinerequest
│ ├── CombineExample.scala
│ ├── RepetitionExample.scala
│ ├── domain
│ └── Company.scala
│ ├── message
│ └── Messages.scala
│ └── service
│ ├── CompanyMaster.scala
│ ├── CorpDetailActor.scala
│ ├── EnterpriseService.scala
│ ├── ForwardCompanyActor.scala
│ ├── InfraMongodbRepo.scala
│ └── InfraResource.scala
├── email-server
├── README.md
├── project
│ ├── Build.scala
│ ├── build.properties
│ ├── plugins.sbt
│ └── sbt-launch.jar
├── sbt
└── src
│ ├── main
│ ├── resources
│ │ └── application.conf
│ └── scala
│ │ └── me
│ │ └── yangbajing
│ │ └── emailserver
│ │ ├── ActorService.scala
│ │ ├── JsonImplicits.scala
│ │ ├── Main.scala
│ │ ├── common
│ │ ├── enums
│ │ │ └── MimeType.scala
│ │ └── settings
│ │ │ └── Settings.scala
│ │ ├── demo
│ │ ├── ConsumerSynchronous.scala
│ │ ├── EmailProducers.scala
│ │ └── ProducerSynchronous.scala
│ │ ├── domain
│ │ └── Emails.scala
│ │ ├── route
│ │ └── Routes.scala
│ │ ├── service
│ │ ├── EmailService.scala
│ │ ├── MQConsumerService.scala
│ │ └── actors
│ │ │ ├── EmailGroupActor.scala
│ │ │ └── EmailMaster.scala
│ │ └── util
│ │ └── Utils.scala
│ └── test
│ └── scala
│ ├── me
│ └── yangbajing
│ │ └── emailserver
│ │ └── common
│ │ └── settings
│ │ └── SettingsTest.scala
│ └── sw.sc
├── file-upload
├── .gitignore
├── .scalafmt.conf
├── Jenkinsfile
├── README.md
├── build.sbt
├── project
│ ├── build.properties
│ ├── plugins.sbt
│ └── sbt-launch.jar
├── sbt
├── sbt-dist
│ ├── bin
│ │ ├── sbt
│ │ ├── sbt-launch-lib.bash
│ │ ├── sbt-launch.jar
│ │ └── sbt.bat
│ └── conf
│ │ ├── sbtconfig.txt
│ │ └── sbtopts
├── sbt.bat
├── src
│ ├── main
│ │ └── scala
│ │ │ └── me
│ │ │ └── yangbajing
│ │ │ └── fileupload
│ │ │ ├── Constants.scala
│ │ │ ├── Main.scala
│ │ │ ├── controller
│ │ │ ├── FileRoute.scala
│ │ │ └── HtmlRoute.scala
│ │ │ ├── io
│ │ │ └── CustomFileSource.scala
│ │ │ ├── model
│ │ │ └── FileInfo.scala
│ │ │ ├── service
│ │ │ ├── FileService.scala
│ │ │ └── FileServiceImpl.scala
│ │ │ └── util
│ │ │ ├── FileUtils.scala
│ │ │ ├── Jackson.scala
│ │ │ ├── JacksonSupport.scala
│ │ │ └── Utils.scala
│ └── test
│ │ └── scala
│ │ └── me
│ │ └── yangbajing
│ │ └── fileupload
│ │ └── controller
│ │ └── FileRouteTest.scala
└── web
│ ├── css
│ ├── bootstrap-grid.css
│ ├── bootstrap-reboot.css
│ └── bootstrap.css
│ ├── js
│ ├── axios.js
│ ├── bootstrap.js
│ ├── jquery.js
│ └── sha256.js
│ └── upload.html
├── jdbc-slick
├── Dockerfile
├── README.md
├── build.sbt
├── init.sql
├── project
│ └── build.properties
├── sbt
└── src
│ ├── main
│ └── scala
│ │ ├── combinerequest
│ │ ├── CombineExample.scala
│ │ ├── RepetitionExample.scala
│ │ ├── domain
│ │ │ └── Company.scala
│ │ ├── message
│ │ │ └── Messages.scala
│ │ └── service
│ │ │ ├── CompanyMaster.scala
│ │ │ ├── CorpDetailActor.scala
│ │ │ ├── EnterpriseService.scala
│ │ │ ├── ForwardCompanyActor.scala
│ │ │ ├── InfraMongodbRepo.scala
│ │ │ └── InfraResource.scala
│ │ └── sample
│ │ ├── model
│ │ └── Org.scala
│ │ ├── respository
│ │ ├── OrgRepository.scala
│ │ ├── Schema.scala
│ │ └── SlickProfile.scala
│ │ ├── route
│ │ └── OrgRoute.scala
│ │ ├── service
│ │ └── OrgService.scala
│ │ └── util
│ │ ├── Jackson.java
│ │ ├── JacksonHelper.java
│ │ └── JacksonSupport.scala
│ └── test
│ └── scala
│ └── sample
│ └── route
│ └── OrgRouteTest.scala
├── scala-js
├── build.sbt
├── index-dev.html
├── project
│ ├── build.properties
│ └── plugins.sbt
├── scalajs-tutorial.html
└── src
│ ├── main
│ └── scala
│ │ └── tutorial
│ │ └── webapp
│ │ └── TutorialApp.scala
│ └── test
│ └── scala
│ └── tutorial
│ └── webapp
│ └── TutorialAppTest.scala
├── scala-script
├── build.sbt
├── sbt
└── src
│ └── main
│ └── scala
│ ├── Demo.scala
│ └── ScalaExample.scala
├── scalatest
├── README.md
├── build.sbt
├── project
│ └── build.properties
├── sbt
└── src
│ └── test
│ └── scala
│ └── first
│ └── FirstTest.scala
├── spark-startup
├── .gitignore
├── build.sbt
├── project
│ ├── build.properties
│ └── plugins.sbt
├── sbt
├── scripts
│ └── SparkApp.sh
└── src
│ ├── main
│ └── scala
│ │ └── example
│ │ └── SparkApp.scala
│ └── test
│ └── scala
│ └── example
│ └── SparkAppTest.scala
└── springscala
├── .gitignore
├── build.gradle
├── common
├── build.gradle
└── src
│ └── main
│ ├── java
│ └── me
│ │ └── yangbajing
│ │ └── demo
│ │ └── common
│ │ └── Constants.java
│ └── scala
│ └── me
│ └── yangbajing
│ └── demo
│ └── common
│ └── Utils.scala
├── settings.gradle
└── web
├── build.gradle
└── src
├── main
├── java
│ └── me
│ │ └── yangbajing
│ │ └── demo
│ │ ├── SpringscalaApplication.java
│ │ ├── config
│ │ └── SpringscalaConfig.java
│ │ └── web
│ │ └── controllers
│ │ └── WebController.java
├── resources
│ └── application.properties
└── scala
│ └── me
│ └── yangbajing
│ └── demo
│ ├── data
│ └── domain
│ │ └── Message.scala
│ └── web
│ └── controllers
│ └── ApiController.scala
└── test
└── java
└── me
└── yangbajing
└── demo
└── SpringscalaApplicationTests.java
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | *.class
3 | *.log
4 |
5 | .~lock.*
6 | .*.swp
7 | .cache
8 | *.error
9 | .history
10 | .DS_Store
11 | .ensime
12 | .classpath
13 | .project
14 | .settings
15 | .Rhistory
16 | .target/
17 | .ensime_lucene/
18 | .worksheet/
19 | .metadata/
20 | .idea_modules/
21 | .idea/
22 |
23 | Backup/
24 |
25 | fine-food/web/src/main/webapp/upload/
26 |
27 | # sbt specific
28 | dist/*
29 | target/
30 | lib_managed/
31 | src_managed/
32 | project/boot/
33 | project/plugins/project/
34 | cqust/jpkc/src/main/webapp/upload/
35 |
36 | # Scala-IDE specific
37 | .scala_dependencies
38 |
39 | # Sphinx rst
40 | *.pyc
41 | docs/_build/
42 | docs/_sphinx/exts/__pycache__/
43 |
--------------------------------------------------------------------------------
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | pipeline {
2 | agent {
3 | // 此处设定构建环境,目前可选有
4 | // default, java-8, python-3.5, ruby-2.3, go-1.11 等
5 | // 详情请阅 https://dev.tencent.com/help/knowledge-base/how-to-use-ci#agents
6 | label "default"
7 | }
8 | stages {
9 |
10 | stage("检出") {
11 | steps {
12 | sh 'ci-init'
13 | checkout(
14 | [$class: 'GitSCM', branches: [[name: env.GIT_BUILD_REF]],
15 | userRemoteConfigs: [[url: env.GIT_REPO_URL]]]
16 | )
17 | }
18 | }
19 |
20 | stage("构建") {
21 | steps {
22 | echo "构建中..."
23 | sh 'go version'
24 | sh 'node -v'
25 | sh 'java -version'
26 | sh 'php -v'
27 | sh 'python -V'
28 | sh 'gcc -v'
29 | sh 'make -v'
30 | // 请在这里放置您项目代码的单元测试调用过程,例如:
31 | // sh 'mvn package' // mvn 示例
32 | // sh 'make' // make 示例
33 | echo "构建完成."
34 | // archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true // 收集构建产物
35 | }
36 | }
37 |
38 | stage("测试") {
39 | steps {
40 | echo "单元测试中..."
41 | // 请在这里放置您项目代码的单元测试调用过程,例如:
42 | // sh 'mvn test' // mvn 示例
43 | // sh 'make test' // make 示例
44 | echo "单元测试完成."
45 | // junit 'target/surefire-reports/*.xml' // 收集单元测试报告的调用过程
46 | }
47 | }
48 |
49 | stage("部署") {
50 | steps {
51 | echo "部署中..."
52 | // 请在这里放置收集单元测试报告的调用过程,例如:
53 | // sh 'mvn tomcat7:deploy' // Maven tomcat7 插件示例:
54 | // sh './deploy.sh' // 自研部署脚本
55 | echo "部署完成"
56 | }
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # scala-applications
2 |
3 | 一些Scala应用程序的尝试
4 |
5 | - [email-server](https://github.com/yangbajing/scala-applications/tree/master/email-server):
6 | 一个邮件发送服务器,使用actor邮箱来保持发送队列。支持REST API和ActiveMQ两种提交发送邮件方式。
7 | - [akka-rest-api](https://github.com/yangbajing/scala-applications/tree/master/akka-rest-api): 使用Akka Http设计REST API。
8 |
9 |
--------------------------------------------------------------------------------
/_lib/sbt-launch.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yangbajing/scala-applications/a41bd2b1e5eef086cf54fe58630bb47d7c16df39/_lib/sbt-launch.jar
--------------------------------------------------------------------------------
/akka-rest-api/README.md:
--------------------------------------------------------------------------------
1 | # 使用Akka Http设计REST API
2 |
3 | - akka-http
4 | - mongo-scala-driver
5 |
6 |
--------------------------------------------------------------------------------
/akka-rest-api/build.sbt:
--------------------------------------------------------------------------------
1 | description := "Akka REST API"
2 |
3 | version := "0.0.2"
4 |
5 | homepage := Some(
6 | new URL(
7 | "https://github.com/yangbajing/scala-applications/tree/master/akka-rest-api"
8 | )
9 | )
10 |
11 | organization := "me.yangbajing"
12 |
13 | organizationHomepage := Some(
14 | new URL("https://github.com/yangbajing/scala-applications")
15 | )
16 |
17 | startYear := Some(2015)
18 |
19 | scalaVersion := "2.11.12"
20 |
21 | scalacOptions ++= Seq(
22 | "-encoding",
23 | "utf8",
24 | "-unchecked",
25 | "-feature",
26 | "-deprecation"
27 | )
28 |
29 | javacOptions ++= Seq(
30 | "-encoding",
31 | "utf8",
32 | "-Xlint:unchecked",
33 | "-Xlint:deprecation"
34 | )
35 |
36 | javaOptions += "-Dproject.base=" + baseDirectory.value
37 |
38 | resolvers ++= Seq(
39 | "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/",
40 | "Sonatype releases" at "http://oss.sonatype.org/content/repositories/releases",
41 | "Typesafe Snapshots" at "http://repo.typesafe.com/typesafe/snapshots/",
42 | "Sonatype snapshots" at "http://oss.sonatype.org/content/repositories/snapshots"
43 | )
44 |
45 | assemblyJarName in assembly := "akka-rest-api.jar"
46 |
47 | mainClass in assembly := Some("me.yangbajing.akkarestapi.Main")
48 |
49 | libraryDependencies ++= Seq(
50 | _mongoScala,
51 | _json4sJackson,
52 | _redisclient,
53 | _akkaHttpCore,
54 | _akkaActor,
55 | _akkaSlf4j,
56 | _logback,
57 | _typesafeConfig,
58 | _scalaLogging,
59 | _scalatest
60 | )
61 |
62 | lazy val _scalaXml = "org.scala-lang.modules" %% "scala-xml" % "1.0.4"
63 | lazy val _scalatest = "org.scalatest" %% "scalatest" % "2.2.5" % "test"
64 | lazy val _typesafeConfig = "com.typesafe" % "config" % "1.3.0"
65 | lazy val _scalaLogging = ("com.typesafe.scala-logging" %% "scala-logging" % "3.1.0")
66 | .exclude("org.scala-lang", "scala-library")
67 | .exclude("org.scala-lang", "scala-reflect")
68 |
69 | lazy val verAkka = "2.5.19"
70 |
71 | lazy val _akkaHttpCore = "com.typesafe.akka" %% "akka-http" % "10.1.6"
72 |
73 | lazy val _akkaActor = "com.typesafe.akka" %% "akka-actor" % verAkka
74 | lazy val _akkaSlf4j = "com.typesafe.akka" %% "akka-slf4j" % verAkka
75 |
76 | lazy val _redisclient = "net.debasishg" %% "redisclient" % "3.0"
77 |
78 | lazy val _json4sJackson = "org.json4s" %% "json4s-jackson" % "3.3.0"
79 |
80 | lazy val _mongoScala = "org.mongodb.scala" %% "mongo-scala-driver" % "1.0.0"
81 |
82 | lazy val _logback = "ch.qos.logback" % "logback-classic" % "1.1.3"
83 | lazy val _commonsEmail = "org.apache.commons" % "commons-email" % "1.4"
84 | lazy val _guava = "com.google.guava" % "guava" % "18.0"
85 |
--------------------------------------------------------------------------------
/akka-rest-api/docs/scripts.txt:
--------------------------------------------------------------------------------
1 | # insert
2 | curl -XPOST -H 'content-type:application/json;charset=utf8' \
3 | -d '{"key":"杭州誉存科技有限公司","tags":["大数据","企业信用"]}' \
4 | http://localhost:44444/api/mongo/dbname/collname/_insert
5 |
6 | # query
7 | curl -XPOST -H 'content-type:application/json' \
8 | -d '{"filter":{"key":"杭州誉存科技有限公司"}}' \
9 | http://localhost:44444/api/mongo/dbname/collname/_query
10 |
--------------------------------------------------------------------------------
/akka-rest-api/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.2.7
2 |
--------------------------------------------------------------------------------
/akka-rest-api/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.9")
2 |
--------------------------------------------------------------------------------
/akka-rest-api/project/sbt-launch.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yangbajing/scala-applications/a41bd2b1e5eef086cf54fe58630bb47d7c16df39/akka-rest-api/project/sbt-launch.jar
--------------------------------------------------------------------------------
/akka-rest-api/sbt:
--------------------------------------------------------------------------------
1 | SCRIPT_DIR=`dirname $0`
2 | java -Dfile.encoding=UTF-8 -noverify -Xms512M -Xmx1024M -Xss1M -XX:+CMSClassUnloadingEnabled -jar "$SCRIPT_DIR/../_lib/sbt-launch.jar" $@
3 |
--------------------------------------------------------------------------------
/akka-rest-api/src/main/resources/application.conf:
--------------------------------------------------------------------------------
1 | akka {
2 | log-dead-letters = off
3 | log-dead-letters-during-shutdown = off
4 | http {
5 | server {
6 | socket-options {
7 | tcp-keep-alive = on
8 | }
9 | }
10 | }
11 | }
12 |
13 | api {
14 | network {
15 | server = "0.0.0.0"
16 | port = 44444
17 | }
18 | }
--------------------------------------------------------------------------------
/akka-rest-api/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | %date - [%level] - from %logger in %thread %n%message%n%xException%n
6 |
7 |
8 |
9 |
10 | logs/application.log
11 |
12 |
13 | logs/application-log-%d{yyyy-MM-dd}.gz
14 |
15 | 30
16 |
17 |
18 | %date{yyyy-MM-dd HH:mm:ss ZZZZ} [%level] from %logger in %thread - %n%message%n%xException%n
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/akka-rest-api/src/main/scala/me/yangbajing/akkarestapi/Main.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.akkarestapi
2 |
3 | import akka.http.scaladsl.Http
4 | import com.typesafe.config.ConfigFactory
5 | import com.typesafe.scalalogging.StrictLogging
6 | import me.yangbajing.akkarestapi.routes.ApiRoute
7 | import me.yangbajing.akkarestapi.service.ContextProps
8 |
9 | import scala.util.{Failure, Success}
10 |
11 | /**
12 | * Main
13 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-11-16.
14 | */
15 | object Main extends StrictLogging {
16 |
17 | def main(args: Array[String]): Unit = {
18 | import SystemUtils.{materializer, system}
19 | import system.dispatcher
20 |
21 | val conf = ConfigFactory.load()
22 |
23 | val props = new ContextProps()
24 |
25 | val bindingFuture = Http().bindAndHandle(ApiRoute(props),
26 | conf.getString("api.network.server"), conf.getInt("api.network.port"))
27 |
28 | bindingFuture.onComplete {
29 | case Success(binding) =>
30 | logger.info(binding.toString)
31 | case Failure(e) =>
32 | logger.error(e.getLocalizedMessage, e)
33 | }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/akka-rest-api/src/main/scala/me/yangbajing/akkarestapi/SystemUtils.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.akkarestapi
2 |
3 | import akka.actor.ActorSystem
4 | import akka.stream.ActorMaterializer
5 |
6 | /**
7 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-11-16.
8 | */
9 | object SystemUtils {
10 | implicit val system = ActorSystem()
11 | implicit val materializer = ActorMaterializer()
12 | }
13 |
--------------------------------------------------------------------------------
/akka-rest-api/src/main/scala/me/yangbajing/akkarestapi/model/NewsResult.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.akkarestapi.model
2 |
3 | import java.time.LocalDateTime
4 |
5 | /**
6 | * News Result
7 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-11-16.
8 | */
9 | case class NewsResult(key: String,
10 | method: String,
11 | count: Int,
12 | items: Seq[NewsItem])
13 |
14 | case class NewsItem(key: String,
15 | time: LocalDateTime,
16 | title: String,
17 | author: String,
18 | summary: String,
19 | content: String)
20 |
--------------------------------------------------------------------------------
/akka-rest-api/src/main/scala/me/yangbajing/akkarestapi/model/NewsStatus.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.akkarestapi.model
2 |
3 | import java.time.LocalDateTime
4 |
5 | /**
6 | * 新闻服务状态
7 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-11-16.
8 | */
9 | case class NewsStatus(now: LocalDateTime, status: String)
10 |
--------------------------------------------------------------------------------
/akka-rest-api/src/main/scala/me/yangbajing/akkarestapi/model/ResponseMessage.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.akkarestapi.model
2 |
3 | /**
4 | * HTTP 响应消息
5 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-11-16.
6 | */
7 | case class ResponseMessage(errcode: Int = 0, errmsg: String = "") extends RuntimeException(errmsg)
8 |
--------------------------------------------------------------------------------
/akka-rest-api/src/main/scala/me/yangbajing/akkarestapi/repo/MongoRepo.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.akkarestapi.repo
2 |
3 | import me.yangbajing.akkarestapi.model.ResponseMessage
4 | import org.mongodb.scala.bson.{BsonDocument, BsonInt32, BsonObjectId}
5 | import org.mongodb.scala.{Document, MongoClient}
6 |
7 | import scala.concurrent.{ExecutionContext, Future}
8 |
9 |
10 | /**
11 | * Mongo Repo
12 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-11-16.
13 | */
14 | class MongoRepo private(mongoClient: MongoClient, dbName: String, collName: String)(implicit ec: ExecutionContext) {
15 | def update(doc: Document): Future[Document] = {
16 | val result =
17 | for {
18 | filter <- doc.get[BsonDocument]("filter")
19 | u <- doc.get[BsonDocument]("update")
20 | } yield {
21 | getColl(dbName, collName).updateOne(filter, u).toFuture().flatMap { results =>
22 | if (results.isEmpty) Future.failed(ResponseMessage(-1, "insert error"))
23 | else Future.successful(Document(u))
24 | }
25 | }
26 |
27 | result getOrElse Future.failed(ResponseMessage(-1, "filter或update不存在"))
28 | }
29 |
30 | def insert(doc: Document): Future[Document] = {
31 | val document = if (doc.contains("_id")) doc else (doc.newBuilder += ("_id" -> BsonObjectId())).result()
32 | getColl(dbName, collName).insertOne(document).toFuture().flatMap { results =>
33 | if (results.isEmpty) Future.failed(ResponseMessage(-1, "insert error"))
34 | else Future.successful(document)
35 | }
36 | }
37 |
38 | def delete(filter: Document): Future[Document] = {
39 | getColl(dbName, collName).deleteOne(filter).toFuture().flatMap { results =>
40 | if (results.isEmpty) Future.failed(ResponseMessage(-1, "delete error"))
41 | else Future.successful(filter)
42 | }
43 | }
44 |
45 | def query(doc: Document): Future[Seq[Document]] = {
46 | val result =
47 | for {
48 | filter <- doc.get[BsonDocument]("filter")
49 | } yield {
50 | val cursor = getColl(dbName, collName).find(filter)
51 | val sortCursor = doc.get[BsonDocument]("sort").map(sort => cursor.sort(sort)).getOrElse(cursor)
52 | val skipCursor = doc.get[BsonInt32]("skip").map(skip => sortCursor.skip(skip.intValue())).getOrElse(sortCursor)
53 | val limitCursor = doc.get[BsonInt32]("limit").map(limit => skipCursor.limit(limit.intValue())).getOrElse(skipCursor)
54 | limitCursor.toFuture()
55 | }
56 |
57 | result getOrElse Future.failed(ResponseMessage(-1, "filter或update不存在"))
58 | }
59 |
60 | def getColl(dbName: String, collName: String) = mongoClient.getDatabase(dbName).getCollection(collName)
61 | }
62 |
63 | object MongoRepo {
64 | def apply(dbName: String, collName: String)(implicit ec: ExecutionContext) =
65 | new MongoRepo(MongoSettings.mongoClient, dbName, collName)
66 | }
67 |
--------------------------------------------------------------------------------
/akka-rest-api/src/main/scala/me/yangbajing/akkarestapi/repo/MongoSettings.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.akkarestapi.repo
2 |
3 | import org.mongodb.scala.MongoClient
4 |
5 | /**
6 | * Mongo Settings
7 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-11-16.
8 | */
9 | object MongoSettings {
10 | val mongoClient = MongoClient("mongodb://localhost")
11 | // val database = mongoClient.getDatabase("news_db")
12 |
13 | // def insert(dbName: String, collName: String, doc: Document) = {
14 | // getCollection(dbName, collName).insertOne(doc).toFuture()
15 | // }
16 |
17 | // def findOne(dbName: String, collName: String, query: Document)
18 |
19 | def getCollection(dbName: String, collName: String) = getDatabase(dbName).getCollection(collName)
20 |
21 | def getDatabase(dbName: String) = mongoClient.getDatabase(dbName)
22 | }
23 |
--------------------------------------------------------------------------------
/akka-rest-api/src/main/scala/me/yangbajing/akkarestapi/repo/NewsRepo.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.akkarestapi.repo
2 |
3 | import me.yangbajing.akkarestapi.model.{NewsItem, ResponseMessage}
4 | import me.yangbajing.akkarestapi.utils.JsonSupport
5 | import org.mongodb.scala.bson.collection.immutable.Document
6 |
7 | import scala.concurrent.{ExecutionContext, Future}
8 |
9 | /**
10 | * News Repo
11 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-11-16.
12 | */
13 | class NewsRepo() {
14 |
15 | import JsonSupport._
16 |
17 | val newsColl = MongoSettings.getCollection("news_db", "news")
18 |
19 | def insert(item: NewsItem)(implicit ec: ExecutionContext): Future[ResponseMessage] = {
20 | val doc = Document(serialization.write(item))
21 | newsColl.insertOne(doc).toFuture().map { completes =>
22 | if (completes.isEmpty)
23 | throw ResponseMessage(-1, "not found")
24 | ResponseMessage()
25 | }
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/akka-rest-api/src/main/scala/me/yangbajing/akkarestapi/routes/ApiRoute.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.akkarestapi.routes
2 |
3 | import akka.http.scaladsl.server.Directives._
4 |
5 | import akka.stream.Materializer
6 | import me.yangbajing.akkarestapi.service.ContextProps
7 |
8 | import scala.concurrent.ExecutionContext
9 |
10 | /**
11 | * Api Route
12 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-11-16.
13 | */
14 | object ApiRoute {
15 |
16 | def apply(props: ContextProps)(implicit ec: ExecutionContext, mat: Materializer) =
17 | handleExceptions(props.myExceptionHandler) {
18 | pathPrefix("api") {
19 | NewsApiRoute(props) ~
20 | MongoApiRoute(props)
21 | }
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/akka-rest-api/src/main/scala/me/yangbajing/akkarestapi/routes/MongoApiRoute.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.akkarestapi.routes
2 |
3 | import akka.http.scaladsl.model.StatusCodes
4 | import akka.stream.Materializer
5 | import me.yangbajing.akkarestapi.repo.MongoRepo
6 | import me.yangbajing.akkarestapi.service.ContextProps
7 | import org.mongodb.scala.bson.collection.immutable.Document
8 |
9 | import scala.concurrent.ExecutionContext
10 | import scala.util.{Failure, Success}
11 |
12 | /**
13 | * Mongo Api
14 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-11-16.
15 | */
16 | object MongoApiRoute {
17 |
18 | import akka.http.scaladsl.server.Directives._
19 | import me.yangbajing.akkarestapi.utils.DocumentSupport._
20 |
21 | def apply(props: ContextProps)(implicit ec: ExecutionContext, mat: Materializer) = {
22 | path("mongo" / Segment / Segment / Segment) { (dbName, collName, method) =>
23 | post {
24 | entity(as[Document]) { doc =>
25 | val mongoRepo = MongoRepo(dbName, collName)
26 | onComplete(method match {
27 | case "_update" =>
28 | mongoRepo.update(doc).map(updateDoc => complete(updateDoc))
29 | case "_insert" =>
30 | mongoRepo.insert(doc).map(doc => complete(StatusCodes.Created, doc))
31 | case "_query" =>
32 | mongoRepo.query(doc).map(docs => complete(docs))
33 | case "_delete" =>
34 | mongoRepo.delete(doc).map(filterDoc => complete(filterDoc))
35 | }) {
36 | case Success(response) =>
37 | response
38 | case Failure(e) =>
39 | e.printStackTrace()
40 | complete(StatusCodes.InternalServerError, e.getLocalizedMessage)
41 | }
42 | }
43 | }
44 | }
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/akka-rest-api/src/main/scala/me/yangbajing/akkarestapi/routes/NewsApiRoute.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.akkarestapi.routes
2 |
3 | import akka.http.scaladsl.model.StatusCodes
4 | import akka.stream.Materializer
5 | import me.yangbajing.akkarestapi.model.NewsItem
6 | import me.yangbajing.akkarestapi.service.ContextProps
7 |
8 | import scala.concurrent.ExecutionContext
9 |
10 | /**
11 | * 新闻 API
12 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-11-16.
13 | */
14 | object NewsApiRoute {
15 |
16 | import akka.http.scaladsl.server.Directives._
17 | import me.yangbajing.akkarestapi.utils.JsonSupport._
18 |
19 | def apply(props: ContextProps)(implicit ec: ExecutionContext, mat: Materializer) = {
20 | pathPrefix("news") {
21 | pathEndOrSingleSlash {
22 | get {
23 | parameters(
24 | 'company,
25 | 'method ? "F") { (company, method) =>
26 | onSuccess(props.newsService.findNews(company, method)) { result =>
27 | complete(result)
28 | }
29 | }
30 | } ~
31 | post {
32 | entity(as[NewsItem]) { item =>
33 | onSuccess(props.newsService.persistNewsItem(item)) { result =>
34 | complete(StatusCodes.Created, result)
35 | }
36 | }
37 | }
38 | } ~
39 | path("status") {
40 | get {
41 | onSuccess(props.newsService.getStatus()) { result =>
42 | complete(result)
43 | }
44 | }
45 | }
46 | }
47 |
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/akka-rest-api/src/main/scala/me/yangbajing/akkarestapi/service/ContextProps.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.akkarestapi.service
2 |
3 | import akka.http.scaladsl.model.StatusCodes
4 | import akka.http.scaladsl.server.{Directives, ExceptionHandler}
5 |
6 | import com.typesafe.scalalogging.StrictLogging
7 | import me.yangbajing.akkarestapi.model.ResponseMessage
8 | import me.yangbajing.akkarestapi.utils.JsonSupport
9 |
10 | /**
11 | * 上下文属性,所有服务都定义在此
12 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-11-17.
13 | */
14 | class ContextProps extends StrictLogging {
15 |
16 | import Directives._
17 | import me.yangbajing.akkarestapi.utils.JsonSupport._
18 |
19 | val myExceptionHandler = ExceptionHandler {
20 | case e: ResponseMessage =>
21 | extractUri { uri =>
22 | logger.error(s"Request to $uri could not be handled normally")
23 | complete(StatusCodes.InternalServerError, JsonSupport.serialization.write(e))
24 | }
25 | // case _: ArithmeticException =>
26 | // extractUri { uri =>
27 | // logger.error(s"Request to $uri could not be handled normally", e)
28 | // complete(StatusCodes.InternalServerError, "Bad numbers, bad result!!!")
29 | // }
30 | }
31 |
32 | val newsService = NewsService()
33 | }
34 |
--------------------------------------------------------------------------------
/akka-rest-api/src/main/scala/me/yangbajing/akkarestapi/service/NewsService.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.akkarestapi.service
2 |
3 | import me.yangbajing.akkarestapi.model.{NewsItem, NewsResult, NewsStatus, ResponseMessage}
4 | import me.yangbajing.akkarestapi.repo.NewsRepo
5 | import me.yangbajing.akkarestapi.utils.Utils
6 |
7 | import scala.concurrent.{ExecutionContext, Future}
8 | import scala.util.Random
9 |
10 | /**
11 | * News Service
12 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-11-16.
13 | */
14 | class NewsService private() {
15 | val newsRepo = new NewsRepo()
16 |
17 | def persistNewsItem(item: NewsItem)(implicit ec: ExecutionContext): Future[ResponseMessage] = {
18 | newsRepo.insert(item)
19 | }
20 |
21 | /**
22 | * 获取新闻
23 | * @param key 搜索关键词
24 | * @param method 搜索方式:S: 摘要,F: 全文
25 | * @return
26 | */
27 | def findNews(key: String,
28 | method: String)(implicit ec: ExecutionContext): Future[NewsResult] = Future {
29 | NewsResult(key, method, Random.nextInt(), Nil)
30 | }
31 |
32 | /**
33 | * 获取当前新闻服务状态
34 | * @return
35 | */
36 | def getStatus()(implicit ec: ExecutionContext): Future[NewsStatus] = Future {
37 | NewsStatus(Utils.now, "ok")
38 | }
39 |
40 | }
41 |
42 | object NewsService {
43 | def apply() = new NewsService()
44 | }
45 |
--------------------------------------------------------------------------------
/akka-rest-api/src/main/scala/me/yangbajing/akkarestapi/utils/DocumentSupport.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.akkarestapi.utils
2 |
3 | import akka.http.scaladsl.marshalling.{Marshaller, ToEntityMarshaller}
4 | import akka.http.scaladsl.model.{ContentType, HttpCharsets, MediaTypes}
5 | import akka.http.scaladsl.unmarshalling.{FromEntityUnmarshaller, Unmarshaller}
6 | import akka.stream.Materializer
7 | import org.mongodb.scala.bson.collection.immutable.Document
8 |
9 | /**
10 | * MongoDB Document Akka Http (un)marshall支持
11 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-11-16.
12 | */
13 | trait DocumentSupport {
14 | implicit def documentUnmarshaller(implicit mat: Materializer): FromEntityUnmarshaller[Document] =
15 | Unmarshaller.byteStringUnmarshaller
16 | .forContentTypes(MediaTypes.`application/json`)
17 | .mapWithCharset { (data, charset) =>
18 | val input = if (charset == HttpCharsets.`UTF-8`) data.utf8String else data.decodeString(charset.nioCharset.name)
19 | Document(input)
20 | }
21 |
22 | implicit val documentMarshaller: ToEntityMarshaller[Document] =
23 | Marshaller.StringMarshaller.wrap(MediaTypes.`application/json`)(v => v.toJson())
24 | implicit val seqDocumentMarshaller: ToEntityMarshaller[Seq[Document]] =
25 | Marshaller.StringMarshaller.wrap(MediaTypes.`application/json`)(vs => vs.map(_.toJson()).mkString("[", ",", "]"))
26 |
27 | }
28 |
29 | object DocumentSupport extends DocumentSupport
--------------------------------------------------------------------------------
/akka-rest-api/src/main/scala/me/yangbajing/akkarestapi/utils/JsonSupport.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.akkarestapi.utils
2 |
3 | import akka.http.scaladsl.marshalling.{Marshaller, ToEntityMarshaller}
4 | import akka.http.scaladsl.model.{ContentTypes, HttpCharsets, MediaTypes}
5 | import akka.http.scaladsl.unmarshalling.{FromEntityUnmarshaller, Unmarshaller}
6 | import akka.stream.Materializer
7 | import org.json4s.jackson.Serialization
8 | import org.json4s.{DefaultFormats, Formats, Serialization}
9 |
10 | /**
11 | * Json Support
12 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-11-16.
13 | */
14 | trait JsonSupport {
15 | implicit val formats = DefaultFormats
16 | implicit val serialization = Serialization
17 |
18 | implicit def json4sUnmarshallerConverter[A: Manifest](serialization: Serialization, formats: Formats)(implicit mat: Materializer): FromEntityUnmarshaller[A] =
19 | json4sUnmarshaller(manifest, serialization, formats, mat)
20 |
21 | implicit def json4sUnmarshaller[A: Manifest](implicit serialization: Serialization, formats: Formats, mat: Materializer): FromEntityUnmarshaller[A] =
22 | Unmarshaller.byteStringUnmarshaller
23 | .forContentTypes(MediaTypes.`application/json`)
24 | .mapWithCharset { (data, charset) =>
25 | val input = if (charset == HttpCharsets.`UTF-8`) data.utf8String else data.decodeString(charset.nioCharset.name)
26 | serialization.read(input)
27 | }
28 |
29 | implicit def json4sMarshallerConverter[A <: AnyRef](serialization: Serialization, formats: Formats): ToEntityMarshaller[A] =
30 | json4sMarshaller(serialization, formats)
31 |
32 | implicit def json4sMarshaller[A <: AnyRef](implicit serialization: Serialization, formats: Formats): ToEntityMarshaller[A] =
33 | Marshaller.StringMarshaller.wrap(MediaTypes.`application/json`)(serialization.write[A])
34 |
35 | }
36 |
37 | object JsonSupport extends JsonSupport
38 |
--------------------------------------------------------------------------------
/akka-rest-api/src/main/scala/me/yangbajing/akkarestapi/utils/Utils.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.akkarestapi.utils
2 |
3 | import java.time.LocalDateTime
4 |
5 | /**
6 | * Utils
7 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-11-16.
8 | */
9 | object Utils {
10 | def now = LocalDateTime.now()
11 | }
12 |
--------------------------------------------------------------------------------
/combine-request/README.md:
--------------------------------------------------------------------------------
1 | # Scala实战:使用Actor来控制集成API的并发请求
2 |
3 | [http://www.yangbajing.me/2016/06/29/scala实战:使用actor来控制集成api的并发请求](http://www.yangbajing.me/2016/06/29/scala%E5%AE%9E%E6%88%98%EF%BC%9A%E4%BD%BF%E7%94%A8actor%E6%9D%A5%E6%8E%A7%E5%88%B6%E9%9B%86%E6%88%90api%E7%9A%84%E5%B9%B6%E5%8F%91%E8%AF%B7%E6%B1%82/)
4 |
5 | - [CombineExample.scala](src/main/scala/combinerequest/CombineExample.scala): 合并请求示例
6 | - [RepetitionExample.scala](src/main/scala/combinerequest/RepetitionExample.scala): 重复请示示例
7 |
--------------------------------------------------------------------------------
/combine-request/build.sbt:
--------------------------------------------------------------------------------
1 | scalaVersion := "2.12.6"
2 |
3 | scalacOptions ++= Seq(
4 | "-encoding", "utf8",
5 | "-unchecked",
6 | "-feature",
7 | "-deprecation"
8 | )
9 |
10 | libraryDependencies ++= Seq(
11 | "com.typesafe.akka" %% "akka-actor" % "2.5.14"
12 | )
13 |
14 |
--------------------------------------------------------------------------------
/combine-request/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.1.6
2 |
--------------------------------------------------------------------------------
/combine-request/sbt:
--------------------------------------------------------------------------------
1 | SCRIPT_DIR=`dirname $0`
2 | java -Dfile.encoding=UTF-8 -noverify -Xms512M -Xmx1024M -Xss1M -XX:+CMSClassUnloadingEnabled -jar "$SCRIPT_DIR/../_lib/sbt-launch.jar" $@
3 |
--------------------------------------------------------------------------------
/combine-request/src/main/scala/combinerequest/CombineExample.scala:
--------------------------------------------------------------------------------
1 | package combinerequest
2 |
3 | import akka.actor.ActorSystem
4 | import combinerequest.service.{EnterpriseService, InfraMongodbRepo, InfraResource}
5 |
6 | import scala.concurrent.Await
7 | import scala.concurrent.duration._
8 | import scala.util.{Failure, Success}
9 |
10 | /**
11 | * 合并同一时间的3个请求
12 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-06-29.
13 | */
14 | object CombineExample {
15 | val system = ActorSystem()
16 |
17 | import system.dispatcher
18 |
19 | def main(args: Array[String]): Unit = {
20 | val infraResource = new InfraResource()
21 | val infraMongodbRepo = new InfraMongodbRepo()
22 | val enterpriseService = new EnterpriseService(system, infraResource, infraMongodbRepo)
23 |
24 | val company1Future = enterpriseService.getCorpDetail("科技公司")
25 | val company2Future = enterpriseService.getCorpDetail("科技公司")
26 | val company3Future = enterpriseService.getCorpDetail("科技公司")
27 |
28 | val companiesFuture = for {
29 | company1 <- company1Future
30 | company2 <- company2Future
31 | company3 <- company3Future
32 | } yield {
33 | List(company1, company2, company3)
34 | }
35 |
36 | Await.ready(companiesFuture, 60.seconds)
37 | .onComplete {
38 | case Success(c1 :: c2 :: c3 :: Nil) =>
39 | // println(c1 eq c2)
40 | // println(c2 eq c3)
41 | println(s"$c1, $c2, $c3")
42 | case Success(unknown) =>
43 | System.err.println(s"收到未期待的结果:$unknown")
44 |
45 | case Failure(e) =>
46 | e.printStackTrace()
47 | }
48 |
49 | try {
50 | Await.result(system.terminate(), 60.seconds)
51 | } catch {
52 | case e: Throwable =>
53 | e.printStackTrace()
54 | System.exit(3)
55 | }
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/combine-request/src/main/scala/combinerequest/RepetitionExample.scala:
--------------------------------------------------------------------------------
1 | package combinerequest
2 |
3 | import akka.actor.ActorSystem
4 | import combinerequest.domain.Company
5 | import combinerequest.service.{CompanyService, InfraMongodbRepo, InfraResource}
6 |
7 | import scala.concurrent.duration._
8 | import scala.concurrent.{Await, ExecutionContext, Future}
9 | import scala.util.{Failure, Success}
10 |
11 | /**
12 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-07-02.
13 | */
14 | object RepetitionExample {
15 | val system = ActorSystem()
16 |
17 | import system.dispatcher
18 |
19 | def main(args: Array[String]): Unit = {
20 | val infraResource = new InfraResource()
21 | val infraMongodbRepo = new InfraMongodbRepo()
22 | val enterpriseService = new RepetitionService(infraResource, infraMongodbRepo)
23 |
24 | val company1Future = enterpriseService.getCorpDetail("科技公司")
25 | val company2Future = enterpriseService.getCorpDetail("科技公司")
26 | val company3Future = enterpriseService.getCorpDetail("科技公司")
27 |
28 | val companiesFuture = for {
29 | company1 <- company1Future
30 | company2 <- company2Future
31 | company3 <- company3Future
32 | } yield {
33 | List(company1, company2, company3)
34 | }
35 |
36 | Await.ready(companiesFuture, 60.seconds)
37 | .onComplete {
38 | case Success(c1 :: c2 :: c3 :: Nil) =>
39 | // println(c1 eq c2)
40 | // println(c2 eq c3)
41 | println(s"$c1, $c2, $c3")
42 | case Success(unknown) =>
43 | System.err.println(s"收到未期待的结果:$unknown")
44 |
45 | case Failure(e) =>
46 | e.printStackTrace()
47 | }
48 |
49 | try {
50 | Await.result(system.terminate(), 60.seconds)
51 | } catch {
52 | case e: Throwable =>
53 | e.printStackTrace()
54 | System.exit(3)
55 | }
56 | }
57 |
58 | }
59 |
60 | class RepetitionService(val infraResource: InfraResource,
61 | val infraMongodbRepo: InfraMongodbRepo) extends CompanyService {
62 |
63 | override def getCorpDetail(companyName: String)(implicit ec: ExecutionContext): Future[Option[Company]] = {
64 | infraMongodbRepo.findCorpDetail(companyName)
65 | .flatMap {
66 | case Some(company) =>
67 | Future.successful(Some(company))
68 | case None =>
69 | infraResource.corpDetail(companyName)
70 | .flatMap {
71 | case Some(company) =>
72 | infraMongodbRepo
73 | .saveCorpDetail(companyName, company)
74 | .map(_ => Some(company))
75 |
76 | case None =>
77 | Future.successful(None)
78 | }
79 | }
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/combine-request/src/main/scala/combinerequest/domain/Company.scala:
--------------------------------------------------------------------------------
1 | package combinerequest.domain
2 |
3 | /**
4 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-06-30.
5 | */
6 | case class Company(companyName: String)
7 |
--------------------------------------------------------------------------------
/combine-request/src/main/scala/combinerequest/message/Messages.scala:
--------------------------------------------------------------------------------
1 | package combinerequest.message
2 |
3 | import akka.actor.{Actor, ActorRef}
4 | import combinerequest.domain.Company
5 |
6 | /**
7 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-06-29.
8 | */
9 | case class QueryCompany(companyName: String, doSender: ActorRef = Actor.noSender)
10 |
11 | case class ReceiveQueryCompanyResult(companyName: String, corpDetail: Option[Company])
12 |
13 | sealed trait GetCompanyMessage {
14 | val companyName: String
15 | }
16 |
17 | /**
18 | * 获取工商消息
19 | *
20 | * @param companyName 公司名
21 | */
22 | case class GetCorpDetail(companyName: String) extends GetCompanyMessage
23 |
--------------------------------------------------------------------------------
/combine-request/src/main/scala/combinerequest/service/CompanyMaster.scala:
--------------------------------------------------------------------------------
1 | package combinerequest.service
2 |
3 | import akka.actor.{Actor, Props}
4 | import combinerequest.message.{GetCorpDetail, QueryCompany}
5 |
6 | /**
7 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-06-29.
8 | */
9 | class CompanyMaster(infraResource: InfraResource,
10 | infraMongodbRepo: InfraMongodbRepo) extends Actor {
11 |
12 | val actorCorpDetail = context.actorOf(CorpDetailActor.props(infraResource, infraMongodbRepo), "corpDetail")
13 |
14 | override def receive = {
15 | case GetCorpDetail(companyName) =>
16 | actorCorpDetail ! QueryCompany(companyName, sender())
17 | }
18 |
19 | }
20 |
21 | object CompanyMaster {
22 |
23 | def props(infraResource: InfraResource,
24 | infraMongodbRepo: InfraMongodbRepo) =
25 | Props(new CompanyMaster(infraResource, infraMongodbRepo))
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/combine-request/src/main/scala/combinerequest/service/CorpDetailActor.scala:
--------------------------------------------------------------------------------
1 | package combinerequest.service
2 |
3 | import akka.actor.Props
4 | import combinerequest.service.ForwardCompanyActor.{ReadFromDB, ReadFromInfra}
5 |
6 | import scala.concurrent.Future
7 |
8 | /**
9 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-06-29.
10 | */
11 | class CorpDetailActor(infraResource: InfraResource,
12 | infraMongodbRepo: InfraMongodbRepo) extends ForwardCompanyActor {
13 |
14 | import context.dispatcher
15 |
16 | override val readFromDB: ReadFromDB = (companyName) => {
17 | infraMongodbRepo.findCorpDetail(companyName)
18 | }
19 |
20 | override val readFromInfra: ReadFromInfra = (companyName) => {
21 | infraResource.corpDetail(companyName)
22 | .flatMap {
23 | case Some(company) =>
24 | infraMongodbRepo
25 | .saveCorpDetail(companyName, company)
26 | .map(_ => Some(company))
27 |
28 | case None =>
29 | Future.successful(None)
30 | }
31 | }
32 |
33 | }
34 |
35 | object CorpDetailActor {
36 | def props(infraResource: InfraResource,
37 | infraMongodbRepo: InfraMongodbRepo) =
38 | Props(new CorpDetailActor(infraResource, infraMongodbRepo))
39 | }
40 |
--------------------------------------------------------------------------------
/combine-request/src/main/scala/combinerequest/service/EnterpriseService.scala:
--------------------------------------------------------------------------------
1 | package combinerequest.service
2 |
3 | import akka.actor.ActorSystem
4 | import akka.pattern.ask
5 | import akka.util.Timeout
6 | import combinerequest.domain.Company
7 | import combinerequest.message.{GetCompanyMessage, GetCorpDetail}
8 |
9 | import scala.concurrent.{ExecutionContext, Future}
10 | import scala.concurrent.duration._
11 |
12 | /**
13 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-06-29.
14 | */
15 | trait CompanyService {
16 | val infraResource: InfraResource
17 | val infraMongodbRepo: InfraMongodbRepo
18 |
19 | def getCorpDetail(companyName: String)(implicit ec: ExecutionContext): Future[Option[Company]]
20 | }
21 |
22 | class EnterpriseService(actorSystem: ActorSystem,
23 | val infraResource: InfraResource,
24 | val infraMongodbRepo: InfraMongodbRepo) extends CompanyService {
25 |
26 | private val master = actorSystem.actorOf(CompanyMaster.props(infraResource, infraMongodbRepo), "infra-company")
27 | private implicit val timeout = Timeout(60.seconds)
28 |
29 | @inline
30 | private def getResult(message: GetCompanyMessage)(implicit ec: ExecutionContext) =
31 | master.ask(message).mapTo[Option[Company]]
32 |
33 | def getCorpDetail(companyName: String)(implicit ec: ExecutionContext) = getResult(GetCorpDetail(companyName))
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/combine-request/src/main/scala/combinerequest/service/ForwardCompanyActor.scala:
--------------------------------------------------------------------------------
1 | package combinerequest.service
2 |
3 | import akka.actor.{Actor, ActorRef}
4 | import combinerequest.domain.Company
5 | import combinerequest.message.{QueryCompany, ReceiveQueryCompanyResult}
6 |
7 | import scala.collection.mutable
8 | import scala.concurrent.Future
9 |
10 | /**
11 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-06-29.
12 | */
13 | trait ForwardCompanyActor extends Actor {
14 |
15 | import ForwardCompanyActor._
16 |
17 | val companyListeners = mutable.Map.empty[String, Set[ActorRef]]
18 |
19 | override def receive = {
20 | case QueryCompany(companyName, doSender) =>
21 | val listener = if (doSender == Actor.noSender) sender() else doSender
22 | registerListener(companyName, listener)
23 |
24 | case ReceiveQueryCompanyResult(companyName, maybeJsValue) =>
25 | dispatchListeners(companyName, maybeJsValue)
26 | }
27 |
28 | def performReadTask(companyName: String): Unit = {
29 | import context.dispatcher
30 | readFromDB(companyName)
31 | .flatMap(maybe => if (maybe.isEmpty) readFromInfra(companyName) else Future.successful(maybe))
32 | .foreach(maybe => self ! ReceiveQueryCompanyResult(companyName, maybe))
33 | }
34 |
35 | def registerListener(companyName: String, listener: ActorRef): Unit =
36 | companyListeners.get(companyName) match {
37 | case Some(actors) =>
38 | companyListeners.put(companyName, actors + listener)
39 | case None =>
40 | companyListeners.put(companyName, Set(listener))
41 | performReadTask(companyName)
42 | }
43 |
44 | def dispatchListeners(companyName: String, maybeJsValue: Option[Company]): Unit = {
45 | val maybeListener = companyListeners.get(companyName)
46 | maybeListener.foreach { listeners =>
47 | for (listener <- listeners) {
48 | listener ! maybeJsValue
49 | }
50 | companyListeners -= companyName
51 | }
52 | }
53 |
54 | val readFromInfra: ReadFromInfra
55 |
56 | val readFromDB: ReadFromDB
57 | }
58 |
59 | object ForwardCompanyActor {
60 |
61 | type ReadFromDB = (String) => Future[Option[Company]]
62 |
63 | type ReadFromInfra = (String) => Future[Option[Company]]
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/combine-request/src/main/scala/combinerequest/service/InfraMongodbRepo.scala:
--------------------------------------------------------------------------------
1 | package combinerequest.service
2 |
3 | import java.time.LocalDateTime
4 | import java.util.concurrent.{ConcurrentHashMap, TimeUnit}
5 |
6 | import combinerequest.domain.Company
7 |
8 | import scala.concurrent.{ExecutionContext, Future}
9 |
10 | /**
11 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-06-29.
12 | */
13 | class InfraMongodbRepo {
14 |
15 | def saveCorpDetail(companyName: String, company: Company)(implicit ec: ExecutionContext): Future[Option[Company]] =
16 | Future {
17 | TimeUnit.MICROSECONDS.sleep(200)
18 | println(s"[${LocalDateTime.now()}] 保存公司: $company 成功")
19 | InfraCompanyDBMock.saveCompany(companyName, company)
20 | }
21 |
22 | def findCorpDetail(companyName: String)(implicit ec: ExecutionContext): Future[Option[Company]] = Future {
23 | TimeUnit.MILLISECONDS.sleep(100)
24 | InfraCompanyDBMock.getCompany(companyName) match {
25 | case some@Some(_) =>
26 | println(s"[${LocalDateTime.now()}] 从本地数据库找到公司:$companyName")
27 | some
28 | case None =>
29 | println(s"[${LocalDateTime.now()}] 本地数据库未找到公司:$companyName")
30 | None
31 | }
32 | }
33 |
34 | }
35 |
36 | object InfraCompanyDBMock {
37 | private val companies = new ConcurrentHashMap[String, Company]()
38 |
39 | def getCompany(companyName: String) = Option(companies.get(companyName))
40 |
41 | def saveCompany(companyName: String, company: Company) = Option(companies.put(companyName, company))
42 | }
43 |
--------------------------------------------------------------------------------
/combine-request/src/main/scala/combinerequest/service/InfraResource.scala:
--------------------------------------------------------------------------------
1 | package combinerequest.service
2 |
3 | import java.time.LocalDateTime
4 | import java.util.concurrent.TimeUnit
5 |
6 | import combinerequest.domain.Company
7 |
8 | import scala.concurrent.{ExecutionContext, Future}
9 |
10 | /**
11 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-06-29.
12 | */
13 | class InfraResource {
14 |
15 | def corpDetail(companyName: String)(implicit ec: ExecutionContext): Future[Option[Company]] = Future {
16 | TimeUnit.SECONDS.sleep(1)
17 | println(s"[${LocalDateTime.now()}] 收到查询:$companyName 工商信息付费请求")
18 | Some(Company(companyName))
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/email-server/README.md:
--------------------------------------------------------------------------------
1 | # Email服务器
2 |
3 | Email发送服务器
4 |
5 | ## 配置
6 |
7 | 推荐在启动时使用 `-Dapplication.file=/usr/app/etc/emailserver/application.conf` 指定配置文件
8 |
9 |
10 | ## 编译与运行
11 |
12 | ```
13 | # 编译
14 | ./sbt assembly
15 |
16 | # 运行
17 | java -Dapplication.file=/usr/app/etc/emailserver/application.conf -jar target/scala-2.11/email-server.jar
18 | ```
19 |
20 |
21 | ## 测试REST服务
22 |
23 | ```
24 | # 查询存在的邮箱发送者:
25 | curl http://localhost:9999/email/users
26 |
27 | # 发送测试邮件
28 | curl -v -XPOST -H "Content-Type: application/json" \
29 | -d '{"sender":"Info@email.cn", "subject":"测试邮件","to":["user1@email.cn", "user2@email.cn"],"content":"测试邮件内容咯~"}' \
30 | http://localhost:9999/email/send
31 | ```
32 |
33 |
34 | ## 使用JMS发送邮件
35 |
36 | **安装`activemq`**
37 |
38 | Downloads:[http://mirrors.hust.edu.cn/apache/activemq/5.11.1/apache-activemq-5.11.1-bin.tar.gz](http://mirrors.hust.edu.cn/apache/activemq/5.11.1/apache-activemq-5.11.1-bin.tar.gz)
39 |
40 | ```
41 | tar zxf ~/Downloads/apache-activemq-5.11.1-bin.tar.gz
42 | cd apache-activemq-5.11.1/
43 | ./bin/activemq-admin start
44 | ```
45 |
46 | `activemq`管理控制台地址:[http://localhost:8161/admin/](http://localhost:8161/admin/),账号:`admin`,密码:`admin`。
47 |
48 | JMS TCP地址:`tcp://localhost:61616`
49 |
50 | **生产邮件发送请求**
51 |
52 | 修改[EmailProducers.scala](https://github.com/yangbajing/scala-applications/blob/master/email-server/src/main/scala/me/yangbajing/emailserver/demo/EmailProducers.scala)的`activeMqUrl`及`mapMessage`参数,执行`EmailProducers`生产一个邮件发送请求。
53 |
54 |
55 | ## changelog
56 |
57 | **ver: 0.0.2 2015-08-11**
58 |
59 | 实现消费`activemq jms`邮件发送消息
60 |
61 | **ver: 0.0.1 2015-08-11**
62 |
63 | 实现基于`REST API`的邮件发送功能。
64 |
--------------------------------------------------------------------------------
/email-server/project/Build.scala:
--------------------------------------------------------------------------------
1 | import sbt.Keys._
2 | import sbt._
3 | import sbtassembly.AssemblyKeys
4 |
5 | object Build extends Build {
6 |
7 | override lazy val settings = super.settings :+ {
8 | shellPrompt := (s => Project.extract(s).currentProject.id + " > ")
9 | }
10 |
11 | lazy val root = Project("email-server", file("."))
12 | .settings(
13 | description := "Email Server",
14 | version := "0.0.2",
15 | homepage := Some(new URL("http://github.com/yangbajing/scala-applications")),
16 | organization := "me.yangbajing",
17 | organizationHomepage := Some(new URL("https://github.com/yangbajing/scala-applications")),
18 | startYear := Some(2015),
19 | scalaVersion := "2.11.12",
20 | scalacOptions ++= Seq(
21 | "-encoding", "utf8",
22 | "-unchecked",
23 | "-feature",
24 | "-deprecation"
25 | ),
26 | javacOptions ++= Seq(
27 | "-encoding", "utf8",
28 | "-Xlint:unchecked",
29 | "-Xlint:deprecation"
30 | ),
31 | javaOptions += "-Dproject.base=" + baseDirectory.value,
32 | publish :=(),
33 | publishLocal :=(),
34 | publishTo := None,
35 | offline := true,
36 | resolvers ++= Seq(
37 | "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/",
38 | "Sonatype releases" at "http://oss.sonatype.org/content/repositories/releases",
39 | "Typesafe Snapshots" at "http://repo.typesafe.com/typesafe/snapshots/",
40 | "Sonatype snapshots" at "http://oss.sonatype.org/content/repositories/snapshots"),
41 | AssemblyKeys.assemblyJarName in AssemblyKeys.assembly := "email-server.jar",
42 | mainClass in AssemblyKeys.assembly := Some("me.yangbajing.emailserver.Main"),
43 | libraryDependencies ++= Seq(
44 | _scalaReflect,
45 | _guice,
46 | _redisclient,
47 | _ssc,
48 | _commonsEmail,
49 | _akkaHttp,
50 | _akkaActor,
51 | _akkaSlf4j,
52 | _logback,
53 | _activemqClient,
54 | _typesafeConfig,
55 | _scalaLogging,
56 | _scalatest))
57 |
58 | val _scalaReflect = "org.scala-lang" % "scala-reflect" % "2.11.12"
59 | val _scalaXml = "org.scala-lang.modules" %% "scala-xml" % "1.0.+"
60 | val _scalatest = "org.scalatest" %% "scalatest" % "2.2.+" % "test"
61 | val _typesafeConfig = "com.typesafe" % "config" % "1.3.+"
62 | val _scalaLogging = ("com.typesafe.scala-logging" %% "scala-logging" % "3.4.0").exclude("org.scala-lang", "scala-library").exclude("org.scala-lang", "scala-reflect")
63 |
64 | // val _akkaHttpPlayJson = "de.heikoseeberger" %% "akka-http-play-json" % "1.0.0"
65 | val verAkka = "2.4.19"
66 |
67 | val _akkaActor = "com.typesafe.akka" %% "akka-actor" % verAkka
68 | val _akkaSlf4j = "com.typesafe.akka" %% "akka-slf4j" % verAkka
69 | val _akkaStream = "com.typesafe.akka" %% "akka-stream" % verAkka
70 | val _akkaHttp = "com.typesafe.akka" %% "akka-http-experimental" % verAkka
71 |
72 | val _ssc = "com.elderresearch" %% "ssc" % "0.2.0"
73 |
74 | val _guice = "com.google.inject" % "guice" % "4.1.0"
75 |
76 | val _redisclient = "net.debasishg" %% "redisclient" % "3.0"
77 |
78 | val _logback = "ch.qos.logback" % "logback-classic" % "1.1.+"
79 | val _commonsEmail = "org.apache.commons" % "commons-email" % "1.4"
80 | val _reactivemongo = "org.reactivemongo" %% "reactivemongo" % "0.11.+"
81 | val _guava = "com.google.guava" % "guava" % "19.+"
82 | val _activemqClient = "org.apache.activemq" % "activemq-client" % "5.13.+"
83 |
84 | }
85 |
86 |
--------------------------------------------------------------------------------
/email-server/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.1.6
2 |
--------------------------------------------------------------------------------
/email-server/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.3.5")
2 |
3 | addSbtPlugin("com.thoughtworks.sbt-api-mappings" % "sbt-api-mappings" % "0.2.3")
4 |
5 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.3")
6 |
--------------------------------------------------------------------------------
/email-server/project/sbt-launch.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yangbajing/scala-applications/a41bd2b1e5eef086cf54fe58630bb47d7c16df39/email-server/project/sbt-launch.jar
--------------------------------------------------------------------------------
/email-server/sbt:
--------------------------------------------------------------------------------
1 | SCRIPT_DIR=`dirname $0`
2 | java -Dfile.encoding=UTF-8 -noverify -Xms512M -Xmx1024M -Xss1M -XX:+CMSClassUnloadingEnabled -jar "$SCRIPT_DIR/../_lib/sbt-launch.jar" $@
3 |
--------------------------------------------------------------------------------
/email-server/src/main/resources/application.conf:
--------------------------------------------------------------------------------
1 | emailserver {
2 | server {
3 | interface = "0.0.0.0"
4 | port = 9999
5 | }
6 |
7 | emails {
8 | email1 {
9 | userName = "email1@email.com"
10 | password = ""
11 | smtp = ""
12 | smtpPort = 0
13 | ssl = true
14 |
15 | # 同一个组内的邮件会串行发送,此key可忽略
16 | group = "1"
17 | }
18 |
19 | email2 {
20 | userName = "email2@email.com"
21 | password = ""
22 | smtp = ""
23 | smtpPort = 0
24 | ssl = true
25 | group = "2"
26 | }
27 | }
28 |
29 | activemq {
30 | url = "tcp://127.0.0.1:61616"
31 | emailQueueName = "EmailQueue"
32 | }
33 | }
--------------------------------------------------------------------------------
/email-server/src/main/scala/me/yangbajing/emailserver/ActorService.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.emailserver
2 |
3 | import akka.actor.ActorSystem
4 | import akka.stream.ActorMaterializer
5 |
6 | /**
7 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-08-11.
8 | */
9 | trait ActorService {
10 | implicit val system: ActorSystem
11 |
12 | implicit val materializer: ActorMaterializer
13 | }
14 |
--------------------------------------------------------------------------------
/email-server/src/main/scala/me/yangbajing/emailserver/JsonImplicits.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.emailserver
2 |
3 | import java.time.LocalDateTime
4 |
5 | import me.yangbajing.emailserver.common.settings.{SettingActivemq, Setting, SettingServer}
6 | import me.yangbajing.emailserver.domain.{EmailSender, SendEmail}
7 | import me.yangbajing.emailserver.util.Utils
8 | import play.api.libs.json._
9 |
10 | /**
11 | * Play Json trans Case Class
12 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-08-10.
13 | */
14 | trait JsonImplicits {
15 | implicit val __localDateTime = new Format[LocalDateTime] {
16 | override def writes(o: LocalDateTime): JsValue = JsString(o.format(Utils.formatterDateTime))
17 |
18 | override def reads(json: JsValue): JsResult[LocalDateTime] =
19 | JsSuccess(LocalDateTime.parse(json.as[String], Utils.formatterDateTime))
20 | }
21 | implicit val __sendEmailFormats = Json.format[SendEmail]
22 | implicit val __emailSenderFormats = Json.format[EmailSender]
23 | implicit val __settingServerFormats = Json.format[SettingServer]
24 | implicit val __settingActivemqFormats = Json.format[SettingActivemq]
25 | implicit val __settingFormats = Json.format[Setting]
26 | }
27 |
28 | object JsonImplicits extends JsonImplicits
29 |
--------------------------------------------------------------------------------
/email-server/src/main/scala/me/yangbajing/emailserver/Main.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.emailserver
2 |
3 | import akka.http.scaladsl.Http
4 | import akka.http.scaladsl.Http.ServerBinding
5 | import me.yangbajing.emailserver.common.settings.Settings
6 | import me.yangbajing.emailserver.route.Routes
7 | import me.yangbajing.emailserver.service.{EmailService, MQConsumerService}
8 | import me.yangbajing.emailserver.util.Utils
9 |
10 | /**
11 | * Email Server Main
12 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-08-10.
13 | */
14 | object Main {
15 |
16 | import Utils.{materializer, system}
17 | import system.dispatcher
18 |
19 | def main(args: Array[String]): Unit = {
20 | val emailService = new EmailService(Settings.config.emails)
21 | val mqConsumerService = new MQConsumerService(Settings.config.activemq, emailService)
22 | val routes = new Routes(emailService, mqConsumerService)
23 | val bindingFuture = Http().bindAndHandle(routes.routes, Settings.config.server.interface, Settings.config.server.port)
24 |
25 | bindingFuture.onSuccess {
26 | case ServerBinding(inet) =>
27 | println("binding to: " + inet)
28 | mqConsumerService.start()
29 |
30 | case other =>
31 | println("biding other: " + other)
32 | mqConsumerService.close()
33 | system.shutdown()
34 | }
35 | bindingFuture.onFailure {
36 | case e: Exception =>
37 | mqConsumerService.close()
38 | system.shutdown()
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/email-server/src/main/scala/me/yangbajing/emailserver/common/enums/MimeType.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.emailserver.common.enums
2 |
3 | import play.api.libs.json._
4 |
5 | /**
6 | * 邮件格式
7 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-08-11.
8 | */
9 | object MimeType extends Enumeration {
10 | type MimeType = Value
11 | val Text = Value("text")
12 | val Html = Value("html")
13 |
14 | implicit val __mimeTypeFormats = new Format[MimeType] {
15 | override def writes(o: MimeType.Value): JsValue = JsString(o.toString)
16 |
17 | override def reads(json: JsValue): JsResult[MimeType.Value] = JsSuccess(MimeType.withName(json.as[String]))
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/email-server/src/main/scala/me/yangbajing/emailserver/common/settings/Settings.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.emailserver.common.settings
2 |
3 | import java.io.File
4 |
5 | import com.typesafe.config.{Config, ConfigFactory}
6 | import me.yangbajing.emailserver.domain.EmailSender
7 |
8 | import scala.collection.JavaConverters._
9 | import scala.util.Try
10 |
11 | /**
12 | * Email Server Setting
13 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-08-11.
14 | */
15 | object Settings {
16 | val config = {
17 | val c =
18 | Option(System.getProperty("application.file")).map(file => ConfigFactory.parseFile(new File(file)))
19 | .getOrElse(ConfigFactory.load()).getConfig("emailserver")
20 | _setting(c)
21 | }
22 |
23 | private def _setting(c: Config) = {
24 | val emailConf = c.getConfig("emails")
25 | val emails = emailConf.entrySet().asScala.toStream
26 | .filter(entry => entry.getKey.endsWith(".userName"))
27 | .map(_.getKey.replace(".userName", ""))
28 | .map(key => _emailSender(emailConf.getConfig(key)))
29 | .toVector
30 | Setting(_server(c.getConfig("server")), emails, _activemq(c.getConfig("activemq")))
31 | }
32 |
33 | private def _emailSender(c: Config): EmailSender = {
34 | EmailSender(c.getString("userName"), c.getString("password"), c.getString("smtp"), c.getInt("smtpPort"),
35 | c.getBoolean("ssl"), Try(c.getBoolean("default")).getOrElse(false), Try(c.getString("group")).getOrElse(""))
36 | }
37 |
38 | private def _server(c: Config) = SettingServer(c.getString("interface"), c.getInt("port"))
39 |
40 | private def _activemq(c: Config) = SettingActivemq(c.getString("url"),
41 | Try(c.getString("userName")).toOption, Try(c.getString("password")).toOption,
42 | c.getString("emailQueueName"))
43 | }
44 |
45 | case class SettingActivemq(url: String, userName: Option[String], password: Option[String], emailQueueName: String)
46 |
47 | case class SettingServer(interface: String, port: Int)
48 |
49 | case class Setting(server: SettingServer, emails: Seq[EmailSender], activemq: SettingActivemq)
50 |
--------------------------------------------------------------------------------
/email-server/src/main/scala/me/yangbajing/emailserver/demo/ConsumerSynchronous.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.emailserver.demo
2 |
3 | import javax.jms._
4 |
5 | import org.apache.activemq.ActiveMQConnectionFactory
6 |
7 | /**
8 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-08-11.
9 | */
10 | object ConsumerSynchronous extends App {
11 | val activeMqUrl = "tcp://192.168.31.116:61616"
12 |
13 | val connFactory = new ActiveMQConnectionFactory(activeMqUrl)
14 | val conn = connFactory.createConnection()
15 | conn.setClientID("ConsumerSynchronous")
16 | conn.start()
17 |
18 | println("Started")
19 |
20 | val session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE)
21 | val queue = session.createQueue("EmailQueue")
22 | val consumer = session.createConsumer(queue)
23 |
24 | val listener = new MessageListener {
25 | override def onMessage(message: Message): Unit = message match {
26 | case txt: TextMessage => {
27 | val replyProducer = session.createProducer(txt.getJMSReplyTo)
28 | replyProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT)
29 |
30 | println("Received message: " + txt.getText)
31 |
32 | val replyMessage = session.createTextMessage("Yes I received your message!")
33 | replyMessage.setJMSCorrelationID(txt.getJMSCorrelationID)
34 |
35 | println("Reply sent!")
36 |
37 | replyProducer.send(replyMessage)
38 | }
39 |
40 | case _ =>
41 | throw new RuntimeException("Unhandled Message Type: " + message)
42 | }
43 | }
44 |
45 | consumer.setMessageListener(listener)
46 | }
47 |
--------------------------------------------------------------------------------
/email-server/src/main/scala/me/yangbajing/emailserver/demo/EmailProducers.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.emailserver.demo
2 |
3 | import javax.jms.{DeliveryMode, Session, TextMessage}
4 |
5 | import org.apache.activemq.ActiveMQConnectionFactory
6 |
7 | import scala.util.Random
8 |
9 | /**
10 | * 邮件生产者
11 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-08-11.
12 | */
13 | object EmailProducers extends App {
14 | val activeMqUrl = "tcp://192.168.31.116:61616"
15 |
16 | val connFactory = new ActiveMQConnectionFactory(activeMqUrl)
17 | val conn = connFactory.createConnection()
18 | conn.setClientID("ProducerEmail")
19 | conn.start()
20 |
21 | val session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE)
22 | val sendQueue = session.createQueue("EmailQueue")
23 | // val replyQueue = session.createQueue("ReplyToSynchronousMsgQueue")
24 |
25 | val producer = session.createProducer(sendQueue)
26 | producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT)
27 |
28 | val correlationId = new Random().nextString(32)
29 | // val replyConsumer = session.createConsumer(replyQueue, s"JMSCorrelationID = '$correlationId'")
30 |
31 | val mapMessage = session.createMapMessage()
32 | // mapMessage.setJMSReplyTo(replyQueue)
33 | mapMessage.setJMSCorrelationID(correlationId)
34 |
35 | mapMessage.setString("subject", "Activemq jms 邮件测试")
36 | mapMessage.setString("to", "yang.xunjing@qq.com;jing.yang@socialcredits.cn")
37 | mapMessage.setString("content", "Activemq jms 邮件测试的内容")
38 | mapMessage.setString("sender", "Info@socialcredits.cn")
39 | mapMessage.setString("mimeType", "text")
40 |
41 | println("Sending message...")
42 |
43 | producer.send(mapMessage)
44 |
45 | println("Waiting for reply...")
46 |
47 | // val reply = replyConsumer.receive(1000)
48 | // replyConsumer.close()
49 |
50 | // reply match {
51 | // case txt: TextMessage => println("Received reply: " + txt.getText)
52 | // case _ => throw new RuntimeException("Invalid Response: " + reply)
53 | // }
54 |
55 | conn.close()
56 | }
57 |
--------------------------------------------------------------------------------
/email-server/src/main/scala/me/yangbajing/emailserver/demo/ProducerSynchronous.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.emailserver.demo
2 |
3 | import javax.jms.{TextMessage, DeliveryMode, Session}
4 |
5 | import org.apache.activemq.ActiveMQConnectionFactory
6 |
7 | import scala.util.Random
8 |
9 | /**
10 | * Active Producer
11 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-08-11.
12 | */
13 | object ProducerSynchronous extends App {
14 | val activeMqUrl = "tcp://192.168.31.116:61616"
15 |
16 | val connFactory = new ActiveMQConnectionFactory(activeMqUrl)
17 | val conn = connFactory.createConnection()
18 | conn.setClientID("ProducerSynchronous")
19 | conn.start()
20 |
21 | val session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE)
22 | val sendQueue = session.createQueue("EmailQueue")
23 | val replyQueue = session.createQueue("ReplyToSynchronousMsgQueue")
24 |
25 | val producer = session.createProducer(sendQueue)
26 | producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT)
27 |
28 | val correlationId = new Random().nextString(32)
29 | val replyConsumer = session.createConsumer(replyQueue, s"JMSCorrelationID = '$correlationId'")
30 |
31 | val textMessage = session.createTextMessage("Hello, please reply immediately to my message!")
32 | textMessage.setJMSReplyTo(replyQueue)
33 | textMessage.setJMSCorrelationID(correlationId)
34 |
35 | println("Sending message...")
36 |
37 | producer.send(textMessage)
38 |
39 | println("Waiting for reply...")
40 |
41 | val reply = replyConsumer.receive(1000)
42 | replyConsumer.close()
43 |
44 | reply match {
45 | case txt: TextMessage => println("Received reply: " + txt.getText)
46 | case _ => throw new RuntimeException("Invalid Response: " + reply)
47 | }
48 |
49 | conn.close()
50 | }
51 |
--------------------------------------------------------------------------------
/email-server/src/main/scala/me/yangbajing/emailserver/domain/Emails.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.emailserver.domain
2 |
3 | import me.yangbajing.emailserver.common.enums.MimeType
4 |
5 | /**
6 | * Email Message
7 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-08-10.
8 | */
9 | case class SendEmail(sender: String, subject: String, to: Seq[String], content: String, from: Option[String],
10 | mimeType: MimeType.MimeType)
11 |
12 | case object GetEmailSenders
13 |
14 | case class EmailSender(userName: String, password: String, smtp: String, smtpPort: Int, ssl: Boolean, group: String)
15 |
--------------------------------------------------------------------------------
/email-server/src/main/scala/me/yangbajing/emailserver/route/Routes.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.emailserver.route
2 |
3 | import akka.actor.ActorSystem
4 | import akka.http.scaladsl.model.StatusCodes
5 | import akka.http.scaladsl.server.Directives._
6 | import akka.stream.ActorMaterializer
7 | import akka.util.Timeout
8 | import de.heikoseeberger.akkahttpplayjson.PlayJsonSupport._
9 | import me.yangbajing.emailserver.ActorService
10 | import me.yangbajing.emailserver.JsonImplicits._
11 | import me.yangbajing.emailserver.domain.SendEmail
12 | import me.yangbajing.emailserver.service.{EmailService, MQConsumerService}
13 | import play.api.libs.json.{JsValue, Json}
14 |
15 | import scala.concurrent.duration._
16 | import scala.util.{Failure, Success}
17 |
18 | /**
19 | * 路由
20 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-08-10.
21 | */
22 | class Routes(val emailService: EmailService,
23 | val mQConsumerService: MQConsumerService)(implicit val system: ActorSystem, val materializer: ActorMaterializer) extends ActorService {
24 | implicit val timeout = Timeout(60.seconds)
25 |
26 | val routes =
27 | pathPrefix("email") {
28 | path("send") {
29 | post {
30 | entity(as[JsValue].map(_.as[SendEmail])) { sendEmail =>
31 | onComplete(emailService.sendEmail(sendEmail)) {
32 | case Success(value) =>
33 | value match {
34 | case Right(msg) => complete(msg)
35 | case Left(msg) => complete(StatusCodes.Forbidden, msg)
36 | }
37 |
38 | case Failure(e) => complete(StatusCodes.InternalServerError, "SendEmail an error occurred: " + e.getMessage)
39 | }
40 | }
41 | }
42 | } ~
43 | path("users") {
44 | get {
45 | onComplete(emailService.getEmailSenders) {
46 | case Success(emailSenders) => complete(Json.toJson(emailSenders))
47 |
48 | case Failure(e) => complete(StatusCodes.InternalServerError, "An error occurred: " + e.getMessage)
49 | }
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/email-server/src/main/scala/me/yangbajing/emailserver/service/EmailService.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.emailserver.service
2 |
3 | import akka.actor.ActorRefFactory
4 | import akka.pattern.ask
5 | import akka.util.Timeout
6 | import me.yangbajing.emailserver.domain.{EmailSender, GetEmailSenders, SendEmail}
7 | import me.yangbajing.emailserver.service.actors.EmailMaster
8 |
9 | /**
10 | * 邮件服务
11 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-08-10.
12 | */
13 | class EmailService(initEmailSenders: Seq[EmailSender])(implicit actorRefFactory: ActorRefFactory) {
14 | val emailMaster = actorRefFactory.actorOf(EmailMaster.props(initEmailSenders), "email-master")
15 |
16 | def sendEmail(email: SendEmail)(implicit timeout: Timeout) = {
17 | (emailMaster ? email).mapTo[Either[String, String]]
18 | }
19 |
20 | def getEmailSenders(implicit timeout: Timeout) = {
21 | (emailMaster ? GetEmailSenders).mapTo[Seq[EmailSender]]
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/email-server/src/main/scala/me/yangbajing/emailserver/service/MQConsumerService.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.emailserver.service
2 |
3 | import javax.jms._
4 |
5 | import com.typesafe.scalalogging.StrictLogging
6 | import me.yangbajing.emailserver.common.enums.MimeType
7 | import me.yangbajing.emailserver.common.settings.SettingActivemq
8 | import me.yangbajing.emailserver.domain.SendEmail
9 | import me.yangbajing.emailserver.util.Utils
10 | import org.apache.activemq.ActiveMQConnectionFactory
11 |
12 | /**
13 | * 消息队列消费者
14 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-08-11.
15 | */
16 | class MQConsumerService(setting: SettingActivemq, emailService: EmailService) extends StrictLogging {
17 |
18 | import Utils.system.dispatcher
19 | import Utils.timeout
20 |
21 | val connFactory = new ActiveMQConnectionFactory(setting.url)
22 | val conn =
23 | (for {
24 | userName <- setting.userName
25 | password <- setting.password
26 | } yield connFactory.createConnection("admin", "admin")) getOrElse connFactory.createConnection()
27 |
28 | def start() {
29 |
30 | conn.setClientID("ConsumerSynchronous")
31 | conn.start()
32 |
33 | val session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE)
34 | val queue = session.createQueue(setting.emailQueueName)
35 | val consumer = session.createConsumer(queue)
36 |
37 | val listener = new MessageListener {
38 | override def onMessage(message: Message): Unit = message match {
39 | case msg: MapMessage => {
40 | val subject = msg.getString("subject")
41 | val sender = msg.getString("sender")
42 | val content = msg.getString("content")
43 | val to = msg.getString("to").split(";")
44 | val mimeType = Option(msg.getString("mimeType")).map(MimeType.withName).getOrElse(MimeType.Text)
45 | val sendEmail = SendEmail(sender, subject, to, content, None, mimeType)
46 | emailService.sendEmail(sendEmail).onComplete(result => logger.debug(result.toString))
47 | }
48 |
49 | case txt: TextMessage => {
50 | val replyProducer = session.createProducer(txt.getJMSReplyTo)
51 | replyProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT)
52 |
53 | logger.debug("Received message: " + txt.getText)
54 |
55 | val replyMessage = session.createTextMessage("Yes I received your message!")
56 | replyMessage.setJMSCorrelationID(txt.getJMSCorrelationID)
57 |
58 | logger.debug("Reply sent!")
59 |
60 | replyProducer.send(replyMessage)
61 | }
62 |
63 | case _ =>
64 | logger.error("Unhandled Message Type: " + message)
65 | }
66 | }
67 |
68 | consumer.setMessageListener(listener)
69 | }
70 |
71 | def close() {
72 | conn.close()
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/email-server/src/main/scala/me/yangbajing/emailserver/service/actors/EmailGroupActor.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.emailserver.service.actors
2 |
3 | import akka.actor.{Actor, ActorLogging, Props}
4 | import me.yangbajing.emailserver.common.enums.MimeType
5 | import me.yangbajing.emailserver.domain.{EmailSender, SendEmail}
6 | import org.apache.commons.mail.{HtmlEmail, SimpleEmail}
7 |
8 | /**
9 | * 邮箱发送组
10 | * 每个邮件发送组内串行发送邮件
11 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-08-11.
12 | */
13 | class EmailGroupActor(emailSenders: Map[String, EmailSender]) extends Actor with ActorLogging {
14 |
15 | override def receive: Receive = {
16 | case se: SendEmail =>
17 | emailSenders.get(se.sender) match {
18 | case Some(emailSender) =>
19 | sendEmail(emailSender, se)
20 |
21 | case None =>
22 | log.error(s"userName: ${se.sender} not found. $se")
23 | }
24 | }
25 |
26 |
27 | /**
28 | * @param emailSender email sender
29 | * @param se
30 | */
31 | private def sendEmail(emailSender: EmailSender, se: SendEmail): Unit = {
32 | val email =
33 | if (se.mimeType == MimeType.Text) generateSimpleEmail(emailSender, se)
34 | else generateHtmlEmail(emailSender, se)
35 | val sendMsg = email.send()
36 | log.debug(s"sendEmail $email: $sendMsg")
37 | }
38 |
39 | private def generateHtmlEmail(emailSender: EmailSender, se: SendEmail) = {
40 | val email = new HtmlEmail()
41 | email.setHostName(emailSender.smtp)
42 | email.setSmtpPort(emailSender.smtpPort)
43 | email.setAuthentication(emailSender.userName, emailSender.password)
44 | email.setSSLOnConnect(emailSender.ssl)
45 | email.addTo(se.to: _*)
46 | email.setFrom(se.from.getOrElse(emailSender.userName))
47 | email.setSubject(se.subject)
48 | email.setHtmlMsg(se.content)
49 | email
50 | }
51 |
52 | private def generateSimpleEmail(emailSender: EmailSender, se: SendEmail) = {
53 | val email = new SimpleEmail()
54 | email.setHostName(emailSender.smtp)
55 | email.setSmtpPort(emailSender.smtpPort)
56 | email.setAuthentication(emailSender.userName, emailSender.password)
57 | email.setSSLOnConnect(emailSender.ssl)
58 | email.addTo(se.to: _*)
59 | email.setFrom(se.from.getOrElse(emailSender.userName))
60 | email.setSubject(se.subject)
61 | email.setMsg(se.content)
62 | email
63 | }
64 | }
65 |
66 | object EmailGroupActor {
67 | val ACTOR_NAME_PREFIX = "group-"
68 |
69 | def props(emailSenders: Map[String, EmailSender]) = Props(new EmailGroupActor(emailSenders))
70 | }
71 |
--------------------------------------------------------------------------------
/email-server/src/main/scala/me/yangbajing/emailserver/service/actors/EmailMaster.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.emailserver.service.actors
2 |
3 | import akka.actor.{Actor, Props}
4 | import me.yangbajing.emailserver.domain.{EmailSender, GetEmailSenders, SendEmail}
5 |
6 | /**
7 | * 邮件发送Master
8 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-08-11.
9 | */
10 | class EmailMaster(initEmailSenders: Seq[EmailSender]) extends Actor {
11 | val groups = initEmailSenders.groupBy(_.group)
12 | val emailSenders = initEmailSenders.map(s => s.userName -> s).toMap
13 |
14 | // // TODO 自定义监管策略
15 | // override def supervisorStrategy: SupervisorStrategy = super.supervisorStrategy
16 |
17 | override def preStart(): Unit = {
18 | for ((group, emailSenders) <- groups) {
19 | context.actorOf(
20 | EmailGroupActor.props(emailSenders.map(s => s.userName -> s).toMap),
21 | EmailGroupActor.ACTOR_NAME_PREFIX + group)
22 | }
23 | }
24 |
25 | override def receive = {
26 | case sendEmail: SendEmail => {
27 | val userName = sendEmail.sender
28 | emailSenders.get(userName) match {
29 | case Some(emailSender) =>
30 | context.child(EmailGroupActor.ACTOR_NAME_PREFIX + emailSender.group) match {
31 | case Some(ref) =>
32 | ref ! sendEmail
33 | sender() ! Right("email added send queue")
34 |
35 | case None =>
36 | sender() ! Left(s"actor: ${EmailGroupActor.ACTOR_NAME_PREFIX}-${emailSender.group} not found")
37 | }
38 |
39 | case None =>
40 | sender() ! Left(s"userName: $userName not found")
41 | }
42 | }
43 |
44 | case GetEmailSenders =>
45 | sender() ! initEmailSenders
46 | }
47 | }
48 |
49 | object EmailMaster {
50 | def props(initEmailSender: Seq[EmailSender]) = Props(new EmailMaster(initEmailSender))
51 | }
--------------------------------------------------------------------------------
/email-server/src/main/scala/me/yangbajing/emailserver/util/Utils.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.emailserver.util
2 |
3 | import java.time.format.DateTimeFormatter
4 | import java.util.concurrent.TimeUnit
5 |
6 | import akka.actor.ActorSystem
7 | import akka.stream.ActorMaterializer
8 | import akka.util.Timeout
9 |
10 | /**
11 | * 工具
12 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-08-10.
13 | */
14 | object Utils {
15 | implicit val system = ActorSystem("email-server")
16 | implicit val materializer = ActorMaterializer()
17 | implicit val timeout = Timeout(60, TimeUnit.SECONDS)
18 |
19 | val formatterDateTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/email-server/src/test/scala/me/yangbajing/emailserver/common/settings/SettingsTest.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.emailserver.common.settings
2 |
3 | import org.scalatest.{Matchers, WordSpec}
4 |
5 | /**
6 | * Settings Test
7 | * Created by Yang Jing (yangbajing@gmail.com) on 2015-08-11.
8 | */
9 | class SettingsTest extends WordSpec with Matchers {
10 | "Settings" should {
11 | "emails is nonEmpty" in {
12 | Settings.config.emails should have size 2
13 | }
14 |
15 | "server is nonEmpty" in {
16 | Settings.config.server.interface should not be empty
17 | Settings.config.server.port should be > 0
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/email-server/src/test/scala/sw.sc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yangbajing/scala-applications/a41bd2b1e5eef086cf54fe58630bb47d7c16df39/email-server/src/test/scala/sw.sc
--------------------------------------------------------------------------------
/file-upload/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### macOS template
3 | # General
4 | .DS_Store
5 | .AppleDouble
6 | .LSOverride
7 |
8 | # Icon must end with two \r
9 | Icon
10 |
11 | # Thumbnails
12 | ._*
13 |
14 | # Files that might appear in the root of a volume
15 | .DocumentRevisions-V100
16 | .fseventsd
17 | .Spotlight-V100
18 | .TemporaryItems
19 | .Trashes
20 | .VolumeIcon.icns
21 | .com.apple.timemachine.donotpresent
22 |
23 | # Directories potentially created on remote AFP share
24 | .AppleDB
25 | .AppleDesktop
26 | Network Trash Folder
27 | Temporary Items
28 | .apdisk
29 | ### Vim template
30 | # Swap
31 | [._]*.s[a-v][a-z]
32 | [._]*.sw[a-p]
33 | [._]s[a-rt-v][a-z]
34 | [._]ss[a-gi-z]
35 | [._]sw[a-p]
36 |
37 | # Session
38 | Session.vim
39 |
40 | # Temporary
41 | .netrwhist
42 | *~
43 | # Auto-generated tag files
44 | tags
45 | # Persistent undo
46 | [._]*.un~
47 | ### NotepadPP template
48 | # Notepad++ backups #
49 | *.bak
50 | ### SBT template
51 | # Simple Build Tool
52 | # http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control
53 |
54 | dist/*
55 | target/
56 | lib_managed/
57 | src_managed/
58 | project/boot/
59 | project/plugins/project/
60 | .history
61 | .cache
62 | .lib/
63 | ### Emacs template
64 | # -*- mode: gitignore; -*-
65 | \#*\#
66 | /.emacs.desktop
67 | /.emacs.desktop.lock
68 | *.elc
69 | auto-save-list
70 | tramp
71 | .\#*
72 |
73 | # Org-mode
74 | .org-id-locations
75 | *_archive
76 |
77 | # flymake-mode
78 | *_flymake.*
79 |
80 | # eshell files
81 | /eshell/history
82 | /eshell/lastdir
83 |
84 | # elpa packages
85 | /elpa/
86 |
87 | # reftex files
88 | *.rel
89 |
90 | # AUCTeX auto folder
91 | /auto/
92 |
93 | # cask packages
94 | .cask/
95 | dist/
96 |
97 | # Flycheck
98 | flycheck_*.el
99 |
100 | # server auth directory
101 | /server/
102 |
103 | # projectiles files
104 | .projectile
105 |
106 | # directory configuration
107 | .dir-locals.el
108 | ### Eclipse template
109 |
110 | .metadata
111 | bin/
112 | tmp/
113 | *.tmp
114 | *.swp
115 | *~.nib
116 | local.properties
117 | .settings/
118 | .loadpath
119 | .recommenders
120 |
121 | # External tool builders
122 | .externalToolBuilders/
123 |
124 | # Locally stored "Eclipse launch configurations"
125 | *.launch
126 |
127 | # PyDev specific (Python IDE for Eclipse)
128 | *.pydevproject
129 |
130 | # CDT-specific (C/C++ Development Tooling)
131 | .cproject
132 |
133 | # CDT- autotools
134 | .autotools
135 |
136 | # Java annotation processor (APT)
137 | .factorypath
138 |
139 | # PDT-specific (PHP Development Tools)
140 | .buildpath
141 |
142 | # sbteclipse plugin
143 | .target
144 |
145 | # Tern plugin
146 | .tern-project
147 |
148 | # TeXlipse plugin
149 | .texlipse
150 |
151 | # STS (Spring Tool Suite)
152 | .springBeans
153 |
154 | # Code Recommenders
155 | .recommenders/
156 |
157 | # Annotation Processing
158 | .apt_generated/
159 |
160 | # Scala IDE specific (Scala & Java development for Eclipse)
161 | .cache-main
162 | .scala_dependencies
163 | .worksheet
164 | ### Windows template
165 | # Windows thumbnail cache files
166 | Thumbs.db
167 | ehthumbs.db
168 | ehthumbs_vista.db
169 |
170 | # Dump file
171 | *.stackdump
172 |
173 | # Folder config file
174 | [Dd]esktop.ini
175 |
176 | # Recycle Bin used on file shares
177 | $RECYCLE.BIN/
178 |
179 | # Windows Installer files
180 | *.cab
181 | *.msi
182 | *.msix
183 | *.msm
184 | *.msp
185 |
186 | # Windows shortcuts
187 | *.lnk
188 | ### JetBrains template
189 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
190 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
191 |
192 | # User-specific stuff
193 | .idea/
194 |
195 | # CMake
196 | cmake-build-*/
197 |
198 | # Mongo Explorer plugin
199 | .idea/**/mongoSettings.xml
200 |
201 | # File-based project format
202 | *.iws
203 |
204 | # IntelliJ
205 | out/
206 |
207 | # mpeltonen/sbt-idea plugin
208 | .idea_modules/
209 |
210 | # JIRA plugin
211 | atlassian-ide-plugin.xml
212 |
213 | # Cursive Clojure plugin
214 | .idea/replstate.xml
215 |
216 | # Crashlytics plugin (for Android Studio and IntelliJ)
217 | com_crashlytics_export_strings.xml
218 | crashlytics.properties
219 | crashlytics-build.properties
220 | fabric.properties
221 |
222 | # Editor-based Rest Client
223 | .idea/httpRequests
224 | ### GitBook template
225 | # Node rules:
226 | ## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
227 | .grunt
228 |
229 | ## Dependency directory
230 | ## Commenting this out is preferred by some people, see
231 | ## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git
232 | node_modules
233 |
234 | # Book build output
235 | _book
236 |
237 | # eBook build output
238 | *.epub
239 | *.mobi
240 | *.pdf
241 | ### NetBeans template
242 | nbproject/private/
243 | build/
244 | nbbuild/
245 | nbdist/
246 | .nb-gradle/
247 | ### Linux template
248 |
249 | # temporary files which can be created if a process still has a handle open of a deleted file
250 | .fuse_hidden*
251 |
252 | # KDE directory preferences
253 | .directory
254 |
255 | # Linux trash folder which might appear on any partition or disk
256 | .Trash-*
257 |
258 | # .nfs files are created when an open file is removed but is still being accessed
259 | .nfs*
260 | ### VisualStudioCode template
261 | .vscode/*
262 | !.vscode/settings.json
263 | !.vscode/tasks.json
264 | !.vscode/launch.json
265 | !.vscode/extensions.json
266 |
--------------------------------------------------------------------------------
/file-upload/.scalafmt.conf:
--------------------------------------------------------------------------------
1 | //version = "2.0.0"
2 | style = defaultWithAlign
3 | lineEndings = unix
4 | encoding = "UTF-8"
5 | project.git = true
6 | docstrings = JavaDoc
7 | maxColumn = 120
8 | //indentOperator = spray
9 | unindentTopLevelOperators = true
10 | align.openParenDefnSite = false
11 | align.openParenCallSite = false
12 | optIn.breakChainOnFirstMethodDot = false
13 | optIn.configStyleArguments = false
14 | danglingParentheses = false
15 | spaces.inImportCurlyBraces = false
16 | newlines.alwaysBeforeTopLevelStatements = true
17 | rewrite.rules = [RedundantParens, AvoidInfix, SortModifiers, PreferCurlyFors, ExpandImportSelectors]
18 | rewrite.sortModifiers.order = [
19 | "implicit", "final", "sealed", "abstract",
20 | "override", "private", "protected", "lazy"
21 | ]
22 | rewrite.neverInfix.excludeFilters = [
23 | and
24 | min
25 | max
26 | until
27 | to
28 | by
29 | eq
30 | ne
31 | "should.*"
32 | "contain.*"
33 | "must.*"
34 | in
35 | ignore
36 | be
37 | taggedAs
38 | thrownBy
39 | synchronized
40 | have
41 | when
42 | size
43 | only
44 | noneOf
45 | oneElementOf
46 | noElementsOf
47 | atLeastOneElementOf
48 | atMostOneElementOf
49 | allElementsOf
50 | inOrderElementsOf
51 | theSameElementsAs
52 | ]
53 |
--------------------------------------------------------------------------------
/file-upload/Jenkinsfile:
--------------------------------------------------------------------------------
1 | pipeline {
2 | agent {
3 | // 此处设定构建环境,目前可选有
4 | // default, java-8, python-3.5, ruby-2.3, go-1.11 等
5 | // 详情请阅 https://dev.tencent.com/help/knowledge-base/how-to-use-ci#agents
6 | label "java-8"
7 | }
8 | stages {
9 |
10 | stage("检出") {
11 | steps {
12 | sh 'ci-init'
13 | checkout(
14 | [$class: 'GitSCM', branches: [[name: env.GIT_BUILD_REF]],
15 | userRemoteConfigs: [[url: env.GIT_REPO_URL]]]
16 | )
17 | }
18 | }
19 |
20 | stage("构建") {
21 | steps {
22 | echo "构建中..."
23 | // 请在这里放置您项目代码的单元测试调用过程,例如:
24 | // sh 'mvn package' // mvn 示例
25 | // sh 'make' // make 示例
26 | sh './sbt test assembly'
27 | echo "构建完成."
28 | // archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true // 收集构建产物
29 | }
30 | }
31 |
32 | stage("测试") {
33 | steps {
34 | echo "单元测试中..."
35 | // 请在这里放置您项目代码的单元测试调用过程,例如:
36 | // sh 'mvn test' // mvn 示例
37 | // sh 'make test' // make 示例
38 | echo "单元测试完成."
39 | // junit 'target/surefire-reports/*.xml' // 收集单元测试报告的调用过程
40 | }
41 | }
42 |
43 | stage("部署") {
44 | steps {
45 | echo "部署中..."
46 | // 请在这里放置收集单元测试报告的调用过程,例如:
47 | // sh 'mvn tomcat7:deploy' // Maven tomcat7 插件示例:
48 | // sh './deploy.sh' // 自研部署脚本
49 | echo "部署完成"
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/file-upload/README.md:
--------------------------------------------------------------------------------
1 | # 大文件断点续传
2 |
3 | ***使用Akka HTTP与HTML 5实现***
4 |
5 | ```
6 | git clone https://github.com/yangbajing/scala-applications.git
7 | cd scala-applications/file-upload
8 | sbt "runMain me.yangbajing.fileupload.Main"
9 | ```
10 |
11 | 打开浏览器访问:[http://localhost:33333/html/upload.html](http://localhost:33333/html/upload.html)
12 |
--------------------------------------------------------------------------------
/file-upload/build.sbt:
--------------------------------------------------------------------------------
1 | name := "file-upload"
2 |
3 | description := "File Upload"
4 |
5 | version := "0.0.1"
6 |
7 | homepage := Some(new URL("https://github.com/yangbajing/scala-applications/tree/master/file-upload"))
8 |
9 | organization := "me.yangbajing"
10 |
11 | organizationHomepage := Some(new URL("https://github.com/yangbajing/scala-applications"))
12 |
13 | startYear := Some(2018)
14 |
15 | scalaVersion := "2.12.8"
16 |
17 | scalacOptions ++= Seq("-encoding", "utf8", "-unchecked", "-feature", "-deprecation")
18 |
19 | javacOptions ++= Seq("-encoding", "utf8", "-Xlint:unchecked", "-Xlint:deprecation")
20 |
21 | javaOptions += "-Dproject.base=" + baseDirectory.value
22 |
23 | scalafmtOnCompile in ThisBuild := true
24 |
25 | resolvers ++= Seq(
26 | "Typesafe Repository".at("http://repo.typesafe.com/typesafe/releases/"),
27 | "Sonatype releases".at("http://oss.sonatype.org/content/repositories/releases"),
28 | "Typesafe Snapshots".at("http://repo.typesafe.com/typesafe/snapshots/"),
29 | "Sonatype snapshots".at("http://oss.sonatype.org/content/repositories/snapshots"))
30 |
31 | assemblyJarName in assembly := "file-upload.jar"
32 |
33 | mainClass in assembly := Some("me.yangbajing.fileupload.Main")
34 |
35 | resolvers ++= Seq(
36 | "ihongka.cn sbt".at("https://artifactory.hongkazhijia.com/artifactory/sbt-release"),
37 | "ihongka.cn maven".at("https://artifactory.hongkazhijia.com/artifactory/libs-release"))
38 |
39 | libraryDependencies ++= Seq(
40 | "com.helloscala.fusion" %% "helloscala-common" % "1.0.0-alpha14",
41 | _typesafeConfig,
42 | _logback,
43 | _scalaLogging,
44 | _scalatest) ++ _akkas ++ _akkaHttps ++ _jsons
45 |
46 | lazy val _scalatest = "org.scalatest" %% "scalatest" % "3.0.7" % Test
47 | lazy val _typesafeConfig = "com.typesafe" % "config" % "1.3.3"
48 | lazy val _scalaLogging = ("com.typesafe.scala-logging" %% "scala-logging" % "3.9.2")
49 | .exclude("org.scala-lang", "scala-library")
50 | .exclude("org.scala-lang", "scala-reflect")
51 |
52 | lazy val _akkaHttps =
53 | Seq("com.typesafe.akka" %% "akka-http" % "10.1.8", "com.typesafe.akka" %% "akka-http-testkit" % "10.1.8" % Test)
54 | lazy val _logback = "ch.qos.logback" % "logback-classic" % "1.2.3"
55 |
56 | val verAkka = "2.5.22"
57 | lazy val _akkas = Seq(
58 | "com.typesafe.akka" %% "akka-actor" % verAkka,
59 | "com.typesafe.akka" %% "akka-stream" % verAkka,
60 | "com.typesafe.akka" %% "akka-slf4j" % verAkka,
61 | "com.typesafe.akka" %% "akka-stream-testkit" % verAkka % Test)
62 |
63 | val versionJackson = "2.9.6"
64 | lazy val _jsons = Seq(
65 | "com.fasterxml.jackson.datatype" % "jackson-datatype-jdk8" % versionJackson,
66 | "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % versionJackson,
67 | "com.fasterxml.jackson.module" % "jackson-module-parameter-names" % versionJackson,
68 | "com.fasterxml.jackson.module" %% "jackson-module-scala" % versionJackson)
69 |
--------------------------------------------------------------------------------
/file-upload/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.2.7
2 |
--------------------------------------------------------------------------------
/file-upload/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.9")
2 |
3 | addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.1")
4 |
--------------------------------------------------------------------------------
/file-upload/project/sbt-launch.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yangbajing/scala-applications/a41bd2b1e5eef086cf54fe58630bb47d7c16df39/file-upload/project/sbt-launch.jar
--------------------------------------------------------------------------------
/file-upload/sbt:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ./sbt-dist/bin/sbt "$@"
--------------------------------------------------------------------------------
/file-upload/sbt-dist/bin/sbt:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 |
4 | ### ------------------------------- ###
5 | ### Helper methods for BASH scripts ###
6 | ### ------------------------------- ###
7 |
8 | realpath () {
9 | (
10 | TARGET_FILE="$1"
11 | FIX_CYGPATH="$2"
12 |
13 | cd "$(dirname "$TARGET_FILE")"
14 | TARGET_FILE=$(basename "$TARGET_FILE")
15 |
16 | COUNT=0
17 | while [ -L "$TARGET_FILE" -a $COUNT -lt 100 ]
18 | do
19 | TARGET_FILE=$(readlink "$TARGET_FILE")
20 | cd "$(dirname "$TARGET_FILE")"
21 | TARGET_FILE=$(basename "$TARGET_FILE")
22 | COUNT=$(($COUNT + 1))
23 | done
24 |
25 | # make sure we grab the actual windows path, instead of cygwin's path.
26 | if [[ "x$FIX_CYGPATH" != "x" ]]; then
27 | echo "$(cygwinpath "$(pwd -P)/$TARGET_FILE")"
28 | else
29 | echo "$(pwd -P)/$TARGET_FILE"
30 | fi
31 | )
32 | }
33 |
34 |
35 | # Uses uname to detect if we're in the odd cygwin environment.
36 | is_cygwin() {
37 | local os=$(uname -s)
38 | case "$os" in
39 | CYGWIN*) return 0 ;;
40 | *) return 1 ;;
41 | esac
42 | }
43 |
44 | # TODO - Use nicer bash-isms here.
45 | CYGWIN_FLAG=$(if is_cygwin; then echo true; else echo false; fi)
46 |
47 |
48 | # This can fix cygwin style /cygdrive paths so we get the
49 | # windows style paths.
50 | cygwinpath() {
51 | local file="$1"
52 | if [[ "$CYGWIN_FLAG" == "true" ]]; then
53 | echo $(cygpath -w $file)
54 | else
55 | echo $file
56 | fi
57 | }
58 |
59 | . "$(dirname "$(realpath "$0")")/sbt-launch-lib.bash"
60 |
61 |
62 | declare -r noshare_opts="-Dsbt.global.base=project/.sbtboot -Dsbt.boot.directory=project/.boot -Dsbt.ivy.home=project/.ivy"
63 | declare -r sbt_opts_file=".sbtopts"
64 | declare -r etc_sbt_opts_file="${sbt_home}/conf/sbtopts"
65 | declare -r win_sbt_opts_file="${sbt_home}/conf/sbtconfig.txt"
66 |
67 | usage() {
68 | cat < path to global settings/plugins directory (default: ~/.sbt)
77 | -sbt-boot path to shared boot directory (default: ~/.sbt/boot in 0.11 series)
78 | -ivy path to local Ivy repository (default: ~/.ivy2)
79 | -mem set memory options (default: $sbt_mem, which is $(get_mem_opts $sbt_mem))
80 | -no-share use all local caches; no sharing
81 | -no-global uses global caches, but does not use global ~/.sbt directory.
82 | -jvm-debug Turn on JVM debugging, open at the given port.
83 | -batch Disable interactive mode
84 |
85 | # sbt version (default: from project/build.properties if present, else latest release)
86 | -sbt-version use the specified version of sbt
87 | -sbt-jar use the specified jar as the sbt launcher
88 | -sbt-rc use an RC version of sbt
89 | -sbt-snapshot use a snapshot version of sbt
90 |
91 | # java version (default: java from PATH, currently $(java -version 2>&1 | grep version))
92 | -java-home alternate JAVA_HOME
93 |
94 | # jvm options and output control
95 | JAVA_OPTS environment variable, if unset uses "$java_opts"
96 | SBT_OPTS environment variable, if unset uses "$default_sbt_opts"
97 | .sbtopts if this file exists in the current directory, it is
98 | prepended to the runner args
99 | /etc/sbt/sbtopts if this file exists, it is prepended to the runner args
100 | -Dkey=val pass -Dkey=val directly to the java runtime
101 | -J-X pass option -X directly to the java runtime
102 | (-J is stripped)
103 | -S-X add -X to sbt's scalacOptions (-S is stripped)
104 |
105 | In the case of duplicated or conflicting options, the order above
106 | shows precedence: JAVA_OPTS lowest, command line options highest.
107 | EOM
108 | }
109 |
110 |
111 |
112 | process_my_args () {
113 | while [[ $# -gt 0 ]]; do
114 | case "$1" in
115 | -no-colors) addJava "-Dsbt.log.noformat=true" && shift ;;
116 | -no-share) addJava "$noshare_opts" && shift ;;
117 | -no-global) addJava "-Dsbt.global.base=$(pwd)/project/.sbtboot" && shift ;;
118 | -sbt-boot) require_arg path "$1" "$2" && addJava "-Dsbt.boot.directory=$2" && shift 2 ;;
119 | -sbt-dir) require_arg path "$1" "$2" && addJava "-Dsbt.global.base=$2" && shift 2 ;;
120 | -debug-inc) addJava "-Dxsbt.inc.debug=true" && shift ;;
121 | -batch) exec &2 "$@"
20 | }
21 | vlog () {
22 | [[ $verbose || $debug ]] && echoerr "$@"
23 | }
24 | dlog () {
25 | [[ $debug ]] && echoerr "$@"
26 | }
27 |
28 | jar_file () {
29 | echo "$(cygwinpath "${sbt_home}/bin/sbt-launch.jar")"
30 | }
31 |
32 | acquire_sbt_jar () {
33 | sbt_jar="$(jar_file)"
34 |
35 | if [[ ! -f "$sbt_jar" ]]; then
36 | echoerr "Could not find launcher jar: $sbt_jar"
37 | exit 2
38 | fi
39 | }
40 |
41 | execRunner () {
42 | # print the arguments one to a line, quoting any containing spaces
43 | [[ $verbose || $debug ]] && echo "# Executing command line:" && {
44 | for arg; do
45 | if printf "%s\n" "$arg" | grep -q ' '; then
46 | printf "\"%s\"\n" "$arg"
47 | else
48 | printf "%s\n" "$arg"
49 | fi
50 | done
51 | echo ""
52 | }
53 |
54 | # THis used to be exec, but we loose the ability to re-hook stty then
55 | # for cygwin... Maybe we should flag the feature here...
56 | "$@"
57 | }
58 |
59 | addJava () {
60 | dlog "[addJava] arg = '$1'"
61 | java_args=( "${java_args[@]}" "$1" )
62 | }
63 | addSbt () {
64 | dlog "[addSbt] arg = '$1'"
65 | sbt_commands=( "${sbt_commands[@]}" "$1" )
66 | }
67 | addResidual () {
68 | dlog "[residual] arg = '$1'"
69 | residual_args=( "${residual_args[@]}" "$1" )
70 | }
71 | addDebugger () {
72 | addJava "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=$1"
73 | }
74 |
75 | get_mem_opts () {
76 | # if we detect any of these settings in ${JAVA_OPTS} or ${JAVA_TOOL_OPTIONS} we need to NOT output our settings.
77 | # The reason is the Xms/Xmx, if they don't line up, cause errors.
78 | if [[ "${JAVA_OPTS}" == *-Xmx* ]] || [[ "${JAVA_OPTS}" == *-Xms* ]] || [[ "${JAVA_OPTS}" == *-XX:MaxPermSize* ]] || [[ "${JAVA_OPTS}" == *-XX:MaxMetaspaceSize* ]] || [[ "${JAVA_OPTS}" == *-XX:ReservedCodeCacheSize* ]]; then
79 | echo ""
80 | elif [[ "${JAVA_TOOL_OPTIONS}" == *-Xmx* ]] || [[ "${JAVA_TOOL_OPTIONS}" == *-Xms* ]] || [[ "${JAVA_TOOL_OPTIONS}" == *-XX:MaxPermSize* ]] || [[ "${JAVA_TOOL_OPTIONS}" == *-XX:MaxMetaspaceSize* ]] || [[ "${JAVA_TOOL_OPTIONS}" == *-XX:ReservedCodeCacheSize* ]]; then
81 | echo ""
82 | elif [[ "${SBT_OPTS}" == *-Xmx* ]] || [[ "${SBT_OPTS}" == *-Xms* ]] || [[ "${SBT_OPTS}" == *-XX:MaxPermSize* ]] || [[ "${SBT_OPTS}" == *-XX:MaxMetaspaceSize* ]] || [[ "${SBT_OPTS}" == *-XX:ReservedCodeCacheSize* ]]; then
83 | echo ""
84 | else
85 | # a ham-fisted attempt to move some memory settings in concert
86 | # so they need not be messed around with individually.
87 | local mem=${1:-1024}
88 | local codecache=$(( $mem / 8 ))
89 | (( $codecache > 128 )) || codecache=128
90 | (( $codecache < 512 )) || codecache=512
91 | local class_metadata_size=$(( $codecache * 2 ))
92 | local class_metadata_opt=$([[ "$java_version" < "1.8" ]] && echo "MaxPermSize" || echo "MaxMetaspaceSize")
93 |
94 | local arg_xms=$([[ "${java_args[@]}" == *-Xms* ]] && echo "" || echo "-Xms${mem}m")
95 | local arg_xmx=$([[ "${java_args[@]}" == *-Xmx* ]] && echo "" || echo "-Xmx${mem}m")
96 | local arg_rccs=$([[ "${java_args[@]}" == *-XX:ReservedCodeCacheSize* ]] && echo "" || echo "-XX:ReservedCodeCacheSize=${codecache}m")
97 | local arg_meta=$([[ "${java_args[@]}" == *-XX:${class_metadata_opt}* ]] && echo "" || echo "-XX:${class_metadata_opt}=${class_metadata_size}m")
98 |
99 | echo "${arg_xms} ${arg_xmx} ${arg_rccs} ${arg_meta}"
100 | fi
101 | }
102 |
103 | require_arg () {
104 | local type="$1"
105 | local opt="$2"
106 | local arg="$3"
107 | if [[ -z "$arg" ]] || [[ "${arg:0:1}" == "-" ]]; then
108 | echo "$opt requires <$type> argument"
109 | exit 1
110 | fi
111 | }
112 |
113 | is_function_defined() {
114 | declare -f "$1" > /dev/null
115 | }
116 |
117 | process_args () {
118 | while [[ $# -gt 0 ]]; do
119 | case "$1" in
120 | -h|-help) usage; exit 1 ;;
121 | -v|-verbose) verbose=1 && shift ;;
122 | -d|-debug) debug=1 && shift ;;
123 |
124 | -ivy) require_arg path "$1" "$2" && addJava "-Dsbt.ivy.home=$2" && shift 2 ;;
125 | -mem) require_arg integer "$1" "$2" && sbt_mem="$2" && shift 2 ;;
126 | -jvm-debug) require_arg port "$1" "$2" && addDebugger $2 && shift 2 ;;
127 | -batch) exec &1 | awk -F '"' '/version/ {print $2}')
146 | vlog "[process_args] java_version = '$java_version'"
147 | }
148 |
149 | # Detect that we have java installed.
150 | checkJava() {
151 | local required_version="$1"
152 | # Now check to see if it's a good enough version
153 | if [[ "$java_version" == "" ]]; then
154 | echo
155 | echo No java installations was detected.
156 | echo Please go to http://www.java.com/getjava/ and download
157 | echo
158 | exit 1
159 | elif [[ ! "$java_version" > "$required_version" ]]; then
160 | echo
161 | echo The java installation you have is not up to date
162 | echo $script_name requires at least version $required_version+, you have
163 | echo version $java_version
164 | echo
165 | echo Please go to http://www.java.com/getjava/ and download
166 | echo a valid Java Runtime and install before running $script_name.
167 | echo
168 | exit 1
169 | fi
170 | }
171 |
172 |
173 | run() {
174 | # no jar? download it.
175 | [[ -f "$sbt_jar" ]] || acquire_sbt_jar "$sbt_version" || {
176 | # still no jar? uh-oh.
177 | echo "Download failed. Obtain the sbt-launch.jar manually and place it at $sbt_jar"
178 | exit 1
179 | }
180 |
181 | # process the combined args, then reset "$@" to the residuals
182 | process_args "$@"
183 | set -- "${residual_args[@]}"
184 | argumentCount=$#
185 |
186 | # TODO - java check should be configurable...
187 | checkJava "1.6"
188 |
189 | #If we're in cygwin, we should use the windows config, and terminal hacks
190 | if [[ "$CYGWIN_FLAG" == "true" ]]; then
191 | stty -icanon min 1 -echo > /dev/null 2>&1
192 | addJava "-Djline.terminal=jline.UnixTerminal"
193 | addJava "-Dsbt.cygwin=true"
194 | fi
195 |
196 | # run sbt
197 | execRunner "$java_cmd" \
198 | $(get_mem_opts $sbt_mem) \
199 | ${JAVA_OPTS} \
200 | ${SBT_OPTS:-$default_sbt_opts} \
201 | ${java_args[@]} \
202 | -jar "$sbt_jar" \
203 | "${sbt_commands[@]}" \
204 | "${residual_args[@]}"
205 |
206 | exit_code=$?
207 |
208 | # Clean up the terminal from cygwin hacks.
209 | if [[ "$CYGWIN_FLAG" == "true" ]]; then
210 | stty icanon echo > /dev/null 2>&1
211 | fi
212 | exit $exit_code
213 | }
214 |
--------------------------------------------------------------------------------
/file-upload/sbt-dist/bin/sbt-launch.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yangbajing/scala-applications/a41bd2b1e5eef086cf54fe58630bb47d7c16df39/file-upload/sbt-dist/bin/sbt-launch.jar
--------------------------------------------------------------------------------
/file-upload/sbt-dist/bin/sbt.bat:
--------------------------------------------------------------------------------
1 | @REM SBT launcher script
2 | @REM
3 | @REM Envioronment:
4 | @REM JAVA_HOME - location of a JDK home dir (mandatory)
5 | @REM SBT_OPTS - JVM options (optional)
6 | @REM Configuration:
7 | @REM sbtconfig.txt found in the SBT_HOME.
8 |
9 | @REM ZOMG! We need delayed expansion to build up CFG_OPTS later
10 | @setlocal enabledelayedexpansion
11 |
12 | @echo off
13 | set SBT_HOME=%~dp0
14 |
15 | rem FIRST we load the config file of extra options.
16 | set FN=%SBT_HOME%\..\conf\sbtconfig.txt
17 | set CFG_OPTS=
18 | FOR /F "tokens=* eol=# usebackq delims=" %%i IN ("%FN%") DO (
19 | set DO_NOT_REUSE_ME=%%i
20 | rem ZOMG (Part #2) WE use !! here to delay the expansion of
21 | rem CFG_OPTS, otherwise it remains "" for this loop.
22 | set CFG_OPTS=!CFG_OPTS! !DO_NOT_REUSE_ME!
23 | )
24 |
25 | rem We use the value of the JAVACMD environment variable if defined
26 | set _JAVACMD=%JAVACMD%
27 |
28 | if "%_JAVACMD%"=="" (
29 | if not "%JAVA_HOME%"=="" (
30 | if exist "%JAVA_HOME%\bin\java.exe" set "_JAVACMD=%JAVA_HOME%\bin\java.exe"
31 | )
32 | )
33 |
34 | if "%_JAVACMD%"=="" set _JAVACMD=java
35 |
36 | rem We use the value of the JAVA_OPTS environment variable if defined, rather than the config.
37 | set _JAVA_OPTS=%JAVA_OPTS%
38 | if "%_JAVA_OPTS%"=="" set _JAVA_OPTS=%CFG_OPTS%
39 |
40 | :run
41 |
42 | "%_JAVACMD%" %_JAVA_OPTS% %SBT_OPTS% -cp "%SBT_HOME%sbt-launch.jar" xsbt.boot.Boot %*
43 | if ERRORLEVEL 1 goto error
44 | goto end
45 |
46 | :error
47 | @endlocal
48 | exit /B 1
49 |
50 |
51 | :end
52 | @endlocal
53 | exit /B 0
54 |
--------------------------------------------------------------------------------
/file-upload/sbt-dist/conf/sbtconfig.txt:
--------------------------------------------------------------------------------
1 | # Set the java args to high
2 |
3 | -Xmx2048M
4 |
5 | -XX:MaxPermSize=256m
6 |
7 | -XX:ReservedCodeCacheSize=128m
8 |
9 | # Set the extra SBT options
10 |
11 | -Dsbt.log.format=true
12 |
13 | -Dsbt.override.build.repos=true
14 |
--------------------------------------------------------------------------------
/file-upload/sbt-dist/conf/sbtopts:
--------------------------------------------------------------------------------
1 | # ------------------------------------------------ #
2 | # The SBT Configuration file. #
3 | # ------------------------------------------------ #
4 |
5 |
6 | # Disable ANSI color codes
7 | #
8 | #-no-colors
9 |
10 | # Starts sbt even if the current directory contains no sbt project.
11 | #
12 | -sbt-create
13 |
14 | # Path to global settings/plugins directory (default: ~/.sbt)
15 | #
16 | #-sbt-dir /etc/sbt
17 |
18 | # Path to shared boot directory (default: ~/.sbt/boot in 0.11 series)
19 | #
20 | #-sbt-boot ~/.sbt/boot
21 |
22 | # Path to local Ivy repository (default: ~/.ivy2)
23 | #
24 | #-ivy ~/.ivy2
25 |
26 | # set memory options
27 | #
28 | #-mem
29 |
30 | # Use local caches for projects, no sharing.
31 | #
32 | #-no-share
33 |
34 | # Put SBT in offline mode.
35 | #
36 | #-offline
37 |
38 | # Sets the SBT version to use.
39 | #-sbt-version 0.11.3
40 |
41 | # Scala version (default: latest release)
42 | #
43 | #-scala-home
44 | #-scala-version
45 |
46 | # java version (default: java from PATH, currently $(java -version |& grep version))
47 | #
48 | #-java-home
49 |
50 |
--------------------------------------------------------------------------------
/file-upload/sbt.bat:
--------------------------------------------------------------------------------
1 | @REM SBT launcher script
2 |
3 | .\sbt-dist\bin\sbt.bat %*
4 |
--------------------------------------------------------------------------------
/file-upload/src/main/scala/me/yangbajing/fileupload/Constants.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.fileupload
2 |
3 | object Constants {
4 | val HASH_LENGTH = 64
5 | val FILE_PART_MAX = 8
6 | }
7 |
--------------------------------------------------------------------------------
/file-upload/src/main/scala/me/yangbajing/fileupload/Main.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.fileupload
2 |
3 | import akka.actor.ActorSystem
4 | import akka.http.scaladsl.Http
5 | import akka.stream.ActorMaterializer
6 | import com.typesafe.scalalogging.StrictLogging
7 | import me.yangbajing.fileupload.controller.FileRoute
8 | import me.yangbajing.fileupload.controller.HtmlRoute
9 | import me.yangbajing.fileupload.service.FileService
10 | import akka.http.scaladsl.server.Directives._
11 |
12 | import scala.util.Failure
13 | import scala.util.Success
14 |
15 | object Main extends StrictLogging {
16 |
17 | def main(args: Array[String]): Unit = {
18 | implicit val system = ActorSystem()
19 | implicit val mat = ActorMaterializer()
20 |
21 | val host = "127.0.0.1"
22 | val port = 33333
23 |
24 | val fileService = FileService(system, mat)
25 | val handler = new FileRoute(fileService).route ~
26 | new HtmlRoute().route
27 |
28 | val bindingFuture = Http().bindAndHandle(handler, host, port)
29 |
30 | bindingFuture.onComplete {
31 | case Success(binding) =>
32 | logger.info(s"startup success, $binding")
33 | case Failure(cause) =>
34 | logger.error("start failure", cause)
35 | system.terminate()
36 | }(system.dispatcher)
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/file-upload/src/main/scala/me/yangbajing/fileupload/controller/FileRoute.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.fileupload.controller
2 |
3 | import akka.http.scaladsl.model.Multipart
4 | import akka.http.scaladsl.model.StatusCodes
5 | import akka.http.scaladsl.server.Directives._
6 | import akka.http.scaladsl.server.Directive0
7 | import akka.http.scaladsl.server.Route
8 | import com.typesafe.scalalogging.StrictLogging
9 | import me.yangbajing.fileupload.service.FileService
10 | import me.yangbajing.fileupload.util.FileUtils
11 |
12 | class FileRoute(fileService: FileService) extends StrictLogging {
13 |
14 | def route: Route = pathPrefix("file") {
15 | log {
16 | uploadRoute ~
17 | downloadRoute ~
18 | progressRoute
19 | }
20 | }
21 |
22 | private def uploadRoute: Route = path("upload") {
23 | post {
24 | withoutSizeLimit {
25 | entity(as[Multipart.FormData]) { formData =>
26 | onSuccess(fileService.handleUpload(formData)) { results =>
27 | import me.yangbajing.fileupload.util.JacksonSupport._
28 | complete(results)
29 | }
30 | }
31 | }
32 | }
33 | }
34 |
35 | // 支持断点续传
36 | private def downloadRoute: Route = path("download" / Segment) { hash =>
37 | getFromFile(FileUtils.getLocalPath(hash).toFile)
38 | }
39 |
40 | // 查询文件上传进度
41 | private def progressRoute: Route = path("progress" / Segment) { hash =>
42 | onSuccess(fileService.progressByHash(hash)) {
43 | case Some(v) =>
44 | import me.yangbajing.fileupload.util.JacksonSupport._
45 | complete(v)
46 | case None => complete(StatusCodes.NotFound)
47 | }
48 | }
49 |
50 | private val log: Directive0 = extractRequest.flatMap { req =>
51 | logger.debug(req.toString())
52 | pass
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/file-upload/src/main/scala/me/yangbajing/fileupload/controller/HtmlRoute.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.fileupload.controller
2 |
3 | import akka.http.scaladsl.server.Directives._
4 | import akka.http.scaladsl.server.Route
5 |
6 | class HtmlRoute {
7 |
8 | def route: Route = pathPrefix("html") {
9 | getFromBrowseableDirectory("web")
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/file-upload/src/main/scala/me/yangbajing/fileupload/io/CustomFileSource.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.fileupload.io
2 |
3 | import java.nio.ByteBuffer
4 | import java.nio.channels.FileChannel
5 | import java.nio.file.Files
6 | import java.nio.file.NoSuchFileException
7 | import java.nio.file.Path
8 | import java.nio.file.StandardOpenOption
9 |
10 | import akka.Done
11 | import akka.stream.Attributes.InputBuffer
12 | import akka.stream.Attributes
13 | import akka.stream.IOResult
14 | import akka.stream.Outlet
15 | import akka.stream.SourceShape
16 | import akka.stream.stage.GraphStageLogic
17 | import akka.stream.stage.GraphStageWithMaterializedValue
18 | import akka.stream.stage.OutHandler
19 | import akka.util.ByteString
20 |
21 | import scala.annotation.tailrec
22 | import scala.concurrent.Future
23 | import scala.concurrent.Promise
24 | import scala.util.Failure
25 | import scala.util.Success
26 | import scala.util.Try
27 | import scala.util.control.NonFatal
28 |
29 | final class CustomFileSource(path: Path, chunkSize: Int, startPosition: Long = 0, endPosition: Long = -1L)
30 | extends GraphStageWithMaterializedValue[SourceShape[ByteString], Future[IOResult]] {
31 | require(chunkSize > 0, "chunkSize must be greater than 0")
32 | val out = Outlet[ByteString]("FileSource.out")
33 |
34 | override val shape = SourceShape(out)
35 |
36 | override def createLogicAndMaterializedValue(inheritedAttributes: Attributes): (GraphStageLogic, Future[IOResult]) = {
37 | val ioResultPromise = Promise[IOResult]()
38 |
39 | val logic = new GraphStageLogic(shape) with OutHandler {
40 | handler =>
41 | val buffer = ByteBuffer.allocate(chunkSize)
42 | val maxReadAhead = inheritedAttributes.get[InputBuffer](InputBuffer(16, 16)).max
43 | var channel: FileChannel = _
44 | var position = startPosition
45 | var chunkCallback: Try[Int] => Unit = _
46 | var eofEncountered = false
47 | var availableChunks: Vector[ByteString] = Vector.empty[ByteString]
48 |
49 | setHandler(out, this)
50 |
51 | override def preStart(): Unit = {
52 | try {
53 | // this is a bit weird but required to keep existing semantics
54 | if (!Files.exists(path)) throw new NoSuchFileException(path.toString)
55 |
56 | require(!Files.isDirectory(path), s"Path '$path' is a directory")
57 | require(Files.isReadable(path), s"Missing read permission for '$path'")
58 |
59 | channel = FileChannel.open(path, StandardOpenOption.READ)
60 | channel.position(position)
61 | // if (endPosition > 0) {
62 | // channel = channel.truncate(endPosition)
63 | // }
64 | } catch {
65 | case ex: Exception =>
66 | ioResultPromise.trySuccess(IOResult(position, Failure(ex)))
67 | throw ex
68 | }
69 | }
70 |
71 | override def onPull(): Unit = {
72 | if (availableChunks.size < maxReadAhead && !eofEncountered) {
73 | availableChunks = readAhead(maxReadAhead, availableChunks)
74 | }
75 |
76 | //if already read something and try
77 |
78 | if (availableChunks.nonEmpty) {
79 | emitMultiple(out, availableChunks.iterator, () => if (eofEncountered) success() else setHandler(out, handler))
80 | availableChunks = Vector.empty[ByteString]
81 | } else if (eofEncountered) success()
82 | }
83 |
84 | private def success(): Unit = {
85 | completeStage()
86 | ioResultPromise.trySuccess(IOResult(position, Success(Done)))
87 | }
88 |
89 | /** BLOCKING I/O READ */
90 | @tailrec def readAhead(maxChunks: Int, chunks: Vector[ByteString]): Vector[ByteString] = {
91 | if (chunks.size < maxChunks && !eofEncountered) {
92 | val readBytes = try channel.read(buffer, position)
93 | catch {
94 | case NonFatal(ex) =>
95 | failStage(ex)
96 | ioResultPromise.trySuccess(IOResult(position, Failure(ex)))
97 | throw ex
98 | }
99 |
100 | if (readBytes > 0) {
101 | buffer.flip()
102 | position += readBytes
103 | val newChunks = chunks :+ ByteString.fromByteBuffer(buffer)
104 | buffer.clear()
105 |
106 | if (readBytes < chunkSize ||
107 | (endPosition > 0 && position >= endPosition)) {
108 | eofEncountered = true
109 | newChunks
110 | } else {
111 | readAhead(maxChunks, newChunks)
112 | }
113 | } else {
114 | eofEncountered = true
115 | chunks
116 | }
117 | } else {
118 | chunks
119 | }
120 | }
121 |
122 | override def onDownstreamFinish(): Unit = success()
123 |
124 | override def postStop(): Unit = {
125 | ioResultPromise.trySuccess(IOResult(position, Success(Done)))
126 | if ((channel ne null) && channel.isOpen) channel.close()
127 | }
128 | }
129 |
130 | (logic, ioResultPromise.future)
131 | }
132 |
133 | override def toString = s"FileSource($path, $chunkSize)"
134 | }
135 |
--------------------------------------------------------------------------------
/file-upload/src/main/scala/me/yangbajing/fileupload/model/FileInfo.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.fileupload.model
2 |
3 | import java.nio.file.Path
4 |
5 | import akka.http.scaladsl.model.HttpHeader
6 | import akka.http.scaladsl.model.Multipart.FormData
7 | import me.yangbajing.fileupload.Constants
8 |
9 | import scala.collection.immutable
10 |
11 | case class FileInfo(bodyPart: FormData.BodyPart, hash: Option[String], contentLength: Long, startPosition: Long) {
12 | override def toString: String =
13 | s"FileInfo(${bodyPart.name}, $hash, $contentLength, $startPosition, ${bodyPart.filename}, ${bodyPart.headers})"
14 | }
15 |
16 | object FileInfo {
17 | val Empty = FileInfo(null, None, 0L, 0L)
18 |
19 | def apply(part: FormData.BodyPart): FileInfo = {
20 | val (hash, contentLength, startPosition) = part.name.split('.') match {
21 | case Array(a, b, c) => (a, b.toLong, c.toLong)
22 | case Array(a, b) => (a, b.toLong, 0L)
23 | case Array(a) => (a, 0L, 0L)
24 | case _ => throw new IllegalArgumentException(s"Multipart.FormData name格式不符合要求:${part.name}")
25 | }
26 | new FileInfo(part, if (Constants.HASH_LENGTH == hash.length) Some(hash) else None, contentLength, startPosition)
27 | }
28 | }
29 |
30 | /**
31 | * 文件元数据
32 | * @param hash 文件HASH(sha256)
33 | * @param size 已上传(bytes)
34 | * @param localPath 本地存储路径
35 | */
36 | case class FileMeta(hash: String, size: Long, localPath: Path)
37 |
38 | case class FileBO(
39 | hash: Option[String],
40 | computedHash: Option[String],
41 | localPath: Path,
42 | contentLength: Long,
43 | filename: Option[String],
44 | headers: immutable.Seq[HttpHeader])
45 |
--------------------------------------------------------------------------------
/file-upload/src/main/scala/me/yangbajing/fileupload/service/FileService.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.fileupload.service
2 |
3 | import akka.actor.ActorSystem
4 | import akka.http.scaladsl.model.Multipart
5 | import akka.stream.ActorMaterializer
6 | import me.yangbajing.fileupload.model.FileBO
7 | import me.yangbajing.fileupload.model.FileMeta
8 |
9 | import scala.concurrent.Future
10 |
11 | trait FileService {
12 |
13 | implicit val system: ActorSystem
14 | implicit val mat: ActorMaterializer
15 |
16 | def progressByHash(hash: String): Future[Option[FileMeta]]
17 |
18 | def handleUpload(formData: Multipart.FormData): Future[Seq[FileBO]]
19 |
20 | }
21 |
22 | object FileService {
23 |
24 | def apply(system: ActorSystem, mat: ActorMaterializer) = new FileServiceImpl(system, mat)
25 | }
26 |
--------------------------------------------------------------------------------
/file-upload/src/main/scala/me/yangbajing/fileupload/service/FileServiceImpl.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.fileupload.service
2 |
3 | import java.util.Objects
4 |
5 | import akka.actor.ActorSystem
6 | import akka.http.scaladsl.model.Multipart
7 | import akka.stream.scaladsl.Sink
8 | import akka.stream.ActorMaterializer
9 | import akka.stream.Materializer
10 | import com.typesafe.scalalogging.StrictLogging
11 | import me.yangbajing.fileupload.Constants
12 | import me.yangbajing.fileupload.model.FileBO
13 | import me.yangbajing.fileupload.model.FileInfo
14 | import me.yangbajing.fileupload.model.FileMeta
15 | import me.yangbajing.fileupload.util.FileUtils
16 |
17 | import scala.concurrent.Future
18 | import scala.concurrent.duration._
19 |
20 | class FileServiceImpl(val system: ActorSystem, implicit val mat: ActorMaterializer)
21 | extends FileService
22 | with StrictLogging {
23 |
24 | override def progressByHash(hash: String): Future[Option[FileMeta]] = {
25 | require(Objects.nonNull(hash) && hash.nonEmpty, "hash 不能为空。")
26 | Future.successful(FileUtils.getFileMeta(hash))
27 | }
28 |
29 | override def handleUpload(formData: Multipart.FormData): Future[Seq[FileBO]] = {
30 | formData.parts
31 | // .groupBy(Constants.FILE_PART_MAX, part => part.name.split('.').head)
32 | // .async
33 | // .foldAsync[FileInfo](FileInfo.Empty)((fileInfo, part) => mergeBodyPart(fileInfo, part))
34 | // .mergeSubstreams
35 | .map(part => FileInfo(part))
36 | .log("fileInfo", info => logger.debug(s"fileInfo: $info"))
37 | .mapAsync(Constants.FILE_PART_MAX)(processFile)
38 | .runWith(Sink.seq)
39 | }
40 |
41 | /**
42 | * sha存在,判断文件是否已上传?
43 | * startPosition存在,正从startPosition位置开始上传
44 | * @param fileInfo
45 | * @return
46 | */
47 | private def processFile(fileInfo: FileInfo)(implicit mat: Materializer): Future[FileBO] = {
48 | import system.dispatcher
49 | val bodyPart = fileInfo.bodyPart
50 | fileInfo.hash.flatMap(FileUtils.getFileMeta) match {
51 | case Some(fileMeta) if fileInfo.contentLength == fileMeta.size => // 已上传完成
52 | Future.successful(
53 | FileBO(
54 | fileInfo.hash,
55 | Some(fileMeta.hash),
56 | fileMeta.localPath,
57 | fileMeta.size,
58 | bodyPart.filename,
59 | bodyPart.headers))
60 | case _ =>
61 | FileUtils.uploadFile(fileInfo)
62 | }
63 | }
64 |
65 | private def mergeBodyPart(fileInfo: FileInfo, part: Multipart.FormData.BodyPart): Future[FileInfo] = {
66 | import system.dispatcher
67 | part.name.split('.') match {
68 | case Array(_) =>
69 | Future.successful(fileInfo.copy(bodyPart = part))
70 | case Array(_, "hash") =>
71 | part.entity.toStrict(1.second).flatMap { entity =>
72 | val hash = entity.data.utf8String
73 | if (Constants.HASH_LENGTH != hash.length)
74 | Future.failed(new IllegalArgumentException("hash值应为64位sha256的16进制字符串"))
75 | else
76 | Future.successful(fileInfo.copy(hash = Some(hash.toLowerCase)))
77 | }
78 | case Array(_, "contentLength") =>
79 | part.entity.toStrict(1.second).map(entity => fileInfo.copy(contentLength = entity.data.utf8String.toLong))
80 | case Array(_, "startPosition") =>
81 | part.entity.toStrict(1.second).map(entity => fileInfo.copy(startPosition = entity.data.utf8String.toLong))
82 | case _ =>
83 | Future.failed(new IllegalArgumentException(s"未知的FormData字段:${part.name}"))
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/file-upload/src/main/scala/me/yangbajing/fileupload/util/FileUtils.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.fileupload.util
2 |
3 | import java.nio.file.StandardOpenOption.APPEND
4 | import java.nio.file._
5 | import java.security.MessageDigest
6 |
7 | import akka.stream.Materializer
8 | import akka.stream.scaladsl.FileIO
9 | import com.typesafe.scalalogging.StrictLogging
10 | import me.yangbajing.fileupload.Constants
11 | import me.yangbajing.fileupload.model.FileBO
12 | import me.yangbajing.fileupload.model.FileInfo
13 | import me.yangbajing.fileupload.model.FileMeta
14 |
15 | import scala.concurrent.ExecutionContext
16 | import scala.concurrent.Future
17 |
18 | object FileUtils extends StrictLogging {
19 | val TMP_DIR: Path = getOrCreateDirectories(Paths.get("/tmp/file-upload/tmp"))
20 | val LOCAL_PATH: String = getOrCreateDirectories(Paths.get("/tmp/file-upload")).toString
21 |
22 | def getOrCreateDirectories(path: Path): Path = {
23 | if (!Files.isDirectory(path)) {
24 | Files.createDirectories(path)
25 | }
26 | path
27 | }
28 |
29 | def getLocalPath(hash: String): Path = Paths.get(LOCAL_PATH, hash.take(2), hash)
30 |
31 | def getFileMeta(hash: String): Option[FileMeta] = {
32 | if (hash == null || Constants.HASH_LENGTH != hash.length) {
33 | None
34 | } else {
35 | val path = getLocalPath(hash)
36 | if (Files.exists(path) && Files.isReadable(path)) Some(FileMeta(hash, Files.size(path), path)) else None
37 | }
38 | }
39 |
40 | def uploadFile(fileInfo: FileInfo)(implicit mat: Materializer, ec: ExecutionContext): Future[FileBO] = {
41 | // TODO 需要校验上传完成文件的hash值与提交hash值是否匹配?
42 | val maybeMeta = fileInfo.hash.flatMap(FileUtils.getFileMeta)
43 | val beContinue = maybeMeta.isDefined && fileInfo.startPosition > 0L
44 | val f = if (beContinue) uploadContinue(fileInfo, maybeMeta.get) else uploadNewFile(fileInfo)
45 | f.andThen {
46 | case tryValue =>
47 | logger.debug(s"文件上传完成:$tryValue")
48 | }
49 | }
50 |
51 | private def uploadContinue(fileInfo: FileInfo, meta: FileMeta)(implicit mat: Materializer, ec: ExecutionContext) = {
52 | val bodyPart = fileInfo.bodyPart
53 | val localPath = FileUtils.getLocalPath(fileInfo.hash.get)
54 | logger.debug(s"断点续传,startPosition:${fileInfo.startPosition},路径:$localPath")
55 | bodyPart.entity.dataBytes
56 | .runWith(FileIO.toPath(localPath, Set(APPEND), fileInfo.startPosition))
57 | .map(ioResult =>
58 | FileBO(fileInfo.hash, None, localPath, meta.size + ioResult.count, bodyPart.filename, bodyPart.headers))
59 | }
60 |
61 | private def uploadNewFile(fileInfo: FileInfo)(implicit mat: Materializer, ec: ExecutionContext) = {
62 | val bodyPart = fileInfo.bodyPart
63 | val tmpPath = fileInfo.hash
64 | .map(h => FileUtils.getLocalPath(h))
65 | .getOrElse(Files.createTempFile(FileUtils.TMP_DIR, bodyPart.filename.getOrElse(""), ""))
66 | val sha = MessageDigest.getInstance("SHA-256")
67 | logger.debug(s"新文件,路径:$tmpPath")
68 | bodyPart.entity.dataBytes
69 | .map { byteString =>
70 | byteString.asByteBuffers.foreach(sha.update)
71 | byteString
72 | }
73 | .runWith(FileIO.toPath(tmpPath))
74 | .map { ioResult =>
75 | val computedHash = Utils.bytesToHex(sha.digest())
76 | fileInfo.hash.foreach { h =>
77 | require(h == computedHash, s"前端上传hash与服务端计算hash值不匹配,$h != $computedHash")
78 | }
79 | val localPath = fileInfo.hash match {
80 | case Some(_) => tmpPath
81 | case _ => move(computedHash, tmpPath, ioResult.count)
82 | }
83 | FileBO(fileInfo.hash, Some(computedHash), localPath, ioResult.count, bodyPart.filename, bodyPart.headers)
84 | }
85 | }
86 |
87 | def move(hash: String, tmpFile: Path, contentLength: Long): Path = {
88 | val targetDir = FileUtils.getOrCreateDirectories(Paths.get(FileUtils.LOCAL_PATH, hash.take(2)))
89 | val target = targetDir.resolve(hash)
90 | require(!Files.exists(target), s"目标文件已存在,$target")
91 | Files.move(tmpFile, target)
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/file-upload/src/main/scala/me/yangbajing/fileupload/util/Jackson.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.fileupload.util
2 | import com.fasterxml.jackson.annotation.JsonInclude
3 | import com.fasterxml.jackson.core.JsonParser
4 | import com.fasterxml.jackson.core.JsonProcessingException
5 | import com.fasterxml.jackson.core.TreeNode
6 | import com.fasterxml.jackson.databind._
7 | import com.fasterxml.jackson.databind.node.ArrayNode
8 | import com.fasterxml.jackson.databind.node.ObjectNode
9 | import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider
10 | import com.fasterxml.jackson.databind.ser.SerializerFactory
11 |
12 | import scala.reflect.ClassTag
13 |
14 | object Jackson {
15 | val defaultObjectMapper: ObjectMapper = createObjectMapper
16 |
17 | def createObjectNode: ObjectNode = defaultObjectMapper.createObjectNode
18 |
19 | def createArrayNode: ArrayNode = defaultObjectMapper.createArrayNode
20 |
21 | def readTree(jstr: String): JsonNode = defaultObjectMapper.readTree(jstr)
22 |
23 | def valueToTree(v: AnyRef): JsonNode = defaultObjectMapper.valueToTree(v)
24 |
25 | def treeToValue[T](tree: TreeNode)(implicit ev1: ClassTag[T]): T =
26 | defaultObjectMapper.treeToValue(tree, ev1.runtimeClass).asInstanceOf[T]
27 |
28 | def stringify(v: AnyRef): String = defaultObjectMapper.writeValueAsString(v)
29 |
30 | def prettyStringify(v: AnyRef): String = defaultObjectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(v)
31 |
32 | def extract[T](tree: TreeNode)(implicit ev1: ClassTag[T]): Either[JsonProcessingException, T] =
33 | try {
34 | Right(defaultObjectMapper.treeToValue(tree, ev1.runtimeClass).asInstanceOf[T])
35 | } catch {
36 | case e: JsonProcessingException =>
37 | Left(e)
38 | }
39 |
40 | @inline def extract[T](compare: Boolean, tree: TreeNode)(implicit ev1: ClassTag[T]): Either[Throwable, T] =
41 | if (compare) extract(tree)
42 | else Left(new IllegalStateException(s"compare比较结果为false,需要类型:${ev1.runtimeClass.getName}"))
43 |
44 | private def createObjectMapper: ObjectMapper = {
45 | new ObjectMapper()
46 | .findAndRegisterModules()
47 | .enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES)
48 | .enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES)
49 | .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
50 | .enable(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS)
51 | .disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES)
52 | .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
53 | .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
54 | // .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
55 | // .disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS)
56 | .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
57 | .disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS)
58 | .setSerializationInclusion(JsonInclude.Include.NON_NULL)
59 | // .setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
60 | // .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY)
61 | }
62 |
63 | private class MassSerializerProvider(src: SerializerProvider, config: SerializationConfig, f: SerializerFactory)
64 | extends DefaultSerializerProvider(src, config, f) {
65 | def this() {
66 | this(null, null, null)
67 | }
68 |
69 | def this(src: MassSerializerProvider) {
70 | this(src, null, null)
71 | }
72 |
73 | override def copy: DefaultSerializerProvider = {
74 | if (getClass ne classOf[MassSerializerProvider]) return super.copy
75 | new MassSerializerProvider(this)
76 | }
77 |
78 | override def createInstance(config: SerializationConfig, jsf: SerializerFactory) =
79 | new MassSerializerProvider(this, config, jsf)
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/file-upload/src/main/scala/me/yangbajing/fileupload/util/JacksonSupport.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.fileupload.util
2 | import akka.http.scaladsl.marshalling.Marshaller
3 | import akka.http.scaladsl.marshalling.ToEntityMarshaller
4 | import akka.http.scaladsl.model.MediaTypes
5 | import akka.http.scaladsl.unmarshalling.FromEntityUnmarshaller
6 | import akka.http.scaladsl.unmarshalling.Unmarshaller
7 | import akka.util.ByteString
8 | import com.fasterxml.jackson.core.util.DefaultPrettyPrinter
9 | import com.fasterxml.jackson.databind.JsonNode
10 | import com.fasterxml.jackson.databind.ObjectMapper
11 |
12 | import scala.reflect.ClassTag
13 |
14 | /**
15 | * Created by yangbajing(yangbajing@gmail.com) on 2017-02-27.
16 | */
17 | object JacksonSupport extends JacksonSupport {
18 |
19 | def stringify(value: AnyRef): String =
20 | Jackson.defaultObjectMapper.writeValueAsString(value)
21 |
22 | def prettyString(value: AnyRef): String = {
23 | val writer = Jackson.defaultObjectMapper.writer(new DefaultPrettyPrinter())
24 | writer.writeValueAsString(value)
25 | }
26 |
27 | def readValue[A](content: String)(
28 | implicit
29 | ct: ClassTag[A],
30 | objectMapper: ObjectMapper = Jackson.defaultObjectMapper): A =
31 | objectMapper.readValue(content, ct.runtimeClass).asInstanceOf[A]
32 |
33 | def getJsonNode(content: String)(implicit objectMapper: ObjectMapper = Jackson.defaultObjectMapper): JsonNode =
34 | objectMapper.readTree(content)
35 |
36 | def isError(content: String)(implicit objectMapper: ObjectMapper = Jackson.defaultObjectMapper): Boolean =
37 | !isSuccess(content)
38 |
39 | def isSuccess(content: String)(implicit objectMapper: ObjectMapper = Jackson.defaultObjectMapper): Boolean = {
40 | val node = getJsonNode(content)
41 | val errCode = node.get("errCode")
42 | if (errCode eq null) true else errCode.asInt(0) == 0
43 | }
44 | }
45 |
46 | /**
47 | * JSON marshalling/unmarshalling using an in-scope Jackson's ObjectMapper
48 | */
49 | trait JacksonSupport {
50 |
51 | private val jsonStringUnmarshaller =
52 | Unmarshaller.byteStringUnmarshaller.forContentTypes(MediaTypes.`application/json`).mapWithCharset {
53 | case (ByteString.empty, _) => throw Unmarshaller.NoContentException
54 | case (data, charset) => data.decodeString(charset.nioCharset.name)
55 | }
56 |
57 | private val jsonStringMarshaller = Marshaller.stringMarshaller(MediaTypes.`application/json`)
58 |
59 | /**
60 | * HTTP entity => `A`
61 | */
62 | implicit def unmarshaller[A](
63 | implicit
64 | ct: ClassTag[A],
65 | objectMapper: ObjectMapper = Jackson.defaultObjectMapper): FromEntityUnmarshaller[A] =
66 | jsonStringUnmarshaller.map { data =>
67 | objectMapper.readValue(data, ct.runtimeClass).asInstanceOf[A]
68 | }
69 |
70 | /**
71 | * `A` => HTTP entity
72 | */
73 | implicit def marshaller[A](
74 | implicit objectMapper: ObjectMapper = Jackson.defaultObjectMapper): ToEntityMarshaller[A] = {
75 | jsonStringMarshaller.compose(objectMapper.writeValueAsString)
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/file-upload/src/main/scala/me/yangbajing/fileupload/util/Utils.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.fileupload.util
2 | import akka.http.scaladsl.coding.Encoder
3 |
4 | object Utils {
5 | private val hexArray = "0123456789abcdef".toCharArray
6 |
7 | def bytesToHex(bytes: Array[Byte]): String = {
8 | val hexChars = new Array[Char](bytes.length * 2)
9 | var j = 0
10 | while (j < bytes.length) {
11 | val v = bytes(j) & 0xFF
12 | hexChars(j * 2) = hexArray(v >>> 4)
13 | hexChars(j * 2 + 1) = hexArray(v & 0x0F)
14 | j += 1
15 | }
16 | new String(hexChars)
17 | }
18 |
19 | Encoder
20 | }
21 |
--------------------------------------------------------------------------------
/file-upload/src/test/scala/me/yangbajing/fileupload/controller/FileRouteTest.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.fileupload.controller
2 |
3 | import java.nio.file.Files
4 | import java.nio.file.Paths
5 |
6 | import akka.actor.ActorSystem
7 | import akka.http.scaladsl.Http
8 | import akka.http.scaladsl.model.ContentType
9 | import akka.http.scaladsl.model.HttpEntity
10 | import akka.http.scaladsl.model.HttpMethods
11 | import akka.http.scaladsl.model.HttpRequest
12 | import akka.http.scaladsl.model.MediaTypes
13 | import akka.http.scaladsl.model.Multipart
14 | import akka.http.scaladsl.model.StatusCodes
15 | import akka.http.scaladsl.unmarshalling.Unmarshal
16 | import akka.stream.ActorMaterializer
17 | import akka.stream.Attributes
18 | import akka.stream.scaladsl.FileIO
19 | import akka.stream.scaladsl.Source
20 | import akka.testkit.TestKit
21 | import com.fasterxml.jackson.databind.node.ArrayNode
22 | import com.fasterxml.jackson.databind.node.ObjectNode
23 | import helloscala.common.util.DigestUtils
24 | import me.yangbajing.fileupload.io.CustomFileSource
25 | import org.scalatest.FunSuiteLike
26 | import org.scalatest.MustMatchers
27 | import org.scalatest.concurrent.ScalaFutures
28 | import org.scalatest.time.Milliseconds
29 | import org.scalatest.time.Seconds
30 | import org.scalatest.time.Span
31 |
32 | import scala.concurrent.Await
33 | import scala.concurrent.duration.Duration
34 |
35 | /**
36 | * 断点上传文件
37 | */
38 | class FileRouteTest extends TestKit(ActorSystem("file-demo")) with FunSuiteLike with MustMatchers with ScalaFutures {
39 | private val BASE_URL = "http://118.89.226.54:8083"
40 | implicit private val mat = ActorMaterializer()
41 |
42 | val CHUNK_SIZE = 8192
43 | val file = Paths.get("/opt/Videos/VID_20190322_104457.mp4")
44 | val hash = DigestUtils.sha256HexFromPath(file)
45 | val fileLength = Files.size(file)
46 | var startPosition = 0L
47 |
48 | test("upload/continue 大文件") {
49 | // 只上传文件的一半内容
50 | val truncateSize = fileLength / 2
51 |
52 | val bodyPart = Multipart.FormData.BodyPart(
53 | s"$hash.$fileLength.0",
54 | HttpEntity.Default(
55 | ContentType(MediaTypes.`video/mp4`),
56 | fileLength,
57 | Source
58 | .fromGraph(new CustomFileSource(file, CHUNK_SIZE, 0L, truncateSize))
59 | .withAttributes(Attributes(Attributes.Name("file")))),
60 | Map("filename" → file.getFileName.toString))
61 |
62 | val formData = Multipart.FormData(bodyPart)
63 | val request =
64 | HttpRequest(HttpMethods.POST, s"$BASE_URL/file/ihongka_files/upload/continue", entity = formData.toEntity())
65 | val responseF = Http().singleRequest(request)
66 | val response = Await.result(responseF, Duration.Inf)
67 | println(s"response: ${response.entity}")
68 | }
69 |
70 | test("upload/continue 大文件上传进度") {
71 | import me.yangbajing.fileupload.util.JacksonSupport._
72 | val request = HttpRequest(HttpMethods.GET, s"$BASE_URL/file/ihongka_files/progress/$hash")
73 | val response = Http().singleRequest(request).futureValue
74 | response.status mustBe StatusCodes.OK
75 | val result = Unmarshal(response.entity).to[ObjectNode].futureValue
76 | println(result)
77 | startPosition = result.get("size").asLong()
78 | }
79 |
80 | test("upload/continue 断点续传") {
81 | import me.yangbajing.fileupload.util.JacksonSupport._
82 | val bodyPart = Multipart.FormData.BodyPart(
83 | s"$hash.$fileLength.$startPosition",
84 | HttpEntity.Default(
85 | ContentType(MediaTypes.`video/mp4`),
86 | Files.size(file),
87 | FileIO.fromPath(file, CHUNK_SIZE, startPosition)))
88 | val formData = Multipart.FormData(bodyPart)
89 | val request =
90 | HttpRequest(HttpMethods.POST, s"$BASE_URL/file/ihongka_files/upload/continue", entity = formData.toEntity())
91 | val responseF = Http().singleRequest(request)
92 | val response = Await.result(responseF, Duration.Inf)
93 | response.status mustBe StatusCodes.OK
94 | val result = Unmarshal(response.entity).to[ArrayNode].futureValue
95 | println(result)
96 | result.get(0).get("_id").asText() mustBe hash
97 | result.get(0).get("uploadSize").asLong() mustBe fileLength
98 | }
99 |
100 | implicit override def patienceConfig: PatienceConfig = PatienceConfig(Span(30, Seconds), Span(50, Milliseconds))
101 | }
102 |
--------------------------------------------------------------------------------
/file-upload/web/css/bootstrap-reboot.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Reboot v4.2.1 (https://getbootstrap.com/)
3 | * Copyright 2011-2018 The Bootstrap Authors
4 | * Copyright 2011-2018 Twitter, Inc.
5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
7 | */
8 | *,
9 | *::before,
10 | *::after {
11 | box-sizing: border-box;
12 | }
13 |
14 | html {
15 | font-family: sans-serif;
16 | line-height: 1.15;
17 | -webkit-text-size-adjust: 100%;
18 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
19 | }
20 |
21 | article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
22 | display: block;
23 | }
24 |
25 | body {
26 | margin: 0;
27 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
28 | font-size: 1rem;
29 | font-weight: 400;
30 | line-height: 1.5;
31 | color: #212529;
32 | text-align: left;
33 | background-color: #fff;
34 | }
35 |
36 | [tabindex="-1"]:focus {
37 | outline: 0 !important;
38 | }
39 |
40 | hr {
41 | box-sizing: content-box;
42 | height: 0;
43 | overflow: visible;
44 | }
45 |
46 | h1, h2, h3, h4, h5, h6 {
47 | margin-top: 0;
48 | margin-bottom: 0.5rem;
49 | }
50 |
51 | p {
52 | margin-top: 0;
53 | margin-bottom: 1rem;
54 | }
55 |
56 | abbr[title],
57 | abbr[data-original-title] {
58 | text-decoration: underline;
59 | -webkit-text-decoration: underline dotted;
60 | text-decoration: underline dotted;
61 | cursor: help;
62 | border-bottom: 0;
63 | text-decoration-skip-ink: none;
64 | }
65 |
66 | address {
67 | margin-bottom: 1rem;
68 | font-style: normal;
69 | line-height: inherit;
70 | }
71 |
72 | ol,
73 | ul,
74 | dl {
75 | margin-top: 0;
76 | margin-bottom: 1rem;
77 | }
78 |
79 | ol ol,
80 | ul ul,
81 | ol ul,
82 | ul ol {
83 | margin-bottom: 0;
84 | }
85 |
86 | dt {
87 | font-weight: 700;
88 | }
89 |
90 | dd {
91 | margin-bottom: .5rem;
92 | margin-left: 0;
93 | }
94 |
95 | blockquote {
96 | margin: 0 0 1rem;
97 | }
98 |
99 | b,
100 | strong {
101 | font-weight: bolder;
102 | }
103 |
104 | small {
105 | font-size: 80%;
106 | }
107 |
108 | sub,
109 | sup {
110 | position: relative;
111 | font-size: 75%;
112 | line-height: 0;
113 | vertical-align: baseline;
114 | }
115 |
116 | sub {
117 | bottom: -.25em;
118 | }
119 |
120 | sup {
121 | top: -.5em;
122 | }
123 |
124 | a {
125 | color: #007bff;
126 | text-decoration: none;
127 | background-color: transparent;
128 | }
129 |
130 | a:hover {
131 | color: #0056b3;
132 | text-decoration: underline;
133 | }
134 |
135 | a:not([href]):not([tabindex]) {
136 | color: inherit;
137 | text-decoration: none;
138 | }
139 |
140 | a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {
141 | color: inherit;
142 | text-decoration: none;
143 | }
144 |
145 | a:not([href]):not([tabindex]):focus {
146 | outline: 0;
147 | }
148 |
149 | pre,
150 | code,
151 | kbd,
152 | samp {
153 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
154 | font-size: 1em;
155 | }
156 |
157 | pre {
158 | margin-top: 0;
159 | margin-bottom: 1rem;
160 | overflow: auto;
161 | }
162 |
163 | figure {
164 | margin: 0 0 1rem;
165 | }
166 |
167 | img {
168 | vertical-align: middle;
169 | border-style: none;
170 | }
171 |
172 | svg {
173 | overflow: hidden;
174 | vertical-align: middle;
175 | }
176 |
177 | table {
178 | border-collapse: collapse;
179 | }
180 |
181 | caption {
182 | padding-top: 0.75rem;
183 | padding-bottom: 0.75rem;
184 | color: #6c757d;
185 | text-align: left;
186 | caption-side: bottom;
187 | }
188 |
189 | th {
190 | text-align: inherit;
191 | }
192 |
193 | label {
194 | display: inline-block;
195 | margin-bottom: 0.5rem;
196 | }
197 |
198 | button {
199 | border-radius: 0;
200 | }
201 |
202 | button:focus {
203 | outline: 1px dotted;
204 | outline: 5px auto -webkit-focus-ring-color;
205 | }
206 |
207 | input,
208 | button,
209 | select,
210 | optgroup,
211 | textarea {
212 | margin: 0;
213 | font-family: inherit;
214 | font-size: inherit;
215 | line-height: inherit;
216 | }
217 |
218 | button,
219 | input {
220 | overflow: visible;
221 | }
222 |
223 | button,
224 | select {
225 | text-transform: none;
226 | }
227 |
228 | button,
229 | [type="button"],
230 | [type="reset"],
231 | [type="submit"] {
232 | -webkit-appearance: button;
233 | }
234 |
235 | button::-moz-focus-inner,
236 | [type="button"]::-moz-focus-inner,
237 | [type="reset"]::-moz-focus-inner,
238 | [type="submit"]::-moz-focus-inner {
239 | padding: 0;
240 | border-style: none;
241 | }
242 |
243 | input[type="radio"],
244 | input[type="checkbox"] {
245 | box-sizing: border-box;
246 | padding: 0;
247 | }
248 |
249 | input[type="date"],
250 | input[type="time"],
251 | input[type="datetime-local"],
252 | input[type="month"] {
253 | -webkit-appearance: listbox;
254 | }
255 |
256 | textarea {
257 | overflow: auto;
258 | resize: vertical;
259 | }
260 |
261 | fieldset {
262 | min-width: 0;
263 | padding: 0;
264 | margin: 0;
265 | border: 0;
266 | }
267 |
268 | legend {
269 | display: block;
270 | width: 100%;
271 | max-width: 100%;
272 | padding: 0;
273 | margin-bottom: .5rem;
274 | font-size: 1.5rem;
275 | line-height: inherit;
276 | color: inherit;
277 | white-space: normal;
278 | }
279 |
280 | progress {
281 | vertical-align: baseline;
282 | }
283 |
284 | [type="number"]::-webkit-inner-spin-button,
285 | [type="number"]::-webkit-outer-spin-button {
286 | height: auto;
287 | }
288 |
289 | [type="search"] {
290 | outline-offset: -2px;
291 | -webkit-appearance: none;
292 | }
293 |
294 | [type="search"]::-webkit-search-decoration {
295 | -webkit-appearance: none;
296 | }
297 |
298 | ::-webkit-file-upload-button {
299 | font: inherit;
300 | -webkit-appearance: button;
301 | }
302 |
303 | output {
304 | display: inline-block;
305 | }
306 |
307 | summary {
308 | display: list-item;
309 | cursor: pointer;
310 | }
311 |
312 | template {
313 | display: none;
314 | }
315 |
316 | [hidden] {
317 | display: none !important;
318 | }
319 | /*# sourceMappingURL=bootstrap-reboot.css.map */
--------------------------------------------------------------------------------
/file-upload/web/upload.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 断点上传
7 |
8 |
9 |
10 |
66 |
67 |
68 |
69 |
134 |
135 |
--------------------------------------------------------------------------------
/jdbc-slick/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM postgresql:10
2 |
3 | RUN localedef -i zh_CN -c -f UTF-8 -A /usr/share/locale/locale.alias zh_CN.UTF-8
4 |
5 | ENV TZ Asia/Shanghai
6 | ENV LANG zh_CN.UTF-8
7 |
8 | COPY init.sql /docker-entrypoint-initdb.d/
9 |
10 | EXPOSE 5432
11 |
--------------------------------------------------------------------------------
/jdbc-slick/README.md:
--------------------------------------------------------------------------------
1 | # 在Scala里使用PostgreSQL
2 |
3 | 见:[https://www.yangbajing.me/2018/08/02/%E5%9C%A8scala%E9%87%8C%E4%BD%BF%E7%94%A8postgresql/](https://www.yangbajing.me/2018/08/02/%E5%9C%A8scala%E9%87%8C%E4%BD%BF%E7%94%A8postgresql/)
4 |
--------------------------------------------------------------------------------
/jdbc-slick/build.sbt:
--------------------------------------------------------------------------------
1 | name := "jdbc-slick"
2 |
3 | organization := "me.yangbajing"
4 |
5 | organizationName := "Yangbajing's Garden"
6 |
7 | organizationHomepage := Some(url("https://www.yangbajing.me"))
8 |
9 | homepage := Some(url("https://github.com/yangbajing/scala-applications/tree/master/jdbc-slick"))
10 |
11 | startYear := Some(2018)
12 |
13 | licenses += ("Apache-2.0", new URL("https://www.apache.org/licenses/LICENSE-2.0.txt"))
14 |
15 | scalaVersion := "2.12.6"
16 |
17 | shellPrompt := { s =>
18 | Project.extract(s).currentProject.id + " > "
19 | }
20 |
21 | scalacOptions ++= Seq(
22 | "-encoding", "utf8",
23 | "-unchecked",
24 | "-feature",
25 | "-deprecation"
26 | )
27 |
28 | libraryDependencies ++= Seq(
29 | "com.fasterxml.jackson.datatype" % "jackson-datatype-jdk8" % "2.9.6",
30 | "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.9.6",
31 | "com.fasterxml.jackson.module" % "jackson-module-parameter-names" % "2.9.6",
32 | "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.9.6",
33 | "org.postgresql" % "postgresql" % "42.2.2",
34 | "com.zaxxer" % "HikariCP" % "3.2.0",
35 | "com.typesafe.scala-logging" %% "scala-logging" % "3.9.0",
36 | "com.typesafe.slick" %% "slick" % "3.2.3",
37 | "com.github.tminglei" %% "slick-pg" % "0.16.2",
38 | "com.github.tminglei" %% "slick-pg_json4s" % "0.16.2",
39 | "com.typesafe.akka" %% "akka-stream" % "2.5.14",
40 | "com.typesafe.akka" %% "akka-http" % "10.1.3",
41 | "com.typesafe.akka" %% "akka-http-testkit" % "10.1.3" % Test,
42 | "com.typesafe.slick" %% "slick-testkit" % "3.2.3" % Test,
43 | "org.scalatest" %% "scalatest" % "3.0.5" % Test
44 | )
45 |
--------------------------------------------------------------------------------
/jdbc-slick/init.sql:
--------------------------------------------------------------------------------
1 | set timezone to 'Asia/Chongqing';
2 | create user scala with nosuperuser
3 | replication
4 | encrypted password 'scala.2018';
5 | create database scaladb owner = scala template = template0 encoding = 'UTF-8' lc_ctype = 'zh_CN.UTF-8' lc_collate = 'zh_CN.UTF-8';
6 |
7 | -- create extension
8 | \c scaladb;
9 | create extension adminpack;
10 | create extension hstore;
11 |
12 | -- create tables ....
13 |
14 | create table book (
15 | id bigserial primary key,
16 | isbn varchar(64) not null,
17 | title varchar(128) not null,
18 | author int [] not null,
19 | description text,
20 | created_at timestamptz,
21 | updated_at timestamptz
22 | );
23 | create unique index book_uidx_isbn
24 | on book (isbn);
25 |
26 | create table author (
27 | id serial primary key,
28 | name varchar(128) not null,
29 | age int,
30 | description text,
31 | created_at timestamptz,
32 | updated_at timestamptz
33 | );
34 |
35 | -- change tables, views, sequences owner to scala
36 | DO $$DECLARE r record;
37 | BEGIN
38 | FOR r IN SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'
39 | LOOP
40 | EXECUTE 'alter table ' || r.table_name || ' owner to scala;';
41 | END LOOP;
42 | END$$;
43 |
44 | DO $$DECLARE r record;
45 | BEGIN
46 | FOR r IN select sequence_name from information_schema.sequences where sequence_schema = 'public'
47 | LOOP
48 | EXECUTE 'alter sequence ' || r.sequence_name || ' owner to scala;';
49 | END LOOP;
50 | END$$;
51 |
52 | DO $$DECLARE r record;
53 | BEGIN
54 | FOR r IN select table_name from information_schema.views where table_schema = 'public'
55 | LOOP
56 | EXECUTE 'alter table ' || r.table_name || ' owner to scala;';
57 | END LOOP;
58 | END$$;
59 | -- grant all privileges on all tables in schema public to scala;
60 | -- grant all privileges on all sequences in schema public to scala;
61 |
62 |
--------------------------------------------------------------------------------
/jdbc-slick/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.1.6
2 |
--------------------------------------------------------------------------------
/jdbc-slick/sbt:
--------------------------------------------------------------------------------
1 | SCRIPT_DIR=`dirname $0`
2 | java -Dfile.encoding=UTF-8 -noverify -Xms512M -Xmx1024M -Xss1M -XX:+CMSClassUnloadingEnabled -jar "$SCRIPT_DIR/../_lib/sbt-launch.jar" $@
3 |
--------------------------------------------------------------------------------
/jdbc-slick/src/main/scala/combinerequest/CombineExample.scala:
--------------------------------------------------------------------------------
1 | package combinerequest
2 |
3 | import akka.actor.ActorSystem
4 | import combinerequest.service.{EnterpriseService, InfraMongodbRepo, InfraResource}
5 |
6 | import scala.concurrent.Await
7 | import scala.concurrent.duration._
8 | import scala.util.{Failure, Success}
9 |
10 | /**
11 | * 合并同一时间的3个请求
12 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-06-29.
13 | */
14 | object CombineExample {
15 | val system = ActorSystem()
16 |
17 | import system.dispatcher
18 |
19 | def main(args: Array[String]): Unit = {
20 | val infraResource = new InfraResource()
21 | val infraMongodbRepo = new InfraMongodbRepo()
22 | val enterpriseService = new EnterpriseService(system, infraResource, infraMongodbRepo)
23 |
24 | val company1Future = enterpriseService.getCorpDetail("科技公司")
25 | val company2Future = enterpriseService.getCorpDetail("科技公司")
26 | val company3Future = enterpriseService.getCorpDetail("科技公司")
27 |
28 | val companiesFuture = for {
29 | company1 <- company1Future
30 | company2 <- company2Future
31 | company3 <- company3Future
32 | } yield {
33 | List(company1, company2, company3)
34 | }
35 |
36 | Await.ready(companiesFuture, 60.seconds)
37 | .onComplete {
38 | case Success(c1 :: c2 :: c3 :: Nil) =>
39 | // println(c1 eq c2)
40 | // println(c2 eq c3)
41 | println(s"$c1, $c2, $c3")
42 | case Success(unknown) =>
43 | System.err.println(s"收到未期待的结果:$unknown")
44 |
45 | case Failure(e) =>
46 | e.printStackTrace()
47 | }
48 |
49 | try {
50 | Await.result(system.terminate(), 60.seconds)
51 | } catch {
52 | case e: Throwable =>
53 | e.printStackTrace()
54 | System.exit(3)
55 | }
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/jdbc-slick/src/main/scala/combinerequest/RepetitionExample.scala:
--------------------------------------------------------------------------------
1 | package combinerequest
2 |
3 | import akka.actor.ActorSystem
4 | import combinerequest.domain.Company
5 | import combinerequest.service.{CompanyService, InfraMongodbRepo, InfraResource}
6 |
7 | import scala.concurrent.duration._
8 | import scala.concurrent.{Await, ExecutionContext, Future}
9 | import scala.util.{Failure, Success}
10 |
11 | /**
12 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-07-02.
13 | */
14 | object RepetitionExample {
15 | val system = ActorSystem()
16 |
17 | import system.dispatcher
18 |
19 | def main(args: Array[String]): Unit = {
20 | val infraResource = new InfraResource()
21 | val infraMongodbRepo = new InfraMongodbRepo()
22 | val enterpriseService = new RepetitionService(infraResource, infraMongodbRepo)
23 |
24 | val company1Future = enterpriseService.getCorpDetail("科技公司")
25 | val company2Future = enterpriseService.getCorpDetail("科技公司")
26 | val company3Future = enterpriseService.getCorpDetail("科技公司")
27 |
28 | val companiesFuture = for {
29 | company1 <- company1Future
30 | company2 <- company2Future
31 | company3 <- company3Future
32 | } yield {
33 | List(company1, company2, company3)
34 | }
35 |
36 | Await.ready(companiesFuture, 60.seconds)
37 | .onComplete {
38 | case Success(c1 :: c2 :: c3 :: Nil) =>
39 | // println(c1 eq c2)
40 | // println(c2 eq c3)
41 | println(s"$c1, $c2, $c3")
42 | case Success(unknown) =>
43 | System.err.println(s"收到未期待的结果:$unknown")
44 |
45 | case Failure(e) =>
46 | e.printStackTrace()
47 | }
48 |
49 | try {
50 | Await.result(system.terminate(), 60.seconds)
51 | } catch {
52 | case e: Throwable =>
53 | e.printStackTrace()
54 | System.exit(3)
55 | }
56 | }
57 |
58 | }
59 |
60 | class RepetitionService(val infraResource: InfraResource,
61 | val infraMongodbRepo: InfraMongodbRepo) extends CompanyService {
62 |
63 | override def getCorpDetail(companyName: String)(implicit ec: ExecutionContext): Future[Option[Company]] = {
64 | infraMongodbRepo.findCorpDetail(companyName)
65 | .flatMap {
66 | case Some(company) =>
67 | Future.successful(Some(company))
68 | case None =>
69 | infraResource.corpDetail(companyName)
70 | .flatMap {
71 | case Some(company) =>
72 | infraMongodbRepo
73 | .saveCorpDetail(companyName, company)
74 | .map(_ => Some(company))
75 |
76 | case None =>
77 | Future.successful(None)
78 | }
79 | }
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/jdbc-slick/src/main/scala/combinerequest/domain/Company.scala:
--------------------------------------------------------------------------------
1 | package combinerequest.domain
2 |
3 | /**
4 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-06-30.
5 | */
6 | case class Company(companyName: String)
7 |
--------------------------------------------------------------------------------
/jdbc-slick/src/main/scala/combinerequest/message/Messages.scala:
--------------------------------------------------------------------------------
1 | package combinerequest.message
2 |
3 | import akka.actor.{Actor, ActorRef}
4 | import combinerequest.domain.Company
5 |
6 | /**
7 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-06-29.
8 | */
9 | case class QueryCompany(companyName: String, doSender: ActorRef = Actor.noSender)
10 |
11 | case class ReceiveQueryCompanyResult(companyName: String, corpDetail: Option[Company])
12 |
13 | sealed trait GetCompanyMessage {
14 | val companyName: String
15 | }
16 |
17 | /**
18 | * 获取工商消息
19 | *
20 | * @param companyName 公司名
21 | */
22 | case class GetCorpDetail(companyName: String) extends GetCompanyMessage
23 |
--------------------------------------------------------------------------------
/jdbc-slick/src/main/scala/combinerequest/service/CompanyMaster.scala:
--------------------------------------------------------------------------------
1 | package combinerequest.service
2 |
3 | import akka.actor.{Actor, Props}
4 | import combinerequest.message.{GetCorpDetail, QueryCompany}
5 |
6 | /**
7 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-06-29.
8 | */
9 | class CompanyMaster(infraResource: InfraResource,
10 | infraMongodbRepo: InfraMongodbRepo) extends Actor {
11 |
12 | val actorCorpDetail = context.actorOf(CorpDetailActor.props(infraResource, infraMongodbRepo), "corpDetail")
13 |
14 | override def receive = {
15 | case GetCorpDetail(companyName) =>
16 | actorCorpDetail ! QueryCompany(companyName, sender())
17 | }
18 |
19 | }
20 |
21 | object CompanyMaster {
22 |
23 | def props(infraResource: InfraResource,
24 | infraMongodbRepo: InfraMongodbRepo) =
25 | Props(new CompanyMaster(infraResource, infraMongodbRepo))
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/jdbc-slick/src/main/scala/combinerequest/service/CorpDetailActor.scala:
--------------------------------------------------------------------------------
1 | package combinerequest.service
2 |
3 | import akka.actor.Props
4 | import combinerequest.service.ForwardCompanyActor.{ReadFromDB, ReadFromInfra}
5 |
6 | import scala.concurrent.Future
7 |
8 | /**
9 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-06-29.
10 | */
11 | class CorpDetailActor(infraResource: InfraResource,
12 | infraMongodbRepo: InfraMongodbRepo) extends ForwardCompanyActor {
13 |
14 | import context.dispatcher
15 |
16 | override val readFromDB: ReadFromDB = (companyName) => {
17 | infraMongodbRepo.findCorpDetail(companyName)
18 | }
19 |
20 | override val readFromInfra: ReadFromInfra = (companyName) => {
21 | infraResource.corpDetail(companyName)
22 | .flatMap {
23 | case Some(company) =>
24 | infraMongodbRepo
25 | .saveCorpDetail(companyName, company)
26 | .map(_ => Some(company))
27 |
28 | case None =>
29 | Future.successful(None)
30 | }
31 | }
32 |
33 | }
34 |
35 | object CorpDetailActor {
36 | def props(infraResource: InfraResource,
37 | infraMongodbRepo: InfraMongodbRepo) =
38 | Props(new CorpDetailActor(infraResource, infraMongodbRepo))
39 | }
40 |
--------------------------------------------------------------------------------
/jdbc-slick/src/main/scala/combinerequest/service/EnterpriseService.scala:
--------------------------------------------------------------------------------
1 | package combinerequest.service
2 |
3 | import akka.actor.ActorSystem
4 | import akka.pattern.ask
5 | import akka.util.Timeout
6 | import combinerequest.domain.Company
7 | import combinerequest.message.{GetCompanyMessage, GetCorpDetail}
8 |
9 | import scala.concurrent.{ExecutionContext, Future}
10 | import scala.concurrent.duration._
11 |
12 | /**
13 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-06-29.
14 | */
15 | trait CompanyService {
16 | val infraResource: InfraResource
17 | val infraMongodbRepo: InfraMongodbRepo
18 |
19 | def getCorpDetail(companyName: String)(implicit ec: ExecutionContext): Future[Option[Company]]
20 | }
21 |
22 | class EnterpriseService(actorSystem: ActorSystem,
23 | val infraResource: InfraResource,
24 | val infraMongodbRepo: InfraMongodbRepo) extends CompanyService {
25 |
26 | private val master = actorSystem.actorOf(CompanyMaster.props(infraResource, infraMongodbRepo), "infra-company")
27 | private implicit val timeout = Timeout(60.seconds)
28 |
29 | @inline
30 | private def getResult(message: GetCompanyMessage)(implicit ec: ExecutionContext) =
31 | master.ask(message).mapTo[Option[Company]]
32 |
33 | def getCorpDetail(companyName: String)(implicit ec: ExecutionContext) = getResult(GetCorpDetail(companyName))
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/jdbc-slick/src/main/scala/combinerequest/service/ForwardCompanyActor.scala:
--------------------------------------------------------------------------------
1 | package combinerequest.service
2 |
3 | import akka.actor.{Actor, ActorRef}
4 | import combinerequest.domain.Company
5 | import combinerequest.message.{QueryCompany, ReceiveQueryCompanyResult}
6 |
7 | import scala.collection.mutable
8 | import scala.concurrent.Future
9 |
10 | /**
11 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-06-29.
12 | */
13 | trait ForwardCompanyActor extends Actor {
14 |
15 | import ForwardCompanyActor._
16 |
17 | val companyListeners = mutable.Map.empty[String, Set[ActorRef]]
18 |
19 | override def receive = {
20 | case QueryCompany(companyName, doSender) =>
21 | val listener = if (doSender == Actor.noSender) sender() else doSender
22 | registerListener(companyName, listener)
23 |
24 | case ReceiveQueryCompanyResult(companyName, maybeJsValue) =>
25 | dispatchListeners(companyName, maybeJsValue)
26 | }
27 |
28 | def performReadTask(companyName: String): Unit = {
29 | import context.dispatcher
30 | readFromDB(companyName)
31 | .flatMap(maybe => if (maybe.isEmpty) readFromInfra(companyName) else Future.successful(maybe))
32 | .foreach(maybe => self ! ReceiveQueryCompanyResult(companyName, maybe))
33 | }
34 |
35 | def registerListener(companyName: String, listener: ActorRef): Unit =
36 | companyListeners.get(companyName) match {
37 | case Some(actors) =>
38 | companyListeners.put(companyName, actors + listener)
39 | case None =>
40 | companyListeners.put(companyName, Set(listener))
41 | performReadTask(companyName)
42 | }
43 |
44 | def dispatchListeners(companyName: String, maybeJsValue: Option[Company]): Unit = {
45 | val maybeListener = companyListeners.get(companyName)
46 | maybeListener.foreach { listeners =>
47 | for (listener <- listeners) {
48 | listener ! maybeJsValue
49 | }
50 | companyListeners -= companyName
51 | }
52 | }
53 |
54 | val readFromInfra: ReadFromInfra
55 |
56 | val readFromDB: ReadFromDB
57 | }
58 |
59 | object ForwardCompanyActor {
60 |
61 | type ReadFromDB = (String) => Future[Option[Company]]
62 |
63 | type ReadFromInfra = (String) => Future[Option[Company]]
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/jdbc-slick/src/main/scala/combinerequest/service/InfraMongodbRepo.scala:
--------------------------------------------------------------------------------
1 | package combinerequest.service
2 |
3 | import java.time.LocalDateTime
4 | import java.util.concurrent.{ConcurrentHashMap, TimeUnit}
5 |
6 | import combinerequest.domain.Company
7 |
8 | import scala.concurrent.{ExecutionContext, Future}
9 |
10 | /**
11 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-06-29.
12 | */
13 | class InfraMongodbRepo {
14 |
15 | def saveCorpDetail(companyName: String, company: Company)(implicit ec: ExecutionContext): Future[Option[Company]] =
16 | Future {
17 | TimeUnit.MICROSECONDS.sleep(200)
18 | println(s"[${LocalDateTime.now()}] 保存公司: $company 成功")
19 | InfraCompanyDBMock.saveCompany(companyName, company)
20 | }
21 |
22 | def findCorpDetail(companyName: String)(implicit ec: ExecutionContext): Future[Option[Company]] = Future {
23 | TimeUnit.MILLISECONDS.sleep(100)
24 | InfraCompanyDBMock.getCompany(companyName) match {
25 | case some@Some(_) =>
26 | println(s"[${LocalDateTime.now()}] 从本地数据库找到公司:$companyName")
27 | some
28 | case None =>
29 | println(s"[${LocalDateTime.now()}] 本地数据库未找到公司:$companyName")
30 | None
31 | }
32 | }
33 |
34 | }
35 |
36 | object InfraCompanyDBMock {
37 | private val companies = new ConcurrentHashMap[String, Company]()
38 |
39 | def getCompany(companyName: String) = Option(companies.get(companyName))
40 |
41 | def saveCompany(companyName: String, company: Company) = Option(companies.put(companyName, company))
42 | }
43 |
--------------------------------------------------------------------------------
/jdbc-slick/src/main/scala/combinerequest/service/InfraResource.scala:
--------------------------------------------------------------------------------
1 | package combinerequest.service
2 |
3 | import java.time.LocalDateTime
4 | import java.util.concurrent.TimeUnit
5 |
6 | import combinerequest.domain.Company
7 |
8 | import scala.concurrent.{ExecutionContext, Future}
9 |
10 | /**
11 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-06-29.
12 | */
13 | class InfraResource {
14 |
15 | def corpDetail(companyName: String)(implicit ec: ExecutionContext): Future[Option[Company]] = Future {
16 | TimeUnit.SECONDS.sleep(1)
17 | println(s"[${LocalDateTime.now()}] 收到查询:$companyName 工商信息付费请求")
18 | Some(Company(companyName))
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/jdbc-slick/src/main/scala/sample/model/Org.scala:
--------------------------------------------------------------------------------
1 | package sample.model
2 |
3 | import java.time.OffsetDateTime
4 |
5 | case class Org(
6 | id: Int,
7 | code: Option[String],
8 | name: String,
9 | contact: String,
10 | parent: Option[Int],
11 | parents: List[Int],
12 | status: Int = 1,
13 | createdAt: OffsetDateTime = OffsetDateTime.now(),
14 | updatedAt: Option[OffsetDateTime] = None
15 | )
16 |
17 | case class OrgCreateReq(
18 | code: Option[String],
19 | name: String,
20 | contact: Option[String]
21 | )
22 |
23 | case class OrgUpdateReq(
24 | code: Option[String],
25 | name: Option[String],
26 | contact: Option[String],
27 | status: Option[Int]
28 | )
29 |
30 | case class OrgPageReq(
31 | code: Option[String],
32 | name: Option[String],
33 | status: Option[Int]
34 | )
35 |
36 | case class OrgPageResp(
37 | content: Seq[Org],
38 | totalElements: Long,
39 | page: Int,
40 | size: Int
41 | )
42 |
--------------------------------------------------------------------------------
/jdbc-slick/src/main/scala/sample/respository/OrgRepository.scala:
--------------------------------------------------------------------------------
1 | package sample.respository
2 |
3 | import scala.concurrent.Future
4 |
5 | class OrgRepository(schema: Schema) {
6 |
7 | import schema.profile.api._
8 | import schema._
9 |
10 | def removeByIds(orgIds: Iterable[Int]): Future[Array[Int]] = {
11 | ???
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/jdbc-slick/src/main/scala/sample/respository/Schema.scala:
--------------------------------------------------------------------------------
1 | package sample.respository
2 |
3 | import java.time.OffsetDateTime
4 |
5 | import SlickProfile.api._
6 | import sample.model.Org
7 |
8 | object Schema {
9 | def apply() = new Schema()
10 | }
11 |
12 | class Schema {
13 | val profile = SlickProfile
14 |
15 | val db = Database.forConfig("sample.datasource")
16 |
17 | def tOrg = TableQuery[TableOrg]
18 | }
19 |
20 | class TableOrg(tag: Tag) extends Table[Org](tag, "t_org") {
21 | def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
22 |
23 | def code = column[Option[String]]("code")
24 |
25 | def name = column[String]("name")
26 |
27 | def contact = column[String]("contact", O.SqlType("text"))
28 |
29 | def status = column[Int]("status", O.Default(1))
30 |
31 | def createdAt = column[OffsetDateTime]("created_at")
32 |
33 | def updatedAt = column[Option[OffsetDateTime]]("update_at")
34 |
35 | def * = (id, code, name, contact, status, createdAt, updatedAt).mapTo[Org]
36 | }
37 |
--------------------------------------------------------------------------------
/jdbc-slick/src/main/scala/sample/respository/SlickProfile.scala:
--------------------------------------------------------------------------------
1 | package sample.respository
2 |
3 | import com.github.tminglei.slickpg.{ExPostgresProfile, PgDate2Support}
4 |
5 | trait SlickProfile extends ExPostgresProfile with PgDate2Support {
6 | override val api: API = new super.API with DateTimeImplicits {}
7 | }
8 |
9 | object SlickProfile extends SlickProfile
10 |
--------------------------------------------------------------------------------
/jdbc-slick/src/main/scala/sample/route/OrgRoute.scala:
--------------------------------------------------------------------------------
1 | package sample.route
2 | import akka.http.scaladsl.server.Directives._
3 | import sample.model.OrgCreateReq
4 | import sample.service.OrgService
5 |
6 | class OrgRoute(orgService: OrgService) {
7 |
8 | def route= pathPrefix("route") {
9 | createRoute
10 | }
11 |
12 | def createRoute = (path("item") & post) {
13 | import sample.util.JacksonSupport._
14 | entity(as[OrgCreateReq]) { req =>
15 | onSuccess(orgService.create(req)) { resp =>
16 | complete(resp)
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/jdbc-slick/src/main/scala/sample/service/OrgService.scala:
--------------------------------------------------------------------------------
1 | package sample.service
2 |
3 | import sample.model.{Org, OrgCreateReq}
4 | import sample.respository.{OrgRepository, Schema}
5 |
6 | import scala.concurrent.Future
7 |
8 | class OrgService(schema: Schema) {
9 |
10 | private val orgRepository= new OrgRepository(schema)
11 |
12 | def create(req: OrgCreateReq): Future[Org] = {
13 | ???
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/jdbc-slick/src/main/scala/sample/util/Jackson.java:
--------------------------------------------------------------------------------
1 | package sample.util;
2 |
3 | import com.fasterxml.jackson.annotation.JsonInclude;
4 | import com.fasterxml.jackson.core.JsonParser;
5 | import com.fasterxml.jackson.databind.DeserializationFeature;
6 | import com.fasterxml.jackson.databind.ObjectMapper;
7 | import com.fasterxml.jackson.databind.SerializationFeature;
8 | import com.fasterxml.jackson.databind.node.ArrayNode;
9 | import com.fasterxml.jackson.databind.node.ObjectNode;
10 |
11 | import java.text.SimpleDateFormat;
12 |
13 | /**
14 | * Jackson全局配置
15 | * Created by yangbajing(yangbajing@gmail.com) on 2017-03-14.
16 | */
17 | public class Jackson {
18 | public static final ObjectMapper defaultObjectMapper = getObjectMapper();
19 |
20 | public static ObjectNode createObjectNode() {
21 | return defaultObjectMapper.createObjectNode();
22 | }
23 |
24 | public static ArrayNode createArrayNode() {
25 | return defaultObjectMapper.createArrayNode();
26 | }
27 |
28 | private static ObjectMapper getObjectMapper() {
29 | return new ObjectMapper()
30 | .findAndRegisterModules()
31 | .setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
32 | // .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY)
33 | .enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES)
34 | .enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES)
35 | .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
36 | .enable(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS)
37 | .disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES)
38 | .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
39 | .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
40 | .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
41 | .disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS)
42 | .setSerializationInclusion(JsonInclude.Include.NON_NULL);
43 | }
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/jdbc-slick/src/main/scala/sample/util/JacksonHelper.java:
--------------------------------------------------------------------------------
1 | package sample.util;
2 |
3 | import akka.Done;
4 | import akka.http.javadsl.marshalling.Marshaller;
5 | import akka.http.javadsl.model.HttpEntity;
6 | import akka.http.javadsl.model.MediaTypes;
7 | import akka.http.javadsl.model.RequestEntity;
8 | import akka.http.javadsl.unmarshalling.Unmarshaller;
9 | import akka.util.ByteString;
10 | import com.fasterxml.jackson.core.JsonProcessingException;
11 | import com.fasterxml.jackson.databind.ObjectMapper;
12 |
13 | import java.io.IOException;
14 | import java.util.HashMap;
15 | import java.util.Map;
16 |
17 | public class JacksonHelper {
18 | public static final Map empty = new HashMap<>();
19 |
20 | public static Marshaller marshaller() {
21 | return marshaller(Jackson.defaultObjectMapper);
22 | }
23 |
24 | public static Marshaller marshaller(ObjectMapper mapper) {
25 | return Marshaller.wrapEntity(
26 | u -> toJSON(mapper, u),
27 | Marshaller.stringToEntity(),
28 | MediaTypes.APPLICATION_JSON
29 | );
30 | }
31 |
32 | public static Unmarshaller byteStringUnmarshaller(Class expectedType) {
33 | return byteStringUnmarshaller(Jackson.defaultObjectMapper, expectedType);
34 | }
35 |
36 | public static Unmarshaller unmarshaller(Class expectedType) {
37 | return unmarshaller(Jackson.defaultObjectMapper, expectedType);
38 | }
39 |
40 | public static Unmarshaller unmarshaller(ObjectMapper mapper, Class expectedType) {
41 | return Unmarshaller.forMediaType(MediaTypes.APPLICATION_JSON, Unmarshaller.entityToString())
42 | .thenApply(s -> fromJSON(mapper, s, expectedType));
43 | }
44 |
45 | public static Unmarshaller byteStringUnmarshaller(ObjectMapper mapper, Class expectedType) {
46 | return Unmarshaller.sync(s -> fromJSON(mapper, s.utf8String(), expectedType));
47 | }
48 |
49 | private static String toJSON(ObjectMapper mapper, Object object) {
50 | try {
51 | if (object instanceof Done)
52 | return mapper.writeValueAsString(empty);
53 |
54 | return mapper.writeValueAsString(object);
55 | } catch (JsonProcessingException e) {
56 | throw new IllegalArgumentException("Cannot marshal to JSON: " + object, e);
57 | }
58 | }
59 |
60 | private static T fromJSON(ObjectMapper mapper, String json, Class expectedType) {
61 | try {
62 | return mapper.readerFor(expectedType).readValue(json);
63 | } catch (IOException e) {
64 | throw new IllegalArgumentException("Cannot unmarshal JSON as " + expectedType.getSimpleName(), e);
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/jdbc-slick/src/main/scala/sample/util/JacksonSupport.scala:
--------------------------------------------------------------------------------
1 | package sample.util
2 |
3 | import akka.http.scaladsl.marshalling.ToEntityMarshaller
4 | import akka.http.scaladsl.model.MediaTypes
5 | import akka.http.scaladsl.unmarshalling.{FromEntityUnmarshaller, Unmarshaller}
6 | import akka.util.ByteString
7 | import com.fasterxml.jackson.core.util.DefaultPrettyPrinter
8 | import com.fasterxml.jackson.databind.{JsonNode, ObjectMapper}
9 |
10 | import scala.reflect.ClassTag
11 |
12 | object JacksonSupport extends JacksonSupport {
13 | def stringify(value: AnyRef): String = {
14 | Jackson.defaultObjectMapper.writeValueAsString(value)
15 | }
16 |
17 | def prettyString(value: AnyRef): String = {
18 | val writer = Jackson.defaultObjectMapper.writer(new DefaultPrettyPrinter())
19 | writer.writeValueAsString(value)
20 | }
21 |
22 | def readValue[A](content: String)(implicit
23 | ct: ClassTag[A],
24 | objectMapper: ObjectMapper =
25 | Jackson.defaultObjectMapper): A = {
26 | objectMapper.readValue(content, ct.runtimeClass).asInstanceOf[A]
27 | }
28 |
29 | def getJsonNode(content: String)(implicit objectMapper: ObjectMapper =
30 | Jackson.defaultObjectMapper): JsonNode = {
31 | objectMapper.readTree(content)
32 | }
33 |
34 | def isError(content: String)(implicit objectMapper: ObjectMapper =
35 | Jackson.defaultObjectMapper): Boolean = {
36 | !isSuccess(content)
37 | }
38 |
39 | def isSuccess(content: String)(implicit objectMapper: ObjectMapper =
40 | Jackson.defaultObjectMapper): Boolean = {
41 | val node = getJsonNode(content)
42 | val errCode = node.get("errCode")
43 | if (errCode eq null) true else errCode.asInt(0) == 0
44 | }
45 | }
46 |
47 | /**
48 | * JSON marshalling/unmarshalling using an in-scope Jackson's ObjectMapper
49 | */
50 | trait JacksonSupport {
51 |
52 | private val jsonStringUnmarshaller =
53 | Unmarshaller.byteStringUnmarshaller
54 | .forContentTypes(MediaTypes.`application/json`)
55 | .mapWithCharset {
56 | case (ByteString.empty, _) => throw Unmarshaller.NoContentException
57 | case (data, charset) => data.decodeString(charset.nioCharset.name)
58 | }
59 |
60 | // private val jsonStringMarshaller = Marshaller.stringMarshaller(MediaTypes.`application/json`)
61 |
62 | /**
63 | * HTTP entity => `A`
64 | */
65 | implicit def unmarshaller[A](implicit
66 | ct: ClassTag[A],
67 | objectMapper: ObjectMapper =
68 | Jackson.defaultObjectMapper)
69 | : FromEntityUnmarshaller[A] =
70 | jsonStringUnmarshaller.map { data =>
71 | objectMapper.readValue(data, ct.runtimeClass).asInstanceOf[A]
72 | }
73 |
74 | /**
75 | * `A` => HTTP entity
76 | */
77 | implicit def marshaller[A](
78 | implicit objectMapper: ObjectMapper = Jackson.defaultObjectMapper)
79 | : ToEntityMarshaller[A] = {
80 | // jsonStringMarshaller.compose(objectMapper.writeValueAsString)
81 | JacksonHelper.marshaller[A](objectMapper)
82 | }
83 |
84 | }
--------------------------------------------------------------------------------
/jdbc-slick/src/test/scala/sample/route/OrgRouteTest.scala:
--------------------------------------------------------------------------------
1 | package sample.route
2 |
3 | import akka.http.scaladsl.model.StatusCodes
4 | import akka.http.scaladsl.testkit.ScalatestRouteTest
5 | import org.scalatest.concurrent.ScalaFutures
6 | import org.scalatest.{BeforeAndAfterAll, MustMatchers, OptionValues, WordSpec}
7 | import sample.model.{Org, OrgCreateReq}
8 | import akka.http.scaladsl.server.Directives._
9 | import sample.respository.OrgRepository
10 | import sample.service.OrgService
11 |
12 | class OrgRouteTest extends WordSpec with BeforeAndAfterAll with ScalatestRouteTest with MustMatchers with OptionValues with ScalaFutures {
13 |
14 | private var orgIds: Set[Int] = Set()
15 | private val schema = Schema()
16 | private val orgService = new OrgService()
17 | private val route = new OrgRoute(orgService).route
18 |
19 | "OrgRoute" should {
20 | import sample.util.JacksonSupport._
21 |
22 | var org: Org = null
23 |
24 | "create" in {
25 | val req = OrgCreateReq(Some("000001"), "测试组织", None)
26 | Post("/org/item", req) ~> route ~> check {
27 | status mustBe StatusCodes.Created
28 | org = responseAs[Org]
29 | orgIds += org.id
30 | org.id must be > 0
31 | org.parent mustBe None
32 | org.updatedAt mustBe None
33 | }
34 | }
35 |
36 | "get" in {
37 | pending
38 | }
39 |
40 | "pageRoute" in {
41 | pending
42 | }
43 |
44 | "updateRoute" in {
45 | pending
46 | }
47 |
48 | "remoteRoute" in {
49 | pending
50 | }
51 | }
52 |
53 | private def cleanup() {
54 | val orgRepository = new OrgRepository()
55 | orgRepository.removeByIds(orgIds).futureValue
56 | }
57 |
58 | override def afterAll() {
59 | cleanup()
60 | super.afterAll()
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/scala-js/build.sbt:
--------------------------------------------------------------------------------
1 | enablePlugins(ScalaJSPlugin)
2 |
3 | name := "Scala.js Tutorial"
4 |
5 | scalaVersion := "2.11.8"
6 |
7 | scalaJSUseRhino in Global := false
8 |
9 | libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "0.9.1"
10 |
11 | libraryDependencies += "be.doeraene" %%% "scalajs-jquery" % "0.9.0"
12 |
13 | libraryDependencies += "com.lihaoyi" %%% "utest" % "0.3.0" % "test"
14 |
15 | libraryDependencies += "com.thoughtworks.binding" %%% "dom" % "latest.release"
16 |
17 | addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)
18 |
19 | skip in packageJSDependencies := false
20 |
21 | jsDependencies +=
22 | "org.webjars" % "jquery" % "2.1.4" / "2.1.4/jquery.js"
23 |
24 | jsDependencies += RuntimeDOM
25 |
26 |
27 | testFrameworks += new TestFramework("utest.runner.Framework")
28 |
29 | scalaJSStage in Global := FullOptStage
30 |
31 | persistLauncher in Compile := true
32 |
33 | persistLauncher in Test := false
34 |
--------------------------------------------------------------------------------
/scala-js/index-dev.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | The Scala.js Tutorial
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
--------------------------------------------------------------------------------
/scala-js/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=0.13.12
2 |
--------------------------------------------------------------------------------
/scala-js/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.11")
2 |
--------------------------------------------------------------------------------
/scala-js/scalajs-tutorial.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | The Scala.js Tutorial
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/scala-js/src/main/scala/tutorial/webapp/TutorialApp.scala:
--------------------------------------------------------------------------------
1 | package tutorial.webapp
2 |
3 | import org.scalajs.dom
4 | import org.scalajs.dom.document
5 | import org.scalajs.jquery.jQuery
6 |
7 | import scala.scalajs.js.JSApp
8 | import scala.scalajs.js.annotation.JSExport
9 |
10 | object TutorialApp extends JSApp {
11 |
12 | def appendPar(targetNode: dom.Node, text: String): Unit = {
13 | val parNode = document.createElement("p")
14 | val textNode = document.createTextNode(text)
15 | parNode.appendChild(textNode)
16 | targetNode.appendChild(parNode)
17 | }
18 |
19 | def setupUI(): Unit = {
20 | // jQuery("#click-me-button").click(addClickedMessage _)
21 | jQuery("body").append("您好, 世界!
")
22 | jQuery("""""")
23 | .click(addClickedMessage _)
24 | .appendTo(jQuery("body"))
25 | }
26 |
27 | @JSExport
28 | def addClickedMessage(): Unit = {
29 | appendPar(document.body, "You clicked the button!")
30 | }
31 |
32 | def main() {
33 | // appendPar(document.body, "Hello world")
34 | // jQuery("body").append("[message]
")
35 | jQuery(setupUI _)
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/scala-js/src/test/scala/tutorial/webapp/TutorialAppTest.scala:
--------------------------------------------------------------------------------
1 | package tutorial.webapp
2 |
3 | import org.scalajs.jquery.jQuery
4 | import utest._
5 |
6 | /**
7 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-08-09.
8 | */
9 | object TutorialAppTest extends TestSuite {
10 |
11 | TutorialApp.setupUI()
12 |
13 | override def tests = TestSuite {
14 | 'HelloWorld {
15 | assert(jQuery("p:contains('您好, 世界!')").length >= 1)
16 | }
17 |
18 | 'ButtonClick {
19 | def messageCount = jQuery("p:contains('You clicked the button!')").length
20 |
21 | val button = jQuery("button:contains('Click me!')")
22 |
23 | assert(button.length == 1)
24 | assert(messageCount == 0)
25 |
26 | for (c <- 1 to 5) {
27 | button.click()
28 | assert(messageCount == c)
29 | }
30 | }
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/scala-script/build.sbt:
--------------------------------------------------------------------------------
1 | scalaVersion := "2.11.8"
2 |
3 | scalacOptions ++= Seq(
4 | "-encoding", "utf8",
5 | "-unchecked",
6 | "-feature",
7 | "-deprecation"
8 | )
9 |
10 | libraryDependencies += "com.typesafe.play" %% "play-ws" % "2.5.4"
11 |
--------------------------------------------------------------------------------
/scala-script/sbt:
--------------------------------------------------------------------------------
1 | SCRIPT_DIR=`dirname $0`
2 | java -Dfile.encoding=UTF-8 -noverify -Xms512M -Xmx1024M -Xss1M -XX:+CMSClassUnloadingEnabled -jar "$SCRIPT_DIR/../_lib/sbt-launch.jar" $@
3 |
--------------------------------------------------------------------------------
/scala-script/src/main/scala/Demo.scala:
--------------------------------------------------------------------------------
1 | import java.nio.file._
2 |
3 | import akka.actor.ActorSystem
4 | import akka.stream.ActorMaterializer
5 | import play.api.libs.ws.ahc.AhcWSClient
6 |
7 | import scala.concurrent.Await
8 | import scala.concurrent.duration._
9 |
10 | object Demo extends App {
11 | implicit val system = ActorSystem()
12 | implicit val mat = ActorMaterializer()
13 | val client = AhcWSClient()
14 |
15 | val paramsList =
16 | """曹某某 4310231982xxxxxx38 158xxxx7233 6217xxxxxxxxxx95922
17 | |陈某 4503041989xxxxxx14 186xxxx1221 6229xxxxxxxxx79419
18 | |雷某某 6121021979xxxxxx19 186xxxxx758 6217xxxxxxxxxx75215
19 | | 季某某 3101151983xxxxxx39 139xxxx4665 6226xxxxxxx63987
20 | |何某某 4101811989xxxxxx14 131xxxx2515 6127xxxxxxxxxx01601
21 | |熊某某 5125281981xxxxxx70 188xxxx7391 6230xxxxxxxxxx61491
22 | | 罗某某 4305811990xxxxxx94 158xxxx6035 6212xxxxxxxxxx20908
23 | |吴某某 5002371986xxxxxx68 188xxxx8725 6228xxxxxxxxxx16476
24 | |罗某 4409811991xxxxxx11 135xxxx3967 6212xxxxxxxxxx62918
25 | |许某 3607351997xxxxxx37 181xxxx7357 6228xxxxxxxxxx41278""".stripMargin
26 | .split("\n")
27 | .map { line =>
28 | //("external", "true") :: (List("personName", "idCard", "tel", "bankCard") zip line.trim.split("""[ ]+"""))
29 | Seq("personName", "idCard", "tel", "bankCard") zip line.trim.split("""[ ]+""")
30 | }
31 | //paramsList.foreach(println)
32 |
33 | def send(params: Seq[(String, String)]) = {
34 | val future = client
35 | .url("http://localhost:8080/api/integration/person/report")
36 | .withQueryString(params: _*)
37 | .get()
38 | val resp = Await.result(future, 10.seconds)
39 | resp.body
40 | }
41 |
42 | val results = paramsList.map(params => send(params))
43 | Files.write(Paths.get("demo.txt"), results.mkString("\n").getBytes("UTF-8"))
44 |
45 | client.close()
46 | Await.result(system.terminate(), 10.seconds)
47 | }
48 |
49 |
--------------------------------------------------------------------------------
/scala-script/src/main/scala/ScalaExample.scala:
--------------------------------------------------------------------------------
1 | import scala.annotation.tailrec
2 |
3 | /**
4 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-05-30.
5 | */
6 | object ScalaExample {
7 |
8 | @tailrec
9 | def factorial(n: Int, acc: Long): Long = {
10 | if (n <= 0) acc
11 | else factorial(n - 1, n * acc)
12 | }
13 |
14 | def main(args: Array[String]): Unit = {
15 | factorial(20, 1L)
16 | var i = 0
17 | var result = 0L
18 | val begin = System.nanoTime()
19 | while (i < 10) {
20 | result = factorial(20, 1L)
21 | i += 1
22 | }
23 | val end = System.nanoTime()
24 | println(s"result: $result, cost time: ${end - begin} ns")
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/scalatest/README.md:
--------------------------------------------------------------------------------
1 | # ScalaTest 示例
2 |
3 | 见:[https://www.yangbajing.me/2018/08/02/测试:使用scalatest/](https://www.yangbajing.me/2018/08/02/测试:使用scalatest/)
4 |
--------------------------------------------------------------------------------
/scalatest/build.sbt:
--------------------------------------------------------------------------------
1 | name := "scalatest"
2 |
3 | organization := "me.yangbajing"
4 |
5 | organizationName := "Yangbajing's Garden"
6 |
7 | organizationHomepage := Some(url("https://www.yangbajing.me"))
8 |
9 | homepage := Some(url("https://github.com/yangbajing/scala-applications/tree/master/scalatest"))
10 |
11 | startYear := Some(2018)
12 |
13 | licenses += ("Apache-2.0", new URL("https://www.apache.org/licenses/LICENSE-2.0.txt"))
14 |
15 | scalaVersion := "2.12.6"
16 |
17 | shellPrompt := { s =>
18 | Project.extract(s).currentProject.id + " > "
19 | }
20 |
21 | scalacOptions ++= Seq(
22 | "-encoding", "utf8",
23 | "-unchecked",
24 | "-feature",
25 | "-deprecation"
26 | )
27 |
28 | libraryDependencies ++= Seq(
29 | "org.scalamock" %% "scalamock" % "4.1.0" % Test,
30 | "org.scalatest" %% "scalatest" % "3.0.5" % Test
31 | )
32 |
33 |
--------------------------------------------------------------------------------
/scalatest/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.1.6
2 |
--------------------------------------------------------------------------------
/scalatest/sbt:
--------------------------------------------------------------------------------
1 | SCRIPT_DIR=`dirname $0`
2 | java -Dfile.encoding=UTF-8 -noverify -Xms512M -Xmx1024M -Xss1M -XX:+CMSClassUnloadingEnabled -jar "$SCRIPT_DIR/../_lib/sbt-launch.jar" $@
3 |
--------------------------------------------------------------------------------
/scalatest/src/test/scala/first/FirstTest.scala:
--------------------------------------------------------------------------------
1 | package first
2 |
3 | import org.scalamock.scalatest.MockFactory
4 |
5 | import scala.collection.mutable
6 | import org.scalatest._
7 | import org.scalatest.concurrent.ScalaFutures
8 | import org.scalatest.time.{Millis, Seconds, Span}
9 |
10 | import scala.concurrent.Future
11 |
12 | class FirstTest extends WordSpec with MustMatchers with OptionValues with ScalaFutures with MockFactory {
13 |
14 | override implicit def patienceConfig = PatienceConfig(Span(60, Seconds), Span(50, Millis))
15 |
16 | // "A Stack" should {
17 | // "pop values in last-in-first-out order" in {
18 | // val stack = mutable.Stack[Int]()
19 | //
20 | // stack.push(1)
21 | // stack.push(2)
22 | // stack.pop() mustBe 2
23 | // stack.pop() mustBe 1
24 | // }
25 | //
26 | // "throw NoSuchElementException if an empty stack is popped" in {
27 | // val emptyStack = mutable.Stack[Int]()
28 | // assertThrows[NoSuchElementException] {
29 | // emptyStack.pop()
30 | // }
31 | // }
32 | //
33 | // }
34 |
35 | // "option" should {
36 | // "value" in {
37 | // val v: Option[Int] = None
38 | // v.value mustBe 2
39 | //// v.get mustBe 1
40 | // }
41 | //
42 | // "other" in {
43 | // 3 mustBe 3
44 | // }
45 | // }
46 |
47 | // "future" should {
48 | // "await result === 3" in {
49 | // import scala.concurrent.ExecutionContext.Implicits.global
50 | // val f = Future{
51 | // Thread.sleep(1000)
52 | // 3
53 | // }
54 | // val result = f.futureValue
55 | // result mustBe 3
56 | // }
57 | // }
58 |
59 | "scalamock" should {
60 | "function mock" in {
61 | val m = mockFunction[Int, String]
62 | m expects 42 returning "Forty two"
63 | m(42) mustBe "Forty two"
64 | }
65 | }
66 |
67 | }
--------------------------------------------------------------------------------
/spark-startup/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### SBT template
3 | # Simple Build Tool
4 | # http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control
5 |
6 | target/
7 | lib_managed/
8 | src_managed/
9 | project/boot/
10 | .history
11 | .cache
12 | ### Scala template
13 | *.class
14 | *.log
15 |
16 | # sbt specific
17 | .lib/
18 | dist/*
19 | project/plugins/project/
20 |
21 | # Scala-IDE specific
22 | .scala_dependencies
23 | .worksheet
24 | ### JetBrains template
25 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
26 |
27 | *.iml
28 |
29 | ## Directory-based project format:
30 | .idea/
31 | # if you remove the above rule, at least ignore the following:
32 |
33 | # User-specific stuff:
34 | # .idea/workspace.xml
35 | # .idea/tasks.xml
36 | # .idea/dictionaries
37 |
38 | # Sensitive or high-churn files:
39 | # .idea/dataSources.ids
40 | # .idea/dataSources.xml
41 | # .idea/sqlDataSources.xml
42 | # .idea/dynamic.xml
43 | # .idea/uiDesigner.xml
44 |
45 | # Gradle:
46 | # .idea/gradle.xml
47 | # .idea/libraries
48 |
49 | # Mongo Explorer plugin:
50 | # .idea/mongoSettings.xml
51 |
52 | ## File-based project format:
53 | *.ipr
54 | *.iws
55 |
56 | ## Plugin-specific files:
57 |
58 | # IntelliJ
59 | /out/
60 |
61 | # mpeltonen/sbt-idea plugin
62 | .idea_modules/
63 |
64 | # JIRA plugin
65 | atlassian-ide-plugin.xml
66 |
67 | # Crashlytics plugin (for Android Studio and IntelliJ)
68 | com_crashlytics_export_strings.xml
69 | crashlytics.properties
70 | crashlytics-build.properties
71 |
72 |
--------------------------------------------------------------------------------
/spark-startup/build.sbt:
--------------------------------------------------------------------------------
1 | scalaVersion := "2.11.12"
2 |
3 | scalacOptions ++= Seq(
4 | "-encoding", "utf8",
5 | "-unchecked",
6 | "-feature",
7 | "-deprecation"
8 | )
9 |
10 | assemblyJarName in assembly := "spark-startup.jar"
11 |
12 | assemblyOption in assembly := (assemblyOption in assembly).value.copy(includeScala = false)
13 |
14 | test in assembly := {}
15 |
16 |
17 | val verSpark = "2.3.1"
18 | val verHadoop = "2.7.5"
19 |
20 | libraryDependencies ++= Seq(
21 | "org.apache.spark" %% "spark-core" % "1.5.2" % "provided,test",
22 | "org.apache.spark" %% "spark-sql" % "1.5.2" % "provided,test",
23 | "org.scalatest" %% "scalatest" % "3.0.5" % "test"
24 | )
25 |
26 |
--------------------------------------------------------------------------------
/spark-startup/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=0.13.11
2 |
--------------------------------------------------------------------------------
/spark-startup/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.2")
2 |
--------------------------------------------------------------------------------
/spark-startup/sbt:
--------------------------------------------------------------------------------
1 | SCRIPT_DIR=`dirname $0`
2 | java -Dfile.encoding=UTF-8 -noverify -Xms512M -Xmx1024M -Xss1M -XX:+CMSClassUnloadingEnabled -jar "$SCRIPT_DIR/../_lib/sbt-launch.jar" $@
3 |
--------------------------------------------------------------------------------
/spark-startup/scripts/SparkApp.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | $SPARK_HOME/bin/spark-submit \
4 | --class sample.SparkApp \
5 | --master spark://sc-data-server-1:7077 \
6 | --name "sample.SparkApp" \
7 | --executor-memory 10G \
8 | --driver-memory 2G \
9 | --total-executor-cores 4 \
10 | --conf spark.serializer=org.apache.spark.serializer.KryoSerializer \
11 | ../target/scala-2.11/spark-startup.jar &
12 |
13 |
--------------------------------------------------------------------------------
/spark-startup/src/main/scala/example/SparkApp.scala:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import org.apache.spark.sql.SQLContext
4 | import org.apache.spark.{SparkConf, SparkContext}
5 |
6 | /**
7 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-03-12.
8 | */
9 | class SparkApp(sc: SparkContext) extends Serializable {
10 | val sqlContext = new SQLContext(sc)
11 |
12 | def run() = {
13 | val jsonStrings = Seq(
14 | """{"name":"杨景","age":31}""",
15 | """{"name":"羊八井","age"31}""",
16 | """{"name":"yangbajing","age":31}"""
17 | )
18 | val rdd = sc.parallelize(jsonStrings)
19 | val sql = sqlContext.read.json(rdd)
20 | sql.show()
21 | }
22 |
23 | }
24 |
25 | object SparkApp {
26 |
27 | def main(args: Array[String]): Unit = {
28 | val conf = new SparkConf()
29 | val sc = new SparkContext(conf)
30 |
31 | val app = new SparkApp(sc)
32 | app.run()
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/spark-startup/src/test/scala/example/SparkAppTest.scala:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import org.apache.spark.{SparkConf, SparkContext}
4 | import org.scalatest.WordSpec
5 |
6 | /**
7 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-03-12.
8 | */
9 | class SparkAppTest extends WordSpec {
10 |
11 | "SparkAppTest" should {
12 |
13 | "run" in {
14 | val conf = new SparkConf()
15 | .setAppName("SparkAppTest")
16 | .setMaster("local[*]")
17 | val sc = new SparkContext(conf)
18 |
19 | val app = new SparkApp(sc)
20 | app.run()
21 | }
22 |
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/springscala/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle/
2 | build/
3 | classes/
4 |
--------------------------------------------------------------------------------
/springscala/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext {
3 | springBootVersion = '1.4.0.RELEASE'
4 | scalaVersion = '2.11.12'
5 | scalaLibVersion = '2.11'
6 | }
7 | repositories {
8 | mavenCentral()
9 | jcenter()
10 | }
11 | dependencies {
12 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
13 | }
14 | }
15 |
16 | task wrapper(type: Wrapper) {
17 | description = 'Generates gradlew[.bat] scripts'
18 | gradleVersion = '2.14.1'
19 | }
20 |
21 | subprojects {
22 | apply plugin: 'java'
23 | apply plugin: 'scala'
24 | //apply plugin: 'eclipse'
25 | apply plugin: 'spring-boot'
26 |
27 | repositories {
28 | mavenCentral()
29 | jcenter()
30 | }
31 |
32 | group = 'me.yangbajing.demospringscala'
33 | version = '1.0'
34 | sourceCompatibility = 1.8
35 | targetCompatibility = 1.8
36 |
37 | compileJava {
38 | options.encoding = 'UTF-8'
39 | options.compilerArgs << "-Xlint:deprecation" << "-Xlint:-options"
40 | }
41 |
42 | dependencies {
43 | compile('org.springframework.boot:spring-boot-starter-actuator')
44 | //compile('org.springframework.boot:spring-boot-starter-data-elasticsearch')
45 | //compile('org.springframework.boot:spring-boot-starter-data-jpa')
46 | //compile('org.springframework.boot:spring-boot-starter-data-redis')
47 | compile('org.springframework.boot:spring-boot-devtools')
48 | //compile('org.springframework.boot:spring-boot-starter-jooq')
49 | compile('org.springframework.boot:spring-boot-starter-mail')
50 | compile('org.springframework.boot:spring-boot-starter-web')
51 | compile("org.scala-lang:scala-library:$scalaVersion")
52 | compile("org.scala-lang:scala-reflect:$scalaVersion")
53 | compile("com.fasterxml.jackson.module:jackson-module-scala_$scalaLibVersion:2.8.0.rc2")
54 | testCompile('org.springframework.boot:spring-boot-starter-test')
55 | }
56 |
57 | sourceSets {
58 | main {
59 | scala {
60 | srcDirs = ['src/main/scala', 'src/main/java']
61 | }
62 | java {
63 | srcDirs = []
64 | }
65 | }
66 | test {
67 | scala {
68 | srcDirs = ['src/main/scala', 'src/main/java']
69 | }
70 | java {
71 | srcDirs = []
72 | }
73 | }
74 | }
75 |
76 | //Java compiler settings
77 | tasks.withType(JavaCompile) {
78 | //ignore conjunction warning
79 | options.compilerArgs << '-Xlint:-options'
80 | }
81 |
82 | tasks.withType(ScalaCompile) {
83 | scalaCompileOptions.additionalParameters = ["-feature", "-language:implicitConversions", "-language:reflectiveCalls"]
84 | scalaCompileOptions.useAnt = false
85 | scalaCompileOptions.with {
86 | force = true
87 | }
88 | }
89 |
90 | jar {
91 | manifest.attributes provider: 'gradle'
92 | }
93 |
94 | //eclipse {
95 | // classpath {
96 | // containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER')
97 | // containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8'
98 | // }
99 | //}
100 |
101 | }
102 |
103 |
--------------------------------------------------------------------------------
/springscala/common/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | description = 'springscala-common'
3 |
4 |
--------------------------------------------------------------------------------
/springscala/common/src/main/java/me/yangbajing/demo/common/Constants.java:
--------------------------------------------------------------------------------
1 | package me.yangbajing.demo.common;
2 |
3 | /**
4 | * Created by yangbajing on 16-8-24.
5 | */
6 | public class Constants {
7 | public static final String CHARSET = "UTF-8";
8 | }
9 |
--------------------------------------------------------------------------------
/springscala/common/src/main/scala/me/yangbajing/demo/common/Utils.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.demo.common
2 |
3 | import scala.util.Random
4 |
5 | /**
6 | * Created by yangbajing on 16-8-24.
7 | */
8 | object Utils {
9 |
10 | def randomString(length: Int): String = {
11 | (0 until length).map(_ => Random.nextPrintableChar()).mkString
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/springscala/settings.gradle:
--------------------------------------------------------------------------------
1 | include "web", "common"
2 |
--------------------------------------------------------------------------------
/springscala/web/build.gradle:
--------------------------------------------------------------------------------
1 | description = 'springscala-web'
2 |
3 | jar {
4 | baseName = 'springscala-web'
5 | }
6 |
7 | dependencies {
8 | compile project(':common')
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/springscala/web/src/main/java/me/yangbajing/demo/SpringscalaApplication.java:
--------------------------------------------------------------------------------
1 | package me.yangbajing.demo;
2 |
3 | import com.fasterxml.jackson.databind.Module;
4 | import com.fasterxml.jackson.module.scala.DefaultScalaModule;
5 | import me.yangbajing.demo.config.SpringscalaConfig;
6 | import org.springframework.boot.SpringApplication;
7 | import org.springframework.boot.autoconfigure.SpringBootApplication;
8 | import org.springframework.context.annotation.Bean;
9 |
10 | @SpringBootApplication
11 | public class SpringscalaApplication {
12 |
13 | public static void main(String[] args) {
14 | SpringApplication.run(SpringscalaApplication.class, args);
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/springscala/web/src/main/java/me/yangbajing/demo/config/SpringscalaConfig.java:
--------------------------------------------------------------------------------
1 | package me.yangbajing.demo.config;
2 |
3 | import com.fasterxml.jackson.databind.Module;
4 | import com.fasterxml.jackson.module.scala.DefaultScalaModule;
5 | import org.springframework.context.annotation.Bean;
6 | import org.springframework.context.annotation.Configuration;
7 |
8 | /**
9 | * Created by yangbajing on 16-8-24.
10 | */
11 | @Configuration
12 | public class SpringscalaConfig {
13 |
14 | @Bean
15 | public Module jacksonModuleScala() {
16 | return new DefaultScalaModule();
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/springscala/web/src/main/java/me/yangbajing/demo/web/controllers/WebController.java:
--------------------------------------------------------------------------------
1 | package me.yangbajing.demo.web.controllers;
2 |
3 | import me.yangbajing.demo.data.domain.Message;
4 | import org.springframework.web.bind.annotation.RequestMapping;
5 | import org.springframework.web.bind.annotation.RequestMethod;
6 | import org.springframework.web.bind.annotation.RestController;
7 |
8 | /**
9 | * Java WebController
10 | * Created by yangbajing on 16-8-24.
11 | */
12 | @RestController
13 | @RequestMapping("/web")
14 | public class WebController {
15 |
16 | @RequestMapping(path = "message", method = RequestMethod.GET)
17 | public Message message() {
18 | return new Message("Yang Jing", 30);
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/springscala/web/src/main/resources/application.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yangbajing/scala-applications/a41bd2b1e5eef086cf54fe58630bb47d7c16df39/springscala/web/src/main/resources/application.properties
--------------------------------------------------------------------------------
/springscala/web/src/main/scala/me/yangbajing/demo/data/domain/Message.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.demo.data.domain
2 |
3 | import me.yangbajing.demo.common.Utils
4 |
5 | import scala.beans.BeanProperty
6 |
7 | case class Message(name: String, age: Int) {
8 | @BeanProperty
9 | val sign: String = Utils.randomString(16)
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/springscala/web/src/main/scala/me/yangbajing/demo/web/controllers/ApiController.scala:
--------------------------------------------------------------------------------
1 | package me.yangbajing.demo.web.controllers
2 |
3 | import me.yangbajing.demo.data.domain.Message
4 | import org.springframework.web.bind.annotation.{RequestMapping, RequestMethod, RestController}
5 |
6 | @RestController
7 | @RequestMapping(Array("/api"))
8 | class ApiController {
9 |
10 | @RequestMapping(path = Array("message"), method = Array(RequestMethod.GET))
11 | def message(): Message = {
12 | Message("Yang Jing", 30)
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/springscala/web/src/test/java/me/yangbajing/demo/SpringscalaApplicationTests.java:
--------------------------------------------------------------------------------
1 | package me.yangbajing.demo;
2 |
3 | import org.junit.Test;
4 | import org.junit.runner.RunWith;
5 | import org.springframework.boot.test.context.SpringBootTest;
6 | import org.springframework.test.context.junit4.SpringRunner;
7 |
8 | @RunWith(SpringRunner.class)
9 | @SpringBootTest
10 | public class SpringscalaApplicationTests {
11 |
12 | @Test
13 | public void contextLoads() {
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------