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

相关文章

如何通过try-catch判断数据库唯一键字段是否重复

《如何通过try-catch判断数据库唯一键字段是否重复》在MyBatis+MySQL中,通过try-catch捕获唯一约束异常可避免重复数据查询,优点是减少数据库交互、提升并发安全,缺点是异常处理开... 目录1、原理2、怎么理解“异常走的是数据库错误路径,开销比普通逻辑分支稍高”?1. 普通逻辑分支 v

Python与MySQL实现数据库实时同步的详细步骤

《Python与MySQL实现数据库实时同步的详细步骤》在日常开发中,数据同步是一项常见的需求,本篇文章将使用Python和MySQL来实现数据库实时同步,我们将围绕数据变更捕获、数据处理和数据写入这... 目录前言摘要概述:数据同步方案1. 基本思路2. mysql Binlog 简介实现步骤与代码示例1

Python实战之SEO优化自动化工具开发指南

《Python实战之SEO优化自动化工具开发指南》在数字化营销时代,搜索引擎优化(SEO)已成为网站获取流量的重要手段,本文将带您使用Python开发一套完整的SEO自动化工具,需要的可以了解下... 目录前言项目概述技术栈选择核心模块实现1. 关键词研究模块2. 网站技术seo检测模块3. 内容优化分析模

使用shardingsphere实现mysql数据库分片方式

《使用shardingsphere实现mysql数据库分片方式》本文介绍如何使用ShardingSphere-JDBC在SpringBoot中实现MySQL水平分库,涵盖分片策略、路由算法及零侵入配置... 目录一、ShardingSphere 简介1.1 对比1.2 核心概念1.3 Sharding-Sp

Go语言连接MySQL数据库执行基本的增删改查

《Go语言连接MySQL数据库执行基本的增删改查》在后端开发中,MySQL是最常用的关系型数据库之一,本文主要为大家详细介绍了如何使用Go连接MySQL数据库并执行基本的增删改查吧... 目录Go语言连接mysql数据库准备工作安装 MySQL 驱动代码实现运行结果注意事项Go语言执行基本的增删改查准备工作

MySQL 数据库表操作完全指南:创建、读取、更新与删除实战

《MySQL数据库表操作完全指南:创建、读取、更新与删除实战》本文系统讲解MySQL表的增删查改(CURD)操作,涵盖创建、更新、查询、删除及插入查询结果,也是贯穿各类项目开发全流程的基础数据交互原... 目录mysql系列前言一、Create(创建)并插入数据1.1 单行数据 + 全列插入1.2 多行数据

MySQL 数据库表与查询操作实战案例

《MySQL数据库表与查询操作实战案例》本文将通过实际案例,详细介绍MySQL中数据库表的设计、数据插入以及常用的查询操作,帮助初学者快速上手,感兴趣的朋友跟随小编一起看看吧... 目录mysql 数据库表操作与查询实战案例项目一:产品相关数据库设计与创建一、数据库及表结构设计二、数据库与表的创建项目二:员

Go语言使用net/http构建一个RESTful API的示例代码

《Go语言使用net/http构建一个RESTfulAPI的示例代码》Go的标准库net/http提供了构建Web服务所需的强大功能,虽然众多第三方框架(如Gin、Echo)已经封装了很多功能,但... 目录引言一、什么是 RESTful API?二、实战目标:用户信息管理 API三、代码实现1. 用户数据

MySQL慢查询工具的使用小结

《MySQL慢查询工具的使用小结》使用MySQL的慢查询工具可以帮助开发者识别和优化性能不佳的SQL查询,本文就来介绍一下MySQL的慢查询工具,具有一定的参考价值,感兴趣的可以了解一下... 目录一、启用慢查询日志1.1 编辑mysql配置文件1.2 重启MySQL服务二、配置动态参数(可选)三、分析慢查

MybatisPlus中removeById删除数据库未变解决方案

《MybatisPlus中removeById删除数据库未变解决方案》MyBatisPlus中,removeById需实体类标注@TableId注解以识别数据库主键,若字段名不一致,应通过value属... 目录MyBATisPlus中removeBypythonId删除数据库未变removeById(Se