Browse Source

Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/activiti

 Conflicts:
	yudao-dependencies/pom.xml
	yudao-framework/pom.xml
YunaiV 3 years ago
parent
commit
0050b1e46d
100 changed files with 2848 additions and 290 deletions
  1. 31 0
      mini-program-test/.eslintrc.js
  2. 1 0
      mini-program-test/README.md
  3. 19 0
      mini-program-test/app.js
  4. 14 0
      mini-program-test/app.json
  5. 10 0
      mini-program-test/app.wxss
  6. 91 0
      mini-program-test/pages/index/index.js
  7. 3 0
      mini-program-test/pages/index/index.json
  8. 29 0
      mini-program-test/pages/index/index.wxml
  9. 19 0
      mini-program-test/pages/index/index.wxss
  10. 18 0
      mini-program-test/pages/logs/logs.js
  11. 4 0
      mini-program-test/pages/logs/logs.json
  12. 6 0
      mini-program-test/pages/logs/logs.wxml
  13. 8 0
      mini-program-test/pages/logs/logs.wxss
  14. 75 0
      mini-program-test/project.config.json
  15. 7 0
      mini-program-test/sitemap.json
  16. 3 0
      mini-program-test/utils/common.js
  17. 19 0
      mini-program-test/utils/util.js
  18. 1 1
      pom.xml
  19. 605 3
      sql/ruoyi-vue-pro.sql
  20. 2 2
      yudao-admin-server/pom.xml
  21. 9 6
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/infra/controller/file/InfFileController.java
  22. 1 1
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/infra/convert/file/InfFileConvert.java
  23. 0 1
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/infra/convert/logger/InfApiAccessLogConvert.java
  24. 6 7
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/infra/dal/mysql/file/InfFileMapper.java
  25. 0 3
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/infra/enums/InfErrorCodeConstants.java
  26. 1 25
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/infra/service/file/InfFileService.java
  27. 4 47
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/infra/service/file/impl/InfFileServiceImpl.java
  28. 6 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/package-info.java
  29. 1 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md
  30. 29 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/job/notify/PayNotifyJob.java
  31. 1 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/job/package-info.java
  32. 7 0
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/package-info.java
  33. 3 2
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/auth/SysAuthController.java
  34. 1 1
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/auth/vo/auth/SysAuthSocialBindReqVO.java
  35. 1 1
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/auth/vo/auth/SysAuthSocialLogin2ReqVO.java
  36. 1 1
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/auth/vo/auth/SysAuthSocialLoginReqVO.java
  37. 1 1
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/auth/vo/auth/SysAuthSocialUnbindReqVO.java
  38. 4 3
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/user/SysUserProfileController.java
  39. 1 1
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/convert/user/SysUserConvert.java
  40. 0 7
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/dal/redis/SysRedisKeyConstants.java
  41. 1 4
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/enums/SysErrorCodeConstants.java
  42. 0 55
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/enums/social/SysSocialTypeEnum.java
  43. 10 8
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/impl/SysAuthServiceImpl.java
  44. 1 1
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/sms/SysSmsChannelService.java
  45. 1 1
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/sms/impl/SysSmsTemplateServiceImpl.java
  46. 2 2
      yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/user/impl/SysUserServiceImpl.java
  47. 3 0
      yudao-admin-server/src/main/resources/application-local.yaml
  48. 9 67
      yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/infra/service/file/InfFileServiceTest.java
  49. 0 3
      yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/infra/service/logger/InfApiAccessLogServiceImplTest.java
  50. 0 3
      yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/infra/service/logger/InfApiErrorLogServiceImplTest.java
  51. 1 1
      yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/SysAuthServiceImplTest.java
  52. 11 10
      yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/social/SysSocialServiceTest.java
  53. 2 2
      yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/user/SysUserServiceImplTest.java
  54. 19 2
      yudao-core-service/pom.xml
  55. 1 1
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/controller/file/vo/InfFileRespVO.java
  56. 2 2
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/convert/logger/InfApiAccessLogCoreConvert.java
  57. 2 2
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/convert/logger/InfApiErrorLogCoreConvert.java
  58. 1 1
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/dal/dataobject/config/InfConfigDO.java
  59. 2 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/dal/dataobject/logger/InfApiErrorLogDO.java
  60. 3 1
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/dal/mysql/file/InfFileCoreMapper.java
  61. 1 1
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/framework/file/config/FileConfiguration.java
  62. 1 1
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/framework/file/config/FileProperties.java
  63. 1 1
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/framework/file/package-info.java
  64. 36 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/service/file/InfFileCoreService.java
  65. 64 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/service/file/impl/InfFileCoreServiceImpl.java
  66. 2 4
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/service/logger/impl/InfApiAccessLogCoreServiceImpl.java
  67. 2 4
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/service/logger/impl/InfApiErrorLogCoreServiceImpl.java
  68. 22 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/convert/order/PayOrderCoreConvert.java
  69. 6 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/convert/package-info.java
  70. 1 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md
  71. 62 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/merchant/PayAppDO.java
  72. 68 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/merchant/PayChannelDO.java
  73. 53 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/merchant/PayMerchantDO.java
  74. 49 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/notify/PayNotifyLogDO.java
  75. 99 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/notify/PayNotifyTaskDO.java
  76. 162 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayOrderDO.java
  77. 82 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayOrderExtensionDO.java
  78. 128 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayRefundDO.java
  79. 9 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/merchant/PayAppCoreMapper.java
  80. 20 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/merchant/PayChannelCoreMapper.java
  81. 9 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/notify/PayNotifyLogCoreMapper.java
  82. 30 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/notify/PayNotifyTaskCoreMapper.java
  83. 22 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/order/PayOrderCoreMapper.java
  84. 20 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/order/PayOrderExtensionCoreMapper.java
  85. 19 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/redis/PayRedisKeyCoreConstants.java
  86. 39 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/redis/notify/PayNotifyLockCoreRedisDAO.java
  87. 31 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/PayErrorCodeCoreConstants.java
  88. 32 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/notify/PayNotifyStatusEnum.java
  89. 28 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/notify/PayNotifyTypeEnum.java
  90. 29 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayOrderNotifyStatusEnum.java
  91. 29 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayOrderRefundStatusEnum.java
  92. 29 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayOrderStatusEnum.java
  93. 7 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/package-info.java
  94. 23 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/merchant/PayAppCoreService.java
  95. 39 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/merchant/PayChannelCoreService.java
  96. 43 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/merchant/impl/PayAppCoreServiceImpl.java
  97. 121 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/merchant/impl/PayChannelCoreServiceImpl.java
  98. 29 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/PayNotifyCoreService.java
  99. 32 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/dto/PayNotifyTaskCreateReqDTO.java
  100. 256 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/impl/PayNotifyCoreServiceImpl.java

+ 31 - 0
mini-program-test/.eslintrc.js

@@ -0,0 +1,31 @@
+/*
+ * Eslint config file
+ * Documentation: https://eslint.org/docs/user-guide/configuring/
+ * Install the Eslint extension before using this feature.
+ */
+module.exports = {
+  env: {
+    es6: true,
+    browser: true,
+    node: true,
+  },
+  ecmaFeatures: {
+    modules: true,
+  },
+  parserOptions: {
+    ecmaVersion: 2018,
+    sourceType: 'module',
+  },
+  globals: {
+    wx: true,
+    App: true,
+    Page: true,
+    getCurrentPages: true,
+    getApp: true,
+    Component: true,
+    requirePlugin: true,
+    requireMiniProgram: true,
+  },
+  // extends: 'eslint:recommended',
+  rules: {},
+}

+ 1 - 0
mini-program-test/README.md

@@ -0,0 +1 @@
+临时项目,作为测试微信小程序登陆之用

+ 19 - 0
mini-program-test/app.js

@@ -0,0 +1,19 @@
+// app.js
+App({
+  onLaunch() {
+    // 展示本地存储能力
+    const logs = wx.getStorageSync('logs') || []
+    logs.unshift(Date.now())
+    wx.setStorageSync('logs', logs)
+
+    // 登录
+    wx.login({
+      success: res => {
+        // 发送 res.code 到后台换取 openId, sessionKey, unionId
+      }
+    })
+  },
+  globalData: {
+    userInfo: null
+  }
+})

+ 14 - 0
mini-program-test/app.json

@@ -0,0 +1,14 @@
+{
+  "pages":[
+    "pages/index/index",
+    "pages/logs/logs"
+  ],
+  "window":{
+    "backgroundTextStyle":"light",
+    "navigationBarBackgroundColor": "#fff",
+    "navigationBarTitleText": "Weixin",
+    "navigationBarTextStyle":"black"
+  },
+  "style": "v2",
+  "sitemapLocation": "sitemap.json"
+}

+ 10 - 0
mini-program-test/app.wxss

@@ -0,0 +1,10 @@
+/**app.wxss**/
+.container {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: space-between;
+  padding: 200rpx 0;
+  box-sizing: border-box;
+} 

+ 91 - 0
mini-program-test/pages/index/index.js

@@ -0,0 +1,91 @@
+// index.js
+
+const common=require('../../utils/common.js')
+// 获取应用实例
+const app = getApp()
+
+Page({
+  data: {
+    motto: 'Hello World',
+    userInfo: {},
+    hasUserInfo: false,
+    canIUse: wx.canIUse('button.open-type.getUserInfo'),
+    canIUseGetUserProfile: false,
+    canIUseOpenData: wx.canIUse('open-data.type.userAvatarUrl') && wx.canIUse('open-data.type.userNickName'), // 如需尝试获取用户信息可改为false
+    holderText: 'to be auth'
+  },
+  // 事件处理函数
+  bindViewTap() {
+    wx.navigateTo({
+      url: '../logs/logs'
+    })
+  },
+  onLoad() {
+    if (wx.getUserProfile) {
+      this.setData({
+        canIUseGetUserProfile: true
+      })
+    }
+  },
+  getUserProfile(e) {
+    // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认,开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
+    wx.getUserProfile({
+      desc: '展示用户信息', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
+      success: (res) => {
+        console.log(res)
+        this.setData({
+          userInfo: res.userInfo,
+          hasUserInfo: true
+        })
+      }
+    })
+  },
+  getUserInfo(e) {
+    // 不推荐使用getUserInfo获取用户信息,预计自2021年4月13日起,getUserInfo将不再弹出弹窗,并直接返回匿名的用户个人信息
+    console.log(e)
+    this.setData({
+      userInfo: e.detail.userInfo,
+      hasUserInfo: true
+    })
+  },
+  // 小程序登录 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
+  wxLogin(e){
+    let page=this;
+    wx.login({
+      success (res) {
+        console.log("res:")
+        console.log(res)
+        if (res.code) {
+          //发起网络请求
+          console.log('发起网络请求'+common.baseurl)
+          wx.request({
+            url: common.baseurl+'/api/social-login2',
+            method: "POST",
+            data: {
+              code: res.code,
+              state: 'empty',
+              type: 33,
+              username: '15601691300',
+              password: 'admin123'
+            },
+            header: {
+              'content-type': 'application/json' // 默认值
+            },
+            success: function(res) {
+              console.log(res.data)
+              let holder="auth success, token:"+res.data.data.token
+              page.setData({holderText: holder})
+            },
+            fail: function(data){
+              console.error("请求出错");
+              console.error(data)
+            }
+            
+          })
+        } else {
+          console.log('登录失败!' + res.errMsg)
+        }
+      }
+    })
+  }
+})

+ 3 - 0
mini-program-test/pages/index/index.json

@@ -0,0 +1,3 @@
+{
+  "usingComponents": {}
+}

+ 29 - 0
mini-program-test/pages/index/index.wxml

@@ -0,0 +1,29 @@
+<!--index.wxml-->
+<view class="container">
+  <view class="userinfo">
+    <block wx:if="{{canIUseOpenData}}">
+
+
+    </block>
+    <block wx:elif="{{!hasUserInfo}}">
+      <button wx:if="{{canIUseGetUserProfile}}" bindtap="getUserProfile"> 获取头像昵称 </button>
+      <button wx:elif="{{canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button>
+      <view wx:else> 请使用1.4.4及以上版本基础库 </view>
+    </block>
+    <block wx:else>
+      <image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>
+      <text class="userinfo-nickname">{{userInfo.nickName}}</text>
+    </block>
+  </view>
+  <view class="usermotto">
+    
+  </view>
+
+
+
+  <text style="position: relative; left: 1rpx; top: -476rpx">授权登录测试1024</text>
+
+  <button style="position: relative; left: 0rpx; top: -361rpx" type="primary" id="login-button" bindtap="wxLogin">点击授权登录</button>
+
+  <text style="position: relative; left: 1rpx; top: -272rpx" id="login-user-id">{{holderText}}</text>
+</view>

+ 19 - 0
mini-program-test/pages/index/index.wxss

@@ -0,0 +1,19 @@
+/**index.wxss**/
+.userinfo {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  color: #aaa;
+}
+
+.userinfo-avatar {
+  overflow: hidden;
+  width: 128rpx;
+  height: 128rpx;
+  margin: 20rpx;
+  border-radius: 50%;
+}
+
+.usermotto {
+  margin-top: 200px;
+}

+ 18 - 0
mini-program-test/pages/logs/logs.js

@@ -0,0 +1,18 @@
+// logs.js
+const util = require('../../utils/util.js')
+
+Page({
+  data: {
+    logs: []
+  },
+  onLoad() {
+    this.setData({
+      logs: (wx.getStorageSync('logs') || []).map(log => {
+        return {
+          date: util.formatTime(new Date(log)),
+          timeStamp: log
+        }
+      })
+    })
+  }
+})

+ 4 - 0
mini-program-test/pages/logs/logs.json

@@ -0,0 +1,4 @@
+{
+  "navigationBarTitleText": "查看启动日志",
+  "usingComponents": {}
+}

+ 6 - 0
mini-program-test/pages/logs/logs.wxml

