restapi(2)- generic restful CRUD:通用的restful风格数据库表维护工具

2024-04-09 04:38

本文主要是介绍restapi(2)- generic restful CRUD:通用的restful风格数据库表维护工具,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

trait ModelBase[M,E] {def to: M => Edef 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",23)))override def getAll: Future[Seq[Person]] = Future.successful(Seq(Person("jonny lee",23),Person("candy wang",45),Person("jimmy kowk",34)))override def filter(expr: Person => Boolean): Future[Seq[Person]] = Future.successful(Seq(Person("jonny lee",23),Person("candy wang",45),Person("jimmy kowk",34)))override def save(row: Person): Future[Person] = Future.successful(row)override def deleteById(id: Long): Future[Int] = Future.successful(1)override def updateById(id: Long, row: Person): Future[Int] = Future.successful(1)}}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.Serializationimport scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Futuretrait 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 = 2, 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.ActorMaterializertrait 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 endimplicit val executionContext = system.dispatcherval 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 <- futTokenjstr <- resp.entity.dataBytes.runFold("") {(s,b) => s + b.utf8String}} yield jstrval jstr =  Await.result[String](respToken,2 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,2 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",18)).to[RequestEntity]response <- Http().singleRequest(saveRequest.copy(entity=reqEntity))} yield responseprintln(Await.result(futPost,2 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.restapiimport 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.dispatcherimplicit 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) = (50081,"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风格数据库表维护工具的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/887190

相关文章

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

MySQL数据库宕机,启动不起来,教你一招搞定!

作者介绍:老苏,10余年DBA工作运维经验,擅长Oracle、MySQL、PG、Mongodb数据库运维(如安装迁移,性能优化、故障应急处理等)公众号:老苏畅谈运维欢迎关注本人公众号,更多精彩与您分享。 MySQL数据库宕机,数据页损坏问题,启动不起来,该如何排查和解决,本文将为你说明具体的排查过程。 查看MySQL error日志 查看 MySQL error日志,排查哪个表(表空间

高效录音转文字:2024年四大工具精选!

在快节奏的工作生活中,能够快速将录音转换成文字是一项非常实用的能力。特别是在需要记录会议纪要、讲座内容或者是采访素材的时候,一款优秀的在线录音转文字工具能派上大用场。以下推荐几个好用的录音转文字工具! 365在线转文字 直达链接:https://www.pdf365.cn/ 365在线转文字是一款提供在线录音转文字服务的工具,它以其高效、便捷的特点受到用户的青睐。用户无需下载安装任何软件,只

MySQL-CRUD入门1

文章目录 认识配置文件client节点mysql节点mysqld节点 数据的添加(Create)添加一行数据添加多行数据两种添加数据的效率对比 数据的查询(Retrieve)全列查询指定列查询查询中带有表达式关于字面量关于as重命名 临时表引入distinct去重order by 排序关于NULL 认识配置文件 在我们的MySQL服务安装好了之后, 会有一个配置文件, 也就

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

超强的截图工具:PixPin

你是否还在为寻找一款功能强大、操作简便的截图工具而烦恼?市面上那么多工具,常常让人无从选择。今天,想给大家安利一款神器——PixPin,一款真正解放双手的截图工具。 想象一下,你只需要按下快捷键就能轻松完成多种截图任务,还能快速编辑、标注甚至保存多种格式的图片。这款工具能满足这些需求吗? PixPin不仅支持全屏、窗口、区域截图等基础功能,它还可以进行延时截图,让你捕捉到每个关键画面。不仅如此

深入理解数据库的 4NF:多值依赖与消除数据异常

在数据库设计中, "范式" 是一个常常被提到的重要概念。许多初学者在学习数据库设计时,经常听到第一范式(1NF)、第二范式(2NF)、第三范式(3NF)以及 BCNF(Boyce-Codd范式)。这些范式都旨在通过消除数据冗余和异常来优化数据库结构。然而,当我们谈到 4NF(第四范式)时,事情变得更加复杂。本文将带你深入了解 多值依赖 和 4NF,帮助你在数据库设计中消除更高级别的异常。 什么是

DM8数据库安装后配置

1 前言 在上篇文章中,我们已经成功将库装好。在安装完成后,为了能够更好地满足应用需求和保障系统的安全稳定运行,通常需要进行一些基本的配置。下面是一些常见的配置项: 数据库服务注册:默认包含14个功能模块,将这些模块注册成服务后,可以更好的启动和管理这些功能;基本的实例参数配置:契合应用场景和发挥系统的最大性能;备份:有备无患;… 2 注册实例服务 注册了实例服务后,可以使用系统服务管理,

速了解MySQL 数据库不同存储引擎

快速了解MySQL 数据库不同存储引擎 MySQL 提供了多种存储引擎,每种存储引擎都有其特定的特性和适用场景。了解这些存储引擎的特性,有助于在设计数据库时做出合理的选择。以下是 MySQL 中几种常用存储引擎的详细介绍。 1. InnoDB 特点: 事务支持:InnoDB 是一个支持 ACID(原子性、一致性、隔离性、持久性)事务的存储引擎。行级锁:使用行级锁来提高并发性,减少锁竞争

开源分布式数据库中间件

转自:https://www.csdn.net/article/2015-07-16/2825228 MyCat:开源分布式数据库中间件 为什么需要MyCat? 虽然云计算时代,传统数据库存在着先天性的弊端,但是NoSQL数据库又无法将其替代。如果传统数据易于扩展,可切分,就可以避免单机(单库)的性能缺陷。 MyCat的目标就是:低成本地将现有的单机数据库和应用平滑迁移到“云”端