一次成功项目实践Scala+Playframework+Akka框架的应用程序(主要部分)

1.项目需求

 通讯协议: HTTP(HyperText Transfer Protocol)

数据格式: post请求传输json数据并返还json格式结果

服务性能:高可用,高并发

图像格式:Base64编码

开发方式:敏捷开发,成熟框架

开发语言:Scala

2.技术知识:

Scala: http://docs.scala-lang.org/

Playframework: https://www.playframework.com/documentation/2.6.x/ScalaHome

Akka:  https://akka.io/

3.开发环境:

Jdk安装(1.8.0版本及以上)

Scala安装(2.11.8版本及以上)

Sbt安装 https://www.scala-sbt.org/download.html

4.编译方法

下载源码地址: 待传

执行如下命令:

sbt clean

sbt compile

sbt dist  

5.关键代码:

编译配置build.sbt

name := """image-project"""
version := "1.0"

lazy val root = (project in file(".")).enablePlugins(PlayScala)
PlayKeys.devSettings := Seq("play.server.http.port" -> "8080")

scalaVersion := "2.11.8"
libraryDependencies += guice
libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "3.0.0" % Test
libraryDependencies += "com.netaporter" %% "scala-uri" % "0.4.16"

libraryDependencies ++= Seq(
  "com.alibaba" % "fastjson" % "1.2.34",
  "commons-io" % "commons-io" % "2.5"
)

//addSbtPlugin("tv.cntt" % "xitrum-package" % "3.28.1")
//XitrumPackage.copy("dirToCopy", "fileToCopy")

系统配置application.conf

akka {
  # "akka.log-config-on-start" is extraordinarly useful because it log the complete
  # configuration at INFO level, including defaults and overrides, so it s worth
  # putting at the very top.
  #
  # Put the following in your conf/logback.xml file:
  #
  # <logger name="akka.actor" level="INFO" />
  #
  # And then uncomment this line to debug the configuration.
  #
  #log-config-on-start = true
}

## Secret key
# http://www.playframework.com/documentation/latest/ApplicationSecret
# ~~~~~
# The secret key is used to sign Play's session cookie.
# This must be changed for production, but we don't recommend you change it in this file.
#play.http.secret.key = "changeme"
play.http.secret.key = "hongyanwang"

## Modules
# https://www.playframework.com/documentation/latest/Modules
# ~~~~~
# Control which modules are loaded when Play starts.  Note that modules are
# the replacement for "GlobalSettings", which are deprecated in 2.5.x.
# Please see https://www.playframework.com/documentation/latest/GlobalSettings
# for more information.
#
# You can also extend Play functionality by using one of the publically available
# Play modules: https://playframework.com/documentation/latest/ModuleDirectory
play.modules {
  # By default, Play will load any class called Module that is defined
  # in the root package (the "app" directory), or you can define them
  # explicitly below.
  # If there are any built-in modules that you want to disable, you can list them here.
  #enabled += my.application.Module
  #enabled += Module
  #enabled += "tasks.TasksModule"

  # If there are any built-in modules that you want to disable, you can list them here.
  #disabled += ""
}

play.i18n {
  # The application languages
  langs = [ "en" ]

  # Whether the language cookie should be secure or not
  #langCookieSecure = true

  # Whether the HTTP only attribute of the cookie should be set to true
  #langCookieHttpOnly = true
}

## Play HTTP settings
# ~~~~~
play.http {

  ## Router
  # https://www.playframework.com/documentation/latest/JavaRouting
  # https://www.playframework.com/documentation/latest/ScalaRouting
  # ~~~~~
  # Define the Router object to use for this application.
  # This router will be looked up first when the application is starting up,
  # so make sure this is the entry point.
  # Furthermore, it's assumed your route file is named properly.
  # So for an application router like `my.application.Router`,
  # you may need to define a router file `conf/my.application.routes`.
  # Default to Routes in the root package (aka "apps" folder) (and conf/routes)
  #router = my.application.Router

  ## Action Creator
  # https://www.playframework.com/documentation/latest/JavaActionCreator
  # ~~~~~
  #actionCreator = null

  ## ErrorHandler
  # https://www.playframework.com/documentation/latest/JavaRouting
  # https://www.playframework.com/documentation/latest/ScalaRouting
  # ~~~~~
  # If null, will attempt to load a class called ErrorHandler in the root package,
  #errorHandler = null

  ## Session & Flash
  # https://www.playframework.com/documentation/latest/JavaSessionFlash
  # https://www.playframework.com/documentation/latest/ScalaSessionFlash
  # ~~~~~
  session {
    # Sets the cookie to be sent only over HTTPS.
    #secure = true

    # Sets the cookie to be accessed only by the server.
    #httpOnly = true

    # Sets the max-age field of the cookie to 5 minutes.
    # NOTE: this only sets when the browser will discard the cookie. Play will consider any
    # cookie value with a valid signature to be a valid session forever. To implement a server side session timeout,
    # you need to put a timestamp in the session and check it at regular intervals to possibly expire it.
    #maxAge = 300

    # Sets the domain on the session cookie.
    #domain = "example.com"
  }

  flash {
    # Sets the cookie to be sent only over HTTPS.
    #secure = true

    # Sets the cookie to be accessed only by the server.
    #httpOnly = true
  }
}

