Akka-CQRS(16)- gRPC用JWT进行权限管理

   前面谈过gRPC的SSL/TLS安全机制,发现设置过程比较复杂:比如证书签名:需要服务端、客户端两头都设置等。想想实际上用JWT会更加便捷,而且更安全和功能强大,因为除JWT的加密签名之外还可以把私密的用户信息放在JWT里加密后在服务端和客户端之间传递。当然,最基本的是通过对JWT的验证机制可以控制客户端对某些功能的使用权限。

通过JWT实现gRPC的函数调用权限管理原理其实很简单:客户端首先从服务端通过身份验证获取JWT,然后在调用服务函数时把这个JWT同时传给服务端进行权限验证。客户端提交身份验证请求返回JWT可以用一个独立的服务函数实现,如下面.proto文件里的GetAuthToken:

message PBPOSCredential {string userid = 1;string password = 2;
}
message PBPOSToken {string jwt = 1;
}service SendCommand {rpc SingleResponse(PBPOSCommand) returns (PBPOSResponse) {};rpc GetTxnItems(PBPOSCommand) returns (stream PBTxnItem) {};rpc GetAuthToken(PBPOSCredential) returns (PBPOSToken) {};}

比较棘手的是如何把JWT从客户端传送至服务端,因为gRPC基本上骑劫了Request和Response。其中一个方法是通过Interceptor来截取Request的header即metadata。客户端将JWT写入metadata,服务端从metadata读取JWT。

我们先看看客户端的Interceptor设置和使用:

  class AuthClientInterceptor(jwt: String) extends ClientInterceptor {def interceptCall[ReqT, RespT](methodDescriptor: MethodDescriptor[ReqT, RespT], callOptions: CallOptions, channel: io.grpc.Channel): ClientCall[ReqT, RespT] =new ForwardingClientCall.SimpleForwardingClientCall[ReqT, RespT](channel.newCall(methodDescriptor, callOptions)) {override def start(responseListener: ClientCall.Listener[RespT], headers: Metadata): Unit = {headers.put(Key.of("jwt", Metadata.ASCII_STRING_MARSHALLER), jwt)super.start(responseListener, headers)}}}...val unsafeChannel = NettyChannelBuilder.forAddress("192.168.0.189",50051).negotiationType(NegotiationType.PLAINTEXT).build()val securedChannel = ClientInterceptors.intercept(unsafeChannel, new AuthClientInterceptor(jwt))val securedClient = SendCommandGrpc.blockingStub(securedChannel)val resp = securedClient.singleResponse(PBPOSCommand())

身份验证请求即JWT获取是不需要Interceptor的,所以要用没有Interceptor的unsafeChannel: 

    //build connection channelval unsafeChannel = NettyChannelBuilder.forAddress("192.168.0.189",50051).negotiationType(NegotiationType.PLAINTEXT).build()val authClient = SendCommandGrpc.blockingStub(unsafeChannel)val jwt = authClient.getAuthToken(PBPOSCredential(userid="johnny",password="p4ssw0rd")).jwtprintln(s"got jwt: $jwt")
 

JWT的构建和使用已经在前面的几篇博文里讨论过了: 

package com.datatech.authimport pdi.jwt._
import org.json4s.native.Json
import org.json4s._
import org.json4s.jackson.JsonMethods._
import pdi.jwt.algorithms._
import scala.util._object AuthBase {type UserInfo = Map[String, Any]case class AuthBase(algorithm: JwtAlgorithm = JwtAlgorithm.HMD5,secret: String = "OpenSesame",getUserInfo: (String,String) => Option[UserInfo] = null) {ctx =>def withAlgorithm(algo: JwtAlgorithm): AuthBase = ctx.copy(algorithm = algo)def withSecretKey(key: String): AuthBase = ctx.copy(secret = key)def withUserFunc(f: (String, String) => Option[UserInfo]): AuthBase = ctx.copy(getUserInfo = f)def authenticateToken(token: String): Option[String] =algorithm match {case algo: JwtAsymmetricAlgorithm =>Jwt.isValid(token, secret, Seq((algorithm.asInstanceOf[JwtAsymmetricAlgorithm]))) match {case true => Some(token)case _ => None}case _ =>Jwt.isValid(token, secret, Seq((algorithm.asInstanceOf[JwtHmacAlgorithm]))) match {case true => Some(token)case _ => None}}def getUserInfo(token: String): Option[UserInfo] = {algorithm match {case algo: JwtAsymmetricAlgorithm =>Jwt.decodeRawAll(token, secret, Seq(algorithm.asInstanceOf[JwtAsymmetricAlgorithm])) match {case Success(parts) => Some(((parse(parts._2).asInstanceOf[JObject]) \ "userinfo").values.asInstanceOf[UserInfo])case Failure(err) => None}case _ =>Jwt.decodeRawAll(token, secret, Seq(algorithm.asInstanceOf[JwtHmacAlgorithm])) match {case Success(parts) => Some(((parse(parts._2).asInstanceOf[JObject]) \ "userinfo").values.asInstanceOf[UserInfo])case Failure(err) => None}}}def issueJwt(userinfo: UserInfo): String = {val claims = JwtClaim() + Json(DefaultFormats).write(("userinfo", userinfo))Jwt.encode(claims, secret, algorithm)}}}