@@ -0,0 +1,6 @@
+<!--logs.wxml-->
+<view class="container log-list">
+  <block wx:for="{{logs}}" wx:key="timeStamp" wx:for-item="log">
+    <text class="log-item">{{index + 1}}. {{log.date}}</text>
+  </block>
+</view>

+ 8 - 0
mini-program-test/pages/logs/logs.wxss

@@ -0,0 +1,8 @@
+.log-list {
+  display: flex;
+  flex-direction: column;
+  padding: 40rpx;
+}
+.log-item {
+  margin: 10rpx;
+}

+ 75 - 0
mini-program-test/project.config.json

@@ -0,0 +1,75 @@
+{
+  "description": "项目配置文件",
+  "packOptions": {
+    "ignore": [
+      {
+        "type": "file",
+        "value": ".eslintrc.js"
+      }
+    ]
+  },
+  "setting": {
+    "bundle": false,
+    "userConfirmedBundleSwitch": false,
+    "urlCheck": true,
+    "scopeDataCheck": false,
+    "coverView": true,
+    "es6": true,
+    "postcss": true,
+    "compileHotReLoad": false,
+    "lazyloadPlaceholderEnable": false,
+    "preloadBackgroundData": false,
+    "minified": true,
+    "autoAudits": false,
+    "newFeature": false,
+    "uglifyFileName": false,
+    "uploadWithSourceMap": true,
+    "useIsolateContext": true,
+    "nodeModules": false,
+    "enhance": true,
+    "useMultiFrameRuntime": true,
+    "useApiHook": true,
+    "useApiHostProcess": true,
+    "showShadowRootInWxmlPanel": true,
+    "packNpmManually": false,
+    "enableEngineNative": false,
+    "packNpmRelationList": [],
+    "minifyWXSS": true,
+    "showES6CompileOption": false,
+    "minifyWXML": true
+  },
+  "compileType": "miniprogram",
+  "libVersion": "2.19.4",
+  "appid": "wx44d047d87e6284d8",
+  "appid1": "wx63c280fe3248a3e7",
+  "projectname": "mini-program-test",
+  "debugOptions": {
+    "hidedInDevtools": []
+  },
+  "scripts": {},
+  "staticServerOptions": {
+    "baseURL": "",
+    "servePath": ""
+  },
+  "isGameTourist": false,
+  "condition": {
+    "search": {
+      "list": []
+    },
+    "conversation": {
+      "list": []
+    },
+    "game": {
+      "list": []
+    },
+    "plugin": {
+      "list": []
+    },
+    "gamePlugin": {
+      "list": []
+    },
+    "miniprogram": {
+      "list": []
+    }
+  }
+}

+ 7 - 0
mini-program-test/sitemap.json

@@ -0,0 +1,7 @@
+{
+  "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
+  "rules": [{
+  "action": "allow",
+  "page": "*"
+  }]
+}

+ 3 - 0
mini-program-test/utils/common.js

@@ -0,0 +1,3 @@
+module.exports = {
+  baseurl: "http://127.0.0.1:28080"
+}

+ 19 - 0
mini-program-test/utils/util.js

