├── .gitignore ├── README.md └── slick-postgresql-redis ├── .gitignore ├── README.md ├── project ├── Build.scala ├── BuildSettings.scala ├── Dependencies.scala ├── build.properties └── plugins.sbt ├── ps-business └── src │ └── main │ └── scala │ └── me │ └── yangbajing │ └── ps │ └── business │ └── service │ ├── CacheService.scala │ └── UserService.scala ├── ps-data └── src │ ├── main │ ├── resources │ │ └── reference.conf │ └── scala │ │ └── me │ │ └── yangbajing │ │ └── ps │ │ └── data │ │ ├── PsSqlDriver.scala │ │ ├── domain │ │ ├── CaseClass.scala │ │ ├── OwnerToken.scala │ │ └── RegisterParam.scala │ │ ├── implicits │ │ ├── JsonImplicits.scala │ │ └── RecordJsonImplicits.scala │ │ ├── record │ │ ├── Credential.scala │ │ ├── DatabaseModule.scala │ │ ├── Schemas.scala │ │ └── User.scala │ │ └── repo │ │ └── UserRepo.scala │ └── test │ └── scala │ └── me │ └── yangbajing │ └── ps │ └── data │ ├── domain │ └── CaseClassTest.scala │ └── record │ └── SchemasTest.scala ├── ps-util └── src │ ├── main │ ├── resources │ │ └── reference.conf │ └── scala │ │ └── me │ │ └── yangbajing │ │ └── ps │ │ ├── setting │ │ └── SettingComponent.scala │ │ ├── types │ │ └── OwnerStatus.scala │ │ └── util │ │ ├── Constants.scala │ │ ├── RandomUtils.scala │ │ ├── Utils.scala │ │ └── enums │ │ └── JsValueEnumUtils.scala │ └── test │ └── scala │ └── me │ └── yangbajing │ ├── UnitPlaySpec.scala │ └── UnitSpec.scala └── ps-web ├── app ├── controllers │ ├── BaseController.scala │ ├── PageController.scala │ ├── UserAction.scala │ ├── WebTools.scala │ └── inapi │ │ └── UserController.scala ├── utils │ ├── ErrorHandler.scala │ └── Filters.scala └── views │ ├── _layout.scala.html │ ├── account │ ├── signin.scala.html │ └── signup.scala.html │ └── home │ └── main.scala.html ├── conf ├── application.conf ├── logback.xml └── routes └── static ├── README ├── gulpfile.js ├── package.json └── src └── site ├── img └── favicon.jpg ├── js ├── sign.js └── static.js └── scss ├── bootstrap ├── _alerts.scss ├── _badges.scss ├── _breadcrumbs.scss ├── _button-groups.scss ├── _buttons.scss ├── _carousel.scss ├── _close.scss ├── _code.scss ├── _component-animations.scss ├── _dropdowns.scss ├── _forms.scss ├── _grid.scss ├── _input-groups.scss ├── _jumbotron.scss ├── _labels.scss ├── _list-group.scss ├── _media.scss ├── _messager.scss ├── _mixins.scss ├── _modals.scss ├── _navbar.scss ├── _navs.scss ├── _normalize.scss ├── _pager.scss ├── _pagination.scss ├── _panels.scss ├── _popovers.scss ├── _print.scss ├── _progress-bars.scss ├── _responsive-embed.scss ├── _responsive-utilities.scss ├── _scaffolding.scss ├── _tables.scss ├── _theme.scss ├── _thumbnails.scss ├── _tooltip.scss ├── _type.scss ├── _utilities.scss ├── _variables.scss ├── _wells.scss ├── bootstrap.scss └── mixins │ ├── _alerts.scss │ ├── _background-variant.scss │ ├── _border-radius.scss │ ├── _buttons.scss │ ├── _center-block.scss │ ├── _clearfix.scss │ ├── _forms.scss │ ├── _gradients.scss │ ├── _grid-framework.scss │ ├── _grid.scss │ ├── _hide-text.scss │ ├── _image.scss │ ├── _labels.scss │ ├── _list-group.scss │ ├── _nav-divider.scss │ ├── _nav-vertical-align.scss │ ├── _opacity.scss │ ├── _pagination.scss │ ├── _panels.scss │ ├── _progress-bar.scss │ ├── _reset-filter.scss │ ├── _resize.scss │ ├── _responsive-visibility.scss │ ├── _size.scss │ ├── _tab-focus.scss │ ├── _table-row.scss │ ├── _text-emphasis.scss │ ├── _text-overflow.scss │ └── _vendor-prefixes.scss └── static.scss /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | .travis.yml 4 | 5 | node_modules/ 6 | 7 | # Ignore Play! working directory # 8 | bin/ 9 | /db 10 | /public/ 11 | .eclipse 12 | /lib/ 13 | /logs/ 14 | /modules 15 | /project/project 16 | /project/target 17 | target/ 18 | tmp/ 19 | test-result 20 | server.pid 21 | *.iml 22 | *.eml 23 | /dist/ 24 | .cache 25 | /ps-web/logs/ 26 | /ps-web/public/ 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # play-seed 2 | 3 | - 作者:杨景(羊八井) 4 | - 邮箱:yangbajing@gmail.com 5 | - 主页:http://www.yangbajing.me 6 | - 微博:http://weibo.com/yangbajing 7 | 8 | 9 | ## 示例工程 10 | 11 | - [slick-postgresql-redis](slick-postgresql-redis) 12 | 13 | -------------------------------------------------------------------------------- /slick-postgresql-redis/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | .travis.yml 4 | 5 | node_modules/ 6 | 7 | # Ignore Play! working directory # 8 | bin/ 9 | /db 10 | /public/ 11 | .eclipse 12 | /lib/ 13 | /logs/ 14 | /modules 15 | /project/project 16 | /project/target 17 | target/ 18 | tmp/ 19 | test-result 20 | server.pid 21 | *.iml 22 | *.eml 23 | /dist/ 24 | .cache 25 | /ps-web/logs/ 26 | /ps-web/public/ 27 | -------------------------------------------------------------------------------- /slick-postgresql-redis/README.md: -------------------------------------------------------------------------------- 1 | # play-seed 2 | 3 | - 作者:杨景(羊八井) 4 | - 邮箱:yangbajing@gmail.com 5 | - 主页:http://www.yangbajing.me 6 | - 微博:http://weibo.com/yangbajing 7 | 8 | - play 2.5 9 | - slick 3.1 10 | - slick-pg 0.14 11 | - scalatest 2.6 12 | - gulp 13 | - redisreact 3.1 14 | - postgresql 9.4 15 | 16 | 实现了简单的用户认证和`session`控制功能。采用`redis`来保存`session`值。自定义`play action`和`play filter`来判断`session`有效性和重设`session`。`session`使用`cookie`实现。 17 | 18 | 数据库层,使用`slick`和`slick-pg`来连接`PostgreSQL`。由`typesafe config`来定义连接参数。 19 | `[ps-util/src/main/resources/reference.conf](https://github.com/yangbajing/play-seed/blob/master/ps-util/src/main/resources/reference.conf)`。 20 | 21 | 定义play监听端口:`PlayKeys.playDefaultPort := 58082`。 22 | 23 | 修改相关配置后,执行如下命令生成数据库实例并启动服务: 24 | 25 | ``` 26 | ./sbt 27 | play-seed-root > ps-data/testOnly me.yangbajing.ps.data.record.SchemasTest 28 | play-seed-root > project ps-web 29 | [ps-web] $ ps-web/run 30 | ``` 31 | 32 | 前端代码使用gulp管理,代码放在`static`目录。编译后的静态文件将保存在`public` 33 | 34 | ``` 35 | npm install 36 | npm run build 37 | ``` 38 | 39 | 访问:[http://localhost:58082/account/signup](http://localhost:58082/account/signup) 注册账号 40 | -------------------------------------------------------------------------------- /slick-postgresql-redis/project/Build.scala: -------------------------------------------------------------------------------- 1 | import _root_.sbt.Keys._ 2 | import _root_.sbt._ 3 | import play.sbt.Play.autoImport._ 4 | import play.sbt.PlayScala 5 | import play.twirl.sbt.Import.TwirlKeys 6 | 7 | object Build extends Build { 8 | 9 | import BuildSettings._ 10 | import Dependencies._ 11 | 12 | override lazy val settings = super.settings :+ { 13 | shellPrompt := (s => Project.extract(s).currentProject.id + " > ") 14 | } 15 | 16 | /////////////////////////////////////////////////////////////// 17 | // playSeed project 18 | /////////////////////////////////////////////////////////////// 19 | lazy val playSeed = Project("play-seed-root", file(".")) 20 | .aggregate(psWeb, psBusiness, psData, psUtil) 21 | 22 | /////////////////////////////////////////////////////////////// 23 | // projects 24 | /////////////////////////////////////////////////////////////// 25 | lazy val psWeb = Project("ps-web", file("ps-web")) 26 | .enablePlugins(PlayScala) 27 | .dependsOn(psBusiness, psData) 28 | .settings(basicSettings: _*) 29 | .settings( 30 | TwirlKeys.templateImports ++= Seq("me.yangbajing.ps.data.record._", "me.yangbajing.ps.data.domain._"), 31 | PlayKeys.playDefaultPort := 58082, 32 | libraryDependencies ++= ( 33 | __compile(_scalaModules) ++ 34 | __compile(_scalaLogging) ++ 35 | __compile(json) ++ 36 | __compile(ws) ++ 37 | __compile(_slf4j) ++ 38 | __compile(_logback)) 39 | ) 40 | 41 | lazy val psBusiness = Project("ps-business", file("ps-business")) 42 | .dependsOn(psData, psUtil % DependsConfigure) 43 | .settings(basicSettings: _*) 44 | .settings( 45 | libraryDependencies ++= ( 46 | __provided(_play) ++ 47 | __compile(_redisClient))) 48 | 49 | lazy val psData = Project("ps-data", file("ps-data")) 50 | .dependsOn(psUtil % DependsConfigure) 51 | .settings(basicSettings: _*) 52 | .settings( 53 | libraryDependencies ++= ( 54 | __provided(json) ++ 55 | __provided(ws) ++ 56 | __provided(_play) ++ 57 | __compile(_postgresql) ++ 58 | __compile(_hikariCP) ++ 59 | __compile(_slick) ++ 60 | __compile(_slickPg) ++ 61 | __compile(_playSlick))) 62 | 63 | lazy val psUtil = Project("ps-util", file("ps-util")) 64 | .settings(basicSettings: _*) 65 | .settings( 66 | libraryDependencies ++= ( 67 | __provided(_commonCodecs) ++ 68 | __provided(_commonsLang3) ++ 69 | __provided(json) ++ 70 | __provided(ws) ++ 71 | __provided(_play) ++ 72 | __compile(_bouncycastle) ++ 73 | __compile(_akkaActor) ++ 74 | __compile(_akkaSlf4j))) 75 | 76 | } 77 | 78 | -------------------------------------------------------------------------------- /slick-postgresql-redis/project/BuildSettings.scala: -------------------------------------------------------------------------------- 1 | import Dependencies._ 2 | import sbt.Keys._ 3 | import sbt._ 4 | 5 | object BuildSettings { 6 | 7 | val DependsConfigure = "compile->compile;test->test" 8 | 9 | def basicSettings = Seq( 10 | version := "2.0.0", 11 | homepage := Some(new URL("https://github.com/yangbajing/play-seed")), 12 | organization := "me.yangbajing", 13 | organizationHomepage := Some(new URL("http://www.yangbajing.me")), 14 | startYear := Some(2015), 15 | scalaVersion := "2.11.8", 16 | scalacOptions := Seq( 17 | "-encoding", "utf8", 18 | //"-Ylog-classpath", 19 | "-feature", 20 | "-unchecked", 21 | "-deprecation"/*, 22 | "-explaintypes", 23 | "-Yno-adapted-args", 24 | "-Ywarn-dead-code", 25 | "-Ywarn-unused"*/ 26 | ), 27 | javacOptions := Seq( 28 | "-encoding", "utf8", 29 | "-deprecation", 30 | "-Xlint:unchecked", 31 | "-Xlint:deprecation" 32 | ), 33 | libraryDependencies ++= ( 34 | __compile(_slf4j) ++ 35 | __compile(_logback) ++ 36 | __compile(_typesafeConfig) ++ 37 | __compile(_scalaLogging) ++ 38 | __compile(_scala) ++ 39 | __test(_playTest) ++ 40 | __test(_scalatestplusPlay) ++ 41 | __test(_scalatest)), 42 | libraryDependencies ~= { 43 | _ map { 44 | case m if m.organization == "com.typesafe.play" => 45 | m.exclude("org.scala-lang", "scala-library"). 46 | // exclude("commons-logging", "commons-logging"). 47 | // exclude("com.google.guava", "guava"). 48 | // exclude("org.asynchttpclient", "async-http-client"). 49 | // exclude("com.typesafe.akka", "akka-actor"). 50 | // exclude("com.typesafe.akka", "akka-slf4j"). 51 | // exclude("org.scala-lang", "scala-compiler"). 52 | // exclude("org.scala-lang", "scala-reflect"). 53 | exclude("org.scala-lang.modules", "scala-xml"). 54 | exclude("org.scala-lang.modules", "scala-parser-combinators"). 55 | excludeAll() 56 | case m => m 57 | } 58 | }, 59 | sources in(Compile, doc) := Seq.empty, 60 | publishArtifact in(Compile, packageDoc) := false, 61 | publishArtifact in packageDoc := false, 62 | fork := true 63 | ) 64 | 65 | lazy val noPublishing = Seq( 66 | publish := (), 67 | publishLocal := () 68 | ) 69 | 70 | } 71 | 72 | -------------------------------------------------------------------------------- /slick-postgresql-redis/project/Dependencies.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import play.core.PlayVersion 3 | 4 | object Dependencies { 5 | def __compile(dep: ModuleID): Seq[ModuleID] = __compile(Seq(dep)) 6 | 7 | def __compile(deps: Seq[ModuleID]): Seq[ModuleID] = deps map (_ % "compile") 8 | 9 | def __provided(dep: ModuleID): Seq[ModuleID] = __provided(Seq(dep)) 10 | 11 | def __provided(deps: Seq[ModuleID]): Seq[ModuleID] = deps map (_ % "provided") 12 | 13 | def __test(dep: ModuleID): Seq[ModuleID] = __test(Seq(dep)) 14 | 15 | def __test(deps: Seq[ModuleID]): Seq[ModuleID] = deps map (_ % "test") 16 | 17 | def __runtime(dep: ModuleID): Seq[ModuleID] = __runtime(Seq(dep)) 18 | 19 | def __runtime(deps: Seq[ModuleID]): Seq[ModuleID] = deps map (_ % "runtime") 20 | 21 | def __container(dep: ModuleID): Seq[ModuleID] = __container(Seq(dep)) 22 | 23 | def __container(deps: Seq[ModuleID]): Seq[ModuleID] = deps map (_ % "container") 24 | 25 | val _scalaModules = Seq( 26 | "org.scala-lang.modules" %% "scala-xml" % "1.0.5", 27 | "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.4" 28 | ).map(_.exclude("org.scala-lang", "scala-library"). 29 | exclude("org.scala-lang", "scala-compiler"). 30 | exclude("org.scala-lang", "scala-reflect")) 31 | 32 | private val verScala = "2.11.8" 33 | val _scala = Seq( 34 | "org.scala-lang" % "scala-library" % verScala, 35 | "org.scala-lang" % "scala-compiler" % verScala, 36 | "org.scala-lang" % "scala-reflect" % verScala 37 | ) 38 | 39 | private val verAkka = "2.4.8" 40 | val _akkaActor = "com.typesafe.akka" %% "akka-actor" % verAkka 41 | val _akkaRemote = "com.typesafe.akka" %% "akka-remote" % verAkka 42 | val _akkaSlf4j = "com.typesafe.akka" %% "akka-slf4j" % verAkka 43 | val _akkaTestkit = Seq( 44 | "com.typesafe.akka" %% "akka-testkit" % verAkka) 45 | 46 | val _akkaStream = "com.typesafe.akka" %% "akka-stream" % verAkka 47 | 48 | val slickVersion = "3.1.1" 49 | val _slick = Seq( 50 | "com.typesafe.slick" %% "slick" % slickVersion, 51 | ("com.typesafe.slick" %% "slick-hikaricp" % slickVersion).exclude("com.zaxxer", "HikariCP-java6") 52 | ) 53 | 54 | val verSlickPg = "0.14.2" 55 | val _slickPg = Seq( 56 | "com.github.tminglei" %% "slick-pg" % verSlickPg, 57 | "com.github.tminglei" %% "slick-pg_date2" % verSlickPg, 58 | ("com.github.tminglei" %% "slick-pg_play-json" % verSlickPg).exclude("com.typesafe.play", "play-json_2.11") 59 | ) 60 | 61 | val _play = "com.typesafe.play" %% "play" % PlayVersion.current 62 | val _playTest = "com.typesafe.play" %% "play-test" % PlayVersion.current 63 | val _scalatestplusPlay = ("org.scalatestplus.play" %% "scalatestplus-play" % "1.5.1").excludeAll(ExclusionRule("com.typesafe.play")) 64 | 65 | val _playSlick = ("com.typesafe.play" %% "play-slick" % "2.0.2").excludeAll(ExclusionRule("com.typesafe.slick")) 66 | 67 | val _scalatest = "org.scalatest" %% "scalatest" % "2.2.6" 68 | 69 | val _redisClient = ("net.debasishg" %% "redisclient" % "3.1").excludeAll(ExclusionRule("com.typesafe.akka")) 70 | 71 | val _typesafeConfig = "com.typesafe" % "config" % "1.3.0" 72 | 73 | val _scalaLogging = ("com.typesafe.scala-logging" %% "scala-logging" % "3.4.0").exclude("org.scala-lang", "scala-library").exclude("org.scala-lang", "scala-reflect") 74 | 75 | val _slf4j = "org.slf4j" % "slf4j-api" % "1.7.12" 76 | 77 | val _logback = "ch.qos.logback" % "logback-classic" % "1.1.3" 78 | 79 | val _bouncycastle = "org.bouncycastle" % "bcprov-jdk15on" % "1.54" 80 | 81 | val _commonsLang3 = "org.apache.commons" % "commons-lang3" % "3.4" 82 | 83 | val _commonCodecs = "commons-codec" % "commons-codec" % "1.10" 84 | 85 | val _postgresql = "org.postgresql" % "postgresql" % "9.4.1209" 86 | 87 | val _hikariCP = "com.zaxxer" % "HikariCP" % "2.4.6" 88 | 89 | val _patchca = "com.github.bingoohuang" % "patchca" % "0.0.1" 90 | } 91 | 92 | -------------------------------------------------------------------------------- /slick-postgresql-redis/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.0.0 2 | -------------------------------------------------------------------------------- /slick-postgresql-redis/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.play" %% "sbt-plugin" % "2.5.4") 2 | 3 | addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.8.2") -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-business/src/main/scala/me/yangbajing/ps/business/service/CacheService.scala: -------------------------------------------------------------------------------- 1 | package me.yangbajing.ps.business.service 2 | 3 | import javax.inject.{Inject, Singleton} 4 | 5 | import akka.actor.ActorSystem 6 | import com.redis.RedisClientPool 7 | import me.yangbajing.ps.setting.SettingComponent 8 | 9 | import scala.concurrent.duration._ 10 | 11 | /** 12 | * 缓存服务 13 | * Created by jingyang on 15/7/17. 14 | */ 15 | @Singleton 16 | class CacheService @Inject()(settingComponent: SettingComponent) 17 | (implicit actorSystem: ActorSystem) { 18 | private val pool = 19 | new RedisClientPool(settingComponent.cache.host, settingComponent.cache.port, settingComponent.cache.database) 20 | 21 | def remove(key: String) = pool.withClient(client => client.del(key)) 22 | 23 | def getString(key: String): Option[String] = pool.withClient(client => client.get(key)) 24 | 25 | def set(key: String, value: String, duration: Duration): Boolean = 26 | pool.withClient(client => client.setex(key, duration.toSeconds.toInt, value)) 27 | 28 | } 29 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-business/src/main/scala/me/yangbajing/ps/business/service/UserService.scala: -------------------------------------------------------------------------------- 1 | package me.yangbajing.ps.business.service 2 | 3 | import javax.inject.{Inject, Singleton} 4 | 5 | import me.yangbajing.ps.data.domain.RegisterParam 6 | import me.yangbajing.ps.data.repo.UserRepo 7 | 8 | import scala.concurrent.{ExecutionContext, Future} 9 | 10 | /** 11 | * 用户服务 12 | * Created by jingyang on 15/7/16. 13 | */ 14 | @Singleton 15 | class UserService @Inject()(userRepo: UserRepo) 16 | (implicit ec: ExecutionContext) { 17 | 18 | def signin(param: RegisterParam): Future[Option[UserRepo.UserInfo]] = { 19 | if (param.email.isDefined) 20 | userRepo.signinByEmail(param.email.get, param.password) 21 | else if (param.phone.isDefined) 22 | userRepo.signinByPhone(param.phone.get, param.password) 23 | else 24 | throw new IllegalArgumentException("email和phone至少传一个") 25 | } 26 | 27 | def register(param: RegisterParam): Future[UserRepo.UserInfo] = { 28 | userRepo.register(param.email, param.phone, param.password) 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-data/src/main/resources/reference.conf: -------------------------------------------------------------------------------- 1 | slick.dbs { 2 | default { 3 | driver = "me.yangbajing.ps.data.PsSqlDriver$" 4 | db { 5 | dataSourceClass = "org.postgresql.ds.PGSimpleDataSource" 6 | driver = "org.postgresql.Driver" 7 | properties = { 8 | serverName = "localhost" 9 | databaseName = "play_seed" 10 | user = "playseeduser" 11 | password = "playseedpass" 12 | } 13 | numThreads = 10 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-data/src/main/scala/me/yangbajing/ps/data/PsSqlDriver.scala: -------------------------------------------------------------------------------- 1 | package me.yangbajing.ps.data 2 | 3 | import com.github.tminglei.slickpg._ 4 | import com.github.tminglei.slickpg.utils.SimpleArrayUtils 5 | import me.yangbajing.ps.types.OwnerStatus 6 | import play.api.libs.json.{JsValue, Json} 7 | import slick.driver.JdbcDriver 8 | 9 | /** 10 | * Slick JDBC Driver 11 | * Created by jingyang on 15/7/16. 12 | */ 13 | 14 | trait CustomColumnTypes { 15 | this: JdbcDriver => 16 | 17 | trait CustomColumnsImplicits { 18 | self: API => 19 | implicit val __ownerStatusColumnType = MappedColumnType.base[OwnerStatus.Value, String](_.toString, OwnerStatus.withName) 20 | } 21 | 22 | } 23 | 24 | trait PsSqlDriver 25 | extends ExPostgresDriver 26 | with PgDate2Support 27 | with PgHStoreSupport 28 | with PgPlayJsonSupport 29 | with PgArraySupport 30 | //with PgRangeSupport 31 | //with PgSearchSupport 32 | //with PgPostGISSupport 33 | with CustomColumnTypes { 34 | 35 | object MyAPI 36 | extends API 37 | with DateTimeImplicits 38 | with ArrayImplicits 39 | with PlayJsonImplicits 40 | with HStoreImplicits 41 | with CustomColumnsImplicits { 42 | implicit val strListTypeMapper = new SimpleArrayJdbcType[String]("text").to(_.toList) 43 | implicit val playJsonArrayTypeMapper = new AdvancedArrayJdbcType[JsValue]( 44 | pgjson, 45 | (s) => SimpleArrayUtils.fromString[JsValue](Json.parse)(s).orNull, 46 | (v) => SimpleArrayUtils.mkString[JsValue](_.toString())(v)) 47 | .to(_.toList) 48 | } 49 | 50 | override def pgjson: String = "jsonb" 51 | 52 | override val api = MyAPI 53 | 54 | val plainAPI = new API with Date2DateTimePlainImplicits 55 | with SimpleArrayPlainImplicits 56 | with PlayJsonPlainImplicits 57 | with SimpleHStorePlainImplicits 58 | with CustomColumnsImplicits 59 | 60 | } 61 | 62 | object PsSqlDriver extends PsSqlDriver 63 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-data/src/main/scala/me/yangbajing/ps/data/domain/CaseClass.scala: -------------------------------------------------------------------------------- 1 | package me.yangbajing.ps.data.domain 2 | 3 | import play.api.libs.json.Json 4 | 5 | /** 6 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-01-04. 7 | */ 8 | case class CaseClass(a: String, 9 | b: String = "", 10 | c: String = "", 11 | d: String = "", 12 | e: String = "", 13 | f: String = "", 14 | g: String = "", 15 | h: String = "", 16 | i: String = "", 17 | j: String = "", 18 | k: String = "", 19 | // l: String = "", 20 | // m: String = "", 21 | // n: String = "", 22 | // o: String = "", 23 | // p: String = "", 24 | // q: String = "", 25 | // r: String = "", 26 | // s: String = "", 27 | // t: String = "", 28 | // u: String = "", 29 | // v: String = "", 30 | // w: String = "", 31 | // x: String = "", 32 | // y: String = "", 33 | z: String = "") { 34 | def this() { 35 | this("") 36 | } 37 | } 38 | 39 | object CaseClass { 40 | implicit val jsonFormats = Json.format[CaseClass] 41 | } 42 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-data/src/main/scala/me/yangbajing/ps/data/domain/OwnerToken.scala: -------------------------------------------------------------------------------- 1 | package me.yangbajing.ps.data.domain 2 | 3 | import me.yangbajing.ps.data.implicits.JsonImplicits 4 | import me.yangbajing.ps.util.Constants 5 | import play.api.libs.json.Json 6 | 7 | case class OwnerToken(ownerId: Long, email: Option[String], phone: Option[String], nick: Option[String]) { 8 | def toValue: String = ownerId.toString + Constants.SPLIT_CHAR + 9 | email.getOrElse("") + Constants.SPLIT_CHAR + 10 | phone.getOrElse("") + Constants.SPLIT_CHAR + 11 | nick.getOrElse("") + Constants.SPLIT_CHAR 12 | 13 | override def toString: String = Json.stringify(Json.toJson(this)(JsonImplicits.__userTokenFormats)) 14 | } 15 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-data/src/main/scala/me/yangbajing/ps/data/domain/RegisterParam.scala: -------------------------------------------------------------------------------- 1 | package me.yangbajing.ps.data.domain 2 | 3 | case class RegisterParam(email: Option[String], phone: Option[String], password: String) 4 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-data/src/main/scala/me/yangbajing/ps/data/implicits/JsonImplicits.scala: -------------------------------------------------------------------------------- 1 | package me.yangbajing.ps.data.implicits 2 | 3 | import me.yangbajing.ps.data.domain.{RegisterParam, OwnerToken} 4 | import play.api.libs.json.{Format, Json} 5 | 6 | trait JsonImplicits extends RecordJsonImplicits { 7 | implicit def __registerParamFormats: Format[RegisterParam] = Json.format[RegisterParam] 8 | implicit def __userTokenFormats: Format[OwnerToken] = Json.format[OwnerToken] 9 | } 10 | 11 | object JsonImplicits extends JsonImplicits 12 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-data/src/main/scala/me/yangbajing/ps/data/implicits/RecordJsonImplicits.scala: -------------------------------------------------------------------------------- 1 | package me.yangbajing.ps.data.implicits 2 | 3 | import me.yangbajing.ps.data.record.User 4 | import play.api.libs.json.{Json, Format} 5 | 6 | /** 7 | * play json format 8 | * Created by jingyang on 15/7/16. 9 | */ 10 | trait RecordJsonImplicits { 11 | implicit def __userFormat: Format[User] = Json.format[User] 12 | } 13 | 14 | object RecordJsonImplicits extends RecordJsonImplicits 15 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-data/src/main/scala/me/yangbajing/ps/data/record/Credential.scala: -------------------------------------------------------------------------------- 1 | package me.yangbajing.ps.data.record 2 | 3 | import java.time.LocalDateTime 4 | 5 | /** 6 | * 保存账号、密码等敏感信息 7 | * Created by jingyang on 15/7/16. 8 | */ 9 | case class Credential(id: Option[Long], 10 | email: Option[String], 11 | phone: Option[String], 12 | salt: Array[Byte], 13 | password: Array[Byte], 14 | created_at: LocalDateTime) 15 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-data/src/main/scala/me/yangbajing/ps/data/record/DatabaseModule.scala: -------------------------------------------------------------------------------- 1 | //package me.yangbajing.ps.data.record 2 | // 3 | //import java.time.LocalDateTime 4 | // 5 | //import me.yangbajing.ps.data.PsSqlDriver$ 6 | //import me.yangbajing.ps.types.OwnerStatus._ 7 | //import play.api.libs.json.JsValue 8 | // 9 | ///** 10 | // * Created by Yang Jing (yangbajing@gmail.com) on 2016-07-27. 11 | // */ 12 | //trait DatabaseModule { 13 | // val driver: PsSqlDriver 14 | // 15 | // import driver.api._ 16 | // 17 | // class TableUser(tag: Tag) extends Table[User](tag, "t_user") { 18 | // val id = column[Long]("id", O.PrimaryKey) 19 | // val attrs = column[JsValue]("attrs") 20 | // val status = column[OwnerStatus]("status") 21 | // val created_at = column[LocalDateTime]("created_at") 22 | // 23 | // def * = (id, attrs, status, created_at) <> (User.tupled, User.unapply) 24 | // } 25 | // 26 | // class TableCredential(tag: Tag) extends Table[Credential](tag, "t_credential") { 27 | // val id = column[Long]("id", O.PrimaryKey, O.AutoInc) 28 | // val email = column[Option[String]]("email") 29 | // val phone = column[Option[String]]("phone") 30 | // val salt = column[Array[Byte]]("salt") 31 | // val password = column[Array[Byte]]("password") 32 | // val created_at = column[LocalDateTime]("created_at") 33 | // 34 | // def * = (id.?, email, phone, salt, password, created_at) <> (Credential.tupled, Credential.unapply) 35 | // } 36 | // 37 | // def tCredential = TableQuery[TableCredential] 38 | // 39 | // def tUser = TableQuery[TableUser] 40 | // 41 | // def schema = 42 | // tCredential.schema ++ 43 | // tUser.schema 44 | //} 45 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-data/src/main/scala/me/yangbajing/ps/data/record/Schemas.scala: -------------------------------------------------------------------------------- 1 | package me.yangbajing.ps.data.record 2 | 3 | import java.time.LocalDateTime 4 | import java.util.concurrent.TimeUnit 5 | import javax.inject.{Inject, Singleton} 6 | 7 | import me.yangbajing.ps.data.PsSqlDriver 8 | import me.yangbajing.ps.types.OwnerStatus.OwnerStatus 9 | import play.api.db.slick.DatabaseConfigProvider 10 | import play.api.libs.json.JsValue 11 | 12 | import scala.concurrent.Await 13 | import scala.concurrent.duration.{Duration, FiniteDuration} 14 | 15 | @Singleton 16 | class Schemas @Inject()(databaseConfigProvider: DatabaseConfigProvider) /* extends DatabaseModule*/ { 17 | val databaseConfig = databaseConfigProvider.get[PsSqlDriver] 18 | val driver: PsSqlDriver = databaseConfig.driver 19 | 20 | import driver.api._ 21 | 22 | def exec[R](action: DBIOAction[R, NoStream, Nothing])(implicit duration: FiniteDuration = Duration(10, TimeUnit.SECONDS)) = { 23 | Await.result(databaseConfig.db.run(action), duration) 24 | } 25 | 26 | def run[R](action: DBIOAction[R, NoStream, Nothing]) = { 27 | databaseConfig.db.run(action) 28 | } 29 | 30 | class TableUser(tag: Tag) extends Table[User](tag, "t_user") { 31 | val id = column[Long]("id", O.PrimaryKey) 32 | val attrs = column[JsValue]("attrs") 33 | val status = column[OwnerStatus]("status") 34 | val created_at = column[LocalDateTime]("created_at") 35 | 36 | def * = (id, attrs, status, created_at) <> (User.tupled, User.unapply) 37 | } 38 | 39 | class TableCredential(tag: Tag) extends Table[Credential](tag, "t_credential") { 40 | val id = column[Long]("id", O.PrimaryKey, O.AutoInc) 41 | val email = column[Option[String]]("email") 42 | val phone = column[Option[String]]("phone") 43 | val salt = column[Array[Byte]]("salt") 44 | val password = column[Array[Byte]]("password") 45 | val created_at = column[LocalDateTime]("created_at") 46 | 47 | def * = (id.?, email, phone, salt, password, created_at) <> (Credential.tupled, Credential.unapply) 48 | } 49 | 50 | def tCredential = TableQuery[TableCredential] 51 | 52 | def tUser = TableQuery[TableUser] 53 | 54 | def schema = 55 | tCredential.schema ++ 56 | tUser.schema 57 | 58 | } 59 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-data/src/main/scala/me/yangbajing/ps/data/record/User.scala: -------------------------------------------------------------------------------- 1 | package me.yangbajing.ps.data.record 2 | 3 | import java.time.LocalDateTime 4 | 5 | import me.yangbajing.ps.data.implicits.RecordJsonImplicits 6 | import me.yangbajing.ps.types.OwnerStatus._ 7 | import play.api.libs.json.{JsValue, Json} 8 | 9 | /** 10 | * 用户 11 | * Created by jingyang on 15/7/16. 12 | */ 13 | case class User(id: Long, 14 | attrs: JsValue, 15 | status: OwnerStatus, 16 | created_at: LocalDateTime) { 17 | def nick = attrs.\("nick").asOpt[String].getOrElse("") 18 | 19 | override def toString: String = Json.stringify(Json.toJson(this)(RecordJsonImplicits.__userFormat)) 20 | } 21 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-data/src/main/scala/me/yangbajing/ps/data/repo/UserRepo.scala: -------------------------------------------------------------------------------- 1 | package me.yangbajing.ps.data.repo 2 | 3 | import java.time.LocalDateTime 4 | import javax.inject.Inject 5 | 6 | import me.yangbajing.ps.data.record.{Credential, Schemas, User} 7 | import me.yangbajing.ps.types.OwnerStatus 8 | import me.yangbajing.ps.util.Utils 9 | import play.api.libs.json.{JsObject, JsValue} 10 | 11 | import scala.concurrent.{ExecutionContext, Future} 12 | 13 | /** 14 | * User数据库访问 15 | * Created by jingyang on 15/7/16. 16 | */ 17 | object UserRepo { 18 | type UserInfo = (User, Option[String], Option[String]) 19 | } 20 | 21 | class UserRepo @Inject()(schemas: Schemas)(implicit ec: ExecutionContext) { 22 | 23 | import schemas.driver.api._ 24 | import schemas._ 25 | 26 | import UserRepo.UserInfo 27 | 28 | def findOneById(id: Long): Future[Option[UserInfo]] = { 29 | val q = for { 30 | c <- tCredential if c.id === id 31 | u <- tUser if u.id === c.id 32 | } yield (u, c.email, c.phone) 33 | run(q.result).map(_.headOption) 34 | } 35 | 36 | def update(id: Long, attrs: JsValue): Future[Int] = { 37 | val action = tUser.filter(_.id === id).map(_.attrs).update(attrs).transactionally 38 | run(action) 39 | } 40 | 41 | def register(email: Option[String], phone: Option[String], pwd: String): Future[UserInfo] = { 42 | require(email.isDefined || phone.isDefined, "email和phone至少指定一个") 43 | 44 | val (salt, password) = Utils.generatePassword(pwd) 45 | val c = Credential(None, email, phone, salt, password, LocalDateTime.now()) 46 | val u = for { 47 | cid <- (tCredential returning tCredential.map(_.id)) += c 48 | ret <- tUser += User(cid, JsObject(Nil), OwnerStatus.ACTIVE, c.created_at) 49 | } yield (User(cid, JsObject(Nil), OwnerStatus.ACTIVE, c.created_at), email, phone) 50 | run(u.transactionally) 51 | } 52 | 53 | def signinByEmail(email: String, pwd: String) = { 54 | signinBy(Some(email), None, pwd) 55 | } 56 | 57 | def signinByPhone(phone: String, pwd: String) = { 58 | signinBy(None, Some(phone), pwd) 59 | } 60 | 61 | private def signinBy(email: Option[String], phone: Option[String], pwd: String): Future[Option[UserInfo]] = { 62 | require(email.isDefined || phone.isDefined, "email和phone至少指定一个") 63 | 64 | val q = for { 65 | c <- tCredential if c.email === email || c.phone === phone 66 | u <- tUser if u.id === c.id 67 | } yield (u, c) 68 | 69 | run(q.result).map { 70 | case (u, c) +: _ => 71 | require(java.util.Arrays.equals(Utils.generatePassword(c.salt, pwd), c.password), "密码不匹配") 72 | Some((u, c.email, c.phone)) 73 | 74 | case _ => 75 | None 76 | } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-data/src/test/scala/me/yangbajing/ps/data/domain/CaseClassTest.scala: -------------------------------------------------------------------------------- 1 | package me.yangbajing.ps.data.domain 2 | 3 | import me.yangbajing.UnitSpec 4 | import play.api.libs.json.Json 5 | 6 | /** 7 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-01-04. 8 | */ 9 | class CaseClassTest extends UnitSpec { 10 | "CaseClassTest" should { 11 | val cc = CaseClass("哈哈") 12 | 13 | "json marshal" in { 14 | val jv = Json.toJson(cc) 15 | jv.\("a").as[String] mustBe cc.a 16 | } 17 | 18 | "json unmarshal" in { 19 | val s = """{"b":"BB","c":"CC}""" 20 | val jv = Json.parse(s) 21 | jv.\("a").asOpt[String] mustBe None 22 | jv.\("c").as[String] mustBe "CC" 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-data/src/test/scala/me/yangbajing/ps/data/record/SchemasTest.scala: -------------------------------------------------------------------------------- 1 | package me.yangbajing.ps.data.record 2 | 3 | import me.yangbajing.UnitPlaySpec 4 | import org.scalatestplus.play.OneAppPerSuite 5 | 6 | /** 7 | * Schemas Test 8 | * Created by jingyang on 15/7/17. 9 | */ 10 | class SchemasTest extends UnitPlaySpec with OneAppPerSuite { 11 | "Schemas ddl" should { 12 | val schemas = app.injector.instanceOf[Schemas] 13 | import schemas.driver.api._ 14 | 15 | "drop tables" in { 16 | val result = schemas.run(schemas.schema.drop.transactionally).futureValue 17 | result.mustBe(()) 18 | } 19 | 20 | "create tables" in { 21 | val result = schemas.run(schemas.schema.create.transactionally).futureValue 22 | result.mustBe(()) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-util/src/main/resources/reference.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loggers = ["akka.event.slf4j.Slf4jLogger"] 3 | loglevel = INFO 4 | fork-join-executor { 5 | # The parallelism factor is used to determine thread pool size using the 6 | # following formula: ceil(available processors * factor). Resulting size 7 | # is then bounded by the parallelism-min and parallelism-max values. 8 | parallelism-factor = 3.0 9 | 10 | # Min number of threads to cap factor-based parallelism number to 11 | parallelism-min = 8 12 | 13 | # Max number of threads to cap factor-based parallelism number to 14 | parallelism-max = 64 15 | } 16 | } 17 | 18 | play-seed { 19 | filter-after-token { 20 | match-all = false 21 | paths = ["/inapi"] 22 | exclude-paths = [ 23 | "/inapi/user/signin", 24 | "/inapi/user/signup" 25 | ] 26 | } 27 | 28 | cache { 29 | host = "localhost" 30 | port = 6379 31 | database = 0 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-util/src/main/scala/me/yangbajing/ps/setting/SettingComponent.scala: -------------------------------------------------------------------------------- 1 | package me.yangbajing.ps.setting 2 | 3 | import com.typesafe.config.{ConfigFactory, Config} 4 | import scala.collection.JavaConverters._ 5 | import scala.util.Try 6 | 7 | import javax.inject.Singleton 8 | 9 | /** 10 | * 配置文件 11 | * Created by jingyang on 15/7/16. 12 | */ 13 | @Singleton 14 | class SettingComponent { 15 | val cache = { 16 | val conf = ConfigFactory.load().getConfig("play-seed.cache") 17 | CacheSetting( 18 | getString(conf, "host").get, 19 | getInt(conf, "port").get, 20 | getInt(conf, "database").get) 21 | } 22 | 23 | val filterAfterToken = { 24 | val conf = ConfigFactory.load().getConfig("play-seed.filter-after-token") 25 | FilterAfterToken( 26 | getBoolean(conf, "match-all", false), 27 | getStringList(conf, "paths"), 28 | getStringList(conf, "exclude-paths")) 29 | } 30 | 31 | private def getStringList(conf: Config, name: String): Seq[String] = try { 32 | conf.getStringList(name).asScala 33 | } catch { 34 | case _: Exception => 35 | Nil 36 | } 37 | 38 | private def getString(conf: Config, name: String, default: => String): String = getString(conf, name).getOrElse(default) 39 | 40 | private def getString(conf: Config, name: String): Try[String] = Try(conf.getString(name)) 41 | 42 | private def getBoolean(conf: Config, name: String, default: => Boolean): Boolean = 43 | getBoolean(conf, name).getOrElse(default) 44 | 45 | private def getBoolean(conf: Config, name: String): Try[Boolean] = Try(conf.getBoolean(name)) 46 | 47 | private def getInt(conf: Config, name: String, default: => Int): Int = getInt(conf, name).getOrElse(default) 48 | 49 | private def getInt(conf: Config, name: String): Try[Int] = Try(conf.getInt(name)) 50 | } 51 | 52 | case class Setting(db: Config) 53 | 54 | case class FilterAfterToken(matchAll: Boolean, paths: Seq[String], excludePaths: Seq[String]) 55 | 56 | case class CacheSetting(host: String, port: Int, database: Int) 57 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-util/src/main/scala/me/yangbajing/ps/types/OwnerStatus.scala: -------------------------------------------------------------------------------- 1 | package me.yangbajing.ps.types 2 | 3 | import me.yangbajing.ps.util.enums.JsValueEnumUtils 4 | import play.api.libs.json.Format 5 | 6 | /** 7 | * 用户状态 8 | * Created by jingyang on 15/7/16. 9 | */ 10 | object OwnerStatus extends Enumeration { 11 | type OwnerStatus = Value 12 | 13 | val ACTIVE = Value("A") 14 | val INACTIVE = Value("I") 15 | val DELETE = Value("D") 16 | 17 | implicit def __ownerStatusFormats: Format[OwnerStatus] = JsValueEnumUtils.enumFormat(OwnerStatus) 18 | } 19 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-util/src/main/scala/me/yangbajing/ps/util/Constants.scala: -------------------------------------------------------------------------------- 1 | package me.yangbajing.ps.util 2 | 3 | import java.nio.charset.Charset 4 | 5 | /** 6 | * 常量 7 | * Created by jingyang on 15/7/16. 8 | */ 9 | object Constants { 10 | val COOKIE_MAX_AGE = 30 // 秒 11 | 12 | val CACHE_KEY_TOKEN_PREFIX = "ps:to:" 13 | 14 | val NICK = "nick" 15 | 16 | val SPLIT_CHAR = '-' 17 | 18 | val SALT_LENGTH = 12 19 | 20 | val CHARSET = Charset.forName("UTF-8") 21 | 22 | val PS_TOKEN_OWNER = "ps-to" 23 | } 24 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-util/src/main/scala/me/yangbajing/ps/util/RandomUtils.scala: -------------------------------------------------------------------------------- 1 | package me.yangbajing.ps.util 2 | 3 | import java.security.SecureRandom 4 | 5 | /** 6 | * 随机资源工具 7 | * Created by jingyang on 15/7/16. 8 | */ 9 | object RandomUtils { 10 | private val LOW = 33 11 | private val HIGH = 127 12 | private val RANDOM_STRING = (0 to 9).mkString + ('a' to 'z').mkString + ('A' to 'Z').mkString + ",.?!~`@#$%^&*()_-+=|[]{};:<>/" 13 | private val RANDOM_STRING_LENGTH = RANDOM_STRING.length 14 | 15 | val random = new SecureRandom() 16 | 17 | def randomString(size: Int): String = { 18 | assert(size > 0, s"size: $size must be > 0") 19 | (0 until size).map(_ => RANDOM_STRING.charAt(random.nextInt(RANDOM_STRING_LENGTH))).mkString 20 | } 21 | 22 | def nextPrintableChar(): Char = { 23 | (random.nextInt(HIGH - LOW) + LOW).toChar 24 | } 25 | 26 | def randomNextInt(begin: Int, bound: Int) = { 27 | random.nextInt(bound - begin) + begin 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-util/src/main/scala/me/yangbajing/ps/util/Utils.scala: -------------------------------------------------------------------------------- 1 | package me.yangbajing.ps.util 2 | 3 | import org.apache.commons.codec.digest.DigestUtils 4 | 5 | /** 6 | * 工具 7 | * Created by jingyang on 15/7/16. 8 | */ 9 | object Utils { 10 | def option[T <: CharSequence](v: T): Option[T] = Option(v).filterNot(_.length() == 0) 11 | 12 | @inline 13 | def generatePassword(pwd: String): (Array[Byte], Array[Byte]) = 14 | generatePassword(pwd.getBytes(Constants.CHARSET)) 15 | 16 | def generatePassword(pwd: Array[Byte]): (Array[Byte], Array[Byte]) = { 17 | val salt = Array.ofDim[Byte](Constants.SALT_LENGTH) 18 | RandomUtils.random.nextBytes(salt) 19 | val digest = DigestUtils.getSha256Digest 20 | digest.update(salt) 21 | digest.update(pwd) 22 | (salt, digest.digest()) 23 | } 24 | 25 | @inline 26 | def generatePassword(salt: Array[Byte], pwd: String): Array[Byte] = 27 | generatePassword(salt, pwd.getBytes(Constants.CHARSET)) 28 | 29 | def generatePassword(salt: Array[Byte], pwd: Array[Byte]): Array[Byte] = { 30 | val digest = DigestUtils.getSha256Digest 31 | digest.update(salt) 32 | digest.update(pwd) 33 | digest.digest() 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-util/src/main/scala/me/yangbajing/ps/util/enums/JsValueEnumUtils.scala: -------------------------------------------------------------------------------- 1 | package me.yangbajing.ps.util.enums 2 | 3 | import play.api.libs.json._ 4 | import scala.language.implicitConversions 5 | 6 | /** 7 | * play json enum utils 8 | * Created by jingyang on 15/7/16. 9 | */ 10 | object JsValueEnumUtils { 11 | 12 | def enumReads[E <: Enumeration](enum: E): Reads[E#Value] = 13 | new Reads[E#Value] { 14 | def reads(json: JsValue): JsResult[E#Value] = json match { 15 | case JsString(s) => { 16 | try { 17 | JsSuccess(enum.withName(s)) 18 | } catch { 19 | case _: NoSuchElementException => 20 | JsError(s"Enumeration expected of type: '${enum.getClass}', but it does not appear to contain the value: '$s'") 21 | } 22 | } 23 | case _ => JsError("String value expected") 24 | } 25 | } 26 | 27 | def enumReadsId[E <: Enumeration](enum: E): Reads[E#Value] = 28 | new Reads[E#Value] { 29 | def reads(json: JsValue): JsResult[E#Value] = json match { 30 | case JsNumber(s) => { 31 | try { 32 | JsSuccess(enum(s.toInt)) 33 | } catch { 34 | case _: NoSuchElementException => 35 | JsError(s"Enumeration expected of type: '${enum.getClass}', but it does not appear to contain the value: '$s'") 36 | } 37 | } 38 | case _ => JsError("String value expected") 39 | } 40 | } 41 | 42 | implicit def enumWrites[E <: Enumeration]: Writes[E#Value] = 43 | new Writes[E#Value] { 44 | def writes(v: E#Value): JsValue = JsString(v.toString) 45 | } 46 | 47 | implicit def enumFormat[E <: Enumeration](enum: E): Format[E#Value] = { 48 | Format(enumReads(enum), enumWrites) 49 | } 50 | 51 | def enumWritesNumber[E <: Enumeration]: Writes[E#Value] = 52 | new Writes[E#Value] { 53 | def writes(v: E#Value): JsValue = JsNumber(v.id) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-util/src/test/scala/me/yangbajing/UnitPlaySpec.scala: -------------------------------------------------------------------------------- 1 | package me.yangbajing 2 | 3 | import org.scalatest.concurrent.ScalaFutures 4 | import org.scalatest.time.{Millis, Seconds, Span} 5 | import org.scalatestplus.play.PlaySpec 6 | 7 | /** 8 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-07-27. 9 | */ 10 | trait UnitPlaySpec extends PlaySpec with ScalaFutures { 11 | 12 | override implicit def patienceConfig: PatienceConfig = 13 | PatienceConfig(timeout = scaled(Span(90, Seconds)), interval = scaled(Span(100, Millis))) 14 | 15 | } 16 | 17 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-util/src/test/scala/me/yangbajing/UnitSpec.scala: -------------------------------------------------------------------------------- 1 | package me.yangbajing 2 | 3 | import org.scalatest._ 4 | import org.scalatest.concurrent.ScalaFutures 5 | import org.scalatest.time.{Millis, Seconds, Span} 6 | 7 | trait UnitSpec extends WordSpec with MustMatchers with OptionValues with ScalaFutures { 8 | 9 | override implicit def patienceConfig: PatienceConfig = 10 | PatienceConfig(timeout = scaled(Span(90, Seconds)), interval = scaled(Span(100, Millis))) 11 | 12 | } 13 | 14 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-web/app/controllers/BaseController.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import com.typesafe.scalalogging.LazyLogging 4 | import me.yangbajing.ps.data.implicits.JsonImplicits 5 | import play.api.libs.concurrent.Execution 6 | import play.api.mvc.Controller 7 | 8 | trait BaseController extends Controller with JsonImplicits with LazyLogging { 9 | this: Controller => 10 | 11 | implicit def __ec = Execution.Implicits.defaultContext 12 | } 13 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-web/app/controllers/PageController.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import javax.inject.{Inject, Singleton} 4 | 5 | import play.api.Environment 6 | import play.api.http.Writeable 7 | import play.api.mvc.{Action, Controller} 8 | 9 | /** 10 | * 页面 11 | * Created by jingyang on 15/7/17. 12 | */ 13 | @Singleton 14 | class PageController @Inject()(webTools: WebTools, 15 | environment: Environment, 16 | userAction: UserAction 17 | ) extends Controller with BaseController { 18 | 19 | def signin = signPage(views.html.account.signin()) 20 | 21 | def signup = signPage(views.html.account.signup()) 22 | 23 | def homeMain = userAction { request => 24 | Ok(views.html.home.main(request.userToken)) 25 | } 26 | 27 | private def signPage[C](content: C)(implicit writeable: Writeable[C]) = Action { implicit request => 28 | webTools.getUserToken 29 | .map { 30 | case Right(_) => 31 | Redirect(controllers.routes.PageController.homeMain()) 32 | case Left(key) => 33 | Ok(content).withCookies(webTools.discardingSession(key)) 34 | } 35 | .getOrElse(Ok(content)) 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-web/app/controllers/UserAction.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import javax.inject.Inject 4 | 5 | import me.yangbajing.ps.business.service.CacheService 6 | import me.yangbajing.ps.data.domain.OwnerToken 7 | import play.api.mvc._ 8 | 9 | import scala.concurrent.Future 10 | 11 | case class UserTokenRequest[A](userToken: OwnerToken, request: Request[A]) extends WrappedRequest[A](request) 12 | 13 | class UserAction @Inject()(cacheService: CacheService, 14 | webTools: WebTools 15 | ) extends ActionBuilder[UserTokenRequest] with ActionRefiner[Request, UserTokenRequest] { 16 | 17 | override protected def refine[A](request: Request[A]): Future[Either[Result, UserTokenRequest[A]]] = { 18 | val result = webTools.getUserToken(request) 19 | .map { 20 | case Right(token) => 21 | Right(UserTokenRequest(token, request)) 22 | case Left(key) => 23 | cacheService.remove(key) 24 | Left(Results.Redirect(controllers.routes.PageController.signin())) 25 | } 26 | .getOrElse(Left(Results.Redirect(controllers.routes.PageController.signin()))) 27 | Future.successful(result) 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-web/app/controllers/WebTools.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import java.util.concurrent.TimeUnit 4 | import javax.inject.{Inject, Singleton} 5 | 6 | import me.yangbajing.ps.business.service.CacheService 7 | import me.yangbajing.ps.data.domain.OwnerToken 8 | import me.yangbajing.ps.util.Constants 9 | import org.apache.commons.codec.digest.DigestUtils 10 | import play.api.libs.concurrent.Execution.Implicits.defaultContext 11 | import play.api.mvc.{Cookie, DiscardingCookie, RequestHeader} 12 | 13 | import scala.concurrent.Future 14 | import scala.concurrent.duration.Duration 15 | import scala.util.Random 16 | 17 | /** 18 | * cache放到redis里。 19 | */ 20 | @Singleton 21 | class WebTools @Inject()(cacheService: CacheService) { 22 | /** 23 | * 24 | * @param request 25 | * @return 26 | * case Some(_) => 27 | * future map { 28 | * case Right(_) => 成功获取到OwnerToken 29 | * case Left(key) => key无效 30 | * } 31 | * case None => token key's value 未找到 32 | */ 33 | def getUserToken(implicit request: RequestHeader): Option[Either[String, OwnerToken]] = { 34 | val str = request.cookies.find(_.name == Constants.PS_TOKEN_OWNER).map(_.value) orElse 35 | request.headers.get(Constants.PS_TOKEN_OWNER) orElse 36 | request.getQueryString(Constants.PS_TOKEN_OWNER) 37 | 38 | str.map { key => 39 | cacheService.getString(Constants.CACHE_KEY_TOKEN_PREFIX + key) match { 40 | case Some(value) => { 41 | val vc = value.split(Constants.SPLIT_CHAR).toVector 42 | val token = OwnerToken(vc(0).toLong, vecGet(vc, 1), vecGet(vc, 2), vecGet(vc, 3)) 43 | Right(token) 44 | } 45 | case None => 46 | Left(key) 47 | } 48 | } 49 | } 50 | 51 | @inline 52 | private def vecGet(vc: Vector[String], idx: Int): Option[String] = { 53 | if (vc.length > idx) Some(vc(idx)) else None 54 | } 55 | 56 | def createCookie(name: String, value: String): Cookie = { 57 | Cookie(name, value, Some(Constants.COOKIE_MAX_AGE), httpOnly = true) 58 | } 59 | 60 | def createSession(ownerToken: OwnerToken) = { 61 | val key = ownerToken.ownerId.toString + Constants.SPLIT_CHAR + 62 | DigestUtils.sha1Hex(Random.nextInt() + Random.nextString(12) + (System.currentTimeMillis() / 1000)) 63 | 64 | // XXX 设置缓存最长时间为一天,登录Session状态满一天后强制退出 65 | cacheService.set(Constants.CACHE_KEY_TOKEN_PREFIX + key, ownerToken.toValue, Duration(1, TimeUnit.DAYS)) 66 | 67 | createCookie(Constants.PS_TOKEN_OWNER, key) 68 | } 69 | 70 | def discardingSession(key: String = null): Cookie = { 71 | if (key == null) { 72 | cacheService.remove(key) 73 | } 74 | DiscardingCookie(Constants.PS_TOKEN_OWNER).toCookie 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-web/app/controllers/inapi/UserController.scala: -------------------------------------------------------------------------------- 1 | package controllers.inapi 2 | 3 | import javax.inject.Inject 4 | 5 | import controllers.{BaseController, WebTools} 6 | import me.yangbajing.ps.business.service.UserService 7 | import me.yangbajing.ps.data.domain.{OwnerToken, RegisterParam} 8 | import me.yangbajing.ps.util.Constants 9 | import play.api.libs.json.{JsObject, Json} 10 | import play.api.mvc.Action 11 | 12 | /** 13 | * User API 14 | * Created by jingyang on 15/7/16. 15 | */ 16 | class UserController @Inject()(userService: UserService, 17 | webTools: WebTools) extends BaseController { 18 | 19 | def signup = Action.async(parse.json.map(_.as[RegisterParam])) { request => 20 | userService.register(request.body).map { case (user, email, phone) => 21 | val bo = Json.toJson(user).asInstanceOf[JsObject] ++ Json.obj("email" -> email, "phone" -> phone) 22 | Ok(bo) 23 | } 24 | } 25 | 26 | def signin() = Action.async(parse.json.map(_.as[RegisterParam])) { request => 27 | userService.signin(request.body).map { 28 | case Some((user, email, phone)) => 29 | val bo = Json.toJson(user).asInstanceOf[JsObject] ++ Json.obj("email" -> email, "phone" -> phone) 30 | val cookie = webTools.createSession(OwnerToken(user.id, email, phone, (user.attrs \ Constants.NICK).asOpt[String])) 31 | Ok(bo).withCookies(cookie) 32 | case None => 33 | NotFound 34 | }.recover { 35 | case e: Exception => 36 | logger.debug(e.toString, e) 37 | Unauthorized 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-web/app/utils/ErrorHandler.scala: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import javax.inject.{Inject, Provider} 4 | 5 | import com.typesafe.scalalogging.StrictLogging 6 | import controllers.WebTools 7 | import play.api.http.DefaultHttpErrorHandler 8 | import play.api.libs.json.Json 9 | import play.api.mvc.{RequestHeader, Result, Results} 10 | import play.api.routing.Router 11 | import play.api.{Configuration, Environment, OptionalSourceMapper} 12 | 13 | import scala.concurrent.Future 14 | 15 | /** 16 | * Created by Yang Jing (yangbajing@gmail.com) on 2016-03-07. 17 | */ 18 | class ErrorHandler @Inject()(webTools: WebTools, 19 | env: Environment, 20 | config: Configuration, 21 | sourceMapper: OptionalSourceMapper, 22 | router: Provider[Router] 23 | ) extends DefaultHttpErrorHandler(env, config, sourceMapper, router) with StrictLogging { 24 | 25 | override def onServerError(request: RequestHeader, ex: Throwable): Future[Result] = { 26 | request.path match { 27 | case s if s.startsWith("/inapi/") => 28 | logger.error(ex.toString, ex) 29 | Future.successful { 30 | Results.InternalServerError(Json.obj("code" -> -1, "message" -> ex.getLocalizedMessage)). 31 | withCookies(webTools.discardingSession()) 32 | } 33 | 34 | case _ => 35 | super.onServerError(request, ex) 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-web/app/utils/Filters.scala: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import javax.inject.Inject 4 | 5 | import akka.stream.Materializer 6 | import com.typesafe.scalalogging.StrictLogging 7 | import controllers.WebTools 8 | import me.yangbajing.ps.setting.SettingComponent 9 | import me.yangbajing.ps.util.Constants 10 | import play.api.http.HttpFilters 11 | import play.api.mvc.{EssentialFilter, Filter, RequestHeader, Result} 12 | 13 | import scala.concurrent.{ExecutionContext, Future} 14 | 15 | class Filters @Inject()(authFilter: FilterAfterForToken) extends HttpFilters { 16 | override def filters: Seq[EssentialFilter] = 17 | Seq(authFilter) 18 | } 19 | 20 | class FilterAfterForToken @Inject()(webTools: WebTools, 21 | settingComponent: SettingComponent, 22 | implicit val mat: Materializer, 23 | implicit val ec: ExecutionContext 24 | ) extends Filter with StrictLogging { 25 | val matchAll = settingComponent.filterAfterToken.matchAll 26 | val paths = settingComponent.filterAfterToken.paths 27 | val excludePaths = settingComponent.filterAfterToken.excludePaths 28 | 29 | override def apply(nextFilter: (RequestHeader) => Future[Result])(rh: RequestHeader): Future[Result] = { 30 | nextFilter(rh).flatMap { result => 31 | logger.debug("current path: " + rh.path) 32 | rh.path match { 33 | case path if !excludePaths.contains(path) && paths.exists(apiPath => path.startsWith(apiPath)) => 34 | val tokenOpt = webTools.getUserToken(rh) 35 | logger.debug("before tokenFilter: " + tokenOpt) 36 | 37 | if (matchAll) { 38 | if (tokenOpt.isEmpty) 39 | throw new IllegalAccessException("token not exists") 40 | } 41 | 42 | Future { 43 | tokenOpt 44 | .map { 45 | case Right(token) => 46 | // XXX 重设token 47 | result.withCookies(webTools.createCookie(Constants.PS_TOKEN_OWNER, token.toValue)) 48 | 49 | case Left(key) => 50 | result.withCookies(webTools.discardingSession(key)) 51 | } 52 | .getOrElse(result) 53 | } 54 | 55 | case _ => 56 | Future.successful(result) 57 | } 58 | } 59 | } 60 | 61 | } 62 | 63 | -------------------------------------------------------------------------------- /slick-postgresql-redis/ps-web/app/views/_layout.scala.html: -------------------------------------------------------------------------------- 1 | @(title: String, 2 | head: Html = Html(""), 3 | foot: Html = Html(""), 4 | keywords: String = "play,scala,slick,slick-pg,postgresql,akka", 5 | description: String = "play seed")(body: Html) 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |