Merge remote-tracking branch 'origin/only-nacos'

# Conflicts:
#	doc/docs/_sidebar.md
#	sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/HttpTool.java
#	sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/RouteDefinition.java
#	sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/ServiceInfo.java
#	sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/ServiceInstance.java
#	sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/RegistryService.java
#	sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/impl/RegistryServiceNacosImpl.java
#	sop-admin/sop-admin-server/src/main/resources/public/static/css/app.c6dfb7ee.css
#	sop-admin/sop-admin-server/src/main/resources/public/static/css/chunk-2c1f2e8f.0314067f.css
#	sop-admin/sop-admin-server/src/main/resources/public/static/css/chunk-4de1c2b6.a37cd815.css
#	sop-admin/sop-admin-server/src/main/resources/public/static/css/chunk-510c5a69.5e48e29a.css
#	sop-admin/sop-admin-server/src/main/resources/public/static/css/chunk-c3ce42fe.6b789903.css
#	sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d2085ef.2e46d1f4.js
#	sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d221c34.0db10350.js
#	sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-4de1c2b6.0188bd8f.js
#	sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-510c5a69.c3dd05bb.js
#	sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-6f78c9fe.bec8af48.js
#	sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-9f479afe.6b795525.js
#	sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-elementUI.298ac98c.js
#	sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-libs.75deb05f.js
#	sop-auth/.gitignore
#	sop-auth/pom.xml
#	sop-auth/readme.md
#	sop-auth/src/main/java/com/gitee/sop/sopauth/SopAuthApplication.java
#	sop-auth/src/main/java/com/gitee/sop/sopauth/auth/AccessToken.java
#	sop-auth/src/main/java/com/gitee/sop/sopauth/auth/AppIdManager.java
#	sop-auth/src/main/java/com/gitee/sop/sopauth/auth/FetchTokenParam.java
#	sop-auth/src/main/java/com/gitee/sop/sopauth/auth/FetchTokenResult.java
#	sop-auth/src/main/java/com/gitee/sop/sopauth/auth/OAuth2Config.java
#	sop-auth/src/main/java/com/gitee/sop/sopauth/auth/OAuth2Manager.java
#	sop-auth/src/main/java/com/gitee/sop/sopauth/auth/OAuth2Service.java
#	sop-auth/src/main/java/com/gitee/sop/sopauth/auth/OpenUser.java
#	sop-auth/src/main/java/com/gitee/sop/sopauth/auth/RefreshToken.java
#	sop-auth/src/main/java/com/gitee/sop/sopauth/auth/TokenPair.java
#	sop-auth/src/main/java/com/gitee/sop/sopauth/auth/exception/LoginErrorException.java
#	sop-auth/src/main/java/com/gitee/sop/sopauth/auth/impl/AppIdManagerImpl.java
#	sop-auth/src/main/java/com/gitee/sop/sopauth/auth/impl/OAuth2ManagerCache.java
#	sop-auth/src/main/java/com/gitee/sop/sopauth/auth/impl/OAuth2ManagerRedis.java
#	sop-auth/src/main/java/com/gitee/sop/sopauth/auth/impl/OAuth2ServiceImpl.java
#	sop-auth/src/main/java/com/gitee/sop/sopauth/config/OpenServiceConfig.java
#	sop-auth/src/main/java/com/gitee/sop/sopauth/controller/CallbackController.java
#	sop-auth/src/main/java/com/gitee/sop/sopauth/controller/OAuth2Controller.java
#	sop-auth/src/main/java/com/gitee/sop/sopauth/entity/IsvInfo.java
#	sop-auth/src/main/java/com/gitee/sop/sopauth/entity/UserInfo.java
#	sop-auth/src/main/java/com/gitee/sop/sopauth/mapper/IsvInfoMapper.java
#	sop-auth/src/main/java/com/gitee/sop/sopauth/mapper/UserInfoMapper.java
#	sop-auth/src/main/resources/application.properties
#	sop-auth/src/main/resources/templates/oauth2login.html
#	sop-auth/src/test/java/com/gitee/sop/sopauth/RefreshTokenTest.java
#	sop-auth/src/test/java/com/gitee/sop/sopauth/SopAuthApplicationTests.java
#	sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/GatewayFilterDefinition.java
#	sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/RouteDefinition.java
#	sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PreEnvGrayFilter.java
#	sop-example/sop-easyopen/src/main/resources/application.properties
#	sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/result/FileUploadResult.java
#	sop-website/.gitignore
#	sop-website/src/main/java/com/gitee/sop/websiteserver/WebsiteServerApplication.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocInfo.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocItem.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocModule.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocParameter.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocParserContext.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/bean/EurekaApplication.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/bean/EurekaApplications.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/bean/EurekaApps.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/bean/EurekaInstance.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/bean/EurekaUri.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/config/CorsConfig.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/config/WebsiteConfig.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/controller/DocController.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/controller/SandboxController.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/manager/DocManager.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/manager/DocParser.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/manager/EasyopenDocParser.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/sign/AlipayApiException.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/sign/AlipayConstants.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/sign/AlipaySignature.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/sign/StreamUtil.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/sign/StringUtils.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/util/UploadUtil.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/vo/DocBaseInfoVO.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/vo/DocInfoVO.java
#	sop-website/src/main/java/com/gitee/sop/websiteserver/vo/ServiceInfoVO.java
#	sop-website/src/main/resources/api.json
#	sop-website/src/main/resources/public/assets/css/global.css
#	sop-website/src/main/resources/public/assets/image/auth.png
#	sop-website/src/main/resources/public/assets/js/format.js
#	sop-website/src/main/resources/public/assets/js/inputcache.js
#	sop-website/src/main/resources/public/assets/lib/jquery/3.2.1/jquery.min.js
#	sop-website/src/main/resources/public/assets/lib/layui/css/layui.css
#	sop-website/src/main/resources/public/assets/lib/layui/css/layui.mobile.css
#	sop-website/src/main/resources/public/assets/lib/layui/css/modules/code.css
#	sop-website/src/main/resources/public/assets/lib/layui/css/modules/laydate/default/laydate.css
#	sop-website/src/main/resources/public/assets/lib/layui/css/modules/layer/default/icon-ext.png
#	sop-website/src/main/resources/public/assets/lib/layui/css/modules/layer/default/icon.png
#	sop-website/src/main/resources/public/assets/lib/layui/css/modules/layer/default/layer.css
#	sop-website/src/main/resources/public/assets/lib/layui/css/modules/layer/default/loading-0.gif
#	sop-website/src/main/resources/public/assets/lib/layui/css/modules/layer/default/loading-1.gif
#	sop-website/src/main/resources/public/assets/lib/layui/css/modules/layer/default/loading-2.gif
#	sop-website/src/main/resources/public/assets/lib/layui/font/iconfont.eot
#	sop-website/src/main/resources/public/assets/lib/layui/font/iconfont.svg
#	sop-website/src/main/resources/public/assets/lib/layui/font/iconfont.ttf
#	sop-website/src/main/resources/public/assets/lib/layui/font/iconfont.woff
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/0.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/1.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/10.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/11.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/12.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/13.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/14.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/15.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/16.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/17.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/18.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/19.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/2.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/20.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/21.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/22.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/23.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/24.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/25.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/26.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/27.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/28.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/29.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/3.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/30.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/31.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/32.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/33.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/34.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/35.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/36.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/37.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/38.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/39.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/4.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/40.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/41.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/42.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/43.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/44.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/45.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/46.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/47.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/48.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/49.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/5.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/50.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/51.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/52.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/53.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/54.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/55.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/56.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/57.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/58.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/59.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/6.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/60.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/61.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/62.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/63.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/64.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/65.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/66.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/67.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/68.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/69.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/7.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/70.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/71.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/8.gif
#	sop-website/src/main/resources/public/assets/lib/layui/images/face/9.gif
#	sop-website/src/main/resources/public/assets/lib/layui/lay/modules/carousel.js
#	sop-website/src/main/resources/public/assets/lib/layui/lay/modules/code.js
#	sop-website/src/main/resources/public/assets/lib/layui/lay/modules/colorpicker.js
#	sop-website/src/main/resources/public/assets/lib/layui/lay/modules/element.js
#	sop-website/src/main/resources/public/assets/lib/layui/lay/modules/flow.js
#	sop-website/src/main/resources/public/assets/lib/layui/lay/modules/form.js
#	sop-website/src/main/resources/public/assets/lib/layui/lay/modules/jquery.js
#	sop-website/src/main/resources/public/assets/lib/layui/lay/modules/laydate.js
#	sop-website/src/main/resources/public/assets/lib/layui/lay/modules/layedit.js
#	sop-website/src/main/resources/public/assets/lib/layui/lay/modules/layer.js
#	sop-website/src/main/resources/public/assets/lib/layui/lay/modules/laypage.js
#	sop-website/src/main/resources/public/assets/lib/layui/lay/modules/laytpl.js
#	sop-website/src/main/resources/public/assets/lib/layui/lay/modules/mobile.js
#	sop-website/src/main/resources/public/assets/lib/layui/lay/modules/rate.js
#	sop-website/src/main/resources/public/assets/lib/layui/lay/modules/slider.js
#	sop-website/src/main/resources/public/assets/lib/layui/lay/modules/table.js
#	sop-website/src/main/resources/public/assets/lib/layui/lay/modules/tree.js
#	sop-website/src/main/resources/public/assets/lib/layui/lay/modules/upload.js
#	sop-website/src/main/resources/public/assets/lib/layui/lay/modules/util.js
#	sop-website/src/main/resources/public/assets/lib/layui/layui.all.js
#	sop-website/src/main/resources/public/assets/lib/layui/layui.js
#	sop-website/src/main/resources/public/assets/lib/layuiext/Form.js
#	sop-website/src/main/resources/public/assets/lib/layuiext/module/treetable-lay/treetable.css
#	sop-website/src/main/resources/public/assets/lib/layuiext/module/treetable-lay/treetable.js
#	sop-website/src/main/resources/public/config/config.js
#	sop-website/src/main/resources/public/index.html
#	sop-website/src/main/resources/public/pages/doc/auth.html
#	sop-website/src/main/resources/public/pages/doc/code.html
#	sop-website/src/main/resources/public/pages/doc/doc.html
#	sop-website/src/main/resources/public/pages/doc/doc.js
#	sop-website/src/main/resources/public/pages/doc/docEvent.js
#	sop-website/src/main/resources/public/pages/doc/sign.html
#	sop-website/src/main/resources/public/pages/sandbox/sandbox.html
#	sop-website/src/main/resources/public/pages/sandbox/sandbox.js
#	sop-website/src/test/java/com/gitee/sop/websiteserver/WebsiteServerApplicationTests.java
pull/1/head
tanghc 5 years ago
commit 6f880a3a51
  1. 60
      doc/docs/_sidebar.md
  2. 10
      doc/docs/files/10021_开发流程.md
  3. 63
      doc/docs/files/90011_原理分析之如何存储路由.md
  4. 437
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/HttpTool.java
  5. 29
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/NacosConfigs.java
  6. 89
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/RouteDefinition.java
  7. 16
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/ServiceInfo.java
  8. 48
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/bean/ServiceInstance.java
  9. 32
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/ConfigPushService.java
  10. 50
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/RegistryService.java
  11. 134
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/service/impl/RegistryServiceNacosImpl.java
  12. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/css/app.c6dfb7ee.css
  13. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/css/chunk-2c1f2e8f.0314067f.css
  14. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/css/chunk-4de1c2b6.a37cd815.css
  15. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/css/chunk-510c5a69.5e48e29a.css
  16. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/css/chunk-c3ce42fe.6b789903.css
  17. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/app.872b3cd0.js
  18. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-25908fca.79487469.js
  19. 10
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2c1f2e8f.019801bd.js
  20. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d2085ef.2e46d1f4.js
  21. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-2d221c34.0db10350.js
  22. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-4de1c2b6.0188bd8f.js
  23. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-510c5a69.c3dd05bb.js
  24. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-6f78c9fe.bec8af48.js
  25. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-73b2dcec.a99ece5c.js
  26. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-9b31c83a.9f4ebfeb.js
  27. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-9f479afe.6b795525.js
  28. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-c3ce42fe.77fc6cc5.js
  29. 1
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-elementUI.298ac98c.js
  30. 53
      sop-admin/sop-admin-server/src/main/resources/public/static/js/chunk-libs.75deb05f.js
  31. 29
      sop-auth/.gitignore
  32. 135
      sop-auth/pom.xml
  33. 11
      sop-auth/readme.md
  34. 15
      sop-auth/src/main/java/com/gitee/sop/sopauth/SopAuthApplication.java
  35. 44
      sop-auth/src/main/java/com/gitee/sop/sopauth/auth/AccessToken.java
  36. 14
      sop-auth/src/main/java/com/gitee/sop/sopauth/auth/AppIdManager.java
  37. 30
      sop-auth/src/main/java/com/gitee/sop/sopauth/auth/FetchTokenParam.java
  38. 39
      sop-auth/src/main/java/com/gitee/sop/sopauth/auth/FetchTokenResult.java
  39. 46
      sop-auth/src/main/java/com/gitee/sop/sopauth/auth/OAuth2Config.java
  40. 85
      sop-auth/src/main/java/com/gitee/sop/sopauth/auth/OAuth2Manager.java
  41. 44
      sop-auth/src/main/java/com/gitee/sop/sopauth/auth/OAuth2Service.java
  42. 22
      sop-auth/src/main/java/com/gitee/sop/sopauth/auth/OpenUser.java
  43. 40
      sop-auth/src/main/java/com/gitee/sop/sopauth/auth/RefreshToken.java
  44. 29
      sop-auth/src/main/java/com/gitee/sop/sopauth/auth/TokenPair.java
  45. 25
      sop-auth/src/main/java/com/gitee/sop/sopauth/auth/exception/LoginErrorException.java
  46. 33
      sop-auth/src/main/java/com/gitee/sop/sopauth/auth/impl/AppIdManagerImpl.java
  47. 128
      sop-auth/src/main/java/com/gitee/sop/sopauth/auth/impl/OAuth2ManagerCache.java
  48. 168
      sop-auth/src/main/java/com/gitee/sop/sopauth/auth/impl/OAuth2ManagerRedis.java
  49. 262
      sop-auth/src/main/java/com/gitee/sop/sopauth/auth/impl/OAuth2ServiceImpl.java
  50. 20
      sop-auth/src/main/java/com/gitee/sop/sopauth/config/OpenServiceConfig.java
  51. 54
      sop-auth/src/main/java/com/gitee/sop/sopauth/controller/CallbackController.java
  52. 87
      sop-auth/src/main/java/com/gitee/sop/sopauth/controller/OAuth2Controller.java
  53. 32
      sop-auth/src/main/java/com/gitee/sop/sopauth/entity/IsvInfo.java
  54. 50
      sop-auth/src/main/java/com/gitee/sop/sopauth/entity/UserInfo.java
  55. 11
      sop-auth/src/main/java/com/gitee/sop/sopauth/mapper/IsvInfoMapper.java
  56. 11
      sop-auth/src/main/java/com/gitee/sop/sopauth/mapper/UserInfoMapper.java
  57. 26
      sop-auth/src/main/resources/application-dev.properties
  58. 1
      sop-auth/src/main/resources/application.properties
  59. 19
      sop-auth/src/main/resources/templates/oauth2login.html
  60. 40
      sop-auth/src/test/java/com/gitee/sop/sopauth/RefreshTokenTest.java
  61. 16
      sop-auth/src/test/java/com/gitee/sop/sopauth/SopAuthApplicationTests.java
  62. 17
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/GatewayFilterDefinition.java
  63. 42
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/GatewayPredicateDefinition.java
  64. 29
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/NacosConfigs.java
  65. 72
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/RouteDefinition.java
  66. 38
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ServiceRouteInfo.java
  67. 46
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/filter/EnvGrayFilter.java
  68. 62
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/gateway/route/GatewayRouteCache.java
  69. 70
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/BaseRouteCache.java
  70. 25
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/RouteLoader.java
  71. 136
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ServiceRoutesLoader.java
  72. 37
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/RequestContextUtil.java
  73. 49
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PreEnvGrayFilter.java
  74. 34
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/route/ZuulRouteCache.java
  75. 91
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/easyopen/EasyopenApiMetaBuilder.java
  76. 24
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/easyopen/EasyopenServiceConfiguration.java
  77. 27
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/easyopen/EasyopenServiceRouteController.java
  78. 100
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ApiMetaBuilder.java
  79. 55
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ServiceRouteController.java
  80. 134
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ServiceRouteInfoBuilder.java
  81. 9
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ServiceRouteInfoHandler.java
  82. 71
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/route/RouteDefinition.java
  83. 7
      sop-example/sop-easyopen/src/main/resources/application-dev.properties
  84. 1
      sop-example/sop-easyopen/src/main/resources/application.properties
  85. 13
      sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/param/CategoryParam.java
  86. 13
      sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/result/CategoryResult.java
  87. 36
      sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/result/FileUploadResult.java
  88. 23
      sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/result/StoryResult.java
  89. 1
      sop-test/src/main/resources/file1.txt
  90. 1
      sop-test/src/main/resources/file2.txt
  91. 29
      sop-website/.gitignore
  92. 13
      sop-website/src/main/java/com/gitee/sop/websiteserver/WebsiteServerApplication.java
  93. 17
      sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocInfo.java
  94. 51
      sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocItem.java
  95. 15
      sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocModule.java
  96. 31
      sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocParameter.java
  97. 16
      sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocParserContext.java
  98. 14
      sop-website/src/main/java/com/gitee/sop/websiteserver/bean/EurekaApplication.java
  99. 13
      sop-website/src/main/java/com/gitee/sop/websiteserver/bean/EurekaApplications.java
  100. 11
      sop-website/src/main/java/com/gitee/sop/websiteserver/bean/EurekaApps.java
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,32 +1,32 @@
* [首页](/?t=1567561041568)
* [首页](/?t=1566794701229)
* 开发文档
* [快速体验](files/10010_快速体验.md?t=1567561041571)
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1567561041592)
* [新增接口](files/10020_新增接口.md?t=1567561041592)
* [开发流程](files/10021_开发流程.md?t=1567561041592)
* [业务参数校验](files/10030_业务参数校验.md?t=1567561041592)
* [错误处理](files/10040_错误处理.md?t=1567561041592)
* [编写文档](files/10041_编写文档.md?t=1567561041592)
* [接口交互详解](files/10050_接口交互详解.md?t=1567561041593)
* [easyopen支持](files/10070_easyopen支持.md?t=1567561041593)
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1567561041593)
* [ISV管理](files/10085_ISV管理.md?t=1567561041593)
* [自定义返回结果](files/10087_自定义返回结果.md?t=1567561041593)
* [自定义过滤器](files/10088_自定义过滤器.md?t=1567561041593)
* [路由授权](files/10090_路由授权.md?t=1567561041594)
* [接口限流](files/10092_接口限流.md?t=1567561041594)
* [监控日志](files/10093_监控日志.md?t=1567561041594)
* [SDK开发](files/10095_SDK开发.md?t=1567561041594)
* [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1567561041594)
* [应用授权](files/10097_应用授权.md?t=1567561041594)
* [传统web开发](files/10100_传统web开发.md?t=1567561041594)
* [文件上传](files/10104_文件上传.md?t=1567561041594)
* [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1567561041594)
* [预发布灰度发布](files/10110_预发布灰度发布.md?t=1567561041595)
* [动态修改请求参数](files/10111_动态修改请求参数.md?t=1567561041595)
* [快速体验](files/10010_快速体验.md?t=1566794701233)
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1566794701264)
* [新增接口](files/10020_新增接口.md?t=1566794701265)
* [开发流程](files/10021_开发流程.md?t=1566794701265)
* [业务参数校验](files/10030_业务参数校验.md?t=1566794701265)
* [错误处理](files/10040_错误处理.md?t=1566794701265)
* [编写文档](files/10041_编写文档.md?t=1566794701265)
* [接口交互详解](files/10050_接口交互详解.md?t=1566794701265)
* [easyopen支持](files/10070_easyopen支持.md?t=1566794701265)
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1566794701266)
* [ISV管理](files/10085_ISV管理.md?t=1566794701266)
* [自定义返回结果](files/10087_自定义返回结果.md?t=1566794701266)
* [自定义过滤器](files/10088_自定义过滤器.md?t=1566794701266)
* [路由授权](files/10090_路由授权.md?t=1566794701266)
* [接口限流](files/10092_接口限流.md?t=1566794701266)
* [监控日志](files/10093_监控日志.md?t=1566794701266)
* [SDK开发](files/10095_SDK开发.md?t=1566794701266)
* [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1566794701267)
* [应用授权](files/10097_应用授权.md?t=1566794701267)
* [传统web开发](files/10100_传统web开发.md?t=1566794701267)
* [文件上传](files/10104_文件上传.md?t=1566794701270)
* [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1566794701270)
* [预发布灰度发布](files/10110_预发布灰度发布.md?t=1566794701270)
* [动态修改请求参数](files/10111_动态修改请求参数.md?t=1566794701270)
* 原理分析
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1567561041595)
* [原理分析之如何存储路由](files/90011_原理分析之如何存储路由.md?t=1567561041595)
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1567561041595)
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1567561041595)
* [常见问题](files/90100_常见问题.md?t=1567561041595)
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1566794701270)
* [原理分析之如何存储路由](files/90011_原理分析之如何存储路由.md?t=1566794701270)
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1566794701270)
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1566794701270)
* [常见问题](files/90100_常见问题.md?t=1566794701270)

