研究关于restapi的初衷是想搞一套通用的平台数据表维护http工具。前面谈过身份验证和使用权限、文件的上传下载,这次来到具体的数据库表维护。我们在这篇示范里设计一套通用的对平台每一个数据表的标准维护方式。http服务端数据表维护CRUD有几个标准的部分组成:Model,Repository,Route。我们先看看这几个类型的基类:

trait ModelBase[M,E] {
def to: M => E
def from: E => M
} trait RepoBase[M] {
def getById(id: Long) : Future[Option[M]]
def getAll : Future[Seq[M]]
def filter(expr: M => Boolean): Future[Seq[M]]
def save(row: M) : Future[AnyRef]
def deleteById(id: Long) : Future[Int]
def updateById(id: Long, row: M) : Future[Int]
} abstract class RouteBase[M](val pathName: String, repository: RepoBase[M])(
implicit m: Manifest[M]) extends Directives with JsonConverter { val route = path(pathName) {
get {
complete(futureToJson(repository.getAll))
} ~ post {
entity(as[String]) { json =>
val extractedEntity = fromJson[M](json)
complete(futureToJson(repository.save(extractedEntity)))
}
}
} ~ path(pathName / LongNumber) { id =>
get {
complete(futureToJson(repository.getById(id)))
} ~ put {
entity(as[String]) { json =>
val extractedEntity = fromJson[M](json)
complete(futureToJsonAny(repository.updateById(id, extractedEntity)))
}
} ~ delete {
complete(futureToJsonAny(repository.deleteById(id)))
}
}
}

很明显,Model是数据库表行类型的表达方式、Repository是数据库表操作方法、Route是操作方法的调用。下面是这几个类型的实例示范:

object MockModels {
case class DataRow (
name: String,
age: Int
)
case class Person(name: String, age: Int)
extends ModelBase[Person,DataRow] {
def to: Person => DataRow = p => DataRow (
name = p.name,
age = p.age
)
def from: DataRow => Person = m => Person(
name = m.name,
age = m.age
)
}
} package com.datatech.restapi
import MockModels._ import scala.concurrent.Future
object MockRepo {
class PersonRepo extends RepoBase[Person] {
override def getById(id: Long): Future[Option[Person]] = Future.successful(Some(Person("johnny lee",))) override def getAll: Future[Seq[Person]] = Future.successful(
Seq(Person("jonny lee",),Person("candy wang",),Person("jimmy kowk",))
) override def filter(expr: Person => Boolean): Future[Seq[Person]] = Future.successful(
Seq(Person("jonny lee",),Person("candy wang",),Person("jimmy kowk",))
) override def save(row: Person): Future[Person] = Future.successful(row) override def deleteById(id: Long): Future[Int] = Future.successful() override def updateById(id: Long, row: Person): Future[Int] = Future.successful()
} } object PersonRoute { class PersonRoute(pathName: String, repo: RepoBase[Person])
extends RouteBase[Person](pathName,repo) val route = new PersonRoute("person",new PersonRepo).route
}

Model代表数据表结构以及某种数据库的表行与Model之间的转换。而repository则代表某种数据库对库表具体操作的实现。我们把焦点拉回到RouteBase上来,这里包含了rest标准的get,post,put,delete http操作。实际上就是request/response处理机制。因为数据需要在线上on-the-wire来回移动,所以需要进行数据转换。通用的数据传输模式是:类->json->类,即序列化/反序列化。akka-http提供了丰富的Marshaller来实现自动的数据转换,但在编译时要提供Marshaller的隐式实例implicit instance,所以用类参数是无法通过编译的。只能手工进行类和json之间的转换。json转换是通过json4s实现的:

import java.text.SimpleDateFormat
import akka.http.scaladsl.model._
import org.json4s.JsonAST.{JNull, JString}
import org.json4s.{CustomSerializer, DefaultFormats, Formats}
import org.json4s.jackson.Serialization import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future trait DateSerializer {
case object SqlDateSerializer extends CustomSerializer[java.sql.Date](format => ( {
case JString(date) => {
val utilDate = new SimpleDateFormat("yyyy-MM-dd").parse(date);
new java.sql.Date(utilDate.getTime)
}
case JNull => null
}, {
case date: java.sql.Date => JString(date.toString)
})) } trait JsonConverter extends DateSerializer {
implicit val formats: Formats = new DefaultFormats {
override def dateFormatter = new SimpleDateFormat("yyyy-MM-dd")
} ++ List(SqlDateSerializer) def toJson(obj: AnyRef): String = {
Serialization.write(obj)
} def futureToJson(obj: Future[AnyRef]): Future[HttpResponse] = {
obj.map { x =>
HttpResponse(status = StatusCodes.OK, entity = HttpEntity(MediaTypes.`application/json`, Serialization.write(x)))
}.recover {
case ex => ex.printStackTrace(); HttpResponse(status = StatusCodes.InternalServerError)
} } def futureToJsonAny(obj: Future[Any]): Future[HttpResponse] = {
obj.map { x =>
HttpResponse(status = StatusCodes.OK, entity = HttpEntity(MediaTypes.`application/json`, s"""{status : ${x}"""))
}.recover {
case ex => HttpResponse(status = StatusCodes.InternalServerError)
} } def fromJson[E](json: String)(implicit m: Manifest[E]): E = {
Serialization.read[E](json)
}
}

