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

相关文章

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Python基于wxPython和FFmpeg开发一个视频标签工具

《Python基于wxPython和FFmpeg开发一个视频标签工具》在当今数字媒体时代,视频内容的管理和标记变得越来越重要,无论是研究人员需要对实验视频进行时间点标记,还是个人用户希望对家庭视频进行... 目录引言1. 应用概述2. 技术栈分析2.1 核心库和模块2.2 wxpython作为GUI选择的优

C# WinForms存储过程操作数据库的实例讲解

《C#WinForms存储过程操作数据库的实例讲解》:本文主要介绍C#WinForms存储过程操作数据库的实例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、存储过程基础二、C# 调用流程1. 数据库连接配置2. 执行存储过程(增删改)3. 查询数据三、事务处

mysql数据库重置表主键id的实现

《mysql数据库重置表主键id的实现》在我们的开发过程中,难免在做测试的时候会生成一些杂乱无章的SQL主键数据,本文主要介绍了mysql数据库重置表主键id的实现,具有一定的参考价值,感兴趣的可以了... 目录关键语法演示案例在我们的开发过程中,难免在做测试的时候会生成一些杂乱无章的SQL主键数据,当我们

使用Java实现通用树形结构构建工具类

《使用Java实现通用树形结构构建工具类》这篇文章主要为大家详细介绍了如何使用Java实现通用树形结构构建工具类,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录完整代码一、设计思想与核心功能二、核心实现原理1. 数据结构准备阶段2. 循环依赖检测算法3. 树形结构构建4. 搜索子

利用Python开发Markdown表格结构转换为Excel工具

《利用Python开发Markdown表格结构转换为Excel工具》在数据管理和文档编写过程中,我们经常使用Markdown来记录表格数据,但它没有Excel使用方便,所以本文将使用Python编写一... 目录1.完整代码2. 项目概述3. 代码解析3.1 依赖库3.2 GUI 设计3.3 解析 Mark

Spring Boot 整合 MyBatis 连接数据库及常见问题

《SpringBoot整合MyBatis连接数据库及常见问题》MyBatis是一个优秀的持久层框架,支持定制化SQL、存储过程以及高级映射,下面详细介绍如何在SpringBoot项目中整合My... 目录一、基本配置1. 添加依赖2. 配置数据库连接二、项目结构三、核心组件实现(示例)1. 实体类2. Ma

利用Go语言开发文件操作工具轻松处理所有文件

《利用Go语言开发文件操作工具轻松处理所有文件》在后端开发中,文件操作是一个非常常见但又容易出错的场景,本文小编要向大家介绍一个强大的Go语言文件操作工具库,它能帮你轻松处理各种文件操作场景... 目录为什么需要这个工具?核心功能详解1. 文件/目录存javascript在性检查2. 批量创建目录3. 文件

jvm调优常用命令行工具详解

《jvm调优常用命令行工具详解》:本文主要介绍jvm调优常用命令行工具的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一 jinfo命令查看参数1.1 查看jvm参数二 jstack命令2.1 查看现场堆栈信息三 jstat 实时查看堆内存,gc情况3.1

MySQL使用binlog2sql工具实现在线恢复数据功能

《MySQL使用binlog2sql工具实现在线恢复数据功能》binlog2sql是大众点评开源的一款用于解析MySQLbinlog的工具,根据不同选项,可以得到原始SQL、回滚SQL等,下面我们就来... 目录背景目标步骤准备工作恢复数据结果验证结论背景生产数据库执行 SQL 脚本,一般会经过正规的审批