@@ -0,0 +1,19 @@
+const formatTime = date => {
+  const year = date.getFullYear()
+  const month = date.getMonth() + 1
+  const day = date.getDate()
+  const hour = date.getHours()
+  const minute = date.getMinutes()
+  const second = date.getSeconds()
+
+  return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second].map(formatNumber).join(':')}`
+}
+
+const formatNumber = n => {
+  n = n.toString()
+  return n[1] ? n : `0${n}`
+}
+
+module.exports = {
+  formatTime
+}

+ 1 - 1
pom.xml

@@ -20,7 +20,7 @@
     <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
 
     <properties>
-        <revision>1.1.0-snapshot</revision>
+        <revision>1.2.0-snapshot</revision>
         <!-- Maven 相关 -->
         <java.version>1.8</java.version>
         <maven.compiler.source>${java.version}</maven.compiler.source>

File diff suppressed because it is too large
+ 605 - 3
sql/ruoyi-vue-pro.sql


+ 2 - 2
yudao-admin-server/pom.xml

@@ -129,11 +129,11 @@
             <artifactId>screw-core</artifactId> <!-- 实现数据库文档 -->
         </dependency>
 
-        <!-- TODO 后续看情况,进行调整 -->
+        <!-- 三方云服务相关 -->
+
         <dependency>
             <groupId>com.xkcoding.justauth</groupId>
             <artifactId>justauth-spring-boot-starter</artifactId>
-            <version>1.4.0</version>
         </dependency>
     </dependencies>
 

+ 9 - 6
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/infra/controller/file/InfFileController.java

@@ -1,13 +1,14 @@
 package cn.iocoder.yudao.adminserver.modules.infra.controller.file;
 
 import cn.hutool.core.io.IoUtil;
+import cn.iocoder.yudao.adminserver.modules.infra.service.file.InfFileService;
+import cn.iocoder.yudao.adminserver.modules.infra.controller.file.vo.InfFilePageReqVO;
+import cn.iocoder.yudao.coreservice.modules.infra.controller.file.vo.InfFileRespVO;
 import cn.iocoder.yudao.coreservice.modules.infra.dal.dataobject.file.InfFileDO;
+import cn.iocoder.yudao.coreservice.modules.infra.service.file.InfFileCoreService;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.adminserver.modules.infra.controller.file.vo.InfFilePageReqVO;
-import cn.iocoder.yudao.adminserver.modules.infra.controller.file.vo.InfFileRespVO;
 import cn.iocoder.yudao.adminserver.modules.infra.convert.file.InfFileConvert;
-import cn.iocoder.yudao.adminserver.modules.infra.service.file.InfFileService;
 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiImplicitParam;
@@ -36,6 +37,8 @@ public class InfFileController {
 
     @Resource
     private InfFileService fileService;
+    @Resource
+    private InfFileCoreService fileCoreService;
 
     @PostMapping("/upload")
     @ApiOperation("上传文件")
@@ -45,7 +48,7 @@ public class InfFileController {
     })
     public CommonResult<String> uploadFile(@RequestParam("file") MultipartFile file,
                                            @RequestParam("path") String path) throws IOException {
-        return success(fileService.createFile(path, IoUtil.readBytes(file.getInputStream())));
+        return success(fileCoreService.createFile(path, IoUtil.readBytes(file.getInputStream())));
     }
 
     @DeleteMapping("/delete")
@@ -53,7 +56,7 @@ public class InfFileController {
     @ApiImplicitParam(name = "id", value = "编号", required = true)
     @PreAuthorize("@ss.hasPermission('infra:file:delete')")
     public CommonResult<Boolean> deleteFile(@RequestParam("id") String id) {
-        fileService.deleteFile(id);
+        fileCoreService.deleteFile(id);
         return success(true);
     }
 
@@ -61,7 +64,7 @@ public class InfFileController {
     @ApiOperation("下载文件")
     @ApiImplicitParam(name = "path", value = "文件附件", required = true, dataTypeClass = MultipartFile.class)
     public void getFile(HttpServletResponse response, @PathVariable("path") String path) throws IOException {
-        InfFileDO file = fileService.getFile(path);
+        InfFileDO file = fileCoreService.getFile(path);
         if (file == null) {
             log.warn("[getFile][path({}) 文件不存在]", path);
             response.setStatus(HttpStatus.NOT_FOUND.value());

+ 1 - 1
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/infra/convert/file/InfFileConvert.java

@@ -1,8 +1,8 @@
 package cn.iocoder.yudao.adminserver.modules.infra.convert.file;
 
+import cn.iocoder.yudao.coreservice.modules.infra.controller.file.vo.InfFileRespVO;
 import cn.iocoder.yudao.coreservice.modules.infra.dal.dataobject.file.InfFileDO;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.adminserver.modules.infra.controller.file.vo.InfFileRespVO;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
 

+ 0 - 1
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/infra/convert/logger/InfApiAccessLogConvert.java

@@ -2,7 +2,6 @@ package cn.iocoder.yudao.adminserver.modules.infra.convert.logger;
 
 import cn.iocoder.yudao.coreservice.modules.infra.dal.dataobject.logger.InfApiAccessLogDO;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.apilog.core.service.dto.ApiAccessLogCreateDTO;
 import cn.iocoder.yudao.adminserver.modules.infra.controller.logger.vo.apiaccesslog.InfApiAccessLogExcelVO;
 import cn.iocoder.yudao.adminserver.modules.infra.controller.logger.vo.apiaccesslog.InfApiAccessLogRespVO;
 import org.mapstruct.Mapper;

+ 6 - 7
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/infra/dal/mysql/file/InfFileMapper.java

@@ -1,19 +1,19 @@
 package cn.iocoder.yudao.adminserver.modules.infra.dal.mysql.file;
 
+import cn.iocoder.yudao.adminserver.modules.infra.controller.file.vo.InfFilePageReqVO;
 import cn.iocoder.yudao.coreservice.modules.infra.dal.dataobject.file.InfFileDO;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
-import cn.iocoder.yudao.adminserver.modules.infra.controller.file.vo.InfFilePageReqVO;
 import org.apache.ibatis.annotations.Mapper;
 
+/**
+ * admin 文件操作 Mapper
+ *
+ * @author 芋道源码
+ */
 @Mapper
 public interface InfFileMapper extends BaseMapperX<InfFileDO> {
-
-    default Integer selectCountById(String id) {
-        return selectCount("id", id);
-    }
-
     default PageResult<InfFileDO> selectPage(InfFilePageReqVO reqVO) {
         return selectPage(reqVO, new QueryWrapperX<InfFileDO>()
                 .likeIfPresent("id", reqVO.getId())
@@ -21,5 +21,4 @@ public interface InfFileMapper extends BaseMapperX<InfFileDO> {
                 .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
                 .orderByDesc("create_time"));
     }
-
 }

+ 0 - 3
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/infra/enums/InfErrorCodeConstants.java

@@ -27,7 +27,4 @@ public interface InfErrorCodeConstants {
     ErrorCode API_ERROR_LOG_NOT_FOUND = new ErrorCode(1001002000, "API 错误日志不存在");
     ErrorCode API_ERROR_LOG_PROCESSED = new ErrorCode(1001002001, "API 错误日志已处理");
 
-    // ========== 文件 1001003000 ==========
-    ErrorCode FILE_NOT_EXISTS = new ErrorCode(1001003000, "文件不存在");
-
 }

+ 1 - 25
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/infra/service/file/InfFileService.java

@@ -1,8 +1,8 @@
 package cn.iocoder.yudao.adminserver.modules.infra.service.file;
 
+import cn.iocoder.yudao.adminserver.modules.infra.controller.file.vo.InfFilePageReqVO;
 import cn.iocoder.yudao.coreservice.modules.infra.dal.dataobject.file.InfFileDO;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.adminserver.modules.infra.controller.file.vo.InfFilePageReqVO;
 
 /**
  * 文件 Service 接口
@@ -11,30 +11,6 @@ import cn.iocoder.yudao.adminserver.modules.infra.controller.file.vo.InfFilePage
  */
 public interface InfFileService {
 
-    /**
-     * 保存文件,并返回文件的访问路径
-     *
-     * @param path 文件路径
-     * @param content 文件内容
-     * @return 文件路径
-     */
-    String createFile(String path, byte[] content);
-
-    /**
-     * 删除文件
-     *
-     * @param id 编号
-     */
-    void deleteFile(String id);
-
-    /**
-     * 获得文件
-     *
-     * @param path 文件路径
-     * @return 文件
-     */
-    InfFileDO getFile(String path);
-
     /**
      * 获得文件分页
      *

+ 4 - 47
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/infra/service/file/impl/InfFileServiceImpl.java

@@ -1,20 +1,14 @@
 package cn.iocoder.yudao.adminserver.modules.infra.service.file.impl;
 
-import cn.hutool.core.io.FileTypeUtil;
-import cn.iocoder.yudao.coreservice.modules.infra.dal.dataobject.file.InfFileDO;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.adminserver.modules.infra.framework.file.config.FileProperties;
-import cn.iocoder.yudao.adminserver.modules.infra.controller.file.vo.InfFilePageReqVO;
 import cn.iocoder.yudao.adminserver.modules.infra.dal.mysql.file.InfFileMapper;
 import cn.iocoder.yudao.adminserver.modules.infra.service.file.InfFileService;
+import cn.iocoder.yudao.adminserver.modules.infra.controller.file.vo.InfFilePageReqVO;
+import cn.iocoder.yudao.coreservice.modules.infra.dal.dataobject.file.InfFileDO;
+import cn.iocoder.yudao.coreservice.modules.infra.service.file.InfFileCoreService;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
-import java.io.ByteArrayInputStream;
-
-import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.adminserver.modules.infra.enums.InfErrorCodeConstants.FILE_NOT_EXISTS;
-import static cn.iocoder.yudao.adminserver.modules.system.enums.SysErrorCodeConstants.FILE_PATH_EXISTS;
 
 /**
  * 文件 Service 实现类
@@ -27,43 +21,6 @@ public class InfFileServiceImpl implements InfFileService {
     @Resource
     private InfFileMapper fileMapper;
 
-    @Resource
-    private FileProperties fileProperties;
-
-    @Override
-    public String createFile(String path, byte[] content) {
-        if (fileMapper.selectCountById(path) > 0) {
-            throw exception(FILE_PATH_EXISTS);
-        }
-        // 保存到数据库
-        InfFileDO file = new InfFileDO();
-        file.setId(path);
-        file.setType(FileTypeUtil.getType(new ByteArrayInputStream(content)));
-        file.setContent(content);
-        fileMapper.insert(file);
-        // 拼接路径返回
-        return fileProperties.getBasePath() + path;
-    }
-
-    @Override
-    public void deleteFile(String id) {
-        // 校验存在
-        this.validateFileExists(id);
-        // 更新
-        fileMapper.deleteById(id);
-    }
-
-    private void validateFileExists(String id) {
-        if (fileMapper.selectById(id) == null) {
-            throw exception(FILE_NOT_EXISTS);
-        }
-    }
-
-    @Override
-    public InfFileDO getFile(String path) {
-        return fileMapper.selectById(path);
-    }
-
     @Override
     public PageResult<InfFileDO> getFilePage(InfFilePageReqVO pageReqVO) {
         return fileMapper.selectPage(pageReqVO);

+ 6 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/package-info.java

@@ -0,0 +1,6 @@
+/**
+ * 提供 POJO 类的实体转换
+ *
+ * 目前使用 MapStruct 框架
+ */
+package cn.iocoder.yudao.adminserver.modules.pay.convert;

+ 1 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md

@@ -0,0 +1 @@
+<http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao>

+ 29 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/job/notify/PayNotifyJob.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.adminserver.modules.pay.job.notify;
+
+import cn.iocoder.yudao.coreservice.modules.pay.service.notify.PayNotifyCoreService;
+import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 支付通知 Job
+ * 通过不断扫描待通知的 PayNotifyTaskDO 记录,回调业务线的回调接口
+ *
+ * @author 芋道源码
+ */
+@Component
+@Slf4j
+public class PayNotifyJob implements JobHandler {
+
+    @Resource
+    private PayNotifyCoreService payNotifyCoreService;
+
+    @Override
+    public String execute(String param) throws Exception {
+        int notifyCount = payNotifyCoreService.executeNotify();
+        return String.format("执行支付通知 %s 个", notifyCount);
+    }
+
+}

+ 1 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/job/package-info.java

@@ -0,0 +1 @@
+package cn.iocoder.yudao.adminserver.modules.pay.job;

+ 7 - 0
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/package-info.java

@@ -0,0 +1,7 @@
+/**
+ * pay 包下,我们放支付业务,提供业务的支付能力。
+ * 例如说:商户、应用、支付、退款等等
+ *
+ * 缩写:pay
+ */
+package cn.iocoder.yudao.adminserver.modules.pay;

+ 3 - 2
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/auth/SysAuthController.java

@@ -8,10 +8,11 @@ import cn.iocoder.yudao.adminserver.modules.system.enums.permission.MenuTypeEnum
 import cn.iocoder.yudao.adminserver.modules.system.service.auth.SysAuthService;
 import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysPermissionService;
 import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysRoleService;
-import cn.iocoder.yudao.adminserver.modules.system.service.social.SysSocialService;
 import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.user.SysUserDO;
+import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialService;
 import cn.iocoder.yudao.coreservice.modules.system.service.user.SysUserCoreService;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
@@ -132,7 +133,7 @@ public class SysAuthController {
     @DeleteMapping("/social-unbind")
     @ApiOperation("取消社交绑定")
     public CommonResult<Boolean> socialUnbind(@RequestBody SysAuthSocialUnbindReqVO reqVO) {
-        socialService.unbindSocialUser(getLoginUserId(), reqVO.getType(), reqVO.getUnionId());
+        socialService.unbindSocialUser(getLoginUserId(), reqVO.getType(), reqVO.getUnionId(), UserTypeEnum.ADMIN);
         return CommonResult.success(true);
     }
 

+ 1 - 1
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/auth/vo/auth/SysAuthSocialBindReqVO.java

@@ -1,6 +1,6 @@
 package cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth;
 
-import cn.iocoder.yudao.adminserver.modules.system.enums.social.SysSocialTypeEnum;
+import cn.iocoder.yudao.coreservice.modules.system.enums.social.SysSocialTypeEnum;
 import cn.iocoder.yudao.framework.common.validation.InEnum;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;

+ 1 - 1
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/auth/vo/auth/SysAuthSocialLogin2ReqVO.java

@@ -1,6 +1,6 @@
 package cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth;
 
-import cn.iocoder.yudao.adminserver.modules.system.enums.social.SysSocialTypeEnum;
+import cn.iocoder.yudao.coreservice.modules.system.enums.social.SysSocialTypeEnum;
 import cn.iocoder.yudao.framework.common.validation.InEnum;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;

+ 1 - 1
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/auth/vo/auth/SysAuthSocialLoginReqVO.java

@@ -1,6 +1,6 @@
 package cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth;
 
-import cn.iocoder.yudao.adminserver.modules.system.enums.social.SysSocialTypeEnum;
+import cn.iocoder.yudao.coreservice.modules.system.enums.social.SysSocialTypeEnum;
 import cn.iocoder.yudao.framework.common.validation.InEnum;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;

+ 1 - 1
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/auth/vo/auth/SysAuthSocialUnbindReqVO.java

@@ -1,6 +1,6 @@
 package cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth;
 
-import cn.iocoder.yudao.adminserver.modules.system.enums.social.SysSocialTypeEnum;
+import cn.iocoder.yudao.coreservice.modules.system.enums.social.SysSocialTypeEnum;
 import cn.iocoder.yudao.framework.common.validation.InEnum;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;

+ 4 - 3
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/user/SysUserProfileController.java

@@ -8,15 +8,16 @@ import cn.iocoder.yudao.adminserver.modules.system.convert.user.SysUserConvert;
 import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.dept.SysDeptDO;
 import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.dept.SysPostDO;
 import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.permission.SysRoleDO;
-import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.social.SysSocialUserDO;
 import cn.iocoder.yudao.adminserver.modules.system.service.dept.SysDeptService;
 import cn.iocoder.yudao.adminserver.modules.system.service.dept.SysPostService;
 import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysPermissionService;
 import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysRoleService;
-import cn.iocoder.yudao.adminserver.modules.system.service.social.SysSocialService;
 import cn.iocoder.yudao.adminserver.modules.system.service.user.SysUserService;
+import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.social.SysSocialUserDO;
 import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.user.SysUserDO;
+import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialService;
 import cn.iocoder.yudao.coreservice.modules.system.service.user.SysUserCoreService;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import io.swagger.annotations.Api;
@@ -77,7 +78,7 @@ public class SysUserProfileController {
             resp.setPosts(SysUserConvert.INSTANCE.convertList02(posts));
         }
         // 获得社交用户信息
-        List<SysSocialUserDO> socialUsers = socialService.getSocialUserList(user.getId());
+        List<SysSocialUserDO> socialUsers = socialService.getSocialUserList(user.getId(), UserTypeEnum.ADMIN);
         resp.setSocialUsers(SysUserConvert.INSTANCE.convertList03(socialUsers));
         return success(resp);
     }

+ 1 - 1
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/convert/user/SysUserConvert.java

@@ -7,7 +7,7 @@ import cn.iocoder.yudao.adminserver.modules.system.controller.user.vo.user.*;
 import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.dept.SysDeptDO;
 import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.dept.SysPostDO;
 import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.permission.SysRoleDO;
-import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.social.SysSocialUserDO;
+import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.social.SysSocialUserDO;
 import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.user.SysUserDO;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;

+ 0 - 7
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/dal/redis/SysRedisKeyConstants.java

@@ -19,12 +19,5 @@ public interface SysRedisKeyConstants {
             "captcha_code:%s", // 参数为 uuid
             STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
 
-    RedisKeyDefine SOCIAL_AUTH_USER = new RedisKeyDefine("社交的授权用户",
-            "social_auth_user:%d:%s", // 参数为 type,code
-            STRING, AuthUser.class, Duration.ofDays(1));
-
-    RedisKeyDefine SOCIAL_AUTH_STATE = new RedisKeyDefine("社交的 state",
-            "social_auth_state:%s", // 参数为 state
-            STRING, String.class, Duration.ofHours(24)); // 值为 state
 
 }

+ 1 - 4
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/enums/SysErrorCodeConstants.java

@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.adminserver.modules.system.enums;
 
 import cn.iocoder.yudao.framework.common.exception.ErrorCode;
-import javafx.beans.binding.MapExpression;
 
 /**
  * System 错误码枚举类
@@ -90,8 +89,6 @@ public interface SysErrorCodeConstants {
     ErrorCode ERROR_CODE_NOT_EXISTS = new ErrorCode(1002013000, "错误码不存在");
     ErrorCode ERROR_CODE_DUPLICATE = new ErrorCode(1002013001, "已经存在编码为【{}】的错误码");
 
-    // ========== 社交模块 1002014000 ==========
-    ErrorCode SOCIAL_AUTH_FAILURE = new ErrorCode(1002014000, "社交授权失败,原因是:{}");
-    ErrorCode SOCIAL_UNBIND_NOT_SELF = new ErrorCode(1002014001, "社交解绑失败,非当前用户绑定");
+
 
 }

+ 0 - 55
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/enums/social/SysSocialTypeEnum.java

@@ -1,55 +0,0 @@
-package cn.iocoder.yudao.adminserver.modules.system.enums.social;
-
-import cn.hutool.core.collection.ListUtil;
-import cn.hutool.core.util.ArrayUtil;
-import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * 社交平台的类型枚举
- *
- * @author 芋道源码
- */
-@Getter
-@AllArgsConstructor
-public enum SysSocialTypeEnum implements IntArrayValuable {
-
-    GITEE(10, "GITEE"), // https://gitee.com/api/v5/oauth_doc#/
-    DINGTALK(20, "DINGTALK"), // https://developers.dingtalk.com/document/app/obtain-identity-credentials
-    WECHAT_ENTERPRISE(30, "WECHAT_ENTERPRISE"), // https://xkcoding.com/2019/08/06/use-justauth-integration-wechat-enterprise.html
-    ;
-
-    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SysSocialTypeEnum::getType).toArray();
-
-    public static final List<Integer> WECHAT_ALL = ListUtil.toList(WECHAT_ENTERPRISE.type);
-
-    /**
-     * 类型
-     */
-    private final Integer type;
-    /**
-     * 类型的标识
-     */
-    private final String source;
-
-    @Override
-    public int[] array() {
-        return ARRAYS;
-    }
-
-    public static SysSocialTypeEnum valueOfType(Integer type) {
-        return ArrayUtil.firstMatch(o -> o.getType().equals(type), values());
-    }
-
-    public static List<Integer> getRelationTypes(Integer type) {
-        if (WECHAT_ALL.contains(type)) {
-            return WECHAT_ALL;
-        }
-        return ListUtil.toList(type);
-    }
-
-}

+ 10 - 8
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/impl/SysAuthServiceImpl.java

@@ -12,18 +12,18 @@ import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.SysAu
 import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.SysAuthSocialLogin2ReqVO;
 import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.SysAuthSocialLoginReqVO;
 import cn.iocoder.yudao.adminserver.modules.system.convert.auth.SysAuthConvert;
-import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.social.SysSocialUserDO;
 import cn.iocoder.yudao.adminserver.modules.system.enums.logger.SysLoginLogTypeEnum;
 import cn.iocoder.yudao.adminserver.modules.system.enums.logger.SysLoginResultEnum;
 import cn.iocoder.yudao.adminserver.modules.system.service.auth.SysAuthService;
 import cn.iocoder.yudao.adminserver.modules.system.service.common.SysCaptchaService;
 import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysPermissionService;
-import cn.iocoder.yudao.adminserver.modules.system.service.social.SysSocialService;
 import cn.iocoder.yudao.adminserver.modules.system.service.user.SysUserService;
+import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.social.SysSocialUserDO;
 import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.user.SysUserDO;
 import cn.iocoder.yudao.coreservice.modules.system.service.auth.SysUserSessionCoreService;
 import cn.iocoder.yudao.coreservice.modules.system.service.logger.SysLoginLogCoreService;
 import cn.iocoder.yudao.coreservice.modules.system.service.logger.dto.SysLoginLogCreateReqDTO;
+import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialService;
 import cn.iocoder.yudao.coreservice.modules.system.service.user.SysUserCoreService;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
@@ -86,6 +86,9 @@ public class SysAuthServiceImpl implements SysAuthService {
     private SysPostService sysPostService;
     private SysSocialService socialService;
 
+    // TODO @timfruit:静态枚举类,需要都大写,例如说 USER_TYPE_ENUM;静态变量,放在普通变量前面;这个实践不错哈。
+    private static final UserTypeEnum userTypeEnum = UserTypeEnum.ADMIN;
+
     @Override
     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
         // 获取 username 对应的 SysUserDO
@@ -219,7 +222,7 @@ public class SysAuthServiceImpl implements SysAuthService {
 
         // 如果未绑定 SysSocialUserDO 用户,则无法自动登录,进行报错
         String unionId = socialService.getAuthUserUnionId(authUser);
-        List<SysSocialUserDO> socialUsers = socialService.getAllSocialUserList(reqVO.getType(), unionId);
+        List<SysSocialUserDO> socialUsers = socialService.getAllSocialUserList(reqVO.getType(), unionId, userTypeEnum);
         if (CollUtil.isEmpty(socialUsers)) {
             throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
         }
@@ -233,11 +236,10 @@ public class SysAuthServiceImpl implements SysAuthService {
 
         // 创建 LoginUser 对象
         LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user);
-        // TODO 芋艿:需要改造下,增加各种登录方式
         loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表
 
         // 绑定社交用户(更新)
-        socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser);
+        socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, userTypeEnum);
 
         // 缓存登录用户到 Redis 中,返回 sessionId 编号
         return userSessionCoreService.createUserSession(loginUser, userIp, userAgent);
@@ -254,7 +256,7 @@ public class SysAuthServiceImpl implements SysAuthService {
         loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表
 
         // 绑定社交用户(新增)
-        socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser);
+        socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, userTypeEnum);
 
         // 缓存登录用户到 Redis 中,返回 sessionId 编号
         return userSessionCoreService.createUserSession(loginUser, userIp, userAgent);
@@ -267,7 +269,7 @@ public class SysAuthServiceImpl implements SysAuthService {
         Assert.notNull(authUser, "授权用户不为空");
 
         // 绑定社交用户(新增)
-        socialService.bindSocialUser(userId, reqVO.getType(), authUser);
+        socialService.bindSocialUser(userId, reqVO.getType(), authUser, userTypeEnum);
     }
 
     @Override
@@ -288,7 +290,7 @@ public class SysAuthServiceImpl implements SysAuthService {
         reqDTO.setLogType(SysLoginLogTypeEnum.LOGOUT_SELF.getType());
         reqDTO.setTraceId(TracerUtils.getTraceId());
         reqDTO.setUserId(userId);
-        reqDTO.setUserType(UserTypeEnum.ADMIN.getValue());
+        reqDTO.setUserType(userTypeEnum.getValue());
         reqDTO.setUsername(username);
         reqDTO.setUserAgent(ServletUtils.getUserAgent());
         reqDTO.setUserIp(ServletUtils.getClientIP());

+ 1 - 1
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/sms/SysSmsChannelService.java

@@ -11,7 +11,7 @@ import java.util.Collection;
 import java.util.List;
 
 /**
- * 短信渠道Service接口
+ * 短信渠道 Service 接口
  *
  * @author zzf
  * @date 2021/1/25 9:24

+ 1 - 1
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/sms/impl/SysSmsTemplateServiceImpl.java

@@ -33,7 +33,7 @@ import static cn.iocoder.yudao.adminserver.modules.system.enums.SysErrorCodeCons
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 
 /**
- * 短信模板Service实现类
+ * 短信模板 Service 实现类
  *
  * @author zzf
  * @date 2021/1/25 9:25

+ 2 - 2
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/user/impl/SysUserServiceImpl.java

@@ -4,7 +4,6 @@ import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.adminserver.modules.infra.service.file.InfFileService;
 import cn.iocoder.yudao.adminserver.modules.system.controller.user.vo.profile.SysUserProfileUpdatePasswordReqVO;
 import cn.iocoder.yudao.adminserver.modules.system.controller.user.vo.profile.SysUserProfileUpdateReqVO;
 import cn.iocoder.yudao.adminserver.modules.system.controller.user.vo.user.*;
@@ -16,6 +15,7 @@ import cn.iocoder.yudao.adminserver.modules.system.service.dept.SysDeptService;
 import cn.iocoder.yudao.adminserver.modules.system.service.dept.SysPostService;
 import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysPermissionService;
 import cn.iocoder.yudao.adminserver.modules.system.service.user.SysUserService;
+import cn.iocoder.yudao.coreservice.modules.infra.service.file.InfFileCoreService;
 import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.user.SysUserDO;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.exception.ServiceException;
@@ -59,7 +59,7 @@ public class SysUserServiceImpl implements SysUserService {
     @Resource
     private PasswordEncoder passwordEncoder;
     @Resource
-    private InfFileService fileService;
+    private InfFileCoreService fileService;
 
     @Override
     public Long createUser(SysUserCreateReqVO reqVO) {

+ 3 - 0
yudao-admin-server/src/main/resources/application-local.yaml

@@ -180,6 +180,9 @@ yudao:
     exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系
       - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
       - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
+  pay:
+    pay-notify-url: http://niubi.natapp1.cc/api/pay/order/notify
+    refund-notify-url: http://niubi.natapp1.cc/api/pay/refund/notify
   demo: false # 关闭演示模式
 
 justauth:

+ 9 - 67
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/infra/service/file/InfFileServiceTest.java

@@ -1,13 +1,12 @@
 package cn.iocoder.yudao.adminserver.modules.infra.service.file;
 
-import cn.hutool.core.io.resource.ResourceUtil;
 import cn.iocoder.yudao.adminserver.BaseDbUnitTest;
+import cn.iocoder.yudao.adminserver.modules.infra.controller.file.vo.InfFilePageReqVO;
 import cn.iocoder.yudao.coreservice.modules.infra.dal.dataobject.file.InfFileDO;
+import cn.iocoder.yudao.coreservice.modules.infra.dal.mysql.file.InfFileCoreMapper;
+import cn.iocoder.yudao.coreservice.modules.infra.framework.file.config.FileProperties;
+import cn.iocoder.yudao.coreservice.modules.infra.service.file.impl.InfFileCoreServiceImpl;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.adminserver.modules.infra.framework.file.config.FileProperties;
-import cn.iocoder.yudao.adminserver.modules.infra.controller.file.vo.InfFilePageReqVO;
-import cn.iocoder.yudao.adminserver.modules.infra.dal.mysql.file.InfFileMapper;
-import cn.iocoder.yudao.adminserver.modules.infra.service.file.impl.InfFileServiceImpl;
 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.mock.mockito.MockBean;
@@ -15,79 +14,22 @@ import org.springframework.context.annotation.Import;
 
 import javax.annotation.Resource;
 
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
-import static cn.iocoder.yudao.adminserver.modules.infra.enums.InfErrorCodeConstants.FILE_NOT_EXISTS;
-import static cn.iocoder.yudao.adminserver.modules.system.enums.SysErrorCodeConstants.FILE_PATH_EXISTS;
-import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
-@Import({InfFileServiceImpl.class, FileProperties.class})
+@Import({InfFileCoreServiceImpl.class, FileProperties.class})
 public class InfFileServiceTest extends BaseDbUnitTest {
 
     @Resource
-    private InfFileServiceImpl fileService;
+    private InfFileService fileService;
 
     @MockBean
     private FileProperties fileProperties;
 
     @Resource
-    private InfFileMapper fileMapper;
-
-    @Test
-    public void testCreateFile_success() {
-        // 准备参数
-        String path = randomString();
-        byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
-
-        // 调用
-        String url = fileService.createFile(path, content);
-        // 断言
-        assertEquals(fileProperties.getBasePath() + path, url);
-        // 校验数据
-        InfFileDO file = fileMapper.selectById(path);
-        assertEquals(path, file.getId());
-        assertEquals("jpg", file.getType());
-        assertArrayEquals(content, file.getContent());
-    }
-
-    @Test
-    public void testCreateFile_exists() {
-        // mock 数据
-        InfFileDO dbFile = randomPojo(InfFileDO.class);
-        fileMapper.insert(dbFile);
-        // 准备参数
-        String path = dbFile.getId(); // 模拟已存在
-        byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
-
-        // 调用,并断言异常
-        assertServiceException(() -> fileService.createFile(path, content), FILE_PATH_EXISTS);
-    }
-
-    @Test
-    public void testDeleteFile_success() {
-        // mock 数据
-        InfFileDO dbFile = randomPojo(InfFileDO.class);
-        fileMapper.insert(dbFile);// @Sql: 先插入出一条存在的数据
-        // 准备参数
-        String id = dbFile.getId();
-
-        // 调用
-        fileService.deleteFile(id);
-        // 校验数据不存在了
-        assertNull(fileMapper.selectById(id));
-    }
-
-    @Test
-    public void testDeleteFile_notExists() {
-        // 准备参数
-        String id = randomString();
-
-        // 调用, 并断言异常
-        assertServiceException(() -> fileService.deleteFile(id), FILE_NOT_EXISTS);
-    }
+    private InfFileCoreMapper fileMapper;
 
     @Test
     public void testGetFilePage() {

+ 0 - 3
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/infra/service/logger/InfApiAccessLogServiceImplTest.java

@@ -1,12 +1,10 @@
 package cn.iocoder.yudao.adminserver.modules.infra.service.logger;
 
-import cn.hutool.core.util.RandomUtil;
 import cn.iocoder.yudao.adminserver.BaseDbUnitTest;
 import cn.iocoder.yudao.coreservice.modules.infra.dal.dataobject.logger.InfApiAccessLogDO;
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.apilog.core.service.dto.ApiAccessLogCreateDTO;
 import cn.iocoder.yudao.adminserver.modules.infra.controller.logger.vo.apiaccesslog.InfApiAccessLogExportReqVO;
 import cn.iocoder.yudao.adminserver.modules.infra.controller.logger.vo.apiaccesslog.InfApiAccessLogPageReqVO;
 import cn.iocoder.yudao.adminserver.modules.infra.dal.mysql.logger.InfApiAccessLogMapper;
@@ -19,7 +17,6 @@ import org.springframework.context.annotation.Import;
 import javax.annotation.Resource;
 import java.util.Date;
 import java.util.List;
-import java.util.concurrent.Future;
 
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime;

+ 0 - 3
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/infra/service/logger/InfApiErrorLogServiceImplTest.java

@@ -1,11 +1,9 @@
 package cn.iocoder.yudao.adminserver.modules.infra.service.logger;
 
-import cn.hutool.core.util.RandomUtil;
 import cn.iocoder.yudao.adminserver.BaseDbUnitTest;
 import cn.iocoder.yudao.coreservice.modules.infra.dal.dataobject.logger.InfApiErrorLogDO;
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.apilog.core.service.dto.ApiErrorLogCreateDTO;
 import cn.iocoder.yudao.adminserver.modules.infra.controller.logger.vo.apierrorlog.InfApiErrorLogExportReqVO;
 import cn.iocoder.yudao.adminserver.modules.infra.controller.logger.vo.apierrorlog.InfApiErrorLogPageReqVO;
 import cn.iocoder.yudao.adminserver.modules.infra.dal.mysql.logger.InfApiErrorLogMapper;
@@ -19,7 +17,6 @@ import org.springframework.context.annotation.Import;
 import javax.annotation.Resource;
 import java.util.Date;
 import java.util.List;
-import java.util.concurrent.Future;
 
 import static cn.iocoder.yudao.adminserver.modules.infra.enums.InfErrorCodeConstants.API_ERROR_LOG_NOT_FOUND;
 import static cn.iocoder.yudao.adminserver.modules.infra.enums.InfErrorCodeConstants.API_ERROR_LOG_PROCESSED;

+ 1 - 1
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/SysAuthServiceImplTest.java

@@ -7,11 +7,11 @@ import cn.iocoder.yudao.adminserver.modules.system.enums.logger.SysLoginResultEn
 import cn.iocoder.yudao.adminserver.modules.system.service.auth.impl.SysAuthServiceImpl;
 import cn.iocoder.yudao.adminserver.modules.system.service.common.SysCaptchaService;
 import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysPermissionService;
-import cn.iocoder.yudao.adminserver.modules.system.service.social.SysSocialService;
 import cn.iocoder.yudao.adminserver.modules.system.service.user.SysUserService;
 import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.user.SysUserDO;
 import cn.iocoder.yudao.coreservice.modules.system.service.auth.SysUserSessionCoreService;
 import cn.iocoder.yudao.coreservice.modules.system.service.logger.SysLoginLogCoreService;
+import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialService;
 import cn.iocoder.yudao.coreservice.modules.system.service.user.SysUserCoreService;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.security.core.LoginUser;

+ 11 - 10
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/social/SysSocialServiceTest.java

@@ -1,11 +1,11 @@
 package cn.iocoder.yudao.adminserver.modules.system.service.social;
 
 import cn.iocoder.yudao.adminserver.BaseDbAndRedisUnitTest;
-import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.social.SysSocialUserDO;
-import cn.iocoder.yudao.adminserver.modules.system.dal.mysql.social.SysSocialUserMapper;
-import cn.iocoder.yudao.adminserver.modules.system.dal.redis.social.SysSocialAuthUserRedisDAO;
-import cn.iocoder.yudao.adminserver.modules.system.enums.social.SysSocialTypeEnum;
-import cn.iocoder.yudao.adminserver.modules.system.service.social.impl.SysSocialServiceImpl;
+import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.social.SysSocialUserDO;
+import cn.iocoder.yudao.coreservice.modules.system.dal.mysql.social.SysSocialUserMapper;
+import cn.iocoder.yudao.coreservice.modules.system.dal.redis.social.SysSocialAuthUserRedisDAO;
+import cn.iocoder.yudao.coreservice.modules.system.enums.social.SysSocialTypeEnum;
+import cn.iocoder.yudao.coreservice.modules.system.service.social.impl.SysSocialServiceImpl;
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import com.xkcoding.justauth.AuthRequestFactory;
 import me.zhyd.oauth.model.AuthUser;
@@ -23,6 +23,7 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
+// TODO @timfruit:这个单元测试,挪到 yudao-core-service
 /**
  * {@link SysSocialServiceImpl} 的单元测试类
  *
@@ -53,7 +54,7 @@ public class SysSocialServiceTest extends BaseDbAndRedisUnitTest {
         // mock 方法
 
         // 调用
-        socialService.bindSocialUser(userId, type, authUser);
+        socialService.bindSocialUser(userId, type, authUser, UserTypeEnum.ADMIN);
         // 断言
         List<SysSocialUserDO> socialUsers = socialUserMapper.selectList("user_id", userId);
         assertEquals(1, socialUsers.size());
@@ -78,7 +79,7 @@ public class SysSocialServiceTest extends BaseDbAndRedisUnitTest {
         // mock 方法
 
         // 调用
-        socialService.bindSocialUser(userId, type, authUser);
+        socialService.bindSocialUser(userId, type, authUser, UserTypeEnum.ADMIN);
         // 断言
         List<SysSocialUserDO> socialUsers = socialUserMapper.selectList("user_id", userId);
         assertEquals(1, socialUsers.size());
@@ -103,7 +104,7 @@ public class SysSocialServiceTest extends BaseDbAndRedisUnitTest {
         // mock 方法
 
         // 调用
-        socialService.bindSocialUser(userId, type, authUser);
+        socialService.bindSocialUser(userId, type, authUser, UserTypeEnum.ADMIN);
         // 断言
         List<SysSocialUserDO> socialUsers = socialUserMapper.selectList("user_id", userId);
         assertEquals(1, socialUsers.size());
@@ -140,7 +141,7 @@ public class SysSocialServiceTest extends BaseDbAndRedisUnitTest {
         String newUnionId = oldSocialUser.getUnionId();
 
         // 调用
-        socialService.unbindOldSocialUser(userId, type, newUnionId);
+        socialService.unbindOldSocialUser(userId, type, newUnionId, UserTypeEnum.ADMIN);
         // 断言
         assertEquals(1L, socialUserMapper.selectCount(null).longValue());
     }
@@ -163,7 +164,7 @@ public class SysSocialServiceTest extends BaseDbAndRedisUnitTest {
         String newUnionId = randomString(10);
 
         // 调用
-        socialService.unbindOldSocialUser(userId, type, newUnionId);
+        socialService.unbindOldSocialUser(userId, type, newUnionId, UserTypeEnum.ADMIN);
         // 断言
         assertEquals(0L, socialUserMapper.selectCount(null).longValue());
     }

+ 2 - 2
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/user/SysUserServiceImplTest.java

@@ -3,7 +3,6 @@ package cn.iocoder.yudao.adminserver.modules.system.service.user;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.RandomUtil;
 import cn.iocoder.yudao.adminserver.BaseDbUnitTest;
-import cn.iocoder.yudao.adminserver.modules.infra.service.file.InfFileService;
 import cn.iocoder.yudao.adminserver.modules.system.controller.user.vo.profile.SysUserProfileUpdatePasswordReqVO;
 import cn.iocoder.yudao.adminserver.modules.system.controller.user.vo.profile.SysUserProfileUpdateReqVO;
 import cn.iocoder.yudao.adminserver.modules.system.controller.user.vo.user.*;
@@ -14,6 +13,7 @@ import cn.iocoder.yudao.adminserver.modules.system.service.dept.SysDeptService;
 import cn.iocoder.yudao.adminserver.modules.system.service.dept.SysPostService;
 import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysPermissionService;
 import cn.iocoder.yudao.adminserver.modules.system.service.user.impl.SysUserServiceImpl;
+import cn.iocoder.yudao.coreservice.modules.infra.service.file.InfFileCoreService;
 import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.user.SysUserDO;
 import cn.iocoder.yudao.coreservice.modules.system.enums.common.SysSexEnum;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
@@ -69,7 +69,7 @@ public class SysUserServiceImplTest extends BaseDbUnitTest {
     @MockBean
     private PasswordEncoder passwordEncoder;
     @MockBean
-    private InfFileService fileService;
+    private InfFileCoreService fileService;
 
     @Test
     public void testCreatUser_success() {

+ 19 - 2
yudao-core-service/pom.xml

@@ -3,9 +3,9 @@
          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">
     <parent>
-        <artifactId>yudao</artifactId>
         <groupId>cn.iocoder.boot</groupId>
-        <version>1.1.0-snapshot</version>
+        <artifactId>yudao</artifactId>
+        <version>${revision}</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -32,6 +32,10 @@
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-spring-boot-starter-biz-sms</artifactId>
         </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-biz-pay</artifactId>
+        </dependency>
 
         <!-- Web 相关 -->
         <dependency>
@@ -73,6 +77,12 @@
             <artifactId>yudao-spring-boot-starter-mq</artifactId>
         </dependency>
 
+        <!-- 服务保障相关 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-protection</artifactId>
+        </dependency>
+
         <!-- Test 测试相关 -->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
@@ -85,6 +95,13 @@
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
         </dependency>
+
+        <!-- 三方云服务相关 -->
+        <dependency>
+            <groupId>com.xkcoding.justauth</groupId>
+            <artifactId>justauth-spring-boot-starter</artifactId>
+        </dependency>
+
     </dependencies>
 
 </project>

+ 1 - 1
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/infra/controller/file/vo/InfFileRespVO.java → yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/controller/file/vo/InfFileRespVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.adminserver.modules.infra.controller.file.vo;
+package cn.iocoder.yudao.coreservice.modules.infra.controller.file.vo;
 
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;

+ 2 - 2
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/convert/logger/InfApiAccessLogCoreConvert.java

@@ -1,6 +1,6 @@
 package cn.iocoder.yudao.coreservice.modules.infra.convert.logger;
 
-import cn.iocoder.yudao.framework.apilog.core.service.dto.ApiAccessLogCreateDTO;
+import cn.iocoder.yudao.framework.apilog.core.service.dto.ApiAccessLogCreateReqDTO;
 import cn.iocoder.yudao.coreservice.modules.infra.dal.dataobject.logger.InfApiAccessLogDO;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
@@ -10,6 +10,6 @@ public interface InfApiAccessLogCoreConvert {
 
     InfApiAccessLogCoreConvert INSTANCE = Mappers.getMapper(InfApiAccessLogCoreConvert.class);
 
-    InfApiAccessLogDO convert(ApiAccessLogCreateDTO bean);
+    InfApiAccessLogDO convert(ApiAccessLogCreateReqDTO bean);
 
 }

+ 2 - 2
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/convert/logger/InfApiErrorLogCoreConvert.java

@@ -1,6 +1,6 @@
 package cn.iocoder.yudao.coreservice.modules.infra.convert.logger;
 
-import cn.iocoder.yudao.framework.apilog.core.service.dto.ApiErrorLogCreateDTO;
+import cn.iocoder.yudao.framework.apilog.core.service.dto.ApiErrorLogCreateReqDTO;
 import cn.iocoder.yudao.coreservice.modules.infra.dal.dataobject.logger.InfApiErrorLogDO;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
@@ -10,6 +10,6 @@ public interface InfApiErrorLogCoreConvert {
 
     InfApiErrorLogCoreConvert INSTANCE = Mappers.getMapper(InfApiErrorLogCoreConvert.class);
 
-    InfApiErrorLogDO convert(ApiErrorLogCreateDTO bean);
+    InfApiErrorLogDO convert(ApiErrorLogCreateReqDTO bean);
 
 }

+ 1 - 1
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/dal/dataobject/config/InfConfigDO.java

@@ -11,7 +11,7 @@ import lombok.ToString;
 /**
  * 参数配置表
  *
- * @author ruoyi
+ * @author 芋道源码
  */
 @TableName("inf_config")
 @Data

+ 2 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/dal/dataobject/logger/InfApiErrorLogDO.java

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.coreservice.modules.infra.dal.dataobject.logger;
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import cn.iocoder.yudao.coreservice.modules.infra.enums.logger.InfApiErrorLogProcessStatusEnum;
+import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.*;
 
@@ -25,6 +26,7 @@ public class InfApiErrorLogDO extends BaseDO {
     /**
      * 编号
      */
+    @TableId
     private Long id;
     /**
      * 用户编号

+ 3 - 1
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/dal/mysql/file/InfFileCoreMapper.java

@@ -6,5 +6,7 @@ import org.apache.ibatis.annotations.Mapper;
 
 @Mapper
 public interface InfFileCoreMapper extends BaseMapperX<InfFileDO> {
-
+    default Integer selectCountById(String id) {
+        return selectCount("id", id);
+    }
 }

+ 1 - 1
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/infra/framework/file/config/FileConfiguration.java → yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/framework/file/config/FileConfiguration.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.adminserver.modules.infra.framework.file.config;
+package cn.iocoder.yudao.coreservice.modules.infra.framework.file.config;
 
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Configuration;

+ 1 - 1
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/infra/framework/file/config/FileProperties.java → yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/framework/file/config/FileProperties.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.adminserver.modules.infra.framework.file.config;
+package cn.iocoder.yudao.coreservice.modules.infra.framework.file.config;
 
 import lombok.Data;
 import org.springframework.boot.context.properties.ConfigurationProperties;

+ 1 - 1
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/infra/framework/file/package-info.java → yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/framework/file/package-info.java

@@ -13,4 +13,4 @@
  * 综合考虑,暂时使用方案 3 的方式,比较适合这样一个 all in one 的项目。
  * 随着文件的量级大了之后,还是推荐采用云服务。
  */
-package cn.iocoder.yudao.adminserver.modules.infra.framework.file;
+package cn.iocoder.yudao.coreservice.modules.infra.framework.file;

+ 36 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/service/file/InfFileCoreService.java

@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.coreservice.modules.infra.service.file;
+
+import cn.iocoder.yudao.coreservice.modules.infra.dal.dataobject.file.InfFileDO;
+
+/**
+ * core service 文件接口
+ *
+ * @author 宋天
+ */
+public interface InfFileCoreService {
+
+
+    /**
+     * 保存文件,并返回文件的访问路径
+     *
+     * @param path 文件路径
+     * @param content 文件内容
+     * @return 文件路径
+     */
+    String createFile(String path, byte[] content);
+
+    /**
+     * 删除文件
+     *
+     * @param id 编号
+     */
+    void deleteFile(String id);
+
+    /**
+     * 获得文件
+     *
+     * @param path 文件路径
+     * @return 文件
+     */
+    InfFileDO getFile(String path);
+}

+ 64 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/service/file/impl/InfFileCoreServiceImpl.java

@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.coreservice.modules.infra.service.file.impl;
+
+import cn.hutool.core.io.FileTypeUtil;
+import cn.iocoder.yudao.coreservice.modules.infra.dal.dataobject.file.InfFileDO;
+import cn.iocoder.yudao.coreservice.modules.infra.dal.mysql.file.InfFileCoreMapper;
+import cn.iocoder.yudao.coreservice.modules.infra.framework.file.config.FileProperties;
+import cn.iocoder.yudao.coreservice.modules.infra.service.file.InfFileCoreService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.io.ByteArrayInputStream;
+
+import static cn.iocoder.yudao.coreservice.modules.system.enums.SysErrorCodeConstants.*;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+
+/**
+ * core service 文件实现类
+ *
+ * @author 宋天
+ */
+@Service
+public class InfFileCoreServiceImpl implements InfFileCoreService {
+
+    @Resource
+    private InfFileCoreMapper fileMapper;
+
+    @Resource
+    private FileProperties fileProperties;
+
+    @Override
+    public String createFile(String path, byte[] content) {
+        if (fileMapper.selectCountById(path) > 0) {
+            throw exception(FILE_PATH_EXISTS);
+        }
+        // 保存到数据库
+        InfFileDO file = new InfFileDO();
+        file.setId(path);
+        file.setType(FileTypeUtil.getType(new ByteArrayInputStream(content)));
+        file.setContent(content);
+        fileMapper.insert(file);
+        // 拼接路径返回
+        return fileProperties.getBasePath() + path;
+    }
+
+    @Override
+    public void deleteFile(String id) {
+        // 校验存在
+        this.validateFileExists(id);
+        // 更新
+        fileMapper.deleteById(id);
+    }
+
+    private void validateFileExists(String id) {
+        if (fileMapper.selectById(id) == null) {
+            throw exception(FILE_NOT_EXISTS);
+        }
+    }
+
+    @Override
+    public InfFileDO getFile(String path) {
+        return fileMapper.selectById(path);
+    }
+
+}

+ 2 - 4
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/service/logger/impl/InfApiAccessLogCoreServiceImpl.java

@@ -1,18 +1,16 @@
 package cn.iocoder.yudao.coreservice.modules.infra.service.logger.impl;
 
 import cn.iocoder.yudao.coreservice.modules.infra.convert.logger.InfApiAccessLogCoreConvert;
-import cn.iocoder.yudao.framework.apilog.core.service.dto.ApiAccessLogCreateDTO;
+import cn.iocoder.yudao.framework.apilog.core.service.dto.ApiAccessLogCreateReqDTO;
 import cn.iocoder.yudao.coreservice.modules.infra.dal.dataobject.logger.InfApiAccessLogDO;
 import cn.iocoder.yudao.coreservice.modules.infra.dal.mysql.logger.InfApiAccessLogCoreMapper;
 import cn.iocoder.yudao.coreservice.modules.infra.service.logger.InfApiAccessLogCoreService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.scheduling.annotation.Async;
-import org.springframework.scheduling.annotation.AsyncResult;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
-import java.util.concurrent.Future;
 
 /**
  * API 访问日志 Service 实现类
@@ -29,7 +27,7 @@ public class InfApiAccessLogCoreServiceImpl implements InfApiAccessLogCoreServic
 
     @Override
     @Async
-    public void createApiAccessLogAsync(ApiAccessLogCreateDTO createDTO) {
+    public void createApiAccessLogAsync(ApiAccessLogCreateReqDTO createDTO) {
         InfApiAccessLogDO apiAccessLog = InfApiAccessLogCoreConvert.INSTANCE.convert(createDTO);
         apiAccessLogMapper.insert(apiAccessLog);
     }

+ 2 - 4
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/infra/service/logger/impl/InfApiErrorLogCoreServiceImpl.java

@@ -5,15 +5,13 @@ import cn.iocoder.yudao.coreservice.modules.infra.dal.dataobject.logger.InfApiEr
 import cn.iocoder.yudao.coreservice.modules.infra.dal.mysql.logger.InfApiErrorLogCoreMapper;
 import cn.iocoder.yudao.coreservice.modules.infra.enums.logger.InfApiErrorLogProcessStatusEnum;
 import cn.iocoder.yudao.coreservice.modules.infra.service.logger.InfApiErrorLogCoreService;
-import cn.iocoder.yudao.framework.apilog.core.service.dto.ApiErrorLogCreateDTO;
+import cn.iocoder.yudao.framework.apilog.core.service.dto.ApiErrorLogCreateReqDTO;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.scheduling.annotation.Async;
-import org.springframework.scheduling.annotation.AsyncResult;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
-import java.util.concurrent.Future;
 
 /**
  * API 错误日志 Service 实现类
@@ -30,7 +28,7 @@ public class InfApiErrorLogCoreServiceImpl implements InfApiErrorLogCoreService
 
     @Override
     @Async
-    public void createApiErrorLogAsync(ApiErrorLogCreateDTO createDTO) {
+    public void createApiErrorLogAsync(ApiErrorLogCreateReqDTO createDTO) {
         InfApiErrorLogDO apiErrorLog = InfApiErrorLogCoreConvert.INSTANCE.convert(createDTO);
         apiErrorLog.setProcessStatus(InfApiErrorLogProcessStatusEnum.INIT.getStatus());
         apiErrorLogMapper.insert(apiErrorLog);

+ 22 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/convert/order/PayOrderCoreConvert.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.coreservice.modules.pay.convert.order;
+
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderExtensionDO;
+import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderCreateReqDTO;
+import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface PayOrderCoreConvert {
+
+    PayOrderCoreConvert INSTANCE = Mappers.getMapper(PayOrderCoreConvert.class);
+
+    PayOrderDO convert(PayOrderCreateReqDTO bean);
+
+    PayOrderExtensionDO convert(PayOrderSubmitReqDTO bean);
+
+    PayOrderUnifiedReqDTO convert2(PayOrderSubmitReqDTO bean);
+
+}

+ 6 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/convert/package-info.java

@@ -0,0 +1,6 @@
+/**
+ * 提供 POJO 类的实体转换
+ *
+ * 目前使用 MapStruct 框架
+ */
+package cn.iocoder.yudao.coreservice.modules.pay.convert;

+ 1 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md

@@ -0,0 +1 @@
+<http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao>

+ 62 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/merchant/PayAppDO.java

@@ -0,0 +1,62 @@
+package cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 支付应用 DO
+ * 一个商户下,可能会有多个支付应用。例如说,京东有京东商城、京东到家等等
+ * 不过一般来说,一个商户,只有一个应用哈~
+ *
+ * 即 PayMerchantDO : PayAppDO = 1 : n
+ *
+ * @author 芋道源码
+ */
+@TableName("pay_app")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class PayAppDO extends BaseDO {
+
+    /**
+     * 应用编号,数据库自增
+     */
+    @TableId
+    private Long id;
+    /**
+     * 应用名
+     */
+    private String name;
+    /**
+     * 状态
+     *
+     * 枚举 {@link CommonStatusEnum}
+     */
+    private Integer status;
+    /**
+     * 备注
+     */
+    private String remark;
+    /**
+     * 支付结果的回调地址
+     */
+    private String payNotifyUrl;
+    /**
+     * 退款结果的回调地址
+     */
+    private String refundNotifyUrl;
+
+    /**
+     * 商户编号
+     *
+     * 关联 {@link PayMerchantDO#getId()}
+     */
+    private Long merchantId;
+
+}

+ 68 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/merchant/PayChannelDO.java

@@ -0,0 +1,68 @@
+package cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
+import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import lombok.*;
+
+/**
+ * 支付渠道 DO
+ * 一个应用下,会有多种支付渠道,例如说微信支付、支付宝支付等等
+ *
+ * 即 PayAppDO : PayChannelDO = 1 : n
+ *
+ * @author 芋道源码
+ */
+@Data
+@TableName(value = "pay_channel", autoResultMap = true)
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class PayChannelDO extends BaseDO {
+
+    /**
+     * 渠道编号,数据库自增
+     */
+    private Long id;
+    /**
+     * 渠道编码
+     *
+     * 枚举 {@link PayChannelEnum}
+     */
+    private String code;
+    /**
+     * 状态
+     *
+     * 枚举 {@link CommonStatusEnum}
+     */
+    private Integer status;
+    /**
+     * 渠道费率,单位:百分比
+     */
+    private Double feeRate;
+
+    /**
+     * 商户编号
+     *
+     * 关联 {@link PayMerchantDO#getId()}
+     */
+    private Long merchantId;
+    /**
+     * 应用编号
+     *
+     * 关联 {@link PayAppDO#getId()}
+     */
+    private Long appId;
+    /**
+     * 支付渠道配置
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private PayClientConfig config;
+
+}

+ 53 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/merchant/PayMerchantDO.java

@@ -0,0 +1,53 @@
+package cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 支付商户信息 DO
+ * 目前暂时没有特别的用途,主要为未来多商户提供基础。
+ *
+ * @author 芋道源码
+ */
+@Data
+@TableName("pay_merchant")
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class PayMerchantDO extends BaseDO {
+
+    /**
+     * 商户编号,数据库自增
+     */
+    @TableId
+    private Long id;
+    /**
+     * 商户号
+     * 例如说,M233666999
+     */
+    private String no;
+    /**
+     * 商户全称
+     */
+    private String name;
+    /**
+     * 商户简称
+     */
+    private String shortName;
+    /**
+     * 状态
+     *
+     * 枚举 {@link CommonStatusEnum}
+     */
+    private Integer status;
+    /**
+     * 备注
+     */
+    private String remark;
+
+}

+ 49 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/notify/PayNotifyLogDO.java

@@ -0,0 +1,49 @@
+package cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.notify;
+
+import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 商户支付、退款等的通知 Log
+ * 每次通知时,都会在该表中,记录一次 Log,方便排查问题
+ *
+ * @author 芋道源码
+ */
+@TableName("pay_notify_log")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class PayNotifyLogDO extends BaseDO {
+
+    /**
+     * 日志编号,自增
+     */
+    private Long id;
+    /**
+     * 通知任务编号
+     *
+     * 关联 {@link PayNotifyTaskDO#getId()}
+     */
+    private Long taskId;
+    /**
+     * 第几次被通知
+     *
+     * 对应到 {@link PayNotifyTaskDO#getNotifyTimes()}
+     */
+    private Integer notifyTimes;
+    /**
+     * HTTP 响应结果
+     */
+    private String response;
+    /**
+     * 支付通知状态
+     *
+     * 外键 {@link PayNotifyStatusEnum}
+     */
+    private Integer status;
+
+}

+ 99 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/notify/PayNotifyTaskDO.java

@@ -0,0 +1,99 @@
+package cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.notify;
+
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
+import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyStatusEnum;
+import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyTypeEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.util.Date;
+
+/**
+ * 商户支付、退款等的通知
+ * 在支付系统收到支付渠道的支付、退款的结果后,需要不断的通知到业务系统,直到成功。
+ *
+ * @author 芋道源码
+ */
+@TableName("pay_notify_task")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Accessors(chain = true)
+public class PayNotifyTaskDO extends BaseDO {
+
+    /**
+     * 通知频率,单位为秒。
+     *
+     * 算上首次的通知,实际是一共 1 + 8 = 9 次。
+     */
+    public static final Integer[] NOTIFY_FREQUENCY = new Integer[]{
+            15, 15, 30, 180,
+            1800, 1800, 1800, 3600
+    };
+
+    /**
+     * 编号,自增
+     */
+    private Long id;
+    /**
+     * 商户编号
+     *
+     * 关联 {@link PayMerchantDO#getId()}
+     */
+    private Long merchantId;
+    /**
+     * 应用编号
+     *
+     * 关联 {@link PayAppDO#getId()}
+     */
+    private Long appId;
+    /**
+     * 通知类型
+     *
+     * 外键 {@link PayNotifyTypeEnum}
+     */
+    private Integer type;
+    /**
+     * 数据编号,根据不同 type 进行关联:
+     *
+     * 1. {@link PayNotifyTypeEnum#ORDER} 时,关联 {@link PayOrderDO#getId()}
+     * 2. {@link PayNotifyTypeEnum#REFUND} 时,关联 {@link PayRefundDO#getId()}
+     */
+    private Long dataId;
+    /**
+     * 商户订单编号
+     */
+    private String merchantOrderId;
+    /**
+     * 通知状态
+     *
+     * 外键 {@link PayNotifyStatusEnum}
+     */
+    private Integer status;
+    /**
+     * 下一次通知时间
+     */
+    private Date nextNotifyTime;
+    /**
+     * 最后一次执行时间
+     */
+    private Date lastExecuteTime;
+    /**
+     * 当前通知次数
+     */
+    private Integer notifyTimes;
+    /**
+     * 最大可通知次数
+     */
+    private Integer maxNotifyTimes;
+    /**
+     * 通知地址
+     */
+    private String notifyUrl;
+
+}

+ 162 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayOrderDO.java

@@ -0,0 +1,162 @@
+package cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order;
+
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO;
+import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderNotifyStatusEnum;
+import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderRefundStatusEnum;
+import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.util.Date;
+
+/**
+ * 支付订单 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("pay_order")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class PayOrderDO extends BaseDO {
+
+    /**
+     * 订单编号,数据库自增
+     */
+    private Long id;
+    /**
+     * 商户编号
+     *
+     * 关联 {@link PayMerchantDO#getId()}
+     */
+    private Long merchantId;
+    /**
+     * 应用编号
+     *
+     * 关联 {@link PayAppDO#getId()}
+     */
+    private Long appId;
+    /**
+     * 渠道编号
+     *
+     * 关联 {@link PayChannelDO#getId()}
+     */
+    private Long channelId;
+    /**
+     * 渠道编码
+     *
+     * 枚举 {@link PayChannelEnum}
+     */
+    private String channelCode;
+
+    // ========== 商户相关字段 ==========
+
+    /**
+     * 商户订单编号
+     * 例如说,内部系统 A 的订单号。需要保证每个 PayMerchantDO 唯一
+     */
+    private String merchantOrderId;
+    /**
+     * 商品标题
+     */
+    private String subject;
+    /**
+     * 商品描述信息
+     */
+    private String body;
+    /**
+     * 异步通知地址
+     */
+    private String notifyUrl;
+    /**
+     * 通知商户支付结果的回调状态
+     *
+     * 枚举 {@link PayOrderNotifyStatusEnum}
+     */
+    private Integer notifyStatus;
+//    /**
+//     * 商户拓展参数
+//     */
+//    private Map<String, String> merchantExtras;
+
+    // ========== 订单相关字段 ==========
+
+    /**
+     * 支付金额,单位:分
+     */
+    private Long amount;
+    /**
+     * 渠道手续费,单位:百分比
+     *
+     * 冗余 {@link PayChannelDO#getFeeRate()}
+     */
+    private Double channelFeeRate;
+    /**
+     * 渠道手续金额,单位:分
+     */
+    private Long channelFeeAmount;
+    /**
+     * 支付状态
+     *
+     * 枚举 {@link PayOrderStatusEnum}
+     */
+    private Integer status;
+    /**
+     * 用户 IP
+     */
+    private String userIp;
+    /**
+     * 订单失效时间
+     */
+    private Date expireTime;
+    /**
+     * 订单支付成功时间
+     */
+    private Date successTime;
+    /**
+     * 订单支付通知时间,即支付渠道的通知时间
+     */
+    private Date notifyTime;
+    /**
+     * 支付成功的订单拓展单编号
+     *
+     * 关联 {@link PayOrderDO#getId()}
+     */
+    private Long successExtensionId;
+
+    // ========== 退款相关字段 ==========
+    /**
+     * 退款状态
+     *
+     * 枚举 {@link PayOrderRefundStatusEnum}
+     */
+    private Integer refundStatus;
+    /**
+     * 退款次数
+     */
+    private Integer refundTimes;
+    /**
+     * 退款总金额,单位:分
+     */
+    private Long refundAmount;
+
+    // ========== 渠道相关字段 ==========
+    /**
+     * 渠道用户编号
+     *
+     * 例如说,微信 openid、支付宝账号
+     */
+    private String channelUserId;
+    /**
+     * 渠道订单号
+     */
+    private String channelOrderNo;
+
+}

+ 82 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayOrderExtensionDO.java

@@ -0,0 +1,82 @@
+package cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order;
+
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
+import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import lombok.*;
+
+import java.util.Map;
+
+/**
+ * 支付订单拓展 DO
+ *
+ *
+ * @author 芋道源码
+ */
+@TableName("pay_order_extension")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class PayOrderExtensionDO extends BaseDO {
+
+    /**
+     * 订单拓展编号,数据库自增
+     */
+    private Long id;
+    /**
+     * 支付订单号,根据规则生成
+     * 调用支付渠道时,使用该字段作为对接的订单号。
+     * 1. 调用微信支付 https://api.mch.weixin.qq.com/pay/unifiedorder 时,使用该字段作为 out_trade_no
+     * 2. 调用支付宝 https://opendocs.alipay.com/apis 时,使用该字段作为 out_trade_no
+     *
+     * 例如说,P202110132239124200055
+     */
+    private String no;
+    /**
+     * 订单号
+     *
+     * 关联 {@link PayOrderDO#getId()}
+     */
+    private Long orderId;
+    /**
+     * 渠道编号
+     *
+     * 关联 {@link PayChannelDO#getId()}
+     */
+    private Long channelId;
+    /**
+     * 渠道编码
+     */
+    private String channelCode;
+    /**
+     * 用户 IP
+     */
+    private String userIp;
+    /**
+     * 支付状态
+     *
+     * 枚举 {@link PayOrderStatusEnum}
+     * 注意,只包含上述枚举的 WAITING 和 SUCCESS
+     */
+    private Integer status;
+    /**
+     * 支付渠道的额外参数
+     *
+     * 参见 https://www.pingxx.com/api/支付渠道%20extra%20参数说明.html
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private Map<String, String> channelExtras;
+    /**
+     * 支付渠道异步通知的内容
+     *
+     * 在支持成功后,会记录回调的数据
+     */
+    private String channelNotifyData;
+
+}

+ 128 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayRefundDO.java

@@ -0,0 +1,128 @@
+package cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order;
+
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO;
+import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 支付退款单 DO
+ * 一个支付订单,可以拥有多个支付退款单
+ *
+ * 即 PayOrderDO : PayRefundDO = 1 : n
+ *
+ * @author 芋道源码
+ */
+@Data
+public class PayRefundDO extends BaseDO {
+
+    /**
+     * 退款单编号,数据库自增
+     */
+    private Long id;
+    /**
+     * 退款单号,根据规则生成
+     *
+     * 例如说,R202109181134287570000
+     */
+    private String no;
+    /**
+     * 商户编号
+     *
+     * 关联 {@link PayMerchantDO#getId()}
+     */
+    private Long merchantId;
+    /**
+     * 应用编号
+     *
+     * 关联 {@link PayAppDO#getId()}
+     */
+    private Long appId;
+    /**
+     * 渠道编号
+     *
+     * 关联 {@link PayChannelDO#getId()}
+     */
+    private Long channelId;
+    /**
+     * 商户编码
+     *
+     * 枚举 {@link PayChannelEnum}
+     */
+    private String channelCode;
+    /**
+     * 订单编号
+     *
+     * 关联 {@link PayOrderDO#getId()}
+     */
+    private Long orderId;
+
+    // ========== 商户相关字段 ==========
+    /**
+     * 商户退款订单号
+     * 例如说,内部系统 A 的退款订单号。需要保证每个 PayMerchantDO 唯一 TODO 芋艿:需要在测试下
+     */
+    private String merchantRefundNo;
+//    /**
+//     * 商户拓展参数
+//     */
+//    private String merchantExtra;
+    /**
+     * 异步通知地址
+     */
+    private String notifyUrl;
+    /**
+     * 通知商户退款结果的回调状态
+     * TODO 芋艿:0 未发送 1 已发送
+     */
+    private Integer notifyStatus;
+
+    // ========== 退款相关字段 ==========
+    /**
+     * 退款状态
+     *
+     * TODO 芋艿:状态枚举
+     */
+    private Integer status;
+    /**
+     * 用户 IP
+     */
+    private String userIp;
+    /**
+     * 退款金额,单位:分
+     */
+    private Long amount;
+    /**
+     * 退款原因
+     */
+    private String reason;
+    /**
+     * 订单退款成功时间
+     */
+    private Date successTime;
+    /**
+     * 退款失效时间
+     */
+    private Date expireTime;
+    /**
+     * 支付渠道的额外参数
+     *
+     * 参见 https://www.pingxx.com/api/Refunds%20退款概述.html
+     */
+    private String channelExtra;
+
+    // ========== 渠道相关字段 ==========
+    /**
+     * 渠道订单号
+     */
+    private String channelOrderNo;
+    /**
+     * 渠道退款号
+     */
+    private String channelRefundNo;
+
+}

+ 9 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/merchant/PayAppCoreMapper.java

@@ -0,0 +1,9 @@
+package cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.merchant;
+
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface PayAppCoreMapper extends BaseMapperX<PayAppDO> {
+}

+ 20 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/merchant/PayChannelCoreMapper.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.merchant;
+
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.Date;
+
+@Mapper
+public interface PayChannelCoreMapper extends BaseMapperX<PayChannelDO> {
+
+    default PayChannelDO selectByAppIdAndCode(Long appId, String code) {
+        return selectOne("app_id", appId, "code", code);
+    }
+
+    @Select("SELECT id FROM pay_channel WHERE update_time > #{maxUpdateTime} LIMIT 1")
+    Long selectExistsByUpdateTimeAfter(Date maxUpdateTime);
+
+}

+ 9 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/notify/PayNotifyLogCoreMapper.java

@@ -0,0 +1,9 @@
+package cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.notify;
+
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.notify.PayNotifyLogDO;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface PayNotifyLogCoreMapper extends BaseMapperX<PayNotifyLogDO> {
+}

+ 30 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/notify/PayNotifyTaskCoreMapper.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.notify;
+
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.notify.PayNotifyTaskDO;
+import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Date;
+import java.util.List;
+
+@Mapper
+public interface PayNotifyTaskCoreMapper extends BaseMapperX<PayNotifyTaskDO> {
+
+    /**
+     * 获得需要通知的 PayNotifyTaskDO 记录。需要满足如下条件:
+     *
+     * 1. status 非成功
+     * 2. nextNotifyTime 小于当前时间
+     *
+     * @return PayTransactionNotifyTaskDO 数组
+     */
+    default List<PayNotifyTaskDO> selectListByNotify() {
+        return selectList(new QueryWrapper<PayNotifyTaskDO>()
+                .in("status", PayNotifyStatusEnum.WAITING.getStatus(), PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus(),
+                        PayNotifyStatusEnum.REQUEST_FAILURE.getStatus())
+                .le("next_notify_time", new Date()));
+    }
+
+}

+ 22 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/order/PayOrderCoreMapper.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order;
+
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderExtensionDO;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface PayOrderCoreMapper extends BaseMapperX<PayOrderDO> {
+
+    default PayOrderDO selectByAppIdAndMerchantOrderId(Long appId, String merchantOrderId) {
+        return selectOne(new QueryWrapper<PayOrderDO>().eq("app_id", appId)
+                .eq("merchant_order_id", merchantOrderId));
+    }
+
+    default int updateByIdAndStatus(Long id, Integer status, PayOrderDO update) {
+        return update(update, new QueryWrapper<PayOrderDO>()
+                .eq("id", id).eq("status", status));
+    }
+
+}

+ 20 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/order/PayOrderExtensionCoreMapper.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order;
+
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderExtensionDO;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface PayOrderExtensionCoreMapper extends BaseMapperX<PayOrderExtensionDO> {
+
+    default PayOrderExtensionDO selectByNo(String no) {
+        return selectOne("no", no);
+    }
+
+    default int updateByIdAndStatus(Long id, Integer status, PayOrderExtensionDO update) {
+        return update(update, new QueryWrapper<PayOrderExtensionDO>()
+                .eq("id", id).eq("status", status));
+    }
+
+}

+ 19 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/redis/PayRedisKeyCoreConstants.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.coreservice.modules.pay.dal.redis;
+
+import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
+import org.redisson.api.RLock;
+
+import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.HASH;
+
+/**
+ * Lock4j Redis Key 枚举类
+ *
+ * @author 芋道源码
+ */
+public interface PayRedisKeyCoreConstants {
+
+    RedisKeyDefine PAY_NOTIFY_LOCK = new RedisKeyDefine("通知任务的分布式锁",
+            "pay_notify:lock:", // 参数来自 DefaultLockKeyBuilder 类
+            HASH, RLock.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); // Redisson 的 Lock 锁,使用 Hash 数据结构
+
+}

+ 39 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/redis/notify/PayNotifyLockCoreRedisDAO.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.coreservice.modules.pay.dal.redis.notify;
+
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.springframework.stereotype.Repository;
+
+import javax.annotation.Resource;
+import java.util.concurrent.TimeUnit;
+
+import static cn.iocoder.yudao.coreservice.modules.pay.dal.redis.PayRedisKeyCoreConstants.PAY_NOTIFY_LOCK;
+
+/**
+ * 支付通知的锁 Redis DAO
+ *
+ * @author 芋道源码
+ */
+@Repository
+public class PayNotifyLockCoreRedisDAO {
+
+    @Resource
+    private RedissonClient redissonClient;
+
+    public void lock(Long id, Long timeoutMillis, Runnable runnable) {
+        String lockKey = formatKey(id);
+        RLock lock = redissonClient.getLock(lockKey);
+        try {
+            lock.lock(timeoutMillis, TimeUnit.MILLISECONDS);
+            // 执行逻辑
+            runnable.run();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private static String formatKey(Long id) {
+        return String.format(PAY_NOTIFY_LOCK.getKeyTemplate(), id);
+    }
+
+}

+ 31 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/PayErrorCodeCoreConstants.java

@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.coreservice.modules.pay.enums;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+
+/**
+ * Pay 错误码 Core 枚举类
+ *
+ * pay 系统,使用 1-007-000-000 段
+ */
+public interface PayErrorCodeCoreConstants {
+
+    // ========== APP 模块 1-007-000-000 ==========
+    ErrorCode PAY_APP_NOT_FOUND = new ErrorCode(1007000000, "App 不存在");
+    ErrorCode PAY_APP_IS_DISABLE = new ErrorCode(1007000002, "App 已经被禁用");
+
+    // ========== CHANNEL 模块 1-007-001-000 ==========
+    ErrorCode PAY_CHANNEL_NOT_FOUND = new ErrorCode(1007001000, "支付渠道的配置不存在");
+    ErrorCode PAY_CHANNEL_IS_DISABLE = new ErrorCode(1007001001, "支付渠道已经禁用");
+    ErrorCode PAY_CHANNEL_CLIENT_NOT_FOUND = new ErrorCode(1007001002, "支付渠道的客户端不存在");
+
+    // ========== ORDER 模块 1-007-002-000 ==========
+    ErrorCode PAY_ORDER_NOT_FOUND = new ErrorCode(1007002000, "支付订单不存在");
+    ErrorCode PAY_ORDER_STATUS_IS_NOT_WAITING = new ErrorCode(1007002001, "支付订单不处于待支付");
+    ErrorCode PAY_ORDER_STATUS_IS_NOT_SUCCESS = new ErrorCode(1007002002, "支付订单不处于已支付");
+    ErrorCode PAY_ORDER_ERROR_USER = new ErrorCode(1007002003, "支付订单用户不正确");
+    // ========== ORDER 模块(拓展单) 1-007-003-000 ==========
+    ErrorCode PAY_ORDER_EXTENSION_NOT_FOUND = new ErrorCode(1007003000, "支付交易拓展单不存在");
+    ErrorCode PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING = new ErrorCode(1007003001, "支付交易拓展单不处于待支付");
+    ErrorCode PAY_ORDER_EXTENSION_STATUS_IS_NOT_SUCCESS = new ErrorCode(1007003002, "支付订单不处于已支付");
+
+}

+ 32 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/notify/PayNotifyStatusEnum.java

@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.coreservice.modules.pay.enums.notify;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 支付通知状态枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PayNotifyStatusEnum {
+
+    WAITING(1, "等待通知"),
+    SUCCESS(2, "通知成功"),
+    FAILURE(3, "通知失败"), // 多次尝试,彻底失败
+    REQUEST_SUCCESS(4, "请求成功,但是结果失败"),
+    REQUEST_FAILURE(5, "请求失败"),
+
+    ;
+
+    /**
+     * 状态
+     */
+    private final Integer status;
+    /**
+     * 名字
+     */
+    private final String name;
+
+}

+ 28 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/notify/PayNotifyTypeEnum.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.coreservice.modules.pay.enums.notify;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 支付通知类型
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PayNotifyTypeEnum {
+
+    ORDER(1, "支付单"),
+    REFUND(2, "退款单"),
+    ;
+
+    /**
+     * 类型
+     */
+    private final Integer type;
+    /**
+     * 名字
+     */
+    private final String name;
+
+}

+ 29 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayOrderNotifyStatusEnum.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.coreservice.modules.pay.enums.order;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 支付订单的通知状态枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PayOrderNotifyStatusEnum implements IntArrayValuable {
+
+    NO(0, "未通知"),
+    SUCCESS(10, "通知成功"),
+    FAILURE(20, "通知失败")
+    ;
+
+    private final Integer status;
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return new int[0];
+    }
+
+}

+ 29 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayOrderRefundStatusEnum.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.coreservice.modules.pay.enums.order;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 支付订单的退款状态枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PayOrderRefundStatusEnum implements IntArrayValuable {
+
+    NO(0, "未退款"),
+    SOME(10, "部分退款"),
+    ALL(20, "全部退款")
+    ;
+
+    private final Integer status;
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return new int[0];
+    }
+
+}

+ 29 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayOrderStatusEnum.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.coreservice.modules.pay.enums.order;
+
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 支付订单的状态枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PayOrderStatusEnum implements IntArrayValuable {
+
+    WAITING(0, "未支付"),
+    SUCCESS(10, "支付成功"),
+    CLOSED(20, "支付关闭"), // 未付款交易超时关闭,或支付完成后全额退款 TODO 芋艿:需要优化下
+    ;
+
+    private final Integer status;
+    private final String name;
+
+    @Override
+    public int[] array() {
+        return new int[0];
+    }
+
+}

+ 7 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/package-info.java

@@ -0,0 +1,7 @@
+/**
+ * pay 包下,我们放支付业务,提供业务的支付能力。
+ * 例如说:商户、应用、支付、退款等等
+ *
+ * 缩写:pay
+ */
+package cn.iocoder.yudao.coreservice.modules.pay;

+ 23 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/merchant/PayAppCoreService.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.coreservice.modules.pay.service.merchant;
+
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
+
+/**
+ * 支付应用 Core Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface PayAppCoreService {
+
+    /**
+     * 支付应用的合法性
+     *
+     * 如果不合法,抛出 {@link ServiceException} 业务异常
+     *
+     * @param id 应用编号
+     * @return 应用信息
+     */
+    PayAppDO validPayApp(Long id);
+
+}

+ 39 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/merchant/PayChannelCoreService.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.coreservice.modules.pay.service.merchant;
+
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
+
+/**
+ * 支付渠道 Core Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface PayChannelCoreService {
+
+    /**
+     * 初始化支付客户端
+     */
+    void initPayClients();
+
+    /**
+     * 支付渠道的合法性
+     *
+     * 如果不合法,抛出 {@link ServiceException} 业务异常
+     *
+     * @param id 渠道编号
+     * @return 渠道信息
+     */
+    PayChannelDO validPayChannel(Long id);
+
+    /**
+     * 支付渠道的合法性
+     *
+     * 如果不合法,抛出 {@link ServiceException} 业务异常
+     *
+     * @param appId 应用编号
+     * @param code 支付渠道
+     * @return 渠道信息
+     */
+    PayChannelDO validPayChannel(Long appId, String code);
+
+}

+ 43 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/merchant/impl/PayAppCoreServiceImpl.java

@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.coreservice.modules.pay.service.merchant.impl;
+
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.merchant.PayAppCoreMapper;
+import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.PayAppCoreService;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.*;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+
+/**
+ * 支付应用 Core Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Valid
+@Slf4j
+public class PayAppCoreServiceImpl implements PayAppCoreService {
+
+    @Resource
+    private PayAppCoreMapper payAppCoreMapper;
+
+    @Override
+    public PayAppDO validPayApp(Long id) {
+        PayAppDO app = payAppCoreMapper.selectById(id);
+        // 校验是否存在
+        if (app == null) {
+            throw exception(PAY_APP_NOT_FOUND);
+        }
+        // 校验是否禁用
+        if (CommonStatusEnum.DISABLE.getStatus().equals(app.getStatus())) {
+            throw exception(PAY_APP_IS_DISABLE);
+        }
+        return app;
+    }
+
+}

+ 121 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/merchant/impl/PayChannelCoreServiceImpl.java

@@ -0,0 +1,121 @@
+package cn.iocoder.yudao.coreservice.modules.pay.service.merchant.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.merchant.PayChannelCoreMapper;
+import cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants;
+import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.PayChannelCoreService;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+
+import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.*;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+
+/**
+ * 支付渠道 Core Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Valid
+@Slf4j
+public class PayChannelCoreServiceImpl implements PayChannelCoreService {
+
+    /**
+     * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
+     * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
+     */
+    private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
+
+    /**
+     * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新
+     */
+    private volatile Date maxUpdateTime;
+
+    @Resource
+    private PayChannelCoreMapper payChannelCoreMapper;
+
+    @Resource
+    private PayClientFactory payClientFactory;
+
+    @Override
+    @PostConstruct
+    public void initPayClients() {
+        // 获取支付渠道,如果有更新
+        List<PayChannelDO> payChannels = this.loadPayChannelIfUpdate(maxUpdateTime);
+        if (CollUtil.isEmpty(payChannels)) {
+            return;
+        }
+
+        // 创建或更新支付 Client
+        payChannels.forEach(payChannel -> payClientFactory.createOrUpdatePayClient(payChannel.getId(),
+                payChannel.getCode(), payChannel.getConfig()));
+
+        // 写入缓存
+        assert payChannels.size() > 0; // 断言,避免告警
+        maxUpdateTime = payChannels.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
+        log.info("[initPayClients][初始化 PayChannel 数量为 {}]", payChannels.size());
+    }
+
+    @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
+    public void schedulePeriodicRefresh() {
+        initPayClients();
+    }
+
+    /**
+     * 如果支付渠道发生变化,从数据库中获取最新的全量支付渠道。
+     * 如果未发生变化,则返回空
+     *
+     * @param maxUpdateTime 当前支付渠道的最大更新时间
+     * @return 支付渠道列表
+     */
+    private List<PayChannelDO> loadPayChannelIfUpdate(Date maxUpdateTime) {
+        // 第一步,判断是否要更新。
+        if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
+            log.info("[loadPayChannelIfUpdate][首次加载全量支付渠道]");
+        } else { // 判断数据库中是否有更新的支付渠道
+            if (payChannelCoreMapper.selectExistsByUpdateTimeAfter(maxUpdateTime) == null) {
+                return null;
+            }
+            log.info("[loadPayChannelIfUpdate][增量加载全量支付渠道]");
+        }
+        // 第二步,如果有更新,则从数据库加载所有支付渠道
+        return payChannelCoreMapper.selectList();
+    }
+
+    @Override
+    public PayChannelDO validPayChannel(Long id) {
+        PayChannelDO channel = payChannelCoreMapper.selectById(id);
+        this.validPayChannel(channel);
+        return channel;
+    }
+
+    @Override
+    public PayChannelDO validPayChannel(Long appId, String code) {
+        PayChannelDO channel = payChannelCoreMapper.selectByAppIdAndCode(appId, code);
+        this.validPayChannel(channel);
+        return channel;
+    }
+
+    private void validPayChannel(PayChannelDO channel) {
+        if (channel == null) {
+            throw exception(PAY_CHANNEL_NOT_FOUND);
+        }
+        if (CommonStatusEnum.DISABLE.getStatus().equals(channel.getStatus())) {
+            throw exception(PayErrorCodeCoreConstants.PAY_CHANNEL_IS_DISABLE);
+        }
+    }
+
+}

+ 29 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/PayNotifyCoreService.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.coreservice.modules.pay.service.notify;
+
+import cn.iocoder.yudao.coreservice.modules.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
+
+import javax.validation.Valid;
+
+/**
+ * 支付通知 Core Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface PayNotifyCoreService {
+
+    /**
+     * 创建支付通知任务
+     *
+     * @param reqDTO 任务信息
+     */
+    void createPayNotifyTask(@Valid PayNotifyTaskCreateReqDTO reqDTO);
+
+    /**
+     * 执行支付通知
+     *
+     * 注意,该方法提供给定时任务调用。目前是 yudao-admin-server 进行调用
+     * @return 通知数量
+     */
+    int executeNotify() throws InterruptedException;
+
+}

+ 32 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/dto/PayNotifyTaskCreateReqDTO.java

@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.coreservice.modules.pay.service.notify.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 支付通知创建 DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class PayNotifyTaskCreateReqDTO {
+
+    /**
+     * 类型
+     */
+    @NotNull(message = "类型不能为空")
+    private Integer type;
+    /**
+     * 数据编号
+     */
+    @NotNull(message = "数据编号不能为空")
+    private Long dataId;
+
+}

+ 256 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/impl/PayNotifyCoreServiceImpl.java

@@ -0,0 +1,256 @@
+package cn.iocoder.yudao.coreservice.modules.pay.service.notify.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.http.HttpUtil;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.notify.PayNotifyLogDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.notify.PayNotifyTaskDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.notify.PayNotifyLogCoreMapper;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.notify.PayNotifyTaskCoreMapper;
+import cn.iocoder.yudao.coreservice.modules.pay.dal.redis.notify.PayNotifyLockCoreRedisDAO;
+import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyStatusEnum;
+import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyTypeEnum;
+import cn.iocoder.yudao.coreservice.modules.pay.service.notify.PayNotifyCoreService;
+import cn.iocoder.yudao.coreservice.modules.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
+import cn.iocoder.yudao.coreservice.modules.pay.service.notify.vo.PayNotifyOrderReqVO;
+import cn.iocoder.yudao.coreservice.modules.pay.service.notify.vo.PayRefundOrderReqVO;
+import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayOrderCoreService;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.date.DateUtils;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static cn.hutool.core.exceptions.ExceptionUtil.getRootCauseMessage;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.SECOND_MILLIS;
+import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
+
+/**
+ * 支付通知 Core Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Valid
+@Slf4j
+public class PayNotifyCoreServiceImpl implements PayNotifyCoreService {
+
+    /**
+     * 通知超时时间,单位:秒
+     */
+    public static final int NOTIFY_TIMEOUT = 120;
+    /**
+     * {@link #NOTIFY_TIMEOUT} 的毫秒
+     */
+    public static final long NOTIFY_TIMEOUT_MILLIS = 120 * SECOND_MILLIS;
+
+    @Resource
+    @Lazy // 循环依赖,避免报错
+    private PayOrderCoreService payOrderCoreService;
+
+    @Resource
+    private PayNotifyTaskCoreMapper payNotifyTaskCoreMapper;
+    @Resource
+    private PayNotifyLogCoreMapper payNotifyLogCoreMapper;
+
+    @Resource
+    private ThreadPoolTaskExecutor threadPoolTaskExecutor; // TODO 芋艿:未来提供独立的线程池
+
+    @Resource
+    private PayNotifyLockCoreRedisDAO payNotifyLockCoreRedisDAO;
+
+    @Resource
+    @Lazy // 循环依赖(自己依赖自己),避免报错
+    private PayNotifyCoreServiceImpl self;
+
+    @Override
+    public void createPayNotifyTask(PayNotifyTaskCreateReqDTO reqDTO) {
+        PayNotifyTaskDO task = new PayNotifyTaskDO();
+        task.setType(reqDTO.getType()).setDataId(reqDTO.getDataId());
+        task.setStatus(PayNotifyStatusEnum.WAITING.getStatus()).setNextNotifyTime(new Date())
+                .setNotifyTimes(0).setMaxNotifyTimes(PayNotifyTaskDO.NOTIFY_FREQUENCY.length + 1);
+        // 补充 merchantId + appId + notifyUrl 字段
+        if (Objects.equals(task.getType(), PayNotifyTypeEnum.ORDER.getType())) {
+            PayOrderDO order = payOrderCoreService.getPayOrder(task.getDataId()); // 不进行非空判断,有问题直接异常
+            task.setMerchantId(order.getMerchantId()).setAppId(order.getAppId()).
+                    setMerchantOrderId(order.getMerchantOrderId()).setNotifyUrl(order.getNotifyUrl());
+        } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) {
+            // TODO 芋艿,需要实现下哈
+            throw new UnsupportedOperationException("需要实现");
+        }
+
+        // 执行插入
+        payNotifyTaskCoreMapper.insert(task);
+
+        // 异步直接发起任务。虽然会有定时任务扫描,但是会导致延迟
+        self.executeNotifyAsync(task);
+    }
+
+    @Override
+    public int executeNotify() throws InterruptedException {
+        // 获得需要通知的任务
+        List<PayNotifyTaskDO> tasks = payNotifyTaskCoreMapper.selectListByNotify();
+        if (CollUtil.isEmpty(tasks)) {
+            return 0;
+        }
+
+        // 遍历,逐个通知
+        CountDownLatch latch = new CountDownLatch(tasks.size());
+        tasks.forEach(task -> threadPoolTaskExecutor.execute(() -> {
+            try {
+                executeNotifySync(task);
+            } finally {
+                latch.countDown();
+            }
+        }));
+        // 等待完成
+        this.awaitExecuteNotify(latch);
+        // 返回执行完成的任务数(成功 + 失败)
+        return tasks.size();
+    }
+
+    /**
+     * 等待全部支付通知的完成
+     * 每 1 秒会打印一次剩余任务数量
+     *
+     * @param latch Latch
+     * @throws InterruptedException 如果被打断
+     */
+    private void awaitExecuteNotify(CountDownLatch latch) throws InterruptedException {
+        long size = latch.getCount();
+        for (int i = 0; i < NOTIFY_TIMEOUT; i++) {
+            if (latch.await(1L, TimeUnit.SECONDS)) {
+                return;
+            }
+            log.info("[awaitExecuteNotify][任务处理中, 总任务数({}) 剩余任务数({})]", size, latch.getCount());
+        }
+        log.error("[awaitExecuteNotify][任务未处理完,总任务数({}) 剩余任务数({})]", size, latch.getCount());
+    }
+
+    /**
+     * 异步执行单个支付通知
+     *
+     * @param task 通知任务
+     */
+    @Async
+    public void executeNotifyAsync(PayNotifyTaskDO task) {
+        self.executeNotifySync(task); // 使用 self,避免事务不发起
+    }
+
+    /**
+     * 同步执行单个支付通知
+     *
+     * @param task 通知任务
+     */
+    public void executeNotifySync(PayNotifyTaskDO task) {
+        // 分布式锁,避免并发问题
+        payNotifyLockCoreRedisDAO.lock(task.getId(), NOTIFY_TIMEOUT_MILLIS, () -> {
+            // 校验,当前任务是否已经被通知过
+            // 虽然已经通过分布式加锁,但是可能同时满足通知的条件,然后都去获得锁。此时,第一个执行完后,第二个还是能拿到锁,然后会再执行一次。
+            PayNotifyTaskDO dbTask = payNotifyTaskCoreMapper.selectById(task.getId());
+            if (DateUtils.afterNow(dbTask.getNextNotifyTime())) {
+                log.info("[executeNotify][dbTask({}) 任务被忽略,原因是未到达下次通知时间,可能是因为并发执行了]", toJsonString(dbTask));
+                return;
+            }
+
+            // 执行通知
+            executeNotify(dbTask);
+        });
+    }
+
+    @Transactional
+    public void executeNotify(PayNotifyTaskDO task) {
+        // 发起回调
+        CommonResult<?> invokeResult = null;
+        Throwable invokeException = null;
+        try {
+            invokeResult = executeNotifyInvoke(task);
+        } catch (Throwable e) {
+            invokeException = e;
+        }
+
+        // 处理
+        Integer newStatus = this.processNotifyResult(task, invokeResult, invokeException);
+
+        // 记录 PayNotifyLog 日志
+        String response = invokeException != null ? getRootCauseMessage(invokeException) : toJsonString(invokeResult);
+        payNotifyLogCoreMapper.insert(PayNotifyLogDO.builder().taskId(task.getId())
+                .notifyTimes(task.getNotifyTimes() + 1).status(newStatus).response(response).build());
+    }
+
+    /**
+     * 执行单个支付任务的 HTTP 调用
+     *
+     * @param task 通知任务
+     * @return HTTP 响应
+     */
+    private CommonResult<?> executeNotifyInvoke(PayNotifyTaskDO task) {
+        // 拼接参数
+        Object request;
+        if (Objects.equals(task.getType(), PayNotifyTypeEnum.ORDER.getType())) {
+            request = PayNotifyOrderReqVO.builder().merchantOrderId(task.getMerchantOrderId())
+                            .payOrderId(task.getDataId()).build();
+        } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) {
+            request = PayRefundOrderReqVO.builder().merchantOrderId(task.getMerchantOrderId())
+                    .payRefundId(task.getDataId()).build();
+        } else {
+            throw new RuntimeException("未知的通知任务类型:" + toJsonString(task));
+        }
+        // 请求地址
+        String response = HttpUtil.post(task.getNotifyUrl(), toJsonString(request),
+                (int) NOTIFY_TIMEOUT_MILLIS);
+        // 解析结果
+        return JsonUtils.parseObject(response, CommonResult.class);
+    }
+
+    /**
+     * 处理并更新通知结果
+     *
+     * @param task 通知任务
+     * @param invokeResult 通知结果
+     * @param invokeException 通知异常
+     * @return 最终任务的状态
+     */
+    private Integer processNotifyResult(PayNotifyTaskDO task, CommonResult<?> invokeResult, Throwable invokeException) {
+        // 设置通用的更新 PayNotifyTaskDO 的字段
+        PayNotifyTaskDO updateTask = new PayNotifyTaskDO()
+                .setId(task.getId())
+                .setLastExecuteTime(new Date())
+                .setNotifyTimes(task.getNotifyTimes() + 1);
+
+        // 情况一:调用成功
+        if (invokeResult != null && invokeResult.isSuccess()) {
+            updateTask.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus());
+            return updateTask.getStatus();
+        }
+        // 情况二:调用失败、调用异常
+        // 2.1 超过最大回调次数
+        if (updateTask.getNotifyTimes() >= PayNotifyTaskDO.NOTIFY_FREQUENCY.length) {
+            updateTask.setStatus(PayNotifyStatusEnum.FAILURE.getStatus());
+            return updateTask.getStatus();
+        }
+        // 2.2 未超过最大回调次数
+        updateTask.setNextNotifyTime(DateUtils.addDate(Calendar.SECOND, PayNotifyTaskDO.NOTIFY_FREQUENCY[updateTask.getNotifyTimes()]));
+        updateTask.setStatus(invokeException != null ? PayNotifyStatusEnum.REQUEST_FAILURE.getStatus()
+                : PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus());
+        return updateTask.getStatus();
+    }
+
+    private void processNotifySuccess(PayNotifyTaskDO task, PayNotifyTaskDO updateTask) {
+        payNotifyTaskCoreMapper.updateById(updateTask);
+    }
+
+}

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