restapi(2)- generic restful CRUD:通用的restful风格数据库表维护工具
研究关于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风格数据库表维护工具的更多相关文章
- SpringBoot Restful Crud
一个简单的Restful Crud实验 默认首页的访问设置: // 注册 自定义的mvc组件,所有的WebMvcConfigurer组件都会一起起作用 @Bean public WebMvcConfi ...
- 使用springboot实现一个简单的restful crud——01、项目简介以及创建项目
前言 之前一段时间学习了一些springboot的一些基础使用方法和敲了一些例子,是时候写一个简单的crud来将之前学的东西做一个整合了 -- 一个员工列表的增删改查. 使用 restful api ...
- 通用且常用的Java正则匹配工具,用以检查邮箱名、电话号码、用户密码、邮政编码等合法性
一个通用且常用的Java正则匹配工具,用以检查邮箱名.电话号码.用户密码.邮政编码等合法性. import java.util.regex.Matcher; import java.util.rege ...
- sql server编写通用脚本自动统计各表数据量心得
工作过程中,如果一个数据库的表比较多,手工编写统计脚本就会比较繁琐,于是摸索出自动生成各表统计数据量脚本的通用方法,直接上代码: /* 脚本来源:https://www.cnblogs.com/zha ...
- Spring Boot 2 快速教程:WebFlux Restful CRUD 实践(三)
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 这是泥瓦匠的第102篇原创 03:WebFlux Web CR ...
- 使用springboot实现一个简单的restful crud——03、前端页面、管理员登陆(注销)功能
前言 这一篇我们就先引入前端页面和相关的静态资源,再做一下管理员的登陆和注销的功能,为后续在页面上操作数据做一个基础. 前端页面 前端的页面是我从网上找的一个基于Bootstrap 的dashboar ...
- (5)Spring Boot web开发 --- Restful CRUD
文章目录 `@RestController` vs `@Controller` 默认访问首页 设置项目名 国际化 登陆 & 拦截 Restful 风格 @RestController vs @ ...
- javaweb各种框架组合案例(七):springboot+jdbcTemplete+通用dao+restful
一.介绍 1.springboot是spring项目的总结+整合 当我们搭smm,ssh,ssjdbc等组合框架时,各种配置不胜其烦,不仅是配置问题,在添加各种依赖时也是让人头疼,关键有些jar包之间 ...
- 使用springboot实现一个简单的restful crud——02、dao层单元测试,测试从数据库取数据
接着上一篇,上一篇我们创建了项目.创建了实体类,以及创建了数据库数据.这一篇就写一下Dao层,以及对Dao层进行单元测试,看下能否成功操作数据库数据. Dao EmpDao package com.j ...
随机推荐
- [Erlang-0016][aque_tcp] 一个 Erlang TCP 组件
项目地址:https://github.com/liangjingyang/aque_tcp 欢迎任何形式的转载,但请务必注明出处:http://www.cnblogs.com/liangjingya ...
- Windows下libevent C++封装类实现
题记 windows平台下对于服务器高并发的网络模型选型中,使用libevent是个不错的选择. 本文的背景基于:国内博客对于libevent大多介绍linux实现,大多是c语言的实现,Windows ...
- GO :互联网时代的 C 语言!
摘要: 每周为您推送最有价值的开源技术内参! 技术干货 标签:独家译文 1.Go 很好,为什么我们不使用它? 在这篇文章中,我将分享一下为什么我认为它很棒,使用它的一些缺点,以及为什么它还不是我们 Z ...
- 系统休眠消息PBT_APMSUSPEND
https://msdn.microsoft.com/en-us/library/windows/desktop/aa372721(v=vs.85).aspx https://msdn.microso ...
- Qt的槽可以使用默认参数
引用自:http://www.ibm.com/developerworks/cn/linux/guitoolkit/qt/signal-slot/ 的一篇经典文章,是关于Qt的信号和槽的分析的.看年份 ...
- Ionic 4 核心概念
对于那些对Ionic应用程序开发完全陌生的人来说,了解项目背后的核心理念,概念和工具可能会有所帮助.下面介绍Ionic Framework的基础知识. UI组件 Ionic Framework是一个U ...
- Hadoop集群(第3期)机器信息分布表
1.分布式环境搭建 采用4台安装Linux环境的机器来构建一个小规模的分布式集群. 图1 集群的架构 其中有一台机器是Master节点,即名称节点,另外三台是Slaver节点,即数据节点.这四台机器彼 ...
- SpringCloud Sleuth入门介绍
案例代码:https://github.com/q279583842q/springcloud-e-book 一.Sleuth介绍 为什么要使用微服务跟踪?它解决了什么问题? 1.微服务的现状? ...
- AIX/Linux/HP-UX查看CPU/内存/磁盘/存储命令
1.1 硬件环境验证方式 硬件环境主要包括CPU.内存.磁盘/存储.网络设备(如F5等).系统特有设备(如密押设备等)等,其中网络设备和系统特有设备由网络管理员或项目组提供为准,本节主要关注CP ...
- 【Webkit Blog翻译】深入研究WebRTC | 内有福利
本文译自 A Closer Look Into WebRTC” 我们在最近的一篇 WebKit博客中宣布了对High Sierra平台和iOS中Safari的WebRTC支持.现在,我们希望能够带领大 ...