@ -0,0 +1,10 @@
# 开发流程
- 启动SOP,注册中心(nacos)、网关(sop-gateway)、后台管理(sop-admin)、文档(sop-website)
以上服务是固定的,启动一次即可,后续不用做改动。
- 在微服务端开发接口,编写swagger注解文档
- 【可选】编写sdk
- 接口开发完成,启动微服务,注册到注册中心。
- ISV调用你刚写的接口

@ -0,0 +1,63 @@
# 原理分析之如何存储路由
SOP基于spring cloud,因此会涉及到网关路由。但是开发者不用去配置文件定义路由的隐射关系,SOP自动帮你解决了这个问题。
## 获取路由信息
首先明确一点,路由信息由各微服务提供,因此网关需要从注册中心获取各微服务实例,这个通过nacos提供的`watch`来实现。
当有新的微服务加入时,Nacos会触发一个事件推送,详见`NacosWatch.java`类
```java
this.publisher.publishEvent(new HeartbeatEvent(this, this.nacosWatchIndex.getAndIncrement()));
```
因此,只要注册了`HeartbeatEvent`事件就能随时感知到微服务实例的变化。如何注册呢,config类实现`ApplicationListener`接口即可。
```java
public class AbstractConfiguration implements ApplicationContextAware, ApplicationListener<HeartbeatEvent> {
/**
* nacos事件监听,每次微服务变化会触发这个方法
* @see org.springframework.cloud.alibaba.nacos.discovery.NacosWatch NacosWatch
* @param heartbeatEvent
*/
@Override
public void onApplicationEvent(HeartbeatEvent heartbeatEvent) {
...这里加载路由信息
}
}
```
然后,每个微服务提供一个restful接口,接口地址为:`http://ip:port/sop/routes`,用来返回自身路由信息。网关请求这个接口用来获取路由信息。
加载路由伪代码如下:
```java
public void onApplicationEvent(HeartbeatEvent heartbeatEvent) {
// 获取nacos中的服务实例列表
List<Instance> allInstances = namingService.getAllInstances(serviceName);
for(Instance instance : allInstances) {
// 微服务提供的接口
String url = "http://" + instance.getIp + ":" + instance.getPort + "/sop/routes";
ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
if (responseEntity.getStatusCode() == HttpStatus.OK) {
// 返回来的路由信息
String body = responseEntity.getBody();
...加载路由到本地
}
}
}
```
完整代码可参看`com.gitee.sop.gatewaycommon.manager.ServiceRoutesLoader.java`
路由的存储方式是一个Map,key为路由id,即接口名+版本号。
```java
/**
* key:nameVersion
*/
private Map<String, ZuulTargetRoute> nameVersionTargetRouteMap;
```
因为客户端调用接口都会传递一个接口名和版本号,因此通过这两个字段能够很快查询出路由信息,进行路由转发操作。

@ -0,0 +1,437 @@
package com.gitee.sop.adminserver.bean;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.FormBody;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.apache.commons.codec.digest.DigestUtils;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* http请求工具基于OKHTTP3
* @author tanghc
*/
public class HttpTool {
private static final MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json; charset=utf-8");
private Map<String, List<Cookie>> cookieStore = new HashMap<String, List<Cookie>>();
private OkHttpClient httpClient;
public HttpTool() {
this(new HttpToolConfig());
}
public HttpTool(HttpToolConfig httpToolConfig) {
this.initHttpClient(httpToolConfig);
}
protected void initHttpClient(HttpToolConfig httpToolConfig) {
httpClient = new OkHttpClient.Builder()
// 设置链接超时时间,默认10秒
.connectTimeout(httpToolConfig.connectTimeoutSeconds, TimeUnit.SECONDS)
.readTimeout(httpToolConfig.readTimeoutSeconds, TimeUnit.SECONDS)
.writeTimeout(httpToolConfig.writeTimeoutSeconds, TimeUnit.SECONDS)
.cookieJar(new CookieJar() {
@Override
public void saveFromResponse(HttpUrl httpUrl, List<Cookie> list) {
cookieStore.put(httpUrl.host(), list);
}
@Override
public List<Cookie> loadForRequest(HttpUrl httpUrl) {
List<Cookie> cookies = cookieStore.get(httpUrl.host());
return cookies != null ? cookies : new ArrayList<Cookie>();
}
}).build();
}
@Data
public static class HttpToolConfig {
/**
* 请求超时时间
*/
private int connectTimeoutSeconds = 10;
/**
* http读取超时时间
*/
private int readTimeoutSeconds = 10;
/**
* http写超时时间
*/
private int writeTimeoutSeconds = 10;
}
/**
* get请求
*
* @param url
* @param header
* @return
* @throws IOException
*/
public String get(String url, Map<String, String> header) throws IOException {
Request.Builder builder = new Request.Builder().url(url).get();
// 添加header
addHeader(builder, header);
Request request = builder.build();
Response response = httpClient.newCall(request).execute();
return response.body().string();
}
/**
* 提交表单
*
* @param url url
* @param form 参数
* @param header header
* @param method 请求方式postget等
* @return
* @throws IOException
*/
public String request(String url, Map<String, ?> form, Map<String, String> header, HTTPMethod method) throws IOException {
Request.Builder requestBuilder = buildRequestBuilder(url, form, method);
// 添加header
addHeader(requestBuilder, header);
Request request = requestBuilder.build();
Response response = httpClient
.newCall(request)
.execute();
try {
return response.body().string();
} finally {
response.close();
}
}
/**
* 请求json数据contentType=application/json
* @param url 请求路径
* @param json json数据
* @param header header
* @return 返回响应结果
* @throws IOException
*/
public String requestJson(String url, String json, Map<String, String> header) throws IOException {
RequestBody body = RequestBody.create(MEDIA_TYPE_JSON, json);
Request.Builder requestBuilder = new Request.Builder()
.url(url)
.post(body);
// 添加header
addHeader(requestBuilder, header);
Request request = requestBuilder.build();
Response response = httpClient
.newCall(request)
.execute();
try {
return response.body().string();
} finally {
response.close();
}
}
public static Request.Builder buildRequestBuilder(String url, Map<String, ?> form, HTTPMethod method) {
switch (method) {
case GET:
return new Request.Builder()
.url(buildHttpUrl(url, form))
.get();
case HEAD:
return new Request.Builder()
.url(buildHttpUrl(url, form))
.head();
case PUT:
return new Request.Builder()
.url(url)
.put(buildFormBody(form));
case DELETE:
return new Request.Builder()
.url(url)
.delete(buildFormBody(form));
default:
return new Request.Builder()
.url(url)
.post(buildFormBody(form));
}
}
public static HttpUrl buildHttpUrl(String url, Map<String, ?> form) {
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
for (Map.Entry<String, ?> entry : form.entrySet()) {
urlBuilder.addQueryParameter(entry.getKey(), String.valueOf(entry.getValue()));
}
return urlBuilder.build();
}
public static FormBody buildFormBody(Map<String, ?> form) {
FormBody.Builder paramBuilder = new FormBody.Builder(StandardCharsets.UTF_8);
for (Map.Entry<String, ?> entry : form.entrySet()) {
paramBuilder.add(entry.getKey(), String.valueOf(entry.getValue()));
}
return paramBuilder.build();
}
/**
* 提交表单并且上传文件
*
* @param url
* @param form
* @param header
* @param files
* @return
* @throws IOException
*/
public String requestFile(String url, Map<String, ?> form, Map<String, String> header, List<UploadFile> files)
throws IOException {
// 创建MultipartBody.Builder,用于添加请求的数据
MultipartBody.Builder bodyBuilder = new MultipartBody.Builder();
bodyBuilder.setType(MultipartBody.FORM);
for (UploadFile uploadFile : files) {
// 请求的名字
bodyBuilder.addFormDataPart(uploadFile.getName(),
// 文件的文字,服务器端用来解析的
uploadFile.getFileName(),
// 创建RequestBody,把上传的文件放入
RequestBody.create(null, uploadFile.getFileData())
);
}
for (Map.Entry<String, ?> entry : form.entrySet()) {
bodyBuilder.addFormDataPart(entry.getKey(), String.valueOf(entry.getValue()));
}
RequestBody requestBody = bodyBuilder.build();
Request.Builder builder = new Request.Builder().url(url).post(requestBody);
// 添加header
addHeader(builder, header);
Request request = builder.build();
Response response = httpClient.newCall(request).execute();
try {
return response.body().string();
} finally {
response.close();
}
}
/**
* 请求数据
*
* @param url 请求url
* @param form 请求数据
* @param header header
* @return 返回Response
* @throws IOException
*/
public Response requestForResponse(String url, Map<String, ?> form, Map<String, String> header, HTTPMethod method) throws IOException {
Request.Builder requestBuilder = buildRequestBuilder(url, form, method);
// 添加header
addHeader(requestBuilder, header);
Request request = requestBuilder.build();
return httpClient
.newCall(request)
.execute();
}
/**
* 下载文件
*
* @param url 请求url
* @param form 请求数据
* @param header header
* @return 返回文件流
* @throws IOException
*/
public InputStream downloadFile(String url, Map<String, ?> form, Map<String, String> header) throws IOException {
Request.Builder requestBuilder = buildRequestBuilder(url, form, HTTPMethod.GET);
// 添加header
addHeader(requestBuilder, header);
Request request = requestBuilder.build();
Response response = httpClient
.newCall(request)
.execute();
if (response.isSuccessful()) {
ResponseBody body = response.body();
return body == null ? null : body.byteStream();
}
return null;
}
private void addHeader(Request.Builder builder, Map<String, String> header) {
if (header != null) {
Set<Map.Entry<String, String>> entrySet = header.entrySet();
for (Map.Entry<String, String> entry : entrySet) {
builder.addHeader(entry.getKey(), String.valueOf(entry.getValue()));
}
}
}
public void setCookieStore(Map<String, List<Cookie>> cookieStore) {
this.cookieStore = cookieStore;
}
public void setHttpClient(OkHttpClient httpClient) {
this.httpClient = httpClient;
}
public enum HTTPMethod {
/** http GET */
GET,
/** http POST */
POST,
/** http PUT */
PUT,
/** http HEAD */
HEAD,
/** http DELETE */
DELETE;
private HTTPMethod() {
}
public String value() {
return this.name();
}
public static HTTPMethod fromValue(String v) {
return valueOf(v.toUpperCase());
}
}
/**
* 文件上传类
* @author tanghc
*/
@Getter
@Setter
public static class UploadFile implements Serializable {
private static final long serialVersionUID = -1100614660944996398L;
/**
* @param name 表单名称不能重复
* @param file 文件
* @throws IOException
*/
public UploadFile(String name, File file) throws IOException {
this(name, file.getName(), FileUtil.toBytes(file));
}
/**
* @param name 表单名称不能重复
* @param fileName 文件名
* @param input 文件流
* @throws IOException
*/
public UploadFile(String name, String fileName, InputStream input) throws IOException {
this(name, fileName, FileUtil.toBytes(input));
}
/**
* @param name 表单名称不能重复
* @param fileName 文件名
* @param fileData 文件数据
*/
public UploadFile(String name, String fileName, byte[] fileData) {
super();
this.name = name;
this.fileName = fileName;
this.fileData = fileData;
this.md5 = DigestUtils.md5Hex(fileData);
}
private String name;
private String fileName;
private byte[] fileData;
private String md5;
}
public static class FileUtil {
/**
* The default buffer size to use.
*/
private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
private static final int EOF = -1;
/**
* 将文件流转换成byte[]
* @param input
* @return
* @throws IOException
*/
public static byte[] toBytes(InputStream input) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
int n = 0;
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
while (EOF != (n = input.read(buffer))) {
output.write(buffer, 0, n);
}
return output.toByteArray();
}
/**
* 将文件转换成数据流
* @param file 文件
* @return 返回数据流
* @throws IOException
*/
public static byte[] toBytes(File file) throws IOException {
if (file.exists()) {
if (file.isDirectory()) {
throw new IOException("File '" + file + "' exists but is a directory");
}
if (file.canRead() == false) {
throw new IOException("File '" + file + "' cannot be read");
}
} else {
throw new FileNotFoundException("File '" + file + "' does not exist");
}
InputStream input = null;
try {
input = new FileInputStream(file);
return toBytes(input);
} finally {
try {
if (input != null) {
input.close();
}
} catch (IOException ioe) {
}
}
}
}
}

@ -0,0 +1,29 @@
package com.gitee.sop.adminserver.bean;
/**
* @author tanghc
*/
public class NacosConfigs {
public static final String GROUP_CHANNEL = "sop:channel";
public static final String GROUP_ROUTE = "sop:route";
public static final String DATA_ID_GRAY = "com.gitee.sop.channel.gray";
public static final String DATA_ID_IP_BLACKLIST = "com.gitee.sop.channel.ipblacklist";
public static final String DATA_ID_ISV = "com.gitee.sop.channel.isv";
public static final String DATA_ID_ROUTE_PERMISSION = "com.gitee.sop.channel.routepermission";
public static final String DATA_ID_LIMIT_CONFIG = "com.gitee.sop.channel.limitconfig";
public static final String DATA_ID_ROUTE_CONFIG = "com.gitee.sop.channel.routeconfig";
private static final String DATA_ID_TPL = "com.gitee.sop.route.%s";
public static String getRouteDataId(String serviceId) {
return String.format(DATA_ID_TPL, serviceId);
}
}

@ -0,0 +1,89 @@
package com.gitee.sop.adminserver.bean;
import com.alibaba.fastjson.annotation.JSONField;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* @author tanghc
*/
@Data
public class RouteDefinition {
/**
* 路由的Id
*/
@ApiDocField(description = "路由id")
private String id = "";
/**
* 接口名
*/
private String name;
/**
* 版本号
*/
private String version;
/**
* 路由断言集合配置
*/
@JSONField(serialize = false)
private List<GatewayPredicateDefinition> predicates = new ArrayList<>();
/**
* 路由过滤器集合配置
*/
@JSONField(serialize = false)
private List<GatewayFilterDefinition> filters = new ArrayList<>();
/**
* 路由规则转发的目标uri
*/
@ApiDocField(description = "uri")
private String uri;
/**
* uri后面跟的path
*/
@ApiDocField(description = "path")
private String path;
/**
* 路由执行的顺序
*/
private int order = 0;
/**
* 是否忽略验证业务参数验证除外
*/
@ApiDocField(description = "是否忽略验证,业务参数验证除外,1:忽略,0:不忽略")
private int ignoreValidate;
/**
* 状态0待审核1启用2禁用
*/
@ApiDocField(description = "状态,0:待审核,1:启用,2:禁用")
private int status = 1;
/**
* 合并结果统一格式输出
*/
@ApiDocField(description = "合并结果,统一格式输出,1:合并,0:不合并")
private int mergeResult = 1;
/**
* 是否需要授权才能访问
*/
@ApiDocField(description = "是否需要授权才能访问,1:是,0:否")
private int permission;
/**
* 是否是自定义路由
*/
@ApiDocField(description = "是否是自定义路由,1:是,0:否")
private int custom;
}