## Netty Provider
# https://www.playframework.com/documentation/latest/SettingsNetty
# ~~~~~
play.server.netty {
  # Whether the Netty wire should be logged
  #log.wire = true

  # If you run Play on Linux, you can use Netty's native socket transport
  # for higher performance with less garbage.
  #transport = "native"
}

## Cache
# https://www.playframework.com/documentation/latest/JavaCache
# https://www.playframework.com/documentation/latest/ScalaCache
# ~~~~~
# Play comes with an integrated cache API that can reduce the operational
# overhead of repeated requests. You must enable this by adding to build.sbt:
#
# libraryDependencies += cache
#
play.cache {
  # If you want to bind several caches, you can bind the individually
  #bindCaches = ["db-cache", "user-cache", "session-cache"]
}

## Filter Configuration
# https://www.playframework.com/documentation/latest/Filters
# ~~~~~
# There are a number of built-in filters that can be enabled and configured
# to give Play greater security.
#
play.filters {

  # Enabled filters are run automatically against Play.
  # CSRFFilter, AllowedHostFilters, and SecurityHeadersFilters are enabled by default.
  enabled += filters.ElapseFilter

  # Disabled filters remove elements from the enabled list.
  #disabled += filters.ExampleFilters

  ## CORS filter configuration
  # https://www.playframework.com/documentation/latest/CorsFilter
  # ~~~~~
  # CORS is a protocol that allows web applications to make requests from the browser
  # across different domains.
  # NOTE: You MUST apply the CORS configuration before the CSRF filter, as CSRF has
  # dependencies on CORS settings.
  cors {
    # Filter paths by a whitelist of path prefixes
    pathPrefixes = ["/", "/image/save.php", "/image/save"]

    # The allowed origins. If null, all origins are allowed.
    #allowedOrigins = ["http://www.example.com"]

    # The allowed HTTP methods. If null, all methods are allowed
    allowedHttpMethods = ["GET", "POST"]
  }

  hosts {
    # Allow requests to example.com, its subdomains, and localhost:9000.
    #allowed = [".example.com", "localhost:9000"]
   allowed = ["."]
  }
}

## Evolutions
# https://www.playframework.com/documentation/latest/Evolutions
# ~~~~~
# Evolutions allows database scripts to be automatically run on startup in dev mode
# for database migrations. You must enable this by adding to build.sbt:
#
# libraryDependencies += evolutions
#
play.evolutions {
  # You can disable evolutions for a specific datasource if necessary
  #db.default.enabled = false
}

## Database Connection Pool
# https://www.playframework.com/documentation/latest/SettingsJDBC
# ~~~~~
# Play doesn't require a JDBC database to run, but you can easily enable one.
#
# libraryDependencies += jdbc
#

include "image.conf"

应用配置image.conf

#支持最大请求数据包
play.http.parser.maxMemoryBuffer = 15M
play.http.parser.maxDishBuffer = 15M

#端口
play.server.http.port = 8080

#图片存储路径
image.path = "d:\\"

#调度任务
task.enable = true
task.interval = 10

路由routers

# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~
# Map static resources from the /public folder to the /assets URL path
GET     /assets/*file               controllers.Assets.versioned(path="/public", file: Asset)
# An example controller showing a sample home page
GET     /                           controllers.HomeController.index
# An example controller showing how to write asynchronous code
POST    /image/save.php             controllers.ImageController.post

模块Module

/**
  * 模块实例化
  * author  hongyan.wang
  * Email   why000007@163.com
  * Date    03/21/2017
  */