当然对于一些特别的数据库表,我们还是希望使用akka-http强大的功能,如streaming。这时对于每一个这样的表单就需要要定制Route了。下面是一个定制Route的例子:

object MockModel {
case class AddressRow (
province: String,
city: String,
street: String,
zip: String
)
case class Address(
province: String,
city: String,
street: String,
zip: String
)
extends ModelBase[Address,AddressRow] {
def to: Address => AddressRow = addr => AddressRow (
province = addr.province,
city = addr.city,
street = addr.street,
zip = addr.zip
)
def from: AddressRow => Address = row => Address(
province = row.province,
city = row.city,
street = row.street,
zip = row.zip
)
}
} object AddressRepo {
def getById(id: Long): Future[Option[Address]] = ??? def getAll: Source[Address,_] = ??? def filter(expr: Address => Boolean): Future[Seq[Address]] = ??? def saveAll(rows: Source[Address,_]): Future[Int] = ???
def saveAll(rows: Future[Seq[Address]]): Future[Int] = ??? def deleteById(id: Long): Future[Address] = ??? def updateById(id: Long, row: Address): Future[Address] = ???
} package com.datatech.restapi
import akka.actor._
import akka.stream._
import akka.http.scaladsl.common._
import spray.json.DefaultJsonProtocol
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.server._
import MockModels.Address
import MockRepo._ trait FormatConverter extends SprayJsonSupport with DefaultJsonProtocol{
implicit val addrFormat = jsonFormat4(Address.apply)
} case class AddressRoute(val pathName: String)(implicit akkaSys: ActorSystem) extends Directives with FormatConverter{
implicit val mat = ActorMaterializer()
implicit val jsonStreamingSupport = EntityStreamingSupport.json()
.withParallelMarshalling(parallelism = , unordered = false) val route = path(pathName) {
get {
complete(AddressRepo.getAll)
} ~ post {
withoutSizeLimit {
entity(asSourceOf[Address]) { source =>
/* val futSavedRows: Future[Seq[Address]] =
source.runFold(Seq[Address]())((acc, addr) => acc :+ addr)
onComplete(futSavedRows) { rows => */
onComplete(AddressRepo.saveAll(source)) {rows =>
complete { s"$rows address saved."}
}
}
} } ~ path(pathName / LongNumber) { id =>
get {
complete(AddressRepo.getById(id)))
} ~ put {
entity(as[Address]) { addr =>
onComplete(AddressRepo.updateById(id,addr)) { addr =>
complete(s"address updated to: $addr")
}
} ~ delete {
onComplete(AddressRepo.deleteById(id)) { addr =>
complete(s"address deleted: $addr")
}
}
}

这样做可以灵活的使用akka-stream提供的功能。

上面的例子Mock PersonRoute.route可以直接贴在主route后面:

  val route =
path("auth") {
authenticateBasic(realm = "auth", authenticator.getUserInfo) { userinfo =>
post { complete(authenticator.issueJwt(userinfo))}
}
} ~
pathPrefix("openspace") {
(path("hello") & get) {
complete(s"Hello, you are in open space.")
}
} ~
pathPrefix("api") {
authenticateOAuth2(realm = "api", authenticator.authenticateToken) { validToken =>
(path("hello") & get) {
complete(s"Hello! userinfo = ${authenticator.getUserInfo(validToken)}")
} ~
(path("how are you") & get) {
complete(s"Hello! userinfo = ${authenticator.getUserInfo(validToken)}")
} ~
PersonRoute.route
// ~ ...
}
}

和前面的示范一样,我们还是写一个客户端来测试:

import akka.actor._
import akka.http.scaladsl.model.headers._
import scala.concurrent._
import scala.concurrent.duration._
import akka.http.scaladsl.Http
import spray.json.DefaultJsonProtocol
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.marshalling._
import akka.http.scaladsl.model._
import akka.stream.ActorMaterializer trait JsonFormats extends SprayJsonSupport with DefaultJsonProtocol
object JsonConverters extends JsonFormats {
case class Person(name: String,age: Int)
implicit val fmtPerson = jsonFormat2(Person)
} object TestCrudClient {
type UserInfo = Map[String,Any]
def main(args: Array[String]): Unit = {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
// needed for the future flatMap/onComplete in the end
implicit val executionContext = system.dispatcher val helloRequest = HttpRequest(uri = "http://192.168.11.189:50081/") val authorization = headers.Authorization(BasicHttpCredentials("johnny", "p4ssw0rd"))
val authRequest = HttpRequest(
HttpMethods.POST,
uri = "http://192.168.11.189:50081/auth",
headers = List(authorization)
) val futToken: Future[HttpResponse] = Http().singleRequest(authRequest) val respToken = for {
resp <- futToken
jstr <- resp.entity.dataBytes.runFold("") {(s,b) => s + b.utf8String}
} yield jstr val jstr = Await.result[String](respToken, seconds)
println(jstr) scala.io.StdIn.readLine() val authentication = headers.Authorization(OAuth2BearerToken(jstr)) val getAllRequest = HttpRequest(
HttpMethods.GET,
uri = "http://192.168.11.189:50081/api/crud/person",
).addHeader(authentication)
val futGet: Future[HttpResponse] = Http().singleRequest(getAllRequest)
println(Await.result(futGet, seconds))
scala.io.StdIn.readLine() import JsonConverters._ val saveRequest = HttpRequest(
HttpMethods.POST,
uri = "http://192.168.11.189:50081/api/crud/person"
).addHeader(authentication)
val futPost: Future[HttpResponse] =
for {
reqEntity <- Marshal(Person("tiger chan",)).to[RequestEntity]
response <- Http().singleRequest(saveRequest.copy(entity=reqEntity))
} yield response println(Await.result(futPost, seconds))
scala.io.StdIn.readLine()
system.terminate()
} }

下面是restapi发展到现在状态的源代码:

build.sbt

name := "restapi"

version := "0.3"

scalaVersion := "2.12.8"

libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-http" % "10.1.8",
"com.typesafe.akka" %% "akka-stream" % "2.5.23",
"com.pauldijou" %% "jwt-core" % "3.0.1",
"de.heikoseeberger" %% "akka-http-json4s" % "1.22.0",
"org.json4s" %% "json4s-native" % "3.6.1",
"com.typesafe.akka" %% "akka-http-spray-json" % "10.1.8",
"com.typesafe.scala-logging" %% "scala-logging" % "3.9.0",
"org.slf4j" % "slf4j-simple" % "1.7.25",
"org.json4s" %% "json4s-jackson" % "3.6.7",
"org.json4s" %% "json4s-ext" % "3.6.7"
)

RestApiServer.scala

package com.datatech.restapi

import akka.actor._
import akka.stream._
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Directives._
import pdi.jwt._
import AuthBase._
import MockUserAuthService._ object RestApiServer extends App { implicit val httpSys = ActorSystem("httpSystem")
implicit val httpMat = ActorMaterializer()
implicit val httpEC = httpSys.dispatcher implicit val authenticator = new AuthBase()
.withAlgorithm(JwtAlgorithm.HS256)
.withSecretKey("OpenSesame")
.withUserFunc(getValidUser) val route =
path("auth") {
authenticateBasic(realm = "auth", authenticator.getUserInfo) { userinfo =>
post { complete(authenticator.issueJwt(userinfo))}
}
} ~
pathPrefix("api") {
authenticateOAuth2(realm = "api", authenticator.authenticateToken) { validToken =>
FileRoute(validToken)
.route ~
(pathPrefix("crud")) {
PersonRoute.route
}
// ~ ...
} ~
(pathPrefix("crud")) {
PersonRoute.route
// ~ ...
}
} val (port, host) = (,"192.168.11.189") val bindingFuture = Http().bindAndHandle(route,host,port) println(s"Server running at $host $port. Press any key to exit ...") scala.io.StdIn.readLine() bindingFuture.flatMap(_.unbind())
.onComplete(_ => httpSys.terminate()) }

restapi(2)- generic restful CRUD:通用的restful风格数据库表维护工具的更多相关文章

  1. SpringBoot Restful Crud

    一个简单的Restful Crud实验 默认首页的访问设置: // 注册 自定义的mvc组件,所有的WebMvcConfigurer组件都会一起起作用 @Bean public WebMvcConfi ...

  2. 使用springboot实现一个简单的restful crud——01、项目简介以及创建项目

    前言 之前一段时间学习了一些springboot的一些基础使用方法和敲了一些例子,是时候写一个简单的crud来将之前学的东西做一个整合了 -- 一个员工列表的增删改查. 使用 restful api ...

  3. 通用且常用的Java正则匹配工具,用以检查邮箱名、电话号码、用户密码、邮政编码等合法性

    一个通用且常用的Java正则匹配工具,用以检查邮箱名.电话号码.用户密码.邮政编码等合法性. import java.util.regex.Matcher; import java.util.rege ...

  4. sql server编写通用脚本自动统计各表数据量心得

    工作过程中,如果一个数据库的表比较多,手工编写统计脚本就会比较繁琐,于是摸索出自动生成各表统计数据量脚本的通用方法,直接上代码: /* 脚本来源:https://www.cnblogs.com/zha ...

  5. Spring Boot 2 快速教程:WebFlux Restful CRUD 实践(三)

    摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 这是泥瓦匠的第102篇原创 03:WebFlux Web CR ...

  6. 使用springboot实现一个简单的restful crud——03、前端页面、管理员登陆(注销)功能

    前言 这一篇我们就先引入前端页面和相关的静态资源,再做一下管理员的登陆和注销的功能,为后续在页面上操作数据做一个基础. 前端页面 前端的页面是我从网上找的一个基于Bootstrap 的dashboar ...

  7. (5)Spring Boot web开发 --- Restful CRUD

    文章目录 `@RestController` vs `@Controller` 默认访问首页 设置项目名 国际化 登陆 & 拦截 Restful 风格 @RestController vs @ ...

  8. javaweb各种框架组合案例(七):springboot+jdbcTemplete+通用dao+restful

    一.介绍 1.springboot是spring项目的总结+整合 当我们搭smm,ssh,ssjdbc等组合框架时,各种配置不胜其烦,不仅是配置问题,在添加各种依赖时也是让人头疼,关键有些jar包之间 ...

  9. 使用springboot实现一个简单的restful crud——02、dao层单元测试,测试从数据库取数据

    接着上一篇,上一篇我们创建了项目.创建了实体类,以及创建了数据库数据.这一篇就写一下Dao层,以及对Dao层进行单元测试,看下能否成功操作数据库数据. Dao EmpDao package com.j ...

随机推荐

  1. Cookie概述与应用

    一.概述 Cookie是Web服务器保存在客户端的一系列文本信息 典型应用一:判断注册用户是否已经登录网站. 典型应用二:"购物车"的处理. Cookie的作用:     对特定对 ...

  2. GetSystemTimeAsFileTime讲解(从1601年1月1日到目前经过的纳秒)

    void WINAPI GetSystemTimeAsFileTime( Out LPFILETIME lpSystemTimeAsFileTime ); 这个函数获取到的是从1601年1月1日到目前 ...

  3. MAC 安装telnet

    https://blog.csdn.net/licheng70356213/article/details/81162660 在10.12及以下版本,都内置了telnet命令,但是在10.13中,已经 ...

  4. 编译gd库出错

    不知道大家有没有遇到在  X64 RedHat5 或者 RedHat4 下.编译安装PHP环境的时候. 安装了libxml,zlib,jpeg,libpng,freetype,libart_lgpl, ...

  5. nginx实现最简单的直播

    系统环境 [root@yunwei-test live]# cat /etc/redhat-release CentOS Linux release (Core) [root@yunwei-test ...

  6. C# 异步转同步 TaskCompletionSource

    本文通过TaskCompletionSource,实现异步转同步 首先有一个异步方法,如下异步任务延时2秒后,返回一个结果 private static async Task<string> ...

  7. 你所不知的spring与mybatis整合方法

    内容目录 1.采用MapperScannerConfigurer2.采用接口org.apache.ibatis.session.SqlSession的实现类org.mybatis.spring.Sql ...

  8. 并发编程-Future+callable+FutureTask 闭锁机制

    项目中经常有些任务需要异步(提交到线程池中)去执行,而主线程往往需要知道异步执行产生的结果,这时我们要怎么做呢?用runnable是无法实现的,我们需要用callable实现. FutureTask ...

  9. mysql的数据存储

    # pycharm 连接mysql import pymysql username = input("输入用户名:") pwd = input("输入密码:") ...

  10. WebLogic 任意文件上传远程代码执行_CVE-2018-2894漏洞复现

    WebLogic 任意文件上传远程代码执行_CVE-2018-2894漏洞复现 一.漏洞描述 Weblogic管理端未授权的两个页面存在任意上传getshell漏洞,可直接获取权限.Oracle 7月 ...