@ -0,0 +1,16 @@
package com.gitee.sop.adminserver.bean;
import lombok.Data;
import java.util.List;
/**
* @author tanghc
*/
@Data
public class ServiceInfo {
/** 服务名称 */
private String serviceId;
/** 实例列表 */
private List<ServiceInstance> instances;
}

@ -0,0 +1,48 @@
package com.gitee.sop.adminserver.bean;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
/**
* @author tanghc
*/
@Data
public class ServiceInstance {
/**
* 实例id
*/
private String instanceId;
/**
* 服务名称
*/
private String serviceId;
/**
* ip
*/
private String ip;
/**
* port
*/
private int port;
/**
* 服务状态UP已上线OUT_OF_SERVICE已下线
*/
private String status;
/**
* 更新时间
*/
private String updateTime;
/**
* user extended attributes
*/
private Map<String, String> metadata = new HashMap<String, String>();
}

@ -0,0 +1,32 @@
package com.gitee.sop.adminserver.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.annotation.NacosInjected;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import com.gitee.sop.adminserver.bean.ChannelMsg;
import com.gitee.sop.adminserver.common.BizException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* @author tanghc
*/
@Slf4j
@Service
public class ConfigPushService {
@NacosInjected
private ConfigService configService;
public void publishConfig(String dataId, String groupId, ChannelMsg channelMsg) {
try {
log.info("nacos配置, dataId={}, groupId={}, operation={}", dataId, groupId, channelMsg.getOperation());
configService.publishConfig(dataId, groupId, JSON.toJSONString(channelMsg));
} catch (NacosException e) {
log.error("nacos配置失败, dataId={}, groupId={}, operation={}", dataId, groupId, channelMsg.getOperation(), e);
throw new BizException("nacos配置失败");
}
}
}

@ -0,0 +1,50 @@
package com.gitee.sop.adminserver.service;
import com.gitee.sop.adminserver.bean.ServiceInfo;
import com.gitee.sop.adminserver.bean.ServiceInstance;
import java.util.List;
/**
* @author tanghc
*/
public interface RegistryService {
String TIMESTAMP_PATTERN = "yyyy-MM-dd HH:mm:ss";
/**
* 获取所有服务列表
*
* @param pageNo 当前页码
* @param pageSize 分页大小
* @return 返回服务列表
* @throws Exception 获取失败抛出异常
*/
List<ServiceInfo> listAllService(int pageNo, int pageSize) throws Exception;
/**
* 服务上线
*
* @param serviceInstance
* @throws Exception 服务上线失败抛出异常
*/
void onlineInstance(ServiceInstance serviceInstance) throws Exception;
/**
* 服务下线
*
* @param serviceInstance
* @throws Exception 服务下线失败抛出异常
*/
void offlineInstance(ServiceInstance serviceInstance) throws Exception;
/**
* 设置实例元数据
*
* @param serviceInstance 实例
* @param key key
* @param value
* @throws Exception 设置实例元数据失败抛出异常
*/
void setMetadata(ServiceInstance serviceInstance, String key, String value) throws Exception;
}

