接触 prisma 有段时间了, 期间也使用过其余几种 graphql 接口自动生成的框架. 总的来讲, 仍是 prisma 生成的接口比较丰富, 使用上也比较方便, 和数据库之间耦合也低.javascript
prisma 文档: https://www.prisma.io/docs (写本文时是 1.34 版)前端
prisma 服务虽然自动生成了接口, 可是这些接口其实不建议直接暴露给前端来用, 由于实际项目中, 最基本的要对接口进行认证和权限控制. 甚至还有其余需求, 不可能只用自动生成的接口就能完成全部的功能.java
因此, 通常在使用 prisma 服务的时候, 通常都会再封装一层(能够称为 gateway), 在 gateway 上作认证, 权限等等, 只有合法的请求才会最终转发到 prisma 服务上. prisma 服务自己能够导出 client SDK, 用来方便 gateway 的编写, 目前支持 4 种格式 (javascript, typescript, golang, flow), javascript 和 typescript 的是 client SDK 功能比较全, golang 功能弱一些, flow 没有尝试过.mysql
我在使用 golang client SDK 写 gateway 的时候, 发现 golang 的 graphql server 相关的库没有 js/ts 那么完善. 因而, 就想用反向代理的方式, 拦截前端的 graphql 请求, 作了相应操做以后直接再将请求内容转发给 prisma 服务. 这种方式不使用 prisma 生成的 client SDK, 也突破语言的限制, 除了 golang, java, C# 等其余语言也能够做为 prisma 的 gatewaygit
采用 golang 的 gin 做为 gateway 的 web 服务框架. 认证部分使用 gin-jwt 中间件. 反向代理和权限部分没有使用现成的框架.github
整个 gateway 的示例包含:golang
prisma.ymlweb
endpoint: http://${env:PRISMA_HOST}:${env:PRISMA_PORT}/illuminant/${env:PRISMA_STAGE} datamodel: datamodel.prisma secret: ${env:PRISMA_MANAGEMENT_API_SECRET} generate: - generator: go-client output: ./
.env正则表达式
PRISMA_HOST=localhost PRISMA_PORT=4466 PRISMA_STAGE=dev PRISMA_MANAGEMENT_API_SECRET=secret-key
datamodel.prismasql
type User { id: ID! @id name: String! @unique realName: String! password: String! createdAt: DateTime! @createdAt updatedAt: DateTime! @updatedAt }
docker-compose.yml
version: '3' services: illuminant: image: prismagraphql/prisma:1.34 # restart: always ports: - "4466:4466" environment: PRISMA_CONFIG: | port: 4466 managementApiSecret: secret-key databases: default: connector: mysql host: mysql-db user: root password: prisma # rawAccess: true port: 3306 migrations: true mysql-db: image: mysql:5.7 # restart: always environment: MYSQL_ROOT_PASSWORD: prisma volumes: - mysql:/var/lib/mysql volumes: mysql: ~
以上文件放在同一个目录便可, 包含了全部 prisma 服务和 mysql 服务所须要的文件
gateway 服务是关键, 也是从此扩展的部分. 采用 golang gin 框架来编写.
authMiddleware := controller.JwtMiddleware() apiV1 := r.Group("/api/v1") // no auth routes apiV1.POST("/login", authMiddleware.LoginHandler) // auth routes authRoute := apiV1.Group("/") authRoute.GET("/refresh_token", authMiddleware.RefreshHandler) authRoute.Use(authMiddleware.MiddlewareFunc()) { // proxy prisma graphql authRoute.POST("/graphql", ReverseProxy()) }
/api/v1/graphql 在知足 jwt 认证的状况下才能够访问.
func ReverseProxy() gin.HandlerFunc { return func(c *gin.Context) { director := func(req *http.Request) { req.URL.Scheme = "http" req.URL.Host = primsa-host req.URL.Path = primsa-endpoint delete(req.Header, "Authorization") req.Header["Authorization"] = []string{"Bearer " + primsa-token} } // 解析出 body 中的内容, 进行权限检查 body, err := c.GetRawData() if err != nil { fmt.Println(err) } // 对 body 进行权限 check // 权限 Check, 解析出 graphql 中请求的函数, 而后判断是否有权限 // 目前的方式是根据请求中函数的名称来判断权限, 也就是只能对表的 CURD 权限进行判断, 对于表中的字段权限还没法检查 // 若是权限检查没有经过, 直接返回, 不要再进行下面的请求转发 // 将 body 反序列化回请求中, 转发给 prisma 服务 c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body)) proxy := &httputil.ReverseProxy{Director: director} proxy.ModifyResponse = controller.RewriteBody proxy.ServeHTTP(c.Writer, c.Request) } }
// 检查权限 func CheckAuthority(body []byte, userId string) bool { var bodyJson struct { Query string `json:"query"` } log := logger.GetLogger() if err := json.Unmarshal(body, &bodyJson); err != nil { log.Error("body convert to json error: %s", err.Error()) return false } graphqlFunc := RegrexGraphqlFunc(bodyJson.Query) if graphqlFunc == "" { return false } // 这里的 userId 是从 jwt 中解析出来的, 而后再判断用户是否有权限 if graphqlFunc == "users" { return false } return true } // 匹配 graphql 请求的函数 func RegrexGraphqlFunc(graphqlReq string) string { graphqlReq = strings.TrimSpace(graphqlReq) // reg examples: // { users {id} } // { users(where: {}) {id} } // mutation{ user(data: {}) {id} } var regStrs = []string{ `^\{\s*(\w+)\s*\{.*\}\s*\}$`, `^\{\s*(\w+)\s*\(.*\)\s*\{.*\}\s*\}$`, `^mutation\s*\{\s*(\w+)\s*\(.*\)\s*\{.*\}\s*\}$`, } for _, regStr := range regStrs { r := regexp.MustCompile(regStr) matches := r.FindStringSubmatch(graphqlReq) if matches != nil && len(matches) > 1 { return matches[1] } } return "" }
这里的权限检查是个实现思路, 不是最终的代码. 其中用正则表达式的方式来匹配请求中的函数只是临时的方案, 不是最好的方式, 最好的方式应该用 golang 对应的 graphql 解析库来解析出请求的结构, 而后再判断解析出的函数时候有权限
采用反向代理的方式, 是为了突破 prisma client SDK 的限制, 若是之后 client SDK 完善以后, 仍是基于 client SDK 来开发 gateway 更加可靠.