服务端Interceptor的构建和设置如下: 

abstract class FutureListener[Q](implicit ec: ExecutionContext) extends Listener[Q] {protected val delegate: Future[Listener[Q]]private val eventually = delegate.foreach _override def onComplete(): Unit = eventually { _.onComplete() }override def onCancel(): Unit = eventually { _.onCancel() }override def onMessage(message: Q): Unit = eventually { _ onMessage message }override def onHalfClose(): Unit = eventually { _.onHalfClose() }override def onReady(): Unit = eventually { _.onReady() }}object Keys {val AUTH_META_KEY: Metadata.Key[String] = of("jwt", Metadata.ASCII_STRING_MARSHALLER)val AUTH_CTX_KEY: Context.Key[String] = key("jwt")
}class AuthorizationInterceptor(implicit ec: ExecutionContext) extends ServerInterceptor {override def interceptCall[Q, R](call: ServerCall[Q, R],headers: Metadata,next: ServerCallHandler[Q, R]): Listener[Q] = {val prevCtx = Context.currentval jwt = headers.get(Keys.AUTH_META_KEY)println(s"!!!!!!!!!!! $jwt !!!!!!!!!!")new FutureListener[Q] {protected val delegate = Future {val nextCtx = prevCtx withValue (Keys.AUTH_CTX_KEY, jwt)Contexts.interceptCall(nextCtx, call, headers, next)}}}
}trait gRPCServer {def runServer(service: ServerServiceDefinition)(implicit actorSys: ActorSystem): Unit = {import actorSys.dispatcherval server = NettyServerBuilder.forPort(50051).addService(ServerInterceptors.intercept(service,new AuthorizationInterceptor)).build.start// make sure our server is stopped when jvm is shut downRuntime.getRuntime.addShutdownHook(new Thread() {override def run(): Unit = {server.shutdown()server.awaitTermination()}})}}

注意:客户端上传的request-header只能在构建server时接触到,在具体服务函数里是无法调用request-header的,但gRPC又一个结构Context可以在两个地方都能调用。所以,我们可以在构建server时把JWT从header搬到Context里。不过,千万注意这个Context的读写必须在同一个线程里。在服务端的Interceptor里我们把JWT从metadata里读出然后写入Context。在需要权限管理的服务函数里再从Context里读取JWT进行验证: 

   override def singleResponse(request: PBPOSCommand): Future[PBPOSResponse] = {val jwt = AUTH_CTX_KEY.getprintln(s"***********$jwt**************")val optUserInfo = authenticator.getUserInfo(jwt)val shopid = optUserInfo match {case Some(m) => m("shopid")case None => "invalid token!"}FastFuture.successful(PBPOSResponse(msg=s"shopid:$shopid"))}

JWT的构建也是一个服务函数: 

   val authenticator = new AuthBase().withAlgorithm(JwtAlgorithm.HS256).withSecretKey("OpenSesame").withUserFunc(getValidUser)override def getAuthToken(request: PBPOSCredential): Future[PBPOSToken] = {getValidUser(request.userid, request.password) match {case Some(userinfo) => FastFuture.successful(PBPOSToken(authenticator.issueJwt(userinfo)))case None => FastFuture.successful(PBPOSToken("Invalid Token!"))}}

还需要一个模拟的身份验证服务函数: 

package com.datatech.authobject MockUserAuthService {type UserInfo = Map[String,Any]case class User(username: String, password: String, userInfo: UserInfo)val validUsers = Seq(User("johnny", "p4ssw0rd",Map("shopid" -> "1101", "userid" -> "101")),User("tiger", "secret", Map("shopid" -> "1101" , "userid" -> "102")))def getValidUser(userid: String, pswd: String): Option[UserInfo] =validUsers.find(user => user.username == userid && user.password == pswd) match {case Some(user) => Some(user.userInfo)case _ => None}
}

下面是本次示范的源代码:

project/plugins.sbt

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.9")
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.2")
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.15")
addSbtPlugin("com.thesamet" % "sbt-protoc" % "0.99.21")
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.2")
libraryDependencies += "com.thesamet.scalapb" %% "compilerplugin" % "0.9.0-M6"

build.sbt

name := "grpc-jwt"version := "0.1"version := "0.1"scalaVersion := "2.12.8"scalacOptions += "-Ypartial-unification"val akkaversion = "2.5.23"libraryDependencies := Seq("com.typesafe.akka" %% "akka-cluster-metrics" % akkaversion,"com.typesafe.akka" %% "akka-cluster-sharding" % akkaversion,"com.typesafe.akka" %% "akka-persistence" % akkaversion,"com.lightbend.akka" %% "akka-stream-alpakka-cassandra" % "1.0.1","org.mongodb.scala" %% "mongo-scala-driver" % "2.6.0","com.lightbend.akka" %% "akka-stream-alpakka-mongodb" % "1.0.1","com.typesafe.akka" %% "akka-persistence-query" % akkaversion,"com.typesafe.akka" %% "akka-persistence-cassandra" % "0.97","com.datastax.cassandra" % "cassandra-driver-core" % "3.6.0","com.datastax.cassandra" % "cassandra-driver-extras" % "3.6.0","ch.qos.logback"  %  "logback-classic"   % "1.2.3","io.monix" %% "monix" % "3.0.0-RC2","org.typelevel" %% "cats-core" % "2.0.0-M1","io.grpc" % "grpc-netty" % scalapb.compiler.Version.grpcJavaVersion,"io.netty" % "netty-tcnative-boringssl-static" % "2.0.22.Final","com.thesamet.scalapb" %% "scalapb-runtime" % scalapb.compiler.Version.scalapbVersion % "protobuf","com.thesamet.scalapb" %% "scalapb-runtime-grpc" % scalapb.compiler.Version.scalapbVersion,"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","org.json4s" %% "json4s-jackson" % "3.6.7","org.json4s" %% "json4s-ext" % "3.6.7")// (optional) If you need scalapb/scalapb.proto or anything from
// google/protobuf/*.proto
//libraryDependencies += "com.thesamet.scalapb" %% "scalapb-runtime" % scalapb.compiler.Version.scalapbVersion % "protobuf"PB.targets in Compile := Seq(scalapb.gen() -> (sourceManaged in Compile).value
)enablePlugins(JavaAppPackaging)

main/protobuf/posmessages.proto

syntax = "proto3";import "google/protobuf/wrappers.proto";
import "google/protobuf/any.proto";
import "scalapb/scalapb.proto";option (scalapb.options) = {// use a custom Scala package name// package_name: "io.ontherocks.introgrpc.demo"// don't append file name to packageflat_package: true// generate one Scala file for all messages (services still get their own file)single_file: true// add imports to generated file// useful when extending traits or using custom types// import: "io.ontherocks.hellogrpc.RockingMessage"// code to put at the top of generated file// works only with `single_file: true`//preamble: "sealed trait SomeSealedTrait"
};package com.datatech.pos.messages;message PBVchState {      //单据状态string opr  = 1;    //收款员int64  jseq = 2;    //begin journal sequence for read-side replayint32  num  = 3;    //当前单号int32  seq  = 4;    //当前序号bool   void = 5;    //取消模式bool   refd = 6;    //退款模式bool   susp = 7;    //挂单bool   canc = 8;    //废单bool   due  = 9;    //当前余额string su   = 10;   //主管编号string mbr  = 11;   //会员号int32  mode = 12;   //当前操作流程:0=logOff, 1=LogOn, 2=Payment
}message PBTxnItem {       //交易记录string txndate    = 1;   //交易日期string txntime    = 2;   //录入时间string opr        = 3;   //操作员int32  num        = 4;   //销售单号int32  seq        = 5;   //交易序号int32  txntype    = 6;   //交易类型int32  salestype  = 7;   //销售类型int32  qty        = 8;   //交易数量int32  price      = 9;   //单价(分)int32  amount     = 10;  //码洋(分)int32  disc       = 11;  //折扣率 (%)int32  dscamt     = 12;  //折扣额:负值  net实洋 = amount + dscamtstring member     = 13;  //会员卡号string code       = 14;  //编号(商品、卡号...)string acct       = 15;  //账号string dpt        = 16;  //部类
}message PBPOSResponse {int32  sts                  = 1;string msg                  = 2;PBVchState voucher          = 3;repeated PBTxnItem txnitems   = 4;}message PBPOSCommand {string commandname = 1;string delimitedparams = 2;
}message PBPOSCredential {string userid = 1;string password = 2;
}
message PBPOSToken {string jwt = 1;
}service SendCommand {rpc SingleResponse(PBPOSCommand) returns (PBPOSResponse) {};rpc GetTxnItems(PBPOSCommand) returns (stream PBTxnItem) {};rpc GetAuthToken(PBPOSCredential) returns (PBPOSToken) {};}

gRPCServer.scala

package com.datatech.grpc.serverimport io.grpc.ServerServiceDefinition
import io.grpc.netty.NettyServerBuilder
import io.grpc.ServerInterceptors
import scala.concurrent._
import io.grpc.Context
import io.grpc.Contexts
import io.grpc.ServerCall
import io.grpc.ServerCallHandler
import io.grpc.ServerInterceptor
import io.grpc.Metadata
import io.grpc.Metadata.Key.of
import io.grpc.Context.key
import io.grpc.ServerCall.Listener
import akka.actor._abstract class FutureListener[Q](implicit ec: ExecutionContext) extends Listener[Q] {protected val delegate: Future[Listener[Q]]private val eventually = delegate.foreach _override def onComplete(): Unit = eventually { _.onComplete() }override def onCancel(): Unit = eventually { _.onCancel() }override def onMessage(message: Q): Unit = eventually { _ onMessage message }override def onHalfClose(): Unit = eventually { _.onHalfClose() }override def onReady(): Unit = eventually { _.onReady() }}object Keys {val AUTH_META_KEY: Metadata.Key[String] = of("jwt", Metadata.ASCII_STRING_MARSHALLER)val AUTH_CTX_KEY: Context.Key[String] = key("jwt")
}class AuthorizationInterceptor(implicit ec: ExecutionContext) extends ServerInterceptor {override def interceptCall[Q, R](call: ServerCall[Q, R],headers: Metadata,next: ServerCallHandler[Q, R]): Listener[Q] = {val prevCtx = Context.currentval jwt = headers.get(Keys.AUTH_META_KEY)println(s"!!!!!!!!!!! $jwt !!!!!!!!!!")new FutureListener[Q] {protected val delegate = Future {val nextCtx = prevCtx withValue (Keys.AUTH_CTX_KEY, jwt)Contexts.interceptCall(nextCtx, call, headers, next)}}}
}trait gRPCServer {def runServer(service: ServerServiceDefinition)(implicit actorSys: ActorSystem): Unit = {import actorSys.dispatcherval server = NettyServerBuilder.forPort(50051).addService(ServerInterceptors.intercept(service,new AuthorizationInterceptor)).build.start// make sure our server is stopped when jvm is shut downRuntime.getRuntime.addShutdownHook(new Thread() {override def run(): Unit = {server.shutdown()server.awaitTermination()}})}}

POSServices.scala

package com.datatech.pos.service
import com.datatech.grpc.server.Keys._
import akka.http.scaladsl.util.FastFuture
import com.datatech.pos.messages._
import com.datatech.grpc.server._
import com.datatech.auth.MockUserAuthService._import scala.concurrent.Future
import com.datatech.auth.AuthBase._
import pdi.jwt._
import akka.actor._
import io.grpc.stub.StreamObserverobject POSServices extends gRPCServer {type UserInfo = Map[String, Any]class POSServices extends SendCommandGrpc.SendCommand {val authenticator = new AuthBase().withAlgorithm(JwtAlgorithm.HS256).withSecretKey("OpenSesame").withUserFunc(getValidUser)override def getTxnItems(request: PBPOSCommand, responseObserver: StreamObserver[PBTxnItem]): Unit = ???override def singleResponse(request: PBPOSCommand): Future[PBPOSResponse] = {val jwt = AUTH_CTX_KEY.getprintln(s"***********$jwt**************")val optUserInfo = authenticator.getUserInfo(jwt)val shopid = optUserInfo match {case Some(m) => m("shopid")case None => "invalid token!"}FastFuture.successful(PBPOSResponse(msg=s"shopid:$shopid"))}override def getAuthToken(request: PBPOSCredential): Future[PBPOSToken] = {getValidUser(request.userid, request.password) match {case Some(userinfo) => FastFuture.successful(PBPOSToken(authenticator.issueJwt(userinfo)))case None => FastFuture.successful(PBPOSToken("Invalid Token!"))}}}def main(args: Array[String]) = {implicit val system = ActorSystem("grpc-system")val svc = SendCommandGrpc.bindService(new POSServices, system.dispatcher)runServer(svc)}
}

AuthBase.scala

package com.datatech.authimport pdi.jwt._
import org.json4s.native.Json
import org.json4s._
import org.json4s.jackson.JsonMethods._
import pdi.jwt.algorithms._
import scala.util._object AuthBase {type UserInfo = Map[String, Any]case class AuthBase(algorithm: JwtAlgorithm = JwtAlgorithm.HMD5,secret: String = "OpenSesame",getUserInfo: (String,String) => Option[UserInfo] = null) {ctx =>def withAlgorithm(algo: JwtAlgorithm): AuthBase = ctx.copy(algorithm = algo)def withSecretKey(key: String): AuthBase = ctx.copy(secret = key)def withUserFunc(f: (String, String) => Option[UserInfo]): AuthBase = ctx.copy(getUserInfo = f)def authenticateToken(token: String): Option[String] =algorithm match {case algo: JwtAsymmetricAlgorithm =>Jwt.isValid(token, secret, Seq((algorithm.asInstanceOf[JwtAsymmetricAlgorithm]))) match {case true => Some(token)case _ => None}case _ =>Jwt.isValid(token, secret, Seq((algorithm.asInstanceOf[JwtHmacAlgorithm]))) match {case true => Some(token)case _ => None}}def getUserInfo(token: String): Option[UserInfo] = {algorithm match {case algo: JwtAsymmetricAlgorithm =>Jwt.decodeRawAll(token, secret, Seq(algorithm.asInstanceOf[JwtAsymmetricAlgorithm])) match {case Success(parts) => Some(((parse(parts._2).asInstanceOf[JObject]) \ "userinfo").values.asInstanceOf[UserInfo])case Failure(err) => None}case _ =>Jwt.decodeRawAll(token, secret, Seq(algorithm.asInstanceOf[JwtHmacAlgorithm])) match {case Success(parts) => Some(((parse(parts._2).asInstanceOf[JObject]) \ "userinfo").values.asInstanceOf[UserInfo])case Failure(err) => None}}}def issueJwt(userinfo: UserInfo): String = {val claims = JwtClaim() + Json(DefaultFormats).write(("userinfo", userinfo))Jwt.encode(claims, secret, algorithm)}}}

POSClient.scala

package com.datatech.pos.clientimport com.datatech.pos.messages.{PBPOSCommand, PBPOSCredential, SendCommandGrpc}
import io.grpc.stub.StreamObserver
import io.grpc.netty.{ NegotiationType, NettyChannelBuilder}
import io.grpc.CallOptions
import io.grpc.ClientCall
import io.grpc.ClientInterceptor
import io.grpc.ForwardingClientCall
import io.grpc.Metadata
import io.grpc.Metadata.Key
import io.grpc.MethodDescriptor
import io.grpc.ClientInterceptorsobject POSClient {class AuthClientInterceptor(jwt: String) extends ClientInterceptor {def interceptCall[ReqT, RespT](methodDescriptor: MethodDescriptor[ReqT, RespT], callOptions: CallOptions, channel: io.grpc.Channel): ClientCall[ReqT, RespT] =new ForwardingClientCall.SimpleForwardingClientCall[ReqT, RespT](channel.newCall(methodDescriptor, callOptions)) {override def start(responseListener: ClientCall.Listener[RespT], headers: Metadata): Unit = {headers.put(Key.of("jwt", Metadata.ASCII_STRING_MARSHALLER), jwt)super.start(responseListener, headers)}}}def main(args: Array[String]): Unit = {//build connection channelval unsafeChannel = NettyChannelBuilder.forAddress("192.168.0.189",50051).negotiationType(NegotiationType.PLAINTEXT).build()val authClient = SendCommandGrpc.blockingStub(unsafeChannel)val jwt = authClient.getAuthToken(PBPOSCredential(userid="johnny",password="p4ssw0rd")).jwtprintln(s"got jwt: $jwt")val securedChannel = ClientInterceptors.intercept(unsafeChannel, new AuthClientInterceptor(jwt))val securedClient = SendCommandGrpc.blockingStub(securedChannel)val resp = securedClient.singleResponse(PBPOSCommand())println(s"secured response: $resp")// wait for async executionscala.io.StdIn.readLine()}}

 

转载于:https://www.cnblogs.com/tiger-xc/p/11188900.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/410690.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

excel转html放在tomcat,Excel转web!把excel在线打开??

如题:把B/S下服务器上的excel,在客户机上用浏览器(web格式)打开,不想用先下载的方式实现。求实现办法?lxbzmy 去看看163 金山在线办公怎么实现的。思路:将excel打开并解析成html格式。你只需要保证足够的兼容性就可以了…

诺基亚2亿欧元建越南新工场

诺基亚昨天颁布发表将会在越南北部河内左近设立扶植一个新的装配厂,方案于2012年尾投入运用,初期投资达到2亿欧元,约合18.3亿人夷易近币。诺基亚目下当今在世界上拥有10个工场,新厂将会进入到诺基亚的举世制造搜集,为他…

Android 一直往文件写数据_( 十 ) 小众但好用:通过 Google drive 备份与同步 Keepass 数据库...

之前有提到过,keepass 的数据库是加密保存在本地的。而且 keepass 又是多平台免费的密码管理软件。所以如果需要实现 keepass 在多平台读取同一个数据库,我们需要自己搭一个服务器才怪!搭服务器太麻烦,还要额外支出费用&#xff0…

mysql操作数据库

转载于:https://www.cnblogs.com/sunpxit/p/11189089.html

[html] 请说说你在写布局时对于浏览器兼容性的感受或总结

[html] 请说说你在写布局时对于浏览器兼容性的感受或总结 要選用某種方式,table,flex,float,定位來完成某一部分的佈局時,不要忘了是要跑在什麼瀏覽器以及設備上,比如定位在ios上是有些坑的,flex兼容性也並…

win7锁定计算机会断网吗,win7系统如何设置电脑定时断网

有些win7系统用户在使用电脑过程中,想要给电脑设置定时断网,但是却不知道要怎么操作,其实我们可以通过系统自带的任务计划功能来进行操作,该怎么操作呢,本教程就给大家讲解一下win7系统设置电脑定时断网的详细步骤。1、…

[浏览器]Apple之Safari 5.0.4

官方主页:http://www.apple.com/safari/ 2010-03-10 发布 Safari 5.0.4版本。 Safari 5.0.4 for Windows(28.2 MB) Safari 5.0.4 Leopard for Mac OS X(26.7 MB) Safari 5.0.4 Snow Leopard for Mac OS X(36.0 MB)转载于:https://www.cnblogs.com/iocn/archive/2011…

[html] H5的video可以播放哪些类型的文件?可以播放rtsp流吗?

[html] H5的video可以播放哪些类型的文件?可以播放rtsp流吗? Ogg、MPEG4、WebM,不能播放rtsp个人简介 我是歌谣,欢迎和大家一起交流前后端知识。放弃很容易, 但坚持一定很酷。欢迎大家一起讨论 主目录 与歌谣一起通关前端面试…

回文_Manacher算法

算法简介:算法的目的是在O(n)的时间复杂度内找到一个字符串中各个字母所在的最大长度的回文串。 此算法用到了一个Rad[]数组的定义,Rad[i]表示回文的半径,即最大的j满足str[i-j1...i] str[i1...ij]。 我们的工作就是把全部的Rad[]求出来。 有两个结论: …

linux 查当前pid_杀死僵尸进程,你需要这些神奇高效的Linux命令行

Linux 高手,其实都是玩儿命令行很熟练的人。命令行的学习捷径Linux 命令有许多强大的功能:从简单的磁盘操作、文件存取,到进行复杂的多媒体图像和流媒体文件的制作,都离不开命令行。在 Linux 的学习中,新手都会遇到这么…

某8位微型计算机地址码为18位,2015广东海洋大学计算机组成原理试题

一、选择题1、在定点运算器中,无论采用双符号位还是单符号位,必须有___溢出判断电路___ ,它一般采用来____异或门__实现。2、同步控制是___由统一时序信号控制的方式___。3、在微型机系统中,外围设备通过__适配器____与主机的系统…

nodejs: mkdirs 递归创建目录

nodejs里面的所有文件操作我们很显然地都会选择异步方式。 nodejs对于文件的基本操作果然是很基本的。例如我们想创建一个二级目录:/abc/def,使用fs.mkdir必须先判断/abc,存在,才继续创建/abc/def。这样几个一层层的callback&…

3d000: no database selected_No.[C9]020

No.[C9]-020”Youre Not Alone,never alone。“别难过,别害怕,这个世界还有那么多爱你的人,和支持你的人,生活可能没有那么趁人心意,每个人都如此,别灰心,你并不孤单。(愿我的好朋友…

js实现树形结构化

我是歌谣 放弃很容易 但是坚持一定很酷 微信公众号关注小歌谣 日常分享前后端知识 前言 今天来讲一下平常我们做权限系统中的树形结构知识 就是如何把对应关系的结构数据转换为可以一个树形结构 const data [{id: 2,pid: 0,path: /course,name: Course,title: 课程管理},{id…

初中计算机课教什么时候,初中计算机教学课程教学方法探讨

黄其浩摘要:初中计算机课程的教学工作对于提升学生的综合素质有着十分重要的作用,本文深入分析了初中计算机教学中存在的问题,并且从两个角度提出了初中计算机教学中的解决问题的思路,为初中计算机课程教学工作提出了有效的管理方…

44. Wildcard Matching

description: 匹配字符串,具体看例子 Given an input string (s) and a pattern (p), implement wildcard pattern matching with support for ? and *. ? Matches any single character. * Matches any sequence of characters (including the empty sequence). …

Android makefile

示例 /** Android.mk文件必须从这一行定义开始,my-dir宏是build系统自带的函数,返回当前目录 */ LOCAL_PATH : $(call my-dir) /** CLEAR_VARS是build系统自带的变量,指示makefile脚本清除所有LOCAL_XXX变量,除了LOCAL_P…

viewer.js实现预览效果

我是歌谣 放弃很容易 但是坚持一定很酷 微信公众号关注小歌谣 日常分享前后端知识 前言 最近涉及一个移动端项目 需要把其中的图片变成可预览的图片 听学弟说 可以利用viewer进行实现 首先 我们需要做的就是先写一个简单demo 先实现其中的效果 实现效果 代码 <!doct…

android 论坛_如何看待百度android吧萎靡现象与吧主的无所作为

百度android吧当初被誉为贴吧中的机锋论坛&#xff0c;现如今缺惨不忍睹&#xff0c;这一切究竟是百度资本家的阴谋还是吧主个人无所作为&#xff1f;然而就此事本人与android吧吧主展开讨论以及对其能力的质疑…这本来只是对android吧吧主随意加精普通的质问…然而该吧主却说我…

计算机控制系统脉冲传递函数,第6.2课 (理解)计算机控制系统理论基础—脉冲传递函数.pdf...

第六章计算机控制系统理论基础计算机控制系统理论基础本章结构• 6.1 概述• 6.2 采样与采样定理• 6.3 信号的恢复与保持• 6.4 Z变换和Z反变换• 6.5 脉冲传递函数6.4 Z变换和Z反变换1 Z变换的由来• 在 《自动控制原理》中&#xff0c;线性连续系统的动态特性可以由微分方…