@ -0,0 +1,134 @@
package com.gitee.sop.adminserver.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingFactory;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.pojo.ListView;
import com.gitee.sop.adminserver.bean.HttpTool;
import com.gitee.sop.adminserver.bean.ServiceInfo;
import com.gitee.sop.adminserver.bean.ServiceInstance;
import com.gitee.sop.adminserver.service.RegistryService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* nacos接口实现, https://nacos.io/zh-cn/docs/open-api.html
* @author tanghc
*/
@Slf4j
public class RegistryServiceNacosImpl implements RegistryService {
private static HttpTool httpTool = new HttpTool();
@Value("${nacos.discovery.server-addr:${registry.nacos-server-addr:}}")
private String nacosAddr;
private NamingService namingService;
@PostConstruct
public void after() throws NacosException {
if (StringUtils.isBlank(nacosAddr)) {
throw new IllegalArgumentException("请在配置文件中指定nacos.discovery.server-addr参数");
}
namingService = NamingFactory.createNamingService(nacosAddr);
}
@Override
public List<ServiceInfo> listAllService(int pageNo, int pageSize) throws Exception {
ListView<String> servicesOfServer = namingService.getServicesOfServer(pageNo, pageSize);
List<String> serverList = servicesOfServer.getData();
List<ServiceInfo> serviceInfoList = new ArrayList<>();
for (String serviceName : serverList) {
ServiceInfo serviceInfo = new ServiceInfo();
serviceInfo.setServiceId(serviceName);
List<Instance> instanceList = namingService.getAllInstances(serviceName);
if (CollectionUtils.isEmpty(instanceList)) {
serviceInfo.setInstances(Collections.emptyList());
} else {
serviceInfo.setInstances(new ArrayList<>(instanceList.size()));
for (Instance instance : instanceList) {
ServiceInstance serviceInstance = new ServiceInstance();
serviceInstance.setInstanceId(instance.getInstanceId());
serviceInstance.setServiceId(serviceInfo.getServiceId());
serviceInstance.setIp(instance.getIp());
serviceInstance.setPort(instance.getPort());
boolean isOnline = instance.getWeight() > 0;
String status = isOnline ? "UP" : "OUT_OF_SERVICE";
serviceInstance.setStatus(status);
serviceInstance.setMetadata(instance.getMetadata());
serviceInfo.getInstances().add(serviceInstance);
}
}
serviceInfoList.add(serviceInfo);
}
serviceInfoList.sort((o1, o2) -> o2.getInstances().size() - o1.getInstances().size());
return serviceInfoList;
}
@Override
public void onlineInstance(ServiceInstance serviceInstance) throws Exception {
// 查询实例
Instance instance = this.getInstance(serviceInstance);
// 上线,把权重设置成1
instance.setWeight(1);
this.updateInstance(instance);
}
@Override
public void offlineInstance(ServiceInstance serviceInstance) throws Exception {
// 查询实例
Instance instance = this.getInstance(serviceInstance);
// 下线,把权重设置成0
instance.setWeight(0);
this.updateInstance(instance);
}
@Override
public void setMetadata(ServiceInstance serviceInstance, String key, String value) throws Exception {
// 查询实例
Instance instance = this.getInstance(serviceInstance);
// 设置metadata
Map<String, String> metadata = instance.getMetadata();
metadata.put(key, value);
this.updateInstance(instance);
}
protected void updateInstance(Instance instance) throws IOException {
String json = JSON.toJSONString(instance);
JSONObject jsonObject = JSON.parseObject(json);
String response = httpTool.request("http://" + nacosAddr + "/nacos/v1/ns/instance", jsonObject, null, HttpTool.HTTPMethod.PUT);
if (!"ok".equalsIgnoreCase(response)) {
throw new RuntimeException(response);
}
}
/**
* 查询实例
* @param serviceInstance 实例信息
* @return 返回nacos实例
* @throws IOException 查询异常
*/
protected Instance getInstance(ServiceInstance serviceInstance) throws IOException {
Map<String, String> params = new HashMap<>(8);
params.put("serviceName", serviceInstance.getServiceId());
params.put("ip", serviceInstance.getIp());
params.put("port", String.valueOf(serviceInstance.getPort()));
String instanceJson = httpTool.request("http://" + nacosAddr + "/nacos/v1/ns/instance", params, null, HttpTool.HTTPMethod.GET);
Instance instance = JSON.parseObject(instanceJson, Instance.class);
instance.setServiceName(serviceInstance.getServiceId());
return instance;
}
}

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
@supports (-webkit-mask:none) and (not (cater-color:#fff)){.login-container .el-input input{color:#fff}}.login-container .el-input{display:inline-block;height:47px;width:85%}.login-container .el-input input{background:transparent;border:0;-webkit-appearance:none;border-radius:0;padding:12px 5px 12px 15px;color:#fff;height:47px;caret-color:#fff}.login-container .el-input input:-webkit-autofill{-webkit-box-shadow:0 0 0 1000px #283443 inset!important;box-shadow:inset 0 0 0 1000px #283443!important;-webkit-text-fill-color:#fff!important}.login-container .el-form-item{border:1px solid hsla(0,0%,100%,.1);background:rgba(0,0,0,.1);border-radius:5px;color:#454545}.login-container[data-v-0204d890]{min-height:100%;width:100%;background-color:#2d3a4b;overflow:hidden}.login-container .login-form[data-v-0204d890]{position:relative;width:520px;max-width:100%;padding:160px 35px 0;margin:0 auto;overflow:hidden}.login-container .tips[data-v-0204d890]{font-size:14px;color:#fff;margin-bottom:10px}.login-container .tips span[data-v-0204d890]:first-of-type{margin-right:16px}.login-container .svg-container[data-v-0204d890]{padding:6px 5px 6px 15px;color:#889aa4;vertical-align:middle;width:30px;display:inline-block}.login-container .title-container[data-v-0204d890]{position:relative}.login-container .title-container .title[data-v-0204d890]{font-size:26px;color:#eee;margin:0 auto 40px auto;text-align:center;font-weight:700}.login-container .show-pwd[data-v-0204d890]{position:absolute;right:10px;top:7px;font-size:16px;color:#889aa4;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}

@ -0,0 +1 @@
.wscn-http404-container[data-v-c095f994]{-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);position:absolute;top:40%;left:50%}.wscn-http404[data-v-c095f994]{position:relative;width:1200px;padding:0 50px;overflow:hidden}.wscn-http404 .pic-404[data-v-c095f994]{position:relative;float:left;width:600px;overflow:hidden}.wscn-http404 .pic-404__parent[data-v-c095f994]{width:100%}.wscn-http404 .pic-404__child[data-v-c095f994]{position:absolute}.wscn-http404 .pic-404__child.left[data-v-c095f994]{width:80px;top:17px;left:220px;opacity:0;-webkit-animation-name:cloudLeft-data-v-c095f994;animation-name:cloudLeft-data-v-c095f994;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:1s;animation-delay:1s}.wscn-http404 .pic-404__child.mid[data-v-c095f994]{width:46px;top:10px;left:420px;opacity:0;-webkit-animation-name:cloudMid-data-v-c095f994;animation-name:cloudMid-data-v-c095f994;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:1.2s;animation-delay:1.2s}.wscn-http404 .pic-404__child.right[data-v-c095f994]{width:62px;top:100px;left:500px;opacity:0;-webkit-animation-name:cloudRight-data-v-c095f994;animation-name:cloudRight-data-v-c095f994;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:1s;animation-delay:1s}@-webkit-keyframes cloudLeft-data-v-c095f994{0%{top:17px;left:220px;opacity:0}20%{top:33px;left:188px;opacity:1}80%{top:81px;left:92px;opacity:1}to{top:97px;left:60px;opacity:0}}@keyframes cloudLeft-data-v-c095f994{0%{top:17px;left:220px;opacity:0}20%{top:33px;left:188px;opacity:1}80%{top:81px;left:92px;opacity:1}to{top:97px;left:60px;opacity:0}}@-webkit-keyframes cloudMid-data-v-c095f994{0%{top:10px;left:420px;opacity:0}20%{top:40px;left:360px;opacity:1}70%{top:130px;left:180px;opacity:1}to{top:160px;left:120px;opacity:0}}@keyframes cloudMid-data-v-c095f994{0%{top:10px;left:420px;opacity:0}20%{top:40px;left:360px;opacity:1}70%{top:130px;left:180px;opacity:1}to{top:160px;left:120px;opacity:0}}@-webkit-keyframes cloudRight-data-v-c095f994{0%{top:100px;left:500px;opacity:0}20%{top:120px;left:460px;opacity:1}80%{top:180px;left:340px;opacity:1}to{top:200px;left:300px;opacity:0}}@keyframes cloudRight-data-v-c095f994{0%{top:100px;left:500px;opacity:0}20%{top:120px;left:460px;opacity:1}80%{top:180px;left:340px;opacity:1}to{top:200px;left:300px;opacity:0}}.wscn-http404 .bullshit[data-v-c095f994]{position:relative;float:left;width:300px;padding:30px 0;overflow:hidden}.wscn-http404 .bullshit__oops[data-v-c095f994]{font-size:32px;line-height:40px;color:#1482f0;margin-bottom:20px;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.wscn-http404 .bullshit__headline[data-v-c095f994],.wscn-http404 .bullshit__oops[data-v-c095f994]{font-weight:700;opacity:0;-webkit-animation-name:slideUp-data-v-c095f994;animation-name:slideUp-data-v-c095f994;-webkit-animation-duration:.5s;animation-duration:.5s}.wscn-http404 .bullshit__headline[data-v-c095f994]{font-size:20px;line-height:24px;color:#222;margin-bottom:10px;-webkit-animation-delay:.1s;animation-delay:.1s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.wscn-http404 .bullshit__info[data-v-c095f994]{font-size:13px;line-height:21px;color:grey;margin-bottom:30px;-webkit-animation-delay:.2s;animation-delay:.2s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.wscn-http404 .bullshit__info[data-v-c095f994],.wscn-http404 .bullshit__return-home[data-v-c095f994]{opacity:0;-webkit-animation-name:slideUp-data-v-c095f994;animation-name:slideUp-data-v-c095f994;-webkit-animation-duration:.5s;animation-duration:.5s}.wscn-http404 .bullshit__return-home[data-v-c095f994]{display:block;float:left;width:110px;height:36px;background:#1482f0;border-radius:100px;text-align:center;color:#fff;font-size:14px;line-height:36px;cursor:pointer;-webkit-animation-delay:.3s;animation-delay:.3s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}@-webkit-keyframes slideUp-data-v-c095f994{0%{-webkit-transform:translateY(60px);transform:translateY(60px);opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}@keyframes slideUp-data-v-c095f994{0%{-webkit-transform:translateY(60px);transform:translateY(60px);opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}

@ -0,0 +1 @@
.custom-tree-node{-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;font-size:14px;padding-right:8px}.el-input.is-disabled .el-input__inner,.el-radio__input.is-disabled+span.el-radio__label{color:#909399}.limit-tip[data-v-385a3259]{cursor:pointer;margin-left:10px}

@ -0,0 +1 @@
.dashboard-container[data-v-b4bd5162]{margin:30px}.dashboard-text[data-v-b4bd5162]{font-size:18px;line-height:46px}

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d221c34"],{cc75:function(a,t,e){"use strict";e.r(t);var i=function(){var a=this,t=a.$createElement,e=a._self._c||t;return e("div",{staticClass:"app-container"},[e("el-form",{staticClass:"demo-form-inline",attrs:{inline:!0,model:a.searchFormData,size:"mini"}},[e("el-form-item",{attrs:{label:"IP"}},[e("el-input",{staticStyle:{width:"250px"},attrs:{clearable:!0,placeholder:"输入IP"},model:{value:a.searchFormData.ip,callback:function(t){a.$set(a.searchFormData,"ip",t)},expression:"searchFormData.ip"}})],1),a._v(" "),e("el-form-item",[e("el-button",{attrs:{type:"primary",icon:"el-icon-search"},on:{click:a.loadTable}},[a._v("查询")])],1)],1),a._v(" "),e("el-button",{staticStyle:{"margin-bottom":"10px"},attrs:{type:"primary",size:"mini",icon:"el-icon-plus"},on:{click:a.onAdd}},[a._v("新增IP")]),a._v(" "),e("el-table",{attrs:{data:a.pageInfo.rows,border:"","highlight-current-row":""}},[e("el-table-column",{attrs:{prop:"ip",label:"IP",width:"200"}}),a._v(" "),e("el-table-column",{attrs:{prop:"remark",label:"备注",width:"300"}}),a._v(" "),e("el-table-column",{attrs:{prop:"gmtCreate",label:"添加时间",width:"160"}}),a._v(" "),e("el-table-column",{attrs:{prop:"gmtModified",label:"修改时间",width:"160"}}),a._v(" "),e("el-table-column",{attrs:{label:"操作",width:"150"},scopedSlots:a._u([{key:"default",fn:function(t){return[e("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(e){return a.onTableUpdate(t.row)}}},[a._v("修改")]),a._v(" "),e("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(e){return a.onTableDelete(t.row)}}},[a._v("删除")])]}}])})],1),a._v(" "),e("el-pagination",{staticStyle:{"margin-top":"5px"},attrs:{background:"","current-page":a.searchFormData.pageIndex,"page-size":a.searchFormData.pageSize,"page-sizes":[5,10,20,40],total:a.pageInfo.total,layout:"total, sizes, prev, pager, next"},on:{"size-change":a.onSizeChange,"current-change":a.onPageIndexChange}}),a._v(" "),e("el-dialog",{attrs:{title:a.dialogTitle,visible:a.dialogVisible,"close-on-click-modal":!1},on:{"update:visible":function(t){a.dialogVisible=t},close:function(t){return a.resetForm("dialogForm")}}},[e("el-form",{ref:"dialogForm",attrs:{rules:a.dialogFormRules,model:a.dialogFormData,"label-width":"120px",size:"mini"}},[e("el-form-item",{attrs:{prop:"ip",label:"IP"}},[e("el-input",{directives:[{name:"show",rawName:"v-show",value:0===a.dialogFormData.id,expression:"dialogFormData.id === 0"}],model:{value:a.dialogFormData.ip,callback:function(t){a.$set(a.dialogFormData,"ip",t)},expression:"dialogFormData.ip"}}),a._v(" "),e("span",{directives:[{name:"show",rawName:"v-show",value:a.dialogFormData.id>0,expression:"dialogFormData.id > 0"}]},[a._v(a._s(a.dialogFormData.ip))])],1),a._v(" "),e("el-form-item",{attrs:{prop:"remark",label:"备注"}},[e("el-input",{model:{value:a.dialogFormData.remark,callback:function(t){a.$set(a.dialogFormData,"remark",t)},expression:"dialogFormData.remark"}})],1)],1),a._v(" "),e("div",{staticClass:"dialog-footer",attrs:{slot:"footer"},slot:"footer"},[e("el-button",{on:{click:function(t){a.dialogVisible=!1}}},[a._v("取 消")]),a._v(" "),e("el-button",{attrs:{type:"primary"},on:{click:a.onDialogSave}},[a._v("保 存")])],1)],1)],1)},o=[],l={data:function(){var a=/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/,t=function(t,e,i){""===e?i(new Error("请输入IP")):(a.test(e)||i(new Error("IP格式不正确")),i())};return{searchFormData:{ip:"",pageIndex:1,pageSize:10},pageInfo:{rows:[],total:0},dialogVisible:!1,dialogTitle:"",dialogFormData:{id:0,ip:"",remark:""},dialogFormRules:{ip:[{validator:t,trigger:"blur"},{min:1,max:64,message:"长度在 1 到 64 个字符",trigger:"blur"}],remark:[{max:100,message:"不能超过 100 个字符",trigger:"blur"}]}}},created:function(){this.loadTable()},methods:{loadTable:function(){this.post("ip.blacklist.page",this.searchFormData,function(a){this.pageInfo=a.data})},onTableUpdate:function(a){var t=this;this.dialogTitle="修改IP",this.dialogVisible=!0,this.$nextTick(function(){Object.assign(t.dialogFormData,a)})},onTableDelete:function(a){this.confirm("确认要移除IP【".concat(a.ip,"】吗?"),function(t){var e={id:a.id};this.post("ip.blacklist.del",e,function(){t(),this.tip("删除成功"),this.loadTable()})})},onDialogSave:function(){var a=this;this.$refs.dialogForm.validate(function(t){if(t){var e=a.dialogFormData.id?"ip.blacklist.update":"ip.blacklist.add";a.post(e,a.dialogFormData,function(){this.dialogVisible=!1,this.loadTable()})}})},onSizeChange:function(a){this.searchFormData.pageSize=a,this.loadTable()},onAdd:function(){this.dialogTitle="新增IP",this.dialogVisible=!0,this.dialogFormData.id=0},onPageIndexChange:function(a){this.searchFormData.pageIndex=a,this.loadTable()}}},r=l,n=e("2877"),s=Object(n["a"])(r,i,o,!1,null,null,null);t["default"]=s.exports}}]);

@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-4de1c2b6"],{"26fc":function(t,s,a){t.exports=a.p+"static/img/404_cloud.0f4bc32b.png"},"8cdb":function(t,s,a){"use strict";a.r(s);var e=function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("div",{staticClass:"wscn-http404-container"},[a("div",{staticClass:"wscn-http404"},[t._m(0),t._v(" "),a("div",{staticClass:"bullshit"},[a("div",{staticClass:"bullshit__oops"},[t._v("OOPS!")]),t._v(" "),t._m(1),t._v(" "),a("div",{staticClass:"bullshit__headline"},[t._v(t._s(t.message))]),t._v(" "),a("div",{staticClass:"bullshit__info"},[t._v("Please check that the URL you entered is correct, or click the button below to return to the homepage.")]),t._v(" "),a("a",{staticClass:"bullshit__return-home",attrs:{href:""}},[t._v("Back to home")])])])])},c=[function(){var t=this,s=t.$createElement,e=t._self._c||s;return e("div",{staticClass:"pic-404"},[e("img",{staticClass:"pic-404__parent",attrs:{src:a("a36b"),alt:"404"}}),t._v(" "),e("img",{staticClass:"pic-404__child left",attrs:{src:a("26fc"),alt:"404"}}),t._v(" "),e("img",{staticClass:"pic-404__child mid",attrs:{src:a("26fc"),alt:"404"}}),t._v(" "),e("img",{staticClass:"pic-404__child right",attrs:{src:a("26fc"),alt:"404"}})])},function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("div",{staticClass:"bullshit__info"},[t._v("All rights reserved\n "),a("a",{staticStyle:{color:"#20a0ff"},attrs:{href:"https://wallstreetcn.com",target:"_blank"}},[t._v("wallstreetcn")])])}],i={name:"Page404",computed:{message:function(){return"The webmaster said that you can not enter this page..."}}},l=i,n=(a("97ef"),a("2877")),r=Object(n["a"])(l,e,c,!1,null,"c095f994",null);s["default"]=r.exports},"97ef":function(t,s,a){"use strict";var e=a("d3f45"),c=a.n(e);c.a},a36b:function(t,s,a){t.exports=a.p+"static/img/404.a57b6f31.png"},d3f45:function(t,s,a){}}]);

@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-c3ce42fe"],{5040:function(t,n,e){"use strict";var c=e("585b"),s=e.n(c);s.a},"585b":function(t,n,e){},9406:function(t,n,e){"use strict";e.r(n);var c=function(){var t=this,n=t.$createElement;t._self._c;return t._m(0)},s=[function(){var t=this,n=t.$createElement,e=t._self._c||n;return e("div",{staticClass:"dashboard-container"},[e("div",{staticClass:"dashboard-text"},[t._v("欢迎使用SOP Admin")])])}],a={created:function(){this.post("admin.userinfo.get",{},function(t){})}},i=a,r=(e("5040"),e("2877")),o=Object(r["a"])(i,c,s,!1,null,"b4bd5162",null);n["default"]=o.exports}}]);

File diff suppressed because one or more lines are too long

@ -0,0 +1,29 @@
HELP.md
/target/
!.mvn/wrapper/maven-wrapper.jar
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
/build/
### VS Code ###
.vscode/

@ -0,0 +1,135 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-auth</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>sop-auth</name>
<description>授权程序</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
<oltu.version>0.31</oltu.version>
</properties>
<dependencies>
<!-- sop相关配置 -->
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<!-- sop相关配置 end-->
<!-- 使用nacos注册中心
版本 0.2.x.RELEASE 对应的是 Spring Boot 2.x 版本,版本 0.1.x.RELEASE 对应的是 Spring Boot 1.x 版本。
https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-alibaba-nacos-discovery
-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.2.2.RELEASE</version>
<exclusions>
<exclusion>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sdk-java</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.oschina.durcframework</groupId>
<artifactId>fastmybatis-spring-boot-starter</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- oauth2服务端 -->
<dependency>
<groupId>org.apache.oltu.oauth2</groupId>
<artifactId>org.apache.oltu.oauth2.authzserver</artifactId>
<version>${oltu.version}</version>
</dependency>
<dependency>
<groupId>org.apache.oltu.oauth2</groupId>
<artifactId>org.apache.oltu.oauth2.resourceserver</artifactId>
<version>${oltu.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.1-jre</version>
<scope>compile</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,11 @@
# 应用授权服务
- 启动注册中心、网关、本服务(sop-auth)
- 浏览器访问:http://localhost:8087/oauth2/appToAppAuth?app_id=2019032617262200001&redirect_uri=http%3a%2f%2flocalhost%3a8087%2foauth2callback
- 输入用户名密码登录,这里是`zhangsan/123456`
授权接口在`OAuth2Controller`中,查看回调接口在`CallbackController`中
回调接口应该由开发者实现,这里为了演示,写在一起。
token的维护,重点关注`OAuth2ManagerRedis.java`

@ -0,0 +1,15 @@
package com.gitee.sop.sopauth;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class SopAuthApplication {
public static void main(String[] args) {
SpringApplication.run(SopAuthApplication.class, args);
}
}

@ -0,0 +1,44 @@
package com.gitee.sop.sopauth.auth;
/**
* @author tanghc
*/
public class AccessToken {
/** 过期时间,毫秒 */
private long expireTimeMillis;
private OpenUser openUser;
/**
* @param expireIn 有效时长
* @param openUser
*/
public AccessToken(long expireIn, OpenUser openUser) {
super();
if(expireIn <= 0) {
throw new IllegalArgumentException("expireIn必须大于0");
}
this.expireTimeMillis = System.currentTimeMillis() + (expireIn * 1000);
this.openUser = openUser;
}
public boolean isExpired() {
return expireTimeMillis < System.currentTimeMillis();
}
public long getExpireTimeMillis() {
return expireTimeMillis;
}
public void setExpireTimeMillis(long expireTimeMillis) {
this.expireTimeMillis = expireTimeMillis;
}
public OpenUser getOpenUser() {
return openUser;
}
public void setOpenUser(OpenUser openUser) {
this.openUser = openUser;
}
}

@ -0,0 +1,14 @@
package com.gitee.sop.sopauth.auth;
/**
* @author tanghc
*/
public interface AppIdManager {
/**
* 是否是合法的appId
*
* @param appId
* @return true:合法
*/
boolean isValidAppId(String appId);
}

@ -0,0 +1,30 @@
package com.gitee.sop.sopauth.auth;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* 获取accessToken参数对象
* @author tanghc
*/
@Data
public class FetchTokenParam {
/**
* 授权类型<br>
* 如果使用app_auth_code换取token则为authorization_code
* 如果使用refresh_token换取新的token则为refresh_token
*/
@NotBlank(message = "授权类型不能为空")
private String grant_type;
/**
* 授权码.与refresh_token二选一用户对应用授权后得到即第一步中开发者获取到的app_auth_code值
*/
private String code;
/**
* 刷新令牌.与code二选一可为空刷新令牌时使用
*/
private String refresh_token;
}

@ -0,0 +1,39 @@
package com.gitee.sop.sopauth.auth;
import lombok.Data;
/**
* 使用app_auth_code换取app_auth_token返回结果
* @author tanghc
*/
@Data
public class FetchTokenResult {
/**
* 授权令牌
*/
private String app_auth_token;
/**
* 用户id
*/
private String user_id;
/**
* 令牌有效期. -1:永久有效
*/
private long expires_in = -1;
/**
* 刷新令牌有效期. -1:永久有效
*/
private long re_expires_in = -1;
/**
* 刷新令牌时使用
* 刷新令牌后我们会保证老的app_auth_token从刷新开始10分钟内可继续使用请及时替换为最新token
*/
private String app_refresh_token;
private String error;
private String error_description;
}

@ -0,0 +1,46 @@
package com.gitee.sop.sopauth.auth;
import lombok.Data;
/**
* @author tanghc
*/
@Data
public class OAuth2Config {
private static OAuth2Config instance = new OAuth2Config();
public static OAuth2Config getInstance() {
return instance;
}
public static void setInstance(OAuth2Config instance) {
OAuth2Config.instance = instance;
}
/**
* 应用授权的app_auth_code唯一的app_auth_code使用一次后失效一天从生成app_auth_code开始的24小时未被使用自动过期
* app_auth_token永久有效
*/
private int codeTimeoutSeconds = 86400;
/**
* accessToken有效时间单位秒
* -1 永久有效
*/
private int accessTokenExpiresIn = -1;
/**
* app_refresh_token有效时间单位秒accessToken有效时间的3倍当小于0永久有效
*/
private int refreshTokenExpiresIn = accessTokenExpiresIn * 3;
/**
* 刷新令牌后我们会保证老的app_auth_token从刷新开始10分钟内可继续使用请及时替换为最新token
*/
private int afterRefreshExpiresIn = 60 * 10;
/**
* 登录视图页面用于mvc视图loginView
*/
private String oauth2LoginUri = "/oauth2/login";
}

@ -0,0 +1,85 @@
package com.gitee.sop.sopauth.auth;
import com.gitee.sop.sopauth.auth.exception.LoginErrorException;
import javax.servlet.http.HttpServletRequest;
/**
* 认证服务需要自己实现
* @author tanghc
*
*/
public interface OAuth2Manager {
/**
* 添加 auth code
*
* @param authCode
* code值
* @param authUser
* 用户
*/
void addAuthCode(String authCode, OpenUser authUser);
/**
* 添加accessToken
* @param accessToken token值
* @param refreshToken refreshToken
* @param authUser 用户
*/
void addAccessToken(String accessToken, String refreshToken, OpenUser authUser);
/**
* 删除这个accessToken
* @param accessToken
*/
void removeAccessToken(String accessToken);
/**
* 删除这个refreshToken
* @param refreshToken
*/
void removeRefreshToken(String refreshToken);
/**
* 获取RefreshToken
* @param refreshToken
* @return 返回Token信息
*/
RefreshToken getRefreshToken(String refreshToken);
/**
* 验证auth code是否有效
*
* @param authCode
* @return 无效返回false
*/
boolean checkAuthCode(String authCode);
/**
* 根据auth code获取用户
*
* @param authCode
* @return 返回用户
*/
OpenUser getUserByAuthCode(String authCode);
/**
* 根据access token获取用户
*
* @param accessToken
* token值
* @return 返回用户
*/
OpenUser getUserByAccessToken(String accessToken);
/**
* 用户登录需判断是否已经登录
* @param request
* @return 返回用户对象
* @throws LoginErrorException 登录失败异常
*/
OpenUser login(HttpServletRequest request) throws LoginErrorException;
}

@ -0,0 +1,44 @@
package com.gitee.sop.sopauth.auth;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.OAuthResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URISyntaxException;
/**
* @author tanghc
*/
public interface OAuth2Service {
/**
* oauth2授权,获取code.
* <pre>
* 1首先通过如http://localhost:8080/api/authorize?client_id=test&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2callback访问授权页面;
2该控制器首先检查clientId是否正确如果错误将返回相应的错误信息
3然后判断用户是否登录了如果没有登录首先到登录页面登录
4登录成功后生成相应的code即授权码然后重定向到客户端地址如http://localhost:8080/oauth2callback?code=6d250650831fea227749f49a5b49ccad;在重定向到的地址中会带上code参数(授权码),接着客户端可以根据授权码去换取accessToken。
* </pre>
*
* @param request req
* @param response resp
* @param authConfig 配置
* @return 返回响应内容
* @throws URISyntaxException
* @throws OAuthSystemException
*/
OAuthResponse authorize(HttpServletRequest request, HttpServletResponse response, OAuth2Config authConfig)
throws URISyntaxException, OAuthSystemException;
/**
* 通过code获取accessToken.
* @param fetchTokenParam
* @param authConfig 配置项
* @return 返回响应内容
* @throws URISyntaxException
* @throws OAuthSystemException
*/
FetchTokenResult accessToken(FetchTokenParam fetchTokenParam, OAuth2Config authConfig);
}

@ -0,0 +1,22 @@
package com.gitee.sop.sopauth.auth;
import java.io.Serializable;
/**
* 如果要使用oauth2功能自己的用户类需要实现这个对象
*
* @author tanghc
*/
public interface OpenUser extends Serializable {
/**
* 返回用户id
* @return
*/
String getUserId();
/**
* 返回用户名
*
* @return 返回用户名
*/
String getUsername();
}

@ -0,0 +1,40 @@
package com.gitee.sop.sopauth.auth;
import java.io.Serializable;
/**
* @author tanghc
*/
public class RefreshToken implements Serializable {
private static final long serialVersionUID = 1L;
private String accessToken;
private OpenUser openUser;
public RefreshToken(String accessToken, OpenUser openUser) {
super();
this.accessToken = accessToken;
this.openUser = openUser;
}
public RefreshToken() {
super();
}
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public OpenUser getOpenUser() {
return openUser;
}
public void setOpenUser(OpenUser openUser) {
this.openUser = openUser;
}
}

@ -0,0 +1,29 @@
package com.gitee.sop.sopauth.auth;
/**
* 存放accessToken和refreshToken
*
* @author tanghc
*
*/
public class TokenPair {
private String accessToken;
private String refreshToken;
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public String getRefreshToken() {
return refreshToken;
}
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
}

@ -0,0 +1,25 @@
package com.gitee.sop.sopauth.auth.exception;
/**
* @author tanghc
*/
public class LoginErrorException extends Exception {
private static final long serialVersionUID = -6721499454527023339L;
public LoginErrorException() {
super();
}
public LoginErrorException(String message, Throwable cause) {
super(message, cause);
}
public LoginErrorException(String message) {
super(message);
}
public LoginErrorException(Throwable cause) {
super(cause);
}
}

@ -0,0 +1,33 @@
package com.gitee.sop.sopauth.auth.impl;
import com.gitee.sop.sopauth.auth.AppIdManager;
import com.gitee.sop.sopauth.entity.IsvInfo;
import com.gitee.sop.sopauth.mapper.IsvInfoMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author tanghc
*/
@Service
@Slf4j
public class AppIdManagerImpl implements AppIdManager {
public static final int FORBIDDEN = 2;
@Autowired
private IsvInfoMapper isvInfoMapper;
@Override
public boolean isValidAppId(String appId) {
IsvInfo isvInfo = isvInfoMapper.getByColumn("app_key", appId);
if (isvInfo == null) {
return false;
}
if (isvInfo.getStatus().intValue() == FORBIDDEN) {
log.error("appId已禁用:{}", appId);
return false;
}
return true;
}
}

@ -0,0 +1,128 @@
package com.gitee.sop.sopauth.auth.impl;
import com.gitee.fastmybatis.core.query.Query;
import com.gitee.sop.sopauth.auth.OAuth2Config;
import com.gitee.sop.sopauth.auth.OAuth2Manager;
import com.gitee.sop.sopauth.auth.OpenUser;
import com.gitee.sop.sopauth.auth.RefreshToken;
import com.gitee.sop.sopauth.auth.exception.LoginErrorException;
import com.gitee.sop.sopauth.entity.UserInfo;
import com.gitee.sop.sopauth.mapper.UserInfoMapper;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;
/**
* oauth2管理默认谷歌缓存实现跟redis实现只能用一个
* 这里为了演示使用本地缓存正式环境请使用redis保存
* @see OAuth2ManagerRedis OAuth2ManagerRedis
* @author tanghc
*
*/
@Service
public class OAuth2ManagerCache implements OAuth2Manager {
/**
* 应用授权的app_auth_code唯一的app_auth_code使用一次后失效一天从生成app_auth_code开始的24小时未被使用自动过期
* app_auth_token永久有效
*/
private int codeTimeoutSeconds = OAuth2Config.getInstance().getCodeTimeoutSeconds();
/**
* accessToken过期时间
* -1 永久有效
*/
private int accessTokenTimeoutSeconds = OAuth2Config.getInstance().getAccessTokenExpiresIn();
private int refreshTokenTimeoutSeconds = OAuth2Config.getInstance().getRefreshTokenExpiresIn();
private LoadingCache<String, OpenUser> codeCache = buildCache(codeTimeoutSeconds);
private LoadingCache<String, OpenUser> accessTokenCache = buildCache(accessTokenTimeoutSeconds);
private LoadingCache<String, RefreshToken> refreshTokenCache = buildCache(refreshTokenTimeoutSeconds);
@Autowired
private UserInfoMapper userInfoMapper;
private static <T> LoadingCache<String, T> buildCache(int timeout) {
CacheBuilder<Object, Object> cacheBuilder = CacheBuilder.newBuilder();
if (timeout > 0) {
cacheBuilder.expireAfterAccess(timeout, TimeUnit.SECONDS);
}
return cacheBuilder
.build(new CacheLoader<String, T>() {
@Override
public T load(String key) throws Exception {
return null;
}
});
}
@Override
public void addAuthCode(String authCode, OpenUser authUser) {
codeCache.put(authCode, authUser);
}
@Override
public void addAccessToken(String accessToken, String refreshToken, OpenUser authUser) {
accessTokenCache.put(accessToken, authUser);
refreshTokenCache.put(refreshToken, new RefreshToken(accessToken, authUser));
}
@Override
public void removeAccessToken(String accessToken) {
accessTokenCache.asMap().remove(accessToken);
}
@Override
public void removeRefreshToken(String refreshToken) {
refreshTokenCache.asMap().remove(refreshToken);
}
@Override
public RefreshToken getRefreshToken(String refreshToken) {
return refreshTokenCache.getIfPresent(refreshToken);
}
@Override
public boolean checkAuthCode(String authCode) {
return codeCache.asMap().containsKey(authCode);
}
@Override
public OpenUser getUserByAuthCode(String authCode) {
return codeCache.getIfPresent(authCode);
}
@Override
public OpenUser getUserByAccessToken(String accessToken) {
return accessTokenCache.getIfPresent(accessToken);
}
@Override
public OpenUser login(HttpServletRequest request) throws LoginErrorException {
// 这里应该先检查用户有没有登录,如果登录直接返回openUser
String username = request.getParameter("username");
String password = request.getParameter("password");
if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
throw new LoginErrorException("用户名密码不能为空");
}
Query query = new Query();
query.eq("username", username)
.eq("password", password);
UserInfo userInfo = userInfoMapper.getByQuery(query);
if(userInfo == null) {
throw new LoginErrorException("用户名密码不正确");
}
return userInfo;
}
}

@ -0,0 +1,168 @@
package com.gitee.sop.sopauth.auth.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.gitee.fastmybatis.core.query.Query;
import com.gitee.sop.sopauth.auth.OAuth2Config;
import com.gitee.sop.sopauth.auth.OAuth2Manager;
import com.gitee.sop.sopauth.auth.OpenUser;
import com.gitee.sop.sopauth.auth.RefreshToken;
import com.gitee.sop.sopauth.auth.exception.LoginErrorException;
import com.gitee.sop.sopauth.entity.UserInfo;
import com.gitee.sop.sopauth.mapper.UserInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;
/**
*
* oauth2管理redis实现这个类跟OAuth2ManagerCache类只能用一个
* 如果要用这个类
* 1注释掉OAuth2ManagerCache的@Service
* 2打开yml中redis部分
* 3启用这个类的@Service
*
* @author tanghc
*/
//@Service
public class OAuth2ManagerRedis implements OAuth2Manager {
private static String CODE_PREFIX = "com.gitee.sop.oauth2_code:";
private static String ACCESS_TOKEN_PREFIX = "com.gitee.sop.oauth2_access_token:";
private static String REFRESH_TOKEN_PREFIX = "com.gitee.sop.oauth2_refresh_token:";
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private UserInfoMapper userInfoMapper;
public static String getCodeKey(String code) {
return CODE_PREFIX + code;
}
public static String getAccessTokenKey(String accessToken) {
return ACCESS_TOKEN_PREFIX + accessToken;
}
public static String getRefreshTokenKey(String refreshToken) {
return REFRESH_TOKEN_PREFIX + refreshToken;
}
@Override
public void addAuthCode(String authCode, OpenUser authUser) {
long codeTimeoutSeconds = OAuth2Config.getInstance().getCodeTimeoutSeconds();
redisTemplate.opsForValue().set(getCodeKey(authCode),
JSON.toJSONString(authUser),
codeTimeoutSeconds,
TimeUnit.SECONDS);
}
@Override
public void addAccessToken(String accessToken, String refreshToken, OpenUser authUser) {
// 存accessToken
long expiresIn = OAuth2Config.getInstance().getAccessTokenExpiresIn();
long reExpiresIn = OAuth2Config.getInstance().getRefreshTokenExpiresIn();
if (expiresIn > 0) {
redisTemplate.opsForValue().set(getAccessTokenKey(accessToken), JSON.toJSONString(authUser), expiresIn, TimeUnit.SECONDS);
} else {
redisTemplate.opsForValue().set(getAccessTokenKey(accessToken), JSON.toJSONString(authUser));
}
// 存refreshToken
if (reExpiresIn > 0) {
redisTemplate.opsForValue().set(
getRefreshTokenKey(refreshToken),
JSON.toJSONString(new RefreshToken(accessToken, authUser)),
// refreshToken过期时间
reExpiresIn,
TimeUnit.SECONDS);
} else {
redisTemplate.opsForValue().set(
getRefreshTokenKey(refreshToken),
JSON.toJSONString(new RefreshToken(accessToken, authUser)));
}
}
@Override
public void removeAccessToken(String accessToken) {
String accessTokenKey = getAccessTokenKey(accessToken);
int afterRefreshExpiresIn = OAuth2Config.getInstance().getAfterRefreshExpiresIn();
// 刷新令牌后,保证老的app_auth_token从刷新开始10分钟内可继续使用
redisTemplate.expire(accessTokenKey, afterRefreshExpiresIn, TimeUnit.SECONDS);
}
@Override
public void removeRefreshToken(String refreshToken) {
redisTemplate.delete(getRefreshTokenKey(refreshToken));
}
@Override
public RefreshToken getRefreshToken(String refreshToken) {
String json = redisTemplate.opsForValue().get(getRefreshTokenKey(refreshToken));
if(StringUtils.isEmpty(json)) {
return null;
}
JSONObject jsonObj = JSON.parseObject(json);
String userJson = jsonObj.getString("openUser");
UserInfo user = JSON.parseObject(userJson, UserInfo.class);
String accessToken = jsonObj.getString("accessToken");
return new RefreshToken(accessToken, user);
}
@Override
public boolean checkAuthCode(String authCode) {
if(StringUtils.isEmpty(authCode)) {
return false;
}
return redisTemplate.hasKey(getCodeKey(authCode));
}
@Override
public OpenUser getUserByAuthCode(String authCode) {
String json = redisTemplate.opsForValue().get(getCodeKey(authCode));
if(StringUtils.isEmpty(json)) {
return null;
}
return JSON.parseObject(json, UserInfo.class);
}
@Override
public OpenUser getUserByAccessToken(String accessToken) {
String json = redisTemplate.opsForValue().get(getAccessTokenKey(accessToken));
if(StringUtils.isEmpty(json)) {
return null;
}
return JSON.parseObject(json, UserInfo.class);
}
@Override
public OpenUser login(HttpServletRequest request) throws LoginErrorException {
// 这里应该先检查用户有没有登录,如果登录直接返回openUser
String username = request.getParameter("username");
String password = request.getParameter("password");
if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
throw new LoginErrorException("用户名密码不能为空");
}
Query query = new Query();
query.eq("username", username)
.eq("password", password);
UserInfo userInfo = userInfoMapper.getByQuery(query);
if(userInfo == null) {
throw new LoginErrorException("用户名密码不正确");
}
return userInfo;
}
}