class Module extends AbstractModule with AkkaGuiceSupport {

  override def configure() = {
    bind(classOf[ImageService]).to(classOf[ImageServiceImpl]).asEagerSingleton()
    bind(classOf[ImageTask]).asEagerSingleton()
    bindActor[ImageActor]("imageActor")
  }
}

返回结果Response

import com.alibaba.fastjson.annotation.JSONField

/**
  * 返回结果
  * author  hongyan.wang
  * Email   why000007@163.com
  * Date    03/21/2017
  */

sealed case class  Response(@beans.BeanProperty @JSONField(ordinal = 1) val result: Int = 0) {

  def this(result: Int, imagePath: String) { this(result); this.imagePath =  imagePath}

  @JSONField(ordinal = 2)
  @beans.BeanProperty
  var imagePath: String = ""

  @JSONField(ordinal = 3)
  @beans.BeanProperty
  var message: String = if( result == 1 ) "处理成功" else "处理失败"

  /**
    *
    * 省略业务字段
    */

}


控制层ImageController

/**
  * 数据接收
  * author  hongyan.wang
  * Email   why000007@163.com
  * Date    03/19/2017
  */

@Singleton class ImageController @Inject()(cc: ControllerComponents, actorSystem: ActorSystem, imageService: ImageService)(implicit exec: ExecutionContext) extends AbstractController(cc) {

  implicit def Response2String(response: Response): String = com.alibaba.fastjson.JSON.toJSONString(response, SerializerFeature.PrettyFormat)

  private[ImageController] val logger = Logger(classOf[ImageController])

  def post() = Action { request =>
    /**
      * 省略数据校验
      */
    //val jsonBody: Option[JsValue] = request.body.asJson
    val jsonObj: JSONObject = com.alibaba.fastjson.JSON.parseObject(request.body.asText.getOrElse("{}"))
    val response: String = imageService.call(jsonObj.getString("image_cont"), jsonObj.getString("image_ext"))
    Result(
      header = ResponseHeader(200, Map.empty),
      body = HttpEntity.Strict(ByteString(response), Some("application/json;charset=utf-8"))
    )
  }
}
过滤器ElapseFilter

/**
  * 过滤器
  * author  hongyan.wang
  * Email   why000007@163.com
  * Date    03/17/2017
  */
@Singleton class ElapseFilter @Inject()(implicit ec: ExecutionContext) extends EssentialFilter {

  private[filters] val logger = Logger(getClass)

  override def apply(next: EssentialAction) = EssentialAction { request =>
    val start = Instant.ofEpochMilli(System.currentTimeMillis())
    logger.info("http[method=%s, source=%s, url=%s?%s]".format(request.method, request.remoteAddress, request.path, request.rawQueryString))
    next(request).map { result =>
      val duration = Duration.between(start, Instant.now())
      logger.info("http[elapse=%s]".format(duration.toMillis))
      result.withHeaders("Elapse-Time" -> duration.toMillis.toString)
    }
  }
}

业务层Services

/**
  * 业务处理
  * author  hongyan.wang
  * Email   why000007@163.com
  * Date    03/21/2017
  */

package object Services {

  implicit def RichFileUtil(fileUtil: FileUtils) = new RichFileUtil(fileUtil)

  protected[Services] val fileUtils = new FileUtils

  lazy private[Services] val config = Configuration(ConfigFactory.load())
  val path = config.getString("image.path").getOrElse(".")

  trait ImageService extends { /***************************/ } with utils.Logger {
    def call(img_cont:String, img_ext:String): Response
  }

  class ImageServiceImpl extends ImageService {

    @throws(classOf[Exception])
    override def call(img_cont: String, img_ext: String): Response = {
      /**
        * 此处省略关键业务处理部分
        */
      val fname = s"${path}${File.separator}${DateUtil.fTime()}-${new StringFormat(Random.nextInt(99)).formatted("%4s").replaceAll(" ","0")}.${img_ext}"
      fileUtils.save64(fname, img_cont)
      new Response(1, fname)
    }
  }

}

调度任务Task

/**
  * 消息接收
  * author  hongyan.wang
  * Email   why000007@163.com
  * Date    03/21/2017
  */
