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

相关文章

MySQL数据库双机热备的配置方法详解

《MySQL数据库双机热备的配置方法详解》在企业级应用中,数据库的高可用性和数据的安全性是至关重要的,MySQL作为最流行的开源关系型数据库管理系统之一,提供了多种方式来实现高可用性,其中双机热备(M... 目录1. 环境准备1.1 安装mysql1.2 配置MySQL1.2.1 主服务器配置1.2.2 从

SpringBoot基于注解实现数据库字段回填的完整方案

《SpringBoot基于注解实现数据库字段回填的完整方案》这篇文章主要为大家详细介绍了SpringBoot如何基于注解实现数据库字段回填的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解... 目录数据库表pom.XMLRelationFieldRelationFieldMapping基础的一些代

基于Python开发Windows自动更新控制工具

《基于Python开发Windows自动更新控制工具》在当今数字化时代,操作系统更新已成为计算机维护的重要组成部分,本文介绍一款基于Python和PyQt5的Windows自动更新控制工具,有需要的可... 目录设计原理与技术实现系统架构概述数学建模工具界面完整代码实现技术深度分析多层级控制理论服务层控制注

使用Node.js和PostgreSQL构建数据库应用

《使用Node.js和PostgreSQL构建数据库应用》PostgreSQL是一个功能强大的开源关系型数据库,而Node.js是构建高效网络应用的理想平台,结合这两个技术,我们可以创建出色的数据驱动... 目录初始化项目与安装依赖建立数据库连接执行CRUD操作查询数据插入数据更新数据删除数据完整示例与最佳

Oracle数据库在windows系统上重启步骤

《Oracle数据库在windows系统上重启步骤》有时候在服务中重启了oracle之后,数据库并不能正常访问,下面:本文主要介绍Oracle数据库在windows系统上重启的相关资料,文中通过代... oracle数据库在Windows上重启的方法我这里是使用oracle自带的sqlplus工具实现的方

MySQL批量替换数据库字符集的实用方法(附详细代码)

《MySQL批量替换数据库字符集的实用方法(附详细代码)》当需要修改数据库编码和字符集时,通常需要对其下属的所有表及表中所有字段进行修改,下面:本文主要介绍MySQL批量替换数据库字符集的实用方法... 目录前言为什么要批量修改字符集?整体脚本脚本逻辑解析1. 设置目标参数2. 生成修改表默认字符集的语句3

基于Go语言开发一个 IP 归属地查询接口工具

《基于Go语言开发一个IP归属地查询接口工具》在日常开发中,IP地址归属地查询是一个常见需求,本文将带大家使用Go语言快速开发一个IP归属地查询接口服务,有需要的小伙伴可以了解下... 目录功能目标技术栈项目结构核心代码(main.go)使用方法扩展功能总结在日常开发中,IP 地址归属地查询是一个常见需求:

Python Excel 通用筛选函数的实现

《PythonExcel通用筛选函数的实现》本文主要介绍了PythonExcel通用筛选函数的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着... 目录案例目的示例数据假定数据来源是字典优化:通用CSV数据处理函数使用说明使用示例注意事项案例目的第一

使用python制作一款文件粉碎工具

《使用python制作一款文件粉碎工具》这篇文章主要为大家详细介绍了如何使用python制作一款文件粉碎工具,能够有效粉碎密码文件和机密Excel表格等,感兴趣的小伙伴可以了解一下... 文件粉碎工具:适用于粉碎密码文件和机密的escel表格等等,主要作用就是防止 别人用数据恢复大师把你刚删除的机密的文件恢

Linux下MySQL数据库定时备份脚本与Crontab配置教学

《Linux下MySQL数据库定时备份脚本与Crontab配置教学》在生产环境中,数据库是核心资产之一,定期备份数据库可以有效防止意外数据丢失,本文将分享一份MySQL定时备份脚本,并讲解如何通过cr... 目录备份脚本详解脚本功能说明授权与可执行权限使用 Crontab 定时执行编辑 Crontab添加定