@ -0,0 +1,262 @@
package com.gitee.sop.sopauth.auth.impl;
import com.gitee.sop.sopauth.auth.AppIdManager;
import com.gitee.sop.sopauth.auth.FetchTokenParam;
import com.gitee.sop.sopauth.auth.FetchTokenResult;
import com.gitee.sop.sopauth.auth.OAuth2Config;
import com.gitee.sop.sopauth.auth.OAuth2Manager;
import com.gitee.sop.sopauth.auth.OAuth2Service;
import com.gitee.sop.sopauth.auth.OpenUser;
import com.gitee.sop.sopauth.auth.RefreshToken;
import com.gitee.sop.sopauth.auth.TokenPair;
import com.gitee.sop.sopauth.auth.exception.LoginErrorException;
import lombok.extern.slf4j.Slf4j;
import org.apache.oltu.oauth2.as.issuer.MD5Generator;
import org.apache.oltu.oauth2.as.issuer.OAuthIssuer;
import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl;
import org.apache.oltu.oauth2.as.request.OAuthAuthzRequest;
import org.apache.oltu.oauth2.as.response.OAuthASResponse;
import org.apache.oltu.oauth2.common.OAuth;
import org.apache.oltu.oauth2.common.error.OAuthError;
import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.OAuthResponse;
import org.apache.oltu.oauth2.common.message.types.GrantType;
import org.apache.oltu.oauth2.common.message.types.ResponseType;
import org.apache.oltu.oauth2.common.utils.OAuthUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URISyntaxException;
/**
* oauth2服务端默认实现
*
* @author tanghc
*/
@Service
@Slf4j
public class OAuth2ServiceImpl implements OAuth2Service {
private static final String TOKEN_TYPE = "Bearer";
public static final String APP_ID_NAME = "app_id";
private OAuthIssuer oauthIssuer = new OAuthIssuerImpl(new MD5Generator());
@Autowired
private OAuth2Manager oauth2Manager;
@Autowired
private AppIdManager appIdManager;
@Override
public OAuthResponse authorize(HttpServletRequest request, HttpServletResponse resp, OAuth2Config authConfig)
throws URISyntaxException, OAuthSystemException {
try {
// 构建OAuth 授权请求
OAuthAuthzRequest oauthRequest = new OAuthAuthzRequest(request);
String clientId = oauthRequest.getClientId();
// 检查传入的客户端id是否正确
if (!appIdManager.isValidAppId(clientId)) {
return OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
.setError(OAuthError.TokenResponse.INVALID_CLIENT)
.setErrorDescription(OAuthError.TokenResponse.INVALID_CLIENT).buildJSONMessage();
}
// 如果用户没有登录,跳转到登陆页面
OpenUser user = null;
try {
user = oauth2Manager.login(request);
} catch (LoginErrorException e) {
log.error(e.getMessage(), e);
request.setAttribute("error", e.getMessage());
try {
request.getRequestDispatcher(authConfig.getOauth2LoginUri()).forward(request, resp);
return null;
} catch (Exception e1) {
log.error(e1.getMessage(), e1);
throw new RuntimeException(e1);
}
}
// 生成授权码
String authorizationCode = null;
// responseType目前仅支持CODE,另外还有TOKEN
String responseType = oauthRequest.getParam(OAuth.OAUTH_RESPONSE_TYPE);
if (responseType.equals(ResponseType.CODE.toString())) {
OAuthIssuerImpl oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
authorizationCode = oauthIssuerImpl.authorizationCode();
oauth2Manager.addAuthCode(authorizationCode, user);
}
// 进行OAuth响应构建
OAuthASResponse.OAuthAuthorizationResponseBuilder builder = OAuthASResponse.authorizationResponse(request,
HttpServletResponse.SC_FOUND);
// 设置授权码
builder.setCode(authorizationCode);
builder.setParam(APP_ID_NAME, clientId);
// 得到到客户端重定向地址
String redirectURI = oauthRequest.getParam(OAuth.OAUTH_REDIRECT_URI);
// 构建响应
return builder.location(redirectURI).buildQueryMessage();
} catch (OAuthProblemException e) {
// 出错处理
String redirectUri = e.getRedirectUri();
if (OAuthUtils.isEmpty(redirectUri)) {
// 告诉客户端没有传入redirectUri直接报错
String error = "OAuth redirectUri needs to be provided by client!!!";
return OAuthASResponse.errorResponse(HttpServletResponse.SC_FOUND)
.error(OAuthProblemException.error(error)).location(redirectUri).buildQueryMessage();
} else {
// 返回错误消息(如?error=)
return OAuthASResponse.errorResponse(HttpServletResponse.SC_FOUND).error(e).location(redirectUri)
.buildQueryMessage();
}
}
}
@Override
public FetchTokenResult accessToken(FetchTokenParam fetchTokenParam, OAuth2Config authConfig) {
try {
// 这里需要检查client_id和client_secret,但是已经通过网关接口访问进来了
// 表示client_id和client_secret是合法的
// 检查验证类型,如果是第一次进来用code换取accessToken
String grant_type = fetchTokenParam.getGrant_type();
if (GrantType.AUTHORIZATION_CODE.toString().equals(grant_type)) {
String authCode = fetchTokenParam.getCode();
if (!oauth2Manager.checkAuthCode(authCode)) {
FetchTokenResult errorResult = new FetchTokenResult();
errorResult.setError(OAuthError.CodeResponse.INVALID_REQUEST);
errorResult.setError_description("invalid request");
return errorResult;
}
// 生成Access Token
OpenUser user = oauth2Manager.getUserByAuthCode(authCode);
if (user == null) {
throw OAuthProblemException.error("Can not found user by code.");
}
TokenPair tokenPair = this.createNewToken(user);
oauth2Manager.addAccessToken(tokenPair.getAccessToken(), tokenPair.getRefreshToken(), user);
// 生成OAuth响应
return this.buildTokenResult(tokenPair, user);
} else if (GrantType.REFRESH_TOKEN.toString().equals(grant_type)) {
// 用refreshToken来刷新accessToken
String refreshToken = fetchTokenParam.getRefresh_token();
if (StringUtils.isEmpty(refreshToken)) {
FetchTokenResult errorResult = new FetchTokenResult();
errorResult.setError(OAuthError.ResourceResponse.EXPIRED_TOKEN);
errorResult.setError_description("expired token");
return errorResult;
}
RefreshToken refreshTokenObj = oauth2Manager.getRefreshToken(refreshToken);
if (refreshTokenObj == null) {
FetchTokenResult errorResult = new FetchTokenResult();
errorResult.setError(OAuthError.ResourceResponse.INVALID_TOKEN);
errorResult.setError_description("invalid token");
return errorResult;
}
OpenUser user = refreshTokenObj.getOpenUser();
// 老的token对
TokenPair oldTokenPair = new TokenPair();
oldTokenPair.setAccessToken(refreshTokenObj.getAccessToken());
oldTokenPair.setRefreshToken(refreshToken);
// 创建一对新的accessToken和refreshToken
TokenPair newTokenPair = this.createRefreshToken(user, oldTokenPair);
this.afterRefreshToken(oldTokenPair, newTokenPair, user);
// 返回新的accessToken和refreshToken
return this.buildTokenResult(newTokenPair, user);
} else {
FetchTokenResult errorResult = new FetchTokenResult();
errorResult.setError(OAuthError.TokenResponse.INVALID_GRANT);
errorResult.setError_description("invalid grant");
return errorResult;
}
} catch (OAuthProblemException e) {
log.error(e.getMessage(), e);
FetchTokenResult errorResult = new FetchTokenResult();
errorResult.setError(e.getMessage());
errorResult.setError_description("invalid grant");
return errorResult;
}
}
/**
* 刷新token后续操作
*
* @param oldTokenPair 老的token
* @param newTokenPair 新的token
* @param user 用户
*/
protected void afterRefreshToken(TokenPair oldTokenPair, TokenPair newTokenPair, OpenUser user) {
// 保存token
oauth2Manager.addAccessToken(newTokenPair.getAccessToken(), newTokenPair.getRefreshToken(), user);
// 删除老的accessToken
oauth2Manager.removeAccessToken(oldTokenPair.getAccessToken());
// 删除老的refreshToken
oauth2Manager.removeRefreshToken(oldTokenPair.getRefreshToken());
}
/**
* 创建新的token
*
* @param user
* @return 返回新token
*/
protected TokenPair createNewToken(OpenUser user) {
return this.createDefaultTokenPair();
}
/**
* 返回刷新后token
*
* @param user 用户
* @param oldTokenPair 旧的token
* @return 返回新的token
*/
protected TokenPair createRefreshToken(OpenUser user, TokenPair oldTokenPair) {
return this.createDefaultTokenPair();
}
private TokenPair createDefaultTokenPair() {
TokenPair tokenPair = new TokenPair();
try {
String accessToken = oauthIssuer.accessToken();
String refreshToken = oauthIssuer.refreshToken();
tokenPair.setAccessToken(accessToken);
tokenPair.setRefreshToken(refreshToken);
return tokenPair;
} catch (OAuthSystemException e) {
throw new RuntimeException(e);
}
}
private FetchTokenResult buildTokenResult(TokenPair tokenPair, OpenUser user) {
OAuth2Config auth2Config = OAuth2Config.getInstance();
FetchTokenResult fetchTokenResult = new FetchTokenResult();
fetchTokenResult.setApp_auth_token(tokenPair.getAccessToken());
fetchTokenResult.setApp_refresh_token(tokenPair.getRefreshToken());
fetchTokenResult.setUser_id(user.getUserId());
fetchTokenResult.setExpires_in(auth2Config.getAccessTokenExpiresIn());
fetchTokenResult.setRe_expires_in(auth2Config.getRefreshTokenExpiresIn());
return fetchTokenResult;
}
}

@ -0,0 +1,20 @@
package com.gitee.sop.sopauth.config;
import com.gitee.sop.servercommon.bean.ServiceConfig;
import com.gitee.sop.servercommon.configuration.AlipayServiceConfiguration;
import org.springframework.context.annotation.Configuration;
/**
* 使用支付宝开放平台功能
*
* @author tanghc
*/
@Configuration
public class OpenServiceConfig extends AlipayServiceConfiguration {
static {
ServiceConfig.getInstance().getI18nModules().add("i18n/isp/goods_error");
}
}