class ImageActor @Inject()(config: Configuration) extends Actor with utils.Logger {

  self ! "Message From Self"
  override def receive: Receive = {
    case Some("delete") => {
      logger.info("收到一条删除任务")
      /**
        * 此处任务处理部分
        */
    }
    case str: String => logger.info(str)
    case _ =>
  }
}
/**
  * 调度任务
  * author  hongyan.wang
  * Email   why000007@163.com
  * Date    03/21/2017
  */
class ImageTask @Inject()(config: Configuration, actorSystem: ActorSystem, @Named("imageActor") actor: ActorRef)(implicit executionContext: ExecutionContext) {

  private val logger = Logger(getClass)

  if(config.getBoolean("task.enable").getOrElse(false)) {
    val interval = config.getInt("task.interval").getOrElse(5)
    actorSystem.scheduler.schedule (
      initialDelay = Duration(60, TimeUnit.SECONDS),
      //interval = Duration(interval, TimeUnit.HOURS),
      interval = Duration(interval, TimeUnit.SECONDS),
      receiver = actor, message = Some("delete")
    )
    logger.info(s"调度任务启动")
  }else {
    logger.warn("调度任务取消")
  }
}

工具类

/**
  * 文件工具
  * author  hongyan.wang
  * Email   why000007@163.com
  * Date    04/06/2017
  */

class RichFileUtil(fileUtils: FileUtils) {

  def exists(fname: String): Boolean = new File(fname).exists
  def mkdirs(fname : String): Boolean = new File(fname).mkdirs()
  def length(fname: String): Long = new File(fname).length()

  def save64(fname: String, text64:String): Boolean = {
    try {
      FileUtils.writeByteArrayToFile(new File(fname),StringUtil.decode64(text64))
      true
    } catch {
      case ex:Exception => ex.printStackTrace(); false
    }
  }

  def read64(fname: String): Option[String] = {
    if(exists(fname)) {
      Some(StringUtil.encode64(FileUtils.readFileToByteArray(new File(fname))))
    } else {
      None
    }
  }
}
/**
  * 字符串工具
  * author  hongyan.wang
  * Email   why000007@163.com
  * Date    03/23/2017
  */
object StringUtil {

  def encode64(content:String, charset: String="UTF-8"): String = Base64.getEncoder().encodeToString(content.getBytes(charset))

  def decode64(content:String): Array[Byte] = Base64.getDecoder().decode(content)

  def encode64(bytes: Array[Byte]): String = Base64.getEncoder().encodeToString(bytes)

  def toString(entity: AnyRef): String = JSON.toJSONString(entity,SerializerFeature.NotWriteRootClassName)
}
/**
  * 资源关闭
  * author  hongyan.wang
  * Email   why000007@163.com
  * Date    03/27/2017
  */
object Using {
  def close[A <: { def close(): Unit }, B](resource: A)(f: A => B): B =
    try {
      f(resource)
    } finally {
      if (resource != null) {
        resource.close()
      }
    }
}

编译结果

sbt dist
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256m; support was removed in 8.0
[warn] Executing in batch mode.
[warn]   For better performance, hit [ENTER] to switch to interactive mode, or
[warn]   consider launching sbt without any commands, or explicitly passing 'shell'
[info] Loading project definition from E:\worspaceS\image_project\project
[info] Set current project to image-project (in build file:/E:/worspaceS/image_project/)
[info] Packaging E:\worspaceS\image_project\target\scala-2.11\image-project_2.11-1.0-sources.jar ...
[info] Updating {file:/E:/worspaceS/image_project/}root...
[info] Done packaging.
[info]
[info] Your package is ready in E:\worspaceS\image_project\target\universal\image-project-1.0.zip (解压该文件,运行/bin/image-project可执行文件)
[info]

[success] Total time: 12 s, completed 2018-4-25 17:55:38

运行与测试

1.解压image_project\target\universal\image-project-1.0.zip 压缩包,运行bin/image-project可执行文件,启动服务端程序

2.客户端调用

>>>> HttpPost URL: http://127.0.0.1:8080/image/save.php
>>>> HttpResponse status[200:OK], cost: 1790 ms.
{
    "result":1,
    "imagePath":"d:\\\\2018-0425-1804-0002-0790-0070.jpg",
    "message":"处理成功"
}

==============================快乐编程=========================








  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值