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

 Conflicts:
	yudao-dependencies/pom.xml
	yudao-framework/pom.xml
This commit is contained in:
YunaiV 2021-10-30 10:24:58 +08:00
commit 0050b1e46d
243 changed files with 8264 additions and 468 deletions

31
mini-program-test/.eslintrc.js Executable file
View File

@ -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: {},
}

View File

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

19
mini-program-test/app.js Executable file
View File

@ -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
mini-program-test/app.json Executable file
View File

@ -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
mini-program-test/app.wxss Executable file
View File

@ -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;
}

View File

@ -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)
}
}
})
}
})

View File

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

View File

@ -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>

View File

@ -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;
}

View File

@ -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
}
})
})
}
})

View File

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

View File

@ -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>

View File

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

View File

@ -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
mini-program-test/sitemap.json Executable file
View File

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

View File

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

19
mini-program-test/utils/util.js Executable file
View File

@ -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
}

View File

@ -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 one or more lines are too long

View File

@ -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>

View File

@ -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());

View File

@ -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;

View File

@ -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;

View File

@ -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"));
}
}

View File

@ -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, "文件不存在");
}

View File

@ -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);
/**
* 获得文件分页
*

View File

@ -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);

View File

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

View File

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

View File

@ -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);
}
}

View File

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

View File

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

View File

@ -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);
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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;

View File

@ -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", // 参数为 typecode
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
}

View File

@ -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, "社交解绑失败,非当前用户绑定");
}

View File

@ -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);
}
}

View File

@ -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());

View File

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

View File

@ -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

View File

@ -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) {

View File

@ -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:

View File

@ -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.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.impl.InfFileServiceImpl;
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.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.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 cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
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() {

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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());
}

View File

@ -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() {

View File

@ -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>

View File

@ -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;

View File

@ -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);
}

View File

@ -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);
}

View File

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

View File

@ -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;
/**
* 用户编号

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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);
}

View File

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

View File

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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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> {
}

View File

@ -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);
}

View File

@ -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> {
}

View File

@ -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()));
}
}

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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 数据结构
}

View File

@ -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);
}
}

View File

@ -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, "支付订单不处于已支付");
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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];
}
}

View File

@ -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];
}
}

View File

@ -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];
}
}

View File

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

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 have changed in this diff Show More