@ -0,0 +1,54 @@
package com.gitee.sop.sopauth.controller;
import com.gitee.sop.sdk.client.OpenClient;
import com.gitee.sop.sdk.model.OpenAuthTokenAppModel;
import com.gitee.sop.sdk.request.OpenAuthTokenAppRequest;
import com.gitee.sop.sdk.response.OpenAuthTokenAppResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author tanghc
*/
@Controller
@Slf4j
public class CallbackController {
String url = "http://localhost:8081";
String appId = "2019032617262200001";
// 平台提供的私钥
String privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqWNA/++OYEV7WYXwexZK/J8LY1OWlP9X0T6wHFOvxNKRvMkJ5544SbgsJpVcvRDPrcxmhPbi/sAhdO4x2PiPKIz9Yni2OtYCCeaiE056B+e1O2jXoLeXbfi9fPivJZkxH/tb4xfLkH3bA8ZAQnQsoXA0SguykMRZntF0TndUfvDrLqwhlR8r5iRdZLB6F8o8qXH6UPDfNEnf/K8wX5T4EB1b8x8QJ7Ua4GcIUqeUxGHdQpzNbJdaQvoi06lgccmL+PHzminkFYON7alj1CjDN833j7QMHdPtS9l7B67fOU/p2LAAkPMtoVBfxQt9aFj7B8rEhGCz02iJIBAgMBAAECggEARqOuIpY0v6WtJBfmR3lGIOOokLrhfJrGTLF8CiZMQha+SRJ7/wOLPlsH9SbjPlopyViTXCuYwbzn2tdABigkBHYXxpDV6CJZjzmRZ+FY3S/0POlTFElGojYUJ3CooWiVfyUMhdg5vSuOq0oCny53woFrf32zPHYGiKdvU5Djku1onbDU0Lw8w+5tguuEZ76kZ/lUcccGy5978FFmYpzY/65RHCpvLiLqYyWTtaNT1aQ/9pw4jX9HO9NfdJ9gYFK8r/2f36ZE4hxluAfeOXQfRC/WhPmiw/ReUhxPznG/WgKaa/OaRtAx3inbQ+JuCND7uuKeRe4osP2jLPHPP6AUwQKBgQDUNu3BkLoKaimjGOjCTAwtp71g1oo+k5/uEInAo7lyEwpV0EuUMwLA/HCqUgR4K9pyYV+Oyb8d6f0+Hz0BMD92I2pqlXrD7xV2WzDvyXM3s63NvorRooKcyfd9i6ccMjAyTR2qfLkxv0hlbBbsPHz4BbU63xhTJp3Ghi0/ey/1HQKBgQC2VsgqC6ykfSidZUNLmQZe3J0p/Qf9VLkfrQ+xaHapOs6AzDU2H2osuysqXTLJHsGfrwVaTs00ER2z8ljTJPBUtNtOLrwNRlvgdnzyVAKHfOgDBGwJgiwpeE9voB1oAV/mXqSaUWNnuwlOIhvQEBwekqNyWvhLqC7nCAIhj3yvNQKBgQCqYbeec56LAhWP903Zwcj9VvG7sESqXUhIkUqoOkuIBTWFFIm54QLTA1tJxDQGb98heoCIWf5x/A3xNI98RsqNBX5JON6qNWjb7/dobitti3t99v/ptDp9u8JTMC7penoryLKK0Ty3bkan95Kn9SC42YxaSghzqkt+uvfVQgiNGQKBgGxU6P2aDAt6VNwWosHSe+d2WWXt8IZBhO9d6dn0f7ORvcjmCqNKTNGgrkewMZEuVcliueJquR47IROdY8qmwqcBAN7Vg2K7r7CPlTKAWTRYMJxCT1Hi5gwJb+CZF3+IeYqsJk2NF2s0w5WJTE70k1BSvQsfIzAIDz2yE1oPHvwVAoGAA6e+xQkVH4fMEph55RJIZ5goI4Y76BSvt2N5OKZKd4HtaV+eIhM3SDsVYRLIm9ZquJHMiZQGyUGnsvrKL6AAVNK7eQZCRDk9KQz+0GKOGqku0nOZjUbAu6A2/vtXAaAuFSFx1rUQVVjFulLexkXR3KcztL1Qu2k5pB6Si0K/uwQ=";
OpenClient openClient = new OpenClient(url, appId, privateKey);
/**
* 模拟开发者回调这里需要开发者自己实现.通过code换取token
* @return
*/
@GetMapping("oauth2callback")
@ResponseBody
public OpenAuthTokenAppResponse callback(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
String app_id = servletRequest.getParameter("app_id");
String code = servletRequest.getParameter("code");
log.info("app_id:{}, code:{}", app_id, code);
OpenAuthTokenAppRequest request = new OpenAuthTokenAppRequest();
OpenAuthTokenAppModel model = new OpenAuthTokenAppModel();
model.setCode(code);
model.setGrant_type("authorization_code");
request.setBizModel(model);
// 根据code获取token
OpenAuthTokenAppResponse response = openClient.execute(request);
if (response.isSuccess()) {
// 成功拿到token,开发者在这里保存token信息
// 后续使用token进行接口访问
log.info("授权成功,body:{}", response.getBody());
}
return response;
}
}

@ -0,0 +1,87 @@
package com.gitee.sop.sopauth.controller;
import com.gitee.sop.servercommon.annotation.ApiMapping;
import com.gitee.sop.sopauth.auth.FetchTokenParam;
import com.gitee.sop.sopauth.auth.FetchTokenResult;
import com.gitee.sop.sopauth.auth.OAuth2Config;
import com.gitee.sop.sopauth.auth.OAuth2Service;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.OAuthResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URI;
import java.net.URISyntaxException;
/**
* 授权认证
*
* @author tanghc
*/
@Controller
@RequestMapping("oauth2")
public class OAuth2Controller {
@Autowired
private OAuth2Service oAuth2Service;
// 第一步:授权URL拼装
// https://localhost:8087/oauth2/appToAppAuth?app_id=2019032617262200001&redirect_uri=http%3a%2f%2flocalhost%3a8087%2foauth2callback
@GetMapping("appToAppAuth")
public String appToAppAuth(HttpServletRequest request, ModelMap modelMap) {
String app_id = request.getParameter("app_id");
String redirect_uri = request.getParameter("redirect_uri");
modelMap.put("response_type", "code");
modelMap.put("client_id", app_id);
modelMap.put("redirect_uri", redirect_uri);
return "oauth2login";
}
/**
* 第二步点击登录到这里拿code
* oauth2认证获取code
* @param request
* @param resp
* @return 返回code
* @throws URISyntaxException
* @throws OAuthSystemException
*/
@RequestMapping("authorize")
public Object authorize(HttpServletRequest request, HttpServletResponse resp) throws URISyntaxException, OAuthSystemException {
OAuthResponse response = oAuth2Service.authorize(request, resp, OAuth2Config.getInstance());
if(response == null) {
return null;
}
HttpHeaders headers = new HttpHeaders();
headers.setLocation(new URI(response.getLocationUri()));
return new ResponseEntity<String>(headers, HttpStatus.valueOf(response.getResponseStatus()));
}
/**
* 第三步通过code获取token
* 或者通过refresh_token换取token
* @param param
* @return
*/
@ApiMapping("open.auth.token.app")
@ResponseBody
public FetchTokenResult fetchToken(FetchTokenParam param) {
FetchTokenResult fetchTokenResult = oAuth2Service.accessToken(param, OAuth2Config.getInstance());
return fetchTokenResult;
}
@RequestMapping("login")
public String oauth2login() {
return "oauth2login";
}
}

@ -0,0 +1,32 @@
package com.gitee.sop.sopauth.entity;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* 表名isv_info
* 备注isv信息表
*
* @author tanghc
*/
@Table(name = "isv_info")
@Data
public class IsvInfo {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
/** 数据库字段:id */
private Long id;
/** appKey, 数据库字段:app_key */
private String appKey;
/** 1启用,2禁用, 数据库字段:status */
private Byte status;
}

@ -0,0 +1,50 @@
package com.gitee.sop.sopauth.entity;
import com.alibaba.fastjson.annotation.JSONField;
import com.gitee.sop.sopauth.auth.OpenUser;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
/**
* 表名user_info
* 备注用户信息表
*
* @author tanghc
*/
@Table(name = "user_info")
@Data
public class UserInfo implements OpenUser {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
/** 数据库字段:id */
private Long id;
/** 用户名, 数据库字段:username */
private String username;
/** 密码, 数据库字段:password */
@JSONField(serialize = false)
private String password;
/** 昵称, 数据库字段:nickname */
private String nickname;
/** 数据库字段:gmt_create */
private Date gmtCreate;
/** 数据库字段:gmt_modified */
private Date gmtModified;
@Override
public String getUserId() {
return String.valueOf(id);
}
}

@ -0,0 +1,11 @@
package com.gitee.sop.sopauth.mapper;
import com.gitee.fastmybatis.core.mapper.CrudMapper;
import com.gitee.sop.sopauth.entity.IsvInfo;
/**
* @author tanghc
*/
public interface IsvInfoMapper extends CrudMapper<IsvInfo, Long> {
}

@ -0,0 +1,11 @@
package com.gitee.sop.sopauth.mapper;
import com.gitee.fastmybatis.core.mapper.CrudMapper;
import com.gitee.sop.sopauth.entity.UserInfo;
/**
* @author tanghc
*/
public interface UserInfoMapper extends CrudMapper<UserInfo, Long> {
}

@ -0,0 +1,26 @@
server.port=8087
spring.application.name=sop-auth
# ------- 需要改的配置 -------
# mysql数据库账号
mysql.host=localhost:3306
mysql.username=root
mysql.password=root
# nacos地址
nacos.url=127.0.0.1:8848
# ------- 需要改的配置end -------
# nacos cloud配置
spring.cloud.nacos.discovery.server-addr=${nacos.url}
# 数据库配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://${mysql.host}/sop?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
spring.datasource.username=${mysql.username}
spring.datasource.password=${mysql.password}
spring.thymeleaf.cache=false
logging.level.com.gitee=debug

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>用户登录</title>
</head>
<body>
<div th:if="${error}" style="color: red;"><span th:text="${error}"></span></div>
<form th:action="@{/oauth2/authorize}" method="post">
<input name="response_type" type="hidden" th:value="${response_type}"/>
<input name="client_id" type="hidden" th:value="${client_id}"/>
<input name="redirect_uri" type="hidden" th:value="${redirect_uri}"/>
用户名: <input type="text" name="username" value="zhangsan"/> <br/>
密码: <input type="password" name="password" value="123456"/> <br/>
<button type="submit">登录并授权</button>
</form>
</body>
</html>

@ -0,0 +1,40 @@
package com.gitee.sop.sopauth;
import com.gitee.sop.sdk.client.OpenClient;
import com.gitee.sop.sdk.model.OpenAuthTokenAppModel;
import com.gitee.sop.sdk.request.OpenAuthTokenAppRequest;
import com.gitee.sop.sdk.response.OpenAuthTokenAppResponse;
import junit.framework.TestCase;
import lombok.extern.slf4j.Slf4j;
/**
* @author tanghc
*/
@Slf4j
public class RefreshTokenTest extends TestCase {
String url = "http://localhost:8081";
String appId = "2019032617262200001";
// 平台提供的私钥
String privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqWNA/++OYEV7WYXwexZK/J8LY1OWlP9X0T6wHFOvxNKRvMkJ5544SbgsJpVcvRDPrcxmhPbi/sAhdO4x2PiPKIz9Yni2OtYCCeaiE056B+e1O2jXoLeXbfi9fPivJZkxH/tb4xfLkH3bA8ZAQnQsoXA0SguykMRZntF0TndUfvDrLqwhlR8r5iRdZLB6F8o8qXH6UPDfNEnf/K8wX5T4EB1b8x8QJ7Ua4GcIUqeUxGHdQpzNbJdaQvoi06lgccmL+PHzminkFYON7alj1CjDN833j7QMHdPtS9l7B67fOU/p2LAAkPMtoVBfxQt9aFj7B8rEhGCz02iJIBAgMBAAECggEARqOuIpY0v6WtJBfmR3lGIOOokLrhfJrGTLF8CiZMQha+SRJ7/wOLPlsH9SbjPlopyViTXCuYwbzn2tdABigkBHYXxpDV6CJZjzmRZ+FY3S/0POlTFElGojYUJ3CooWiVfyUMhdg5vSuOq0oCny53woFrf32zPHYGiKdvU5Djku1onbDU0Lw8w+5tguuEZ76kZ/lUcccGy5978FFmYpzY/65RHCpvLiLqYyWTtaNT1aQ/9pw4jX9HO9NfdJ9gYFK8r/2f36ZE4hxluAfeOXQfRC/WhPmiw/ReUhxPznG/WgKaa/OaRtAx3inbQ+JuCND7uuKeRe4osP2jLPHPP6AUwQKBgQDUNu3BkLoKaimjGOjCTAwtp71g1oo+k5/uEInAo7lyEwpV0EuUMwLA/HCqUgR4K9pyYV+Oyb8d6f0+Hz0BMD92I2pqlXrD7xV2WzDvyXM3s63NvorRooKcyfd9i6ccMjAyTR2qfLkxv0hlbBbsPHz4BbU63xhTJp3Ghi0/ey/1HQKBgQC2VsgqC6ykfSidZUNLmQZe3J0p/Qf9VLkfrQ+xaHapOs6AzDU2H2osuysqXTLJHsGfrwVaTs00ER2z8ljTJPBUtNtOLrwNRlvgdnzyVAKHfOgDBGwJgiwpeE9voB1oAV/mXqSaUWNnuwlOIhvQEBwekqNyWvhLqC7nCAIhj3yvNQKBgQCqYbeec56LAhWP903Zwcj9VvG7sESqXUhIkUqoOkuIBTWFFIm54QLTA1tJxDQGb98heoCIWf5x/A3xNI98RsqNBX5JON6qNWjb7/dobitti3t99v/ptDp9u8JTMC7penoryLKK0Ty3bkan95Kn9SC42YxaSghzqkt+uvfVQgiNGQKBgGxU6P2aDAt6VNwWosHSe+d2WWXt8IZBhO9d6dn0f7ORvcjmCqNKTNGgrkewMZEuVcliueJquR47IROdY8qmwqcBAN7Vg2K7r7CPlTKAWTRYMJxCT1Hi5gwJb+CZF3+IeYqsJk2NF2s0w5WJTE70k1BSvQsfIzAIDz2yE1oPHvwVAoGAA6e+xQkVH4fMEph55RJIZ5goI4Y76BSvt2N5OKZKd4HtaV+eIhM3SDsVYRLIm9ZquJHMiZQGyUGnsvrKL6AAVNK7eQZCRDk9KQz+0GKOGqku0nOZjUbAu6A2/vtXAaAuFSFx1rUQVVjFulLexkXR3KcztL1Qu2k5pB6Si0K/uwQ=";
OpenClient openClient = new OpenClient(url, appId, privateKey);
/**
* 根据refreshToken换取token
*/
public void testRefreshToken() {
OpenAuthTokenAppRequest request = new OpenAuthTokenAppRequest();
OpenAuthTokenAppModel model = new OpenAuthTokenAppModel();
model.setGrant_type("refresh_token");
model.setRefresh_token("c9e4003c06fe59066eed73d64ea074ca");
request.setBizModel(model);
OpenAuthTokenAppResponse response = openClient.execute(request);
if (response.isSuccess()) {
// 成功拿到token,开发者在这里保存token信息
// 后续使用token进行接口访问
log.info("换取token成功,body:{}", response.getBody());
}
}
}

@ -0,0 +1,16 @@
package com.gitee.sop.sopauth;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SopAuthApplicationTests {
@Test
public void contextLoads() {
}
}

@ -0,0 +1,17 @@
package com.gitee.sop.gatewaycommon.bean;
import lombok.Data;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author tanghc
*/
@Data
public class GatewayFilterDefinition {
/** Filter Name */
private String name;
/** 对应的路由规则 */
private Map<String, String> args = new LinkedHashMap<>();
}

@ -0,0 +1,42 @@
package com.gitee.sop.gatewaycommon.bean;
import lombok.Data;
import org.springframework.util.StringUtils;
import javax.validation.ValidationException;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author tanghc
*/
@Data
public class GatewayPredicateDefinition {
public static final String GEN_KEY = "_genkey_";
/** 断言对应的Name */
private String name;
/** 配置的断言规则 */
private Map<String, String> args = new LinkedHashMap<>();
public GatewayPredicateDefinition() {
}
public GatewayPredicateDefinition(String text) {
int eqIdx = text.indexOf(61);
if (eqIdx <= 0) {
throw new ValidationException("Unable to parse GatewayPredicateDefinition text '" + text + "', must be of the form name=value");
} else {
this.setName(text.substring(0, eqIdx));
String[] params = StringUtils.tokenizeToStringArray(text.substring(eqIdx + 1), ",");
for(int i = 0; i < params.length; ++i) {
this.args.put(generateName(i), params[i]);
}
}
}
public static String generateName(int i) {
return GEN_KEY + i;
}
}

@ -0,0 +1,29 @@
package com.gitee.sop.gatewaycommon.bean;
/**
* @author tanghc
*/
public class NacosConfigs {
public static final String GROUP_CHANNEL = "sop:channel";
public static final String GROUP_ROUTE = "sop:route";
public static final String DATA_ID_GRAY = "com.gitee.sop.channel.gray";
public static final String DATA_ID_IP_BLACKLIST = "com.gitee.sop.channel.ipblacklist";
public static final String DATA_ID_ISV = "com.gitee.sop.channel.isv";
public static final String DATA_ID_ROUTE_PERMISSION = "com.gitee.sop.channel.routepermission";
public static final String DATA_ID_LIMIT_CONFIG = "com.gitee.sop.channel.limitconfig";
public static final String DATA_ID_ROUTE_CONFIG = "com.gitee.sop.channel.routeconfig";
private static final String DATA_ID_TPL = "com.gitee.sop.route.%s";
public static String getRouteDataId(String serviceId) {
return String.format(DATA_ID_TPL, serviceId);
}
}

