研究关于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. Delphi指针运用理解

    现在是面向对象漫天飞的年代了,大家都在在谈面向对象编程.Java对指针“避而不谈”,C#虽然支持指针运用,但是也淡化处理. 然而,指针还是好完全掌握为妙,省得在开发过程碰钉子,至于对指针的运用在于开发 ...

  2. js&jq遇到的问题(不断更新中)

    1.普通事件和事件绑定: 代码: 普通事件: var btn=document.getElementById('btn'); btn.onclick=function(){ alert("c ...

  3. Python基础,day3

    本节内容 1. 函数基本语法及特性 2. 参数与局部变量 3. 返回值 嵌套函数 4.递归 5.匿名函数 6.函数式编程介绍 7.高阶函数 8.内置函数 1.函数基本语法及特性 如何不重复代码,其实很 ...

  4. Python时间戳的一些使用

    Python时间戳的一些使用 为什么写下这篇文档? 由于我本身是做Python爬虫的,在爬取网站的过程当中,会遇到形形色色的验证码,目前对于自己而言,可能简单的验证码可以进行自己识别 发现大多数的网站 ...

  5. Liferay6.1 配置友好的URL映射

    说明:以下内容和官方文档相差不大,如果您英文较好,建议直接去读官方文档,地址是:https://dev.liferay.com/develop/tutorials/-/knowledge_base/6 ...

  6. js简单对象List自定义属性排序

    简单对象List自定义属性排序 <script type="text/javascript"> var objectList = new Array(); functi ...

  7. 【docker学习二】CentOS7.5+Docker 镜像(容器)的使用

    承接上篇:https://mp.csdn.net/postedit/82744127 上文介绍了容器与镜像的基本操作,这里总结下容器的使用. 先在官网找到一个镜像: https://hub.docke ...

  8. ThinkPHP判断post,get操作

    define('REQUEST_METHOD',$_SERVER['REQUEST_METHOD']); define('IS_GET', REQUEST_METHOD =='GET' ? true ...

  9. yii中find()指定条件

    find()查找指定的条件 $modelName::model->find(); 使用条件对象 $criteria = new CDbCriteria(); $criteria->sele ...

  10. docker search/pull 报错

    docker报错 Get https://registry-1.docker.io/v2/: x509: certificate has expired or is not yet valid 这种错 ...