@ -0,0 +1,72 @@
package com.gitee.sop.gatewaycommon.bean;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* @author tanghc
*/
@Data
public class RouteDefinition {
/**
* 路由的Id接口名+版本号确保此id全局唯一
*/
private String id;
/**
* 接口名
*/
private String name;
/**
* 版本号
*/
private String version;
/**
* 路由断言集合配置
*/
private List<GatewayPredicateDefinition> predicates = new ArrayList<>();
/**
* 路由过滤器集合配置
*/
private List<GatewayFilterDefinition> filters = new ArrayList<>();
/**
* 路由规则转发的目标uri
*/
private String uri;
/**
* uri后面跟的path
*/
private String path;
/**
* 路由执行的顺序
*/
private int order = 0;
/**
* 是否忽略验证业务参数验证除外
*/
private int ignoreValidate;
/**
* 状态0待审核1启用2禁用
*/
private int status = 1;
/**
* 是否合并结果
*/
private int mergeResult;
/**
* 是否需要授权才能访问
*/
private int permission;
}

@ -0,0 +1,38 @@
package com.gitee.sop.gatewaycommon.bean;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import java.util.Date;
import java.util.List;
/**
* @author tanghc
*/
@Data
public class ServiceRouteInfo {
/**
* 服务名称对应spring.application.name
*/
private String serviceId;
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date createTime = new Date();
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date updateTime = new Date();
private String description;
/**
* 路由信息md5md5(sort(routeIdList))
*/
private String md5;
private List<RouteDefinition> routeDefinitionList;
public String fetchServiceIdLowerCase() {
return serviceId.toLowerCase();
}
}

@ -0,0 +1,46 @@
package com.gitee.sop.gatewaycommon.gateway.filter;
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
import com.gitee.sop.gatewaycommon.manager.EnvGrayManager;
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.param.ParamNames;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @author tanghc
*/
public class EnvGrayFilter implements GlobalFilter, Ordered {
@Autowired
private EnvGrayManager envGrayManager;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange);
String nameVersion = apiParam.fetchNameVersion();
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(nameVersion);
if (targetRoute == null) {
return null;
}
String serviceId = targetRoute.getServiceRouteInfo().fetchServiceIdLowerCase();
// 如果服务在灰度阶段,返回一个灰度版本号
String version = envGrayManager.getVersion(serviceId, nameVersion);
if (version != null && envGrayManager.containsKey(serviceId, apiParam.fetchAppKey())) {
ServerWebExchange serverWebExchange = ServerWebExchangeUtil.addHeaders(exchange, httpHeaders -> httpHeaders.set(ParamNames.HEADER_VERSION_NAME, version));
return chain.filter(serverWebExchange);
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return Orders.ENV_GRAY_FILTER_ORDER;
}
}

@ -0,0 +1,62 @@
package com.gitee.sop.gatewaycommon.gateway.route;
import com.gitee.sop.gatewaycommon.bean.RouteDefinition;
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo;
import com.gitee.sop.gatewaycommon.manager.BaseRouteCache;
import com.gitee.sop.gatewaycommon.manager.RouteRepository;
import org.springframework.beans.BeanUtils;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import java.net.URI;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author tanghc
*/
public class GatewayRouteCache extends BaseRouteCache<GatewayTargetRoute> {
public GatewayRouteCache(RouteRepository<GatewayTargetRoute> routeRepository) {
super(routeRepository);
}
@Override
protected GatewayTargetRoute buildTargetRoute(ServiceRouteInfo serviceRouteInfo, RouteDefinition routeDefinition) {
org.springframework.cloud.gateway.route.RouteDefinition targetRoute = new org.springframework.cloud.gateway.route.RouteDefinition();
targetRoute.setId(routeDefinition.getId());
targetRoute.setUri(URI.create(routeDefinition.getUri() + "#" + routeDefinition.getPath()));
targetRoute.setOrder(routeDefinition.getOrder());
// 添加过滤器
List<FilterDefinition> filterDefinitionList = routeDefinition.getFilters()
.stream()
.map(filter -> {
FilterDefinition filterDefinition = new FilterDefinition();
BeanUtils.copyProperties(filter, filterDefinition);
return filterDefinition;
})
.collect(Collectors.toList());
LinkedList<PredicateDefinition> predicateDefinitionList = routeDefinition.getPredicates()
.stream()
.map(predicate -> {
PredicateDefinition predicateDefinition = new PredicateDefinition();
BeanUtils.copyProperties(predicate, predicateDefinition);
return predicateDefinition;
})
.collect(Collectors.toCollection(LinkedList::new));
// 下面两个自定义的断言添加到顶部,注意:ReadBody需要放在最前面
// 对应断言:
// NameVersion -> com.gitee.sop.gatewaycommon.gateway.route.NameVersionRoutePredicateFactory
// ReadBody -> com.gitee.sop.gatewaycommon.gateway.route.ReadBodyRoutePredicateFactory
predicateDefinitionList.addFirst(new PredicateDefinition("NameVersion=" + routeDefinition.getId()));
predicateDefinitionList.addFirst(new PredicateDefinition("ReadBody="));
targetRoute.setFilters(filterDefinitionList);
targetRoute.setPredicates(predicateDefinitionList);
return new GatewayTargetRoute(serviceRouteInfo, routeDefinition, targetRoute);
}
}

@ -0,0 +1,70 @@
package com.gitee.sop.gatewaycommon.manager;
import com.alibaba.fastjson.JSON;
import com.gitee.sop.gatewaycommon.bean.RouteDefinition;
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo;
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
/**
* @author tanghc
*/
@Slf4j
public abstract class BaseRouteCache<T extends TargetRoute> implements RouteLoader {
/**
* KEY:serviceId, value: md5
*/
private Map<String, String> serviceIdMd5Map = new HashMap<>();
private RouteRepository<T> routeRepository;
/**
* 构建目标路由对象zuul和gateway定义的路由对象
*
* @param serviceRouteInfo 路由服务对象
* @param gatewayRouteDefinition 路由对象
* @return 返回目标路由对象
*/
protected abstract T buildTargetRoute(ServiceRouteInfo serviceRouteInfo, RouteDefinition gatewayRouteDefinition);
public BaseRouteCache(RouteRepository<T> routeRepository) {
this.routeRepository = routeRepository;
}
@Override
public void load(ServiceRouteInfo serviceRouteInfo, Consumer<Object> callback) {
try {
String serviceId = serviceRouteInfo.getServiceId();
String newMd5 = serviceRouteInfo.getMd5();
String oldMd5 = serviceIdMd5Map.get(serviceId);
if (Objects.equals(newMd5, oldMd5)) {
return;
}
serviceIdMd5Map.put(serviceId, newMd5);
List<RouteDefinition> routeDefinitionList = serviceRouteInfo.getRouteDefinitionList();
for (RouteDefinition routeDefinition : routeDefinitionList) {
T targetRoute = this.buildTargetRoute(serviceRouteInfo, routeDefinition);
routeRepository.add(targetRoute);
if (log.isDebugEnabled()) {
log.debug("新增路由:{}", JSON.toJSONString(routeDefinition));
}
}
callback.accept(null);
} catch (Exception e) {
log.error("加载路由信息失败,serviceRouteInfo:{}", serviceRouteInfo, e);
}
}
@Override
public void remove(String serviceId) {
serviceIdMd5Map.remove(serviceId);
routeRepository.deleteAll(serviceId);
}
}

@ -0,0 +1,25 @@
package com.gitee.sop.gatewaycommon.manager;
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo;
import java.util.function.Consumer;
/**
* @author tanghc
*/
public interface RouteLoader {
/**
* 加载路由
*
* @param serviceRouteInfo 服务路由信息
* @param callback 加载成功后回调
*/
void load(ServiceRouteInfo serviceRouteInfo, Consumer<Object> callback);
/**
* 移除某个微服务下的所有路由信息
*
* @param serviceId 服务id
*/
void remove(String serviceId);
}

@ -0,0 +1,136 @@
package com.gitee.sop.gatewaycommon.manager;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.annotation.NacosInjected;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.pojo.ServiceInfo;
import com.gitee.sop.gatewaycommon.bean.NacosConfigs;
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo;
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.alibaba.nacos.NacosDiscoveryProperties;
import org.springframework.context.ApplicationEvent;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* 发现新服务更新路由信息
*
* @author tanghc
*/
@Slf4j
public class ServiceRoutesLoader<T extends TargetRoute> {
private static final String SECRET = "a3d9sf!1@odl90zd>fkASwq";
private static final int FIVE_SECONDS = 1000 * 5;
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@NacosInjected
private ConfigService configService;
@Autowired
private BaseRouteCache<T> baseRouteCache;
private RestTemplate restTemplate = new RestTemplate();
private Map<String, Long> updateTimeMap = new HashMap<>(16);
public ServiceRoutesLoader() {
// 解决statusCode不等于200,就抛异常问题
restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
@Override
protected boolean hasError(HttpStatus statusCode) {
return statusCode == null;
}
});
}
public synchronized void load(ApplicationEvent event) {
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
List<ServiceInfo> subscribes = null;
try {
subscribes = namingService.getSubscribeServices();
} catch (NacosException e) {
log.error("namingService.getSubscribeServices()错误", e);
}
if (CollectionUtils.isEmpty(subscribes)) {
return;
}
// subscribe
String thisServiceId = nacosDiscoveryProperties.getService();
// ConfigService configService = nacosConfigProperties.configServiceInstance();
for (ServiceInfo serviceInfo : subscribes) {
String serviceName = serviceInfo.getName();
// 如果是本机服务,跳过
if (Objects.equals(thisServiceId, serviceName)) {
continue;
}
// nacos会不停的触发事件,这里做了一层拦截
// 同一个serviceId5秒内允许访问一次
Long lastUpdateTime = updateTimeMap.getOrDefault(serviceName, 0L);
long now = System.currentTimeMillis();
if (now - lastUpdateTime < FIVE_SECONDS) {
continue;
}
updateTimeMap.put(serviceName, now);
try {
String dataId = NacosConfigs.getRouteDataId(serviceName);
String groupId = NacosConfigs.GROUP_ROUTE;
List<Instance> allInstances = namingService.getAllInstances(serviceName);
if (CollectionUtils.isEmpty(allInstances)) {
// 如果没有服务列表,则删除所有路由信息
baseRouteCache.remove(serviceName);
configService.removeConfig(dataId, groupId);
} else {
for (Instance instance : allInstances) {
String url = getRouteRequestUrl(instance);
ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
if (responseEntity.getStatusCode() == HttpStatus.OK) {
String body = responseEntity.getBody();
ServiceRouteInfo serviceRouteInfo = JSON.parseObject(body, ServiceRouteInfo.class);
baseRouteCache.load(serviceRouteInfo, callback -> {
try {
log.info("加载服务路由,serviceId:{}, instance:{}",serviceName, instance);
configService.publishConfig(dataId, groupId, body);
} catch (NacosException e) {
log.error("nacos推送失败,serviceId:{}, instance:{}",serviceName, instance);
}
});
}
}
}
} catch (NacosException e) {
log.error("选择服务实例失败,serviceName:{}", serviceName, e);
}
}
}
private static String getRouteRequestUrl(Instance instance) {
String query = buildQuery(SECRET);
return "http://" + instance.getIp() + ":" + instance.getPort() + "/sop/routes" + query;
}
private static String buildQuery(String secret) {
String time = String.valueOf(System.currentTimeMillis());
String source = secret + time + secret;
String sign = DigestUtils.md5DigestAsHex(source.getBytes());
return "?time=" + time + "&sign=" + sign;
}
}

@ -0,0 +1,37 @@
package com.gitee.sop.gatewaycommon.zuul;
import com.netflix.util.Pair;
import com.netflix.zuul.context.RequestContext;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import java.util.List;
/**
* @author tanghc
*/
public class RequestContextUtil {
/**
* 获取微服务端传递过来的header
*
* @param name header名
* @return 返回value没有返回null
*/
public static String getZuulResponseHeader(RequestContext requestContext, String name) {
List<Pair<String, String>> zuulResponseHeaders = requestContext.getZuulResponseHeaders();
return zuulResponseHeaders.stream()
.filter(pair -> StringUtils.containsIgnoreCase(pair.first(), name))
.findFirst()
.map(Pair::second)
.orElse(null);
}
/**
* 获取微服务端的content-type
* @return 返回content-type
*/
public static String getZuulContentType(RequestContext requestContext) {
return getZuulResponseHeader(requestContext, HttpHeaders.CONTENT_TYPE);
}
}

@ -0,0 +1,49 @@
package com.gitee.sop.gatewaycommon.zuul.filter;
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
import com.gitee.sop.gatewaycommon.manager.EnvGrayManager;
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.param.ParamNames;
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 灰度发布判断改变版本号
*
* @author tanghc
*/
public class PreEnvGrayFilter extends BaseZuulFilter {
@Autowired
private EnvGrayManager envGrayManager;
@Override
protected FilterType getFilterType() {
return FilterType.PRE;
}
@Override
protected int getFilterOrder() {
return PRE_ENV_GRAY_FILTER_ORDER;
}
@Override
protected Object doRun(RequestContext requestContext) throws ZuulException {
ApiParam apiParam = ZuulContext.getApiParam();
String nameVersion = apiParam.fetchNameVersion();
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(nameVersion);
if (targetRoute == null) {
return null;
}
String serviceId = targetRoute.getServiceRouteInfo().fetchServiceIdLowerCase();
// 如果服务在灰度阶段,返回一个灰度版本号
String version = envGrayManager.getVersion(serviceId, nameVersion);
if (version != null && envGrayManager.containsKey(serviceId, apiParam.fetchAppKey())) {
requestContext.addZuulRequestHeader(ParamNames.HEADER_VERSION_NAME, version);
}
return null;
}
}

@ -0,0 +1,34 @@
package com.gitee.sop.gatewaycommon.zuul.route;
import com.gitee.sop.gatewaycommon.bean.RouteDefinition;
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo;
import com.gitee.sop.gatewaycommon.manager.BaseRouteCache;
import com.gitee.sop.gatewaycommon.manager.RouteRepository;
import com.gitee.sop.gatewaycommon.util.RouteUtil;
import org.springframework.cloud.netflix.zuul.filters.Route;
/**
* @author tanghc
*/
public class ZuulRouteCache extends BaseRouteCache<ZuulTargetRoute> {
/** 路由重试 */
private static final boolean RETRYABLE = true;
public ZuulRouteCache(RouteRepository<ZuulTargetRoute> routeRepository) {
super(routeRepository);
}
@Override
protected ZuulTargetRoute buildTargetRoute(ServiceRouteInfo serviceRouteInfo, RouteDefinition gatewayRouteDefinition) {
Route route = new Route(
gatewayRouteDefinition.getId()
, gatewayRouteDefinition.getPath()
, RouteUtil.getZuulLocation(gatewayRouteDefinition.getUri())
, ""
, RETRYABLE
, null
);
return new ZuulTargetRoute(serviceRouteInfo, gatewayRouteDefinition, route);
}
}

@ -0,0 +1,91 @@
package com.gitee.sop.servercommon.easyopen;
import com.gitee.easyopen.annotation.Api;
import com.gitee.easyopen.util.ReflectionUtil;
import com.gitee.sop.servercommon.bean.ServiceApiInfo;
import com.gitee.sop.servercommon.manager.ApiMetaBuilder;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* @author tanghc
*/
public class EasyopenApiMetaBuilder extends ApiMetaBuilder {
/** IndexController上面的@RequestMapping()中的值,如:/api */
private final String prefixPath;
private final String contextPath;
private ApplicationContext applicationContext;
private Environment environment;
public EasyopenApiMetaBuilder(ApplicationContext applicationContext, Environment environment) {
this.applicationContext = applicationContext;
this.environment = environment;
String path = environment.getProperty("easyopen.prefix-path");
if (path == null) {
throw new IllegalArgumentException("请在application.propertis中设置easyopen.prefix-path属性,填IndexController上面的@RequestMapping()中的值");
}
if (!path.startsWith("/")) {
path = "/" + path;
}
this.prefixPath = path;
this.contextPath = environment.getProperty("server.servlet.context-path", "/");
}
@Override
protected List<ServiceApiInfo.ApiMeta> buildApiMetaList(RequestMappingHandlerMapping requestMappingHandlerMapping) {
String[] apiServiceNames = ReflectionUtil.findApiServiceNames(applicationContext);
List<ServiceApiInfo.ApiMeta> apiMetaList = new ArrayList<>();
for (String apiServiceName : apiServiceNames) {
Object bean = applicationContext.getBean(apiServiceName);
doWithMethods(bean.getClass(), method -> {
Api api = AnnotationUtils.findAnnotation(method, Api.class);
ServiceApiInfo.ApiMeta apiMeta = new ServiceApiInfo.ApiMeta();
apiMeta.setName(api.name());
apiMeta.setVersion(api.version());
apiMeta.setIgnoreValidate(BooleanUtils.toInteger(api.ignoreValidate()));
// 对结果不合并
apiMeta.setMergeResult(BooleanUtils.toInteger(false));
// /api/goods.get/
String servletPath = this.buildPath(api);
apiMeta.setPath(servletPath);
apiMetaList.add(apiMeta);
});
}
return apiMetaList;
}
protected void doWithMethods(Class<?> clazz, Consumer<Method> consumer) {
// Keep backing up the inheritance hierarchy.
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
boolean match = !method.isSynthetic() && AnnotationUtils.findAnnotation(method, Api.class) != null;
if (match) {
consumer.accept(method);
}
}
}
protected String buildPath(Api api) {
// /api/goods.get/
String servletPath = prefixPath + "/" + api.name() + "/";
String version = api.version();
if (StringUtils.hasLength(version)) {
// /api/goods.get/1.0/
servletPath = servletPath + version + "/";
}
return contextPath + prefixPath + servletPath;
}
}

@ -0,0 +1,24 @@
package com.gitee.sop.servercommon.easyopen;
import com.gitee.easyopen.ApiContext;
import com.gitee.sop.servercommon.configuration.BaseServiceConfiguration;
import com.gitee.sop.servercommon.manager.ServiceRouteController;
import org.springframework.context.annotation.Bean;
/**
* 提供给easyopen项目使用
*
* @author tanghc
*/
public class EasyopenServiceConfiguration extends BaseServiceConfiguration {
static {
ApiContext.getApiConfig().setIgnoreValidate(true);
}
@Bean
ServiceRouteController serviceRouteController() {
return new EasyopenServiceRouteController();
}
}

@ -0,0 +1,27 @@
package com.gitee.sop.servercommon.easyopen;
import com.gitee.sop.servercommon.manager.ApiMetaBuilder;
import com.gitee.sop.servercommon.manager.ServiceRouteController;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.bind.annotation.RestController;
/**
* @author tanghc
*/
@RestController
public class EasyopenServiceRouteController extends ServiceRouteController implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
protected ApiMetaBuilder getApiMetaBuilder() {
return new EasyopenApiMetaBuilder(applicationContext, getEnvironment());
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}

@ -0,0 +1,100 @@
package com.gitee.sop.servercommon.manager;
import com.gitee.sop.servercommon.bean.ServiceApiInfo;
import com.gitee.sop.servercommon.bean.ServiceConfig;
import com.gitee.sop.servercommon.mapping.ApiMappingInfo;
import com.gitee.sop.servercommon.mapping.ApiMappingRequestCondition;
import com.gitee.sop.servercommon.mapping.RouteUtil;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author tanghc
*/
public class ApiMetaBuilder {
/** 接口名规则:允许字母、数字、点、下划线 */
private static final String REGEX_API_NAME = "^[a-zA-Z0-9\\.\\_\\-]+$";
public ServiceApiInfo getServiceApiInfo(String serviceId, RequestMappingHandlerMapping requestMappingHandlerMapping) {
if (serviceId == null) {
throw new IllegalArgumentException("请在application.properties中指定spring.application.name属性");
}
List<ServiceApiInfo.ApiMeta> apis = this.buildApiMetaList(requestMappingHandlerMapping);
// 排序
apis.sort(Comparator.comparing(ServiceApiInfo.ApiMeta::fetchNameVersion));
ServiceApiInfo serviceApiInfo = new ServiceApiInfo();
serviceApiInfo.setServiceId(serviceId);
serviceApiInfo.setApis(apis);
return serviceApiInfo;
}
protected List<ServiceApiInfo.ApiMeta> buildApiMetaList(RequestMappingHandlerMapping requestMappingHandlerMapping) {
Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
Set<RequestMappingInfo> requestMappingInfos = handlerMethods.keySet();
List<String> store = new ArrayList<>();
List<ServiceApiInfo.ApiMeta> apis = new ArrayList<>(requestMappingInfos.size());
for (Map.Entry<RequestMappingInfo, HandlerMethod> handlerMethodEntry : handlerMethods.entrySet()) {
ServiceApiInfo.ApiMeta apiMeta = this.buildApiMeta(handlerMethodEntry);
if (apiMeta == null) {
continue;
}
String key = apiMeta.fetchNameVersion();
if (store.contains(key)) {
throw new IllegalArgumentException("重复申明接口,请检查path和version,path:" + apiMeta.getPath() + ", version:" + apiMeta.getVersion());
} else {
store.add(key);
}
apis.add(apiMeta);
}
return apis;
}
protected ServiceApiInfo.ApiMeta buildApiMeta(Map.Entry<RequestMappingInfo, HandlerMethod> handlerMethodEntry) {
RequestMappingInfo requestMappingInfo = handlerMethodEntry.getKey();
Set<String> patterns = requestMappingInfo.getPatternsCondition().getPatterns();
RequestCondition<?> customCondition = requestMappingInfo.getCustomCondition();
if (customCondition instanceof ApiMappingRequestCondition) {
ApiMappingRequestCondition condition = (ApiMappingRequestCondition) customCondition;
ApiMappingInfo apiMappingInfo = condition.getApiMappingInfo();
String name = apiMappingInfo.getName();
String version = apiMappingInfo.getVersion();
// 方法完整的path,如: /goods/listGoods,/users/user/get
String path = patterns.iterator().next();
// 不是ApiMapping注解的接口,name属性是null
if (name == null || ServiceConfig.getInstance().isWebappMode()) {
name = buildName(path);
}
this.checkApiName(name);
ServiceApiInfo.ApiMeta apiMeta = new ServiceApiInfo.ApiMeta(name, path, version);
apiMeta.setIgnoreValidate(BooleanUtils.toInteger(apiMappingInfo.isIgnoreValidate()));
apiMeta.setMergeResult(BooleanUtils.toInteger(apiMappingInfo.isMergeResult()));
apiMeta.setPermission(BooleanUtils.toInteger(apiMappingInfo.isPermission()));
return apiMeta;
}
return null;
}
protected void checkApiName(String name) {
if (!name.matches(REGEX_API_NAME)) {
throw new IllegalArgumentException("接口名称只允许【字母、数字、点(.)、下划线(_)、减号(-)】,错误接口:" + name);
}
}
protected String buildName(String path) {
return RouteUtil.buildApiName(path);
}
}

@ -0,0 +1,55 @@
package com.gitee.sop.servercommon.manager;
import com.gitee.sop.servercommon.bean.ServiceApiInfo;
import com.gitee.sop.servercommon.route.ServiceRouteInfo;
import com.gitee.sop.servercommon.util.OpenUtil;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author tanghc
*/
@Slf4j
@Getter
@RestController
public class ServiceRouteController {
private static final String SECRET = "a3d9sf!1@odl90zd>fkASwq";
@Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;
@Autowired
private Environment environment;
@RequestMapping("/sop/routes")
public ServiceRouteInfo listRoutes(HttpServletRequest request, HttpServletResponse response) {
if (!OpenUtil.validateSimpleSign(request, SECRET)) {
log.error("签名验证失败, params:{}", request.getQueryString());
return null;
}
return getServiceRouteInfo(request, response);
}
protected ApiMetaBuilder getApiMetaBuilder() {
return new ApiMetaBuilder();
}
protected ServiceRouteInfo getServiceRouteInfo(HttpServletRequest request, HttpServletResponse response) {
String serviceId = environment.getProperty("spring.application.name");
ApiMetaBuilder apiMetaBuilder = getApiMetaBuilder();
ServiceApiInfo serviceApiInfo = apiMetaBuilder.getServiceApiInfo(serviceId, requestMappingHandlerMapping);
ServiceRouteInfoBuilder serviceRouteInfoBuilder = new ServiceRouteInfoBuilder(environment);
return serviceRouteInfoBuilder.build(serviceApiInfo);
}
}

@ -0,0 +1,134 @@
package com.gitee.sop.servercommon.manager;
import com.alibaba.fastjson.JSON;
import com.gitee.sop.servercommon.bean.ServiceApiInfo;
import com.gitee.sop.servercommon.route.RouteDefinition;
import com.gitee.sop.servercommon.route.ServiceRouteInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.core.env.Environment;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author tanghc
*/
@Slf4j
public class ServiceRouteInfoBuilder {
/**
* 网关对应的LoadBalance协议
*/
private static final String PROTOCOL_LOAD_BALANCE = "lb://";
private static final String PATH_SPLIT = "/";
private static final String DEFAULT_CONTEXT_PATH = "/";
private static ServiceApiInfo.ApiMeta FIRST_API_META = new ServiceApiInfo.ApiMeta("_first_route_", "/", "1.0.0");
private Environment environment;
public ServiceRouteInfoBuilder(Environment environment) {
this.environment = environment;
}
public ServiceRouteInfo build(ServiceApiInfo serviceApiInfo) {
return this.buildServiceGatewayInfo(serviceApiInfo);
}
/**
* 构建接口信息符合spring cloud gateway的格式
*
* @param serviceApiInfo 服务接口信息
* @return 返回服务路由信息
*/
protected ServiceRouteInfo buildServiceGatewayInfo(ServiceApiInfo serviceApiInfo) {
List<ServiceApiInfo.ApiMeta> apis = serviceApiInfo.getApis();
List<RouteDefinition> routeDefinitionList = new ArrayList<>(apis.size() + 1);
// 在第一个位置放一个没用的路由,SpringCloudGateway会从第二个路由开始找,原因不详
routeDefinitionList.add(this.getFirstRoute(serviceApiInfo));
for (ServiceApiInfo.ApiMeta apiMeta : apis) {
RouteDefinition gatewayRouteDefinition = this.buildGatewayRouteDefinition(serviceApiInfo, apiMeta);
routeDefinitionList.add(gatewayRouteDefinition);
}
ServiceRouteInfo serviceRouteInfo = new ServiceRouteInfo();
serviceRouteInfo.setServiceId(serviceApiInfo.getServiceId());
serviceRouteInfo.setRouteDefinitionList(routeDefinitionList);
String md5 = buildMd5(routeDefinitionList);
serviceRouteInfo.setMd5(md5);
return serviceRouteInfo;
}
/**
* 添加com.gitee.sop.gatewaycommon.routeDefinition.ReadBodyRoutePredicateFactory,解决form表单获取不到问题
*
* @return 返回路由定义
*/
protected RouteDefinition getFirstRoute(ServiceApiInfo serviceApiInfo) {
RouteDefinition firstRoute = this.buildGatewayRouteDefinition(serviceApiInfo, FIRST_API_META);
firstRoute.setOrder(Integer.MIN_VALUE);
return firstRoute;
}
/**
* 构建路由id MD5
*
* @param routeDefinitionList 路由列表
* @return 返回MD5
*/
protected String buildMd5(List<RouteDefinition> routeDefinitionList) {
List<String> routeIdList = routeDefinitionList.stream()
.map(JSON::toJSONString)
.sorted()
.collect(Collectors.toList());
String md5Source = org.apache.commons.lang3.StringUtils.join(routeIdList, "");
return DigestUtils.md5DigestAsHex(md5Source.getBytes(StandardCharsets.UTF_8));
}
protected RouteDefinition buildGatewayRouteDefinition(ServiceApiInfo serviceApiInfo, ServiceApiInfo.ApiMeta apiMeta) {
RouteDefinition routeDefinition = new RouteDefinition();
// 唯一id规则:接口名 + 版本号
String routeId = apiMeta.fetchNameVersion();
this.checkPath(routeId, "接口定义(" + routeId + ")不能有斜杠字符'/'");
BeanUtils.copyProperties(apiMeta, routeDefinition);
routeDefinition.setId(routeId);
routeDefinition.setFilters(Collections.emptyList());
routeDefinition.setPredicates(Collections.emptyList());
String uri = this.buildUri(serviceApiInfo, apiMeta);
String path = this.buildServletPath(serviceApiInfo, apiMeta);
routeDefinition.setUri(uri);
routeDefinition.setPath(path);
return routeDefinition;
}
protected String buildUri(ServiceApiInfo serviceApiInfo, ServiceApiInfo.ApiMeta apiMeta) {
return PROTOCOL_LOAD_BALANCE + serviceApiInfo.getServiceId();
}
protected String buildServletPath(ServiceApiInfo serviceApiInfo, ServiceApiInfo.ApiMeta apiMeta) {
String contextPath = environment.getProperty("server.servlet.context-path", DEFAULT_CONTEXT_PATH);
String servletPath = apiMeta.getPath();
if (servletPath == null) {
servletPath = "";
}
servletPath = StringUtils.trimLeadingCharacter(servletPath, '/');
return contextPath + servletPath;
}
private void checkPath(String path, String errorMsg) {
if (path.contains(PATH_SPLIT)) {
throw new IllegalArgumentException(errorMsg);
}
}
public Environment getEnvironment() {
return environment;
}
}

@ -0,0 +1,9 @@
package com.gitee.sop.servercommon.manager;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @author tanghc
*/
public interface ServiceRouteInfoHandler {
}

@ -0,0 +1,71 @@
package com.gitee.sop.servercommon.route;
import lombok.Data;
import java.util.List;
/**
* @author tanghc
*/
@Data
public class RouteDefinition {
/**
* 路由的Id接口名+版本号确保此id全局唯一
*/
private String id;
/**
* 接口名
*/
private String name;
/**
* 版本号
*/
private String version;
/**
* 路由断言集合配置
*/
private List<GatewayPredicateDefinition> predicates;
/**
* 路由过滤器集合配置
*/
private List<GatewayFilterDefinition> filters;
/**
* 路由规则转发的目标uri
*/
private String uri;
/**
* uri后面跟的path
*/
private String path;
/**
* 路由执行的顺序
*/
private int order = 0;
/**
* 是否忽略验证业务参数验证除外
*/
private int ignoreValidate;
/**
* 状态0待审核1启用2禁用
*/
private int status = 1;
/**
* 是否合并结果
*/
private int mergeResult;
/**
* 是否需要授权才能访问
*/
private int permission;
}

@ -0,0 +1,7 @@
server.port=2121
spring.application.name=easyopen-service
# nacos注册中心
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
easyopen.prefix-path=/api

@ -0,0 +1,13 @@
package com.gitee.sop.storyweb.controller.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class CategoryParam {
@ApiModelProperty(value = "分类名称", example = "娱乐")
private String categoryName;
@ApiModelProperty(value = "分类故事")
private StoryParam storyParam;
}

@ -0,0 +1,13 @@
package com.gitee.sop.storyweb.controller.result;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class CategoryResult {
@ApiModelProperty(value = "分类名称", example = "娱乐")
private String categoryName;
@ApiModelProperty(value = "分类故事")
private StoryResult storyResult;
}

@ -0,0 +1,36 @@
package com.gitee.sop.storyweb.controller.result;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* @author tanghc
*/
@Data
public class FileUploadResult {
private List<FileMeta> files = new ArrayList();
@Data
public static class FileMeta {
public FileMeta(String filename, long size, String content) {
this.filename = filename;
this.size = size;
this.content = content;
}
public FileMeta() {
}
@ApiModelProperty(value = "文件名称", example = "1.txt")
private String filename;
@ApiModelProperty(value = "文件大小", example = "109")
private long size;
@ApiModelProperty(value = "文件内容", example = "啊啊啊")
private String content;
}
}

@ -0,0 +1,23 @@
package com.gitee.sop.storyweb.controller.result;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
import java.util.Date;
/**
* @author tanghc
*/
@Data
public class StoryResult {
@ApiModelProperty(value = "故事ID", example = "1")
private Long id;
@ApiModelProperty(value = "故事名称", example = "海底小纵队")
private String name;
@ApiModelProperty(value = "创建时间", example = "2019-04-14 19:02:12")
private Date gmt_create;
}

@ -0,0 +1 @@
file1 content,内容1

@ -0,0 +1 @@
file2 content,内容2

@ -0,0 +1,29 @@
HELP.md
/target/
!.mvn/wrapper/maven-wrapper.jar
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
/build/
### VS Code ###
.vscode/

@ -0,0 +1,13 @@
package com.gitee.sop.websiteserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class WebsiteServerApplication {
public static void main(String[] args) {
SpringApplication.run(WebsiteServerApplication.class, args);
}
}

@ -0,0 +1,17 @@
package com.gitee.sop.websiteserver.bean;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import java.util.List;
/**
* @author tanghc
*/
@Data
public class DocInfo {
private String title;
@JSONField(serialize = false)
private String serviceId;
private List<DocModule> docModuleList;
}

@ -0,0 +1,51 @@
package com.gitee.sop.websiteserver.bean;
import lombok.Data;
import java.util.Collection;
import java.util.List;
/**
* @author tanghc
*/
@Data
public class DocItem {
private String module;
private String name;
private String version;
private String summary;
private String description;
/** 是否多文件上传 */
private boolean multiple;
/** http method列表 */
private Collection<String> httpMethodList;
private Collection<String> produces;
private int moduleOrder;
List<DocParameter> requestParameters;
List<DocParameter> responseParameters;
public String getNameVersion() {
return name + version;
}
/**
* 是否是上传文件请求
* @return
*/
public boolean isUploadRequest() {
boolean upload = false;
if (requestParameters != null) {
for (DocParameter requestParameter : requestParameters) {
String type = requestParameter.getType();
if ("file".equalsIgnoreCase(type)) {
upload = true;
break;
}
}
}
return multiple || upload;
}
}

@ -0,0 +1,15 @@
package com.gitee.sop.websiteserver.bean;
import lombok.Data;
import java.util.List;
/**
* @author tanghc
*/
@Data
public class DocModule {
private String module;
private List<DocItem> docItems;
private int order;
}

@ -0,0 +1,31 @@
package com.gitee.sop.websiteserver.bean;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import org.apache.commons.lang.StringUtils;
import java.util.List;
/**
* 参数 类型 是否必填 最大长度 描述 示例值
* @author tanghc
*/
@Data
public class DocParameter {
private String module;
private String name;
private String type;
private String maxLength = "-";
private boolean required;
private String description;
private String example = "";
@JSONField(name = "x-example")
private String x_example = "";
private List<DocParameter> refs;
public String getParamExample() {
return StringUtils.isBlank(example) ? x_example : example;
}
}

@ -0,0 +1,16 @@
package com.gitee.sop.websiteserver.bean;
import com.google.common.collect.Sets;
import java.util.Set;
/**
* @author tanghc
*/
public class DocParserContext {
/**
* 忽略显示
*/
public static volatile Set<String> ignoreHttpMethods = Sets.newHashSet("put", "options", "head", "put", "delete", "patch");
}

@ -0,0 +1,14 @@
package com.gitee.sop.websiteserver.bean;
import lombok.Data;
import java.util.List;
/**
* @author tanghc
*/
@Data
public class EurekaApplication {
private String name;
private List<EurekaInstance> instance;
}

@ -0,0 +1,13 @@
package com.gitee.sop.websiteserver.bean;
import lombok.Data;
import java.util.List;
/**
* @author tanghc
*/
@Data
public class EurekaApplications {
private List<EurekaApplication> application;
}

@ -0,0 +1,11 @@
package com.gitee.sop.websiteserver.bean;
import lombok.Data;
/**
* @author tanghc
*/
@Data
public class EurekaApps {
private EurekaApplications applications;
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save