Merge remote-tracking branch 'origin/master'

# Conflicts:
#	system/system-service-impl/src/main/java/cn/iocoder/mall/admin/service/SmsYunPianPlatform.java
This commit is contained in:
sin 2019-05-18 10:35:09 +08:00
commit 1e3ea29dbb
127 changed files with 816 additions and 1191 deletions

View File

@ -30,19 +30,26 @@
# 演示
> 艿艿:目前的开发者,都是后端出身。所以,一帮没有审美自觉的人,撸出来的前端界面,可能是东半球倒数第二难看。
>
> 迫切希望,有前端能力不错的小伙伴,加入我们,一起来完善「一个商城」。
>
> 啊啊啊!我好像做店铺装修功能。
## H5 商城
[体验传送门](http://h5.shop.iocoder.cn:18099)
TODO 此处应有一个演示的装逼 GIF 图。
![GIF 图-耐心等待](https://raw.githubusercontent.com/YunaiV/Blog/master/Mall/onemall-h5-min.gif)
## 管理后台
[体验传送门](http://admin.shop.iocoder.cn:18099)
TODO 暂时不提供管理后台的账号密码,等后面提供。
* 账号yudaoyuanma
* 密码yudaoyuanma
TODO 此处应有一个演示的装逼 GIF 图。
![GIF 图-耐心等待](https://raw.githubusercontent.com/YunaiV/Blog/master/Mall/onemall-admin-min.gif)
## 其它演示

View File

@ -35,4 +35,8 @@ public class StringUtil {
return org.apache.commons.lang3.StringUtils.substring(str, start);
}
public static void main(String[] args) {
System.out.println(StringUtil.split("cn.iocoder.mall.order.api.OrderService#updatePaySuccess#1.0.0", "#").size());
}
}

View File

@ -1 +0,0 @@
package cn.iocoder.mall.spring.boot;

View File

@ -2,6 +2,7 @@ package cn.iocoder.mall.spring.boot.web;
import cn.iocoder.common.framework.constant.MallConstants;
import cn.iocoder.common.framework.servlet.CorsFilter;
import cn.iocoder.mall.admin.sdk.interceptor.AdminDemoInterceptor;
import cn.iocoder.mall.spring.boot.web.interceptor.AccessLogInterceptor;
import cn.iocoder.mall.admin.sdk.interceptor.AdminSecurityInterceptor;
import cn.iocoder.mall.spring.boot.web.handler.GlobalExceptionHandler;
@ -34,6 +35,12 @@ public class AdminMVCAutoConfiguration implements WebMvcConfigurer {
return new AdminSecurityInterceptor();
}
@Bean
@ConditionalOnMissingBean(AdminDemoInterceptor.class)
public AdminDemoInterceptor adminDemoInterceptor() {
return new AdminDemoInterceptor();
}
@Bean
@ConditionalOnMissingBean(GlobalResponseBodyHandler.class)
public GlobalResponseBodyHandler globalReturnValueHandler() {
@ -50,6 +57,7 @@ public class AdminMVCAutoConfiguration implements WebMvcConfigurer {
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(adminAccessLogInterceptor()).addPathPatterns(MallConstants.ROOT_PATH_ADMIN + "/**");
registry.addInterceptor(adminSecurityInterceptor()).addPathPatterns(MallConstants.ROOT_PATH_ADMIN + "/**");
registry.addInterceptor(adminDemoInterceptor()).addPathPatterns(MallConstants.ROOT_PATH_ADMIN + "/**");
}
@Bean

View File

@ -29,4 +29,6 @@
- 用户相关
- [x] 登陆
- [x] 注册
- [ ] 个人信息
- [x] 个人信息
- [ ] 手机改绑
- [ ] 微信登陆

View File

@ -10,31 +10,48 @@
- [ ] 支付单 20% 【待认领】
- [ ] 退款单 20% 【待认领】
- TODO 需要补充
- [ ] 店铺装修【迫切需要靠谱前端一起做】
- [ ] H5 装修
- [ ] 小程序装修
- [ ] 自定义页面
- [ ] 商品管理
- [x] 发布商品
- [x] 商品列表
- [x] 展示类目
- [ ] 品牌管理【待认领】
- [ ] 品牌管理【开发中 @黑子
- [ ] 商品标签
- [ ] 订单管理
- [ ] 销售单 开发中
- [ ] 售后单 开发中
- [ ] 订单评价【开发中】
- [x] 销售单
- [x] 售后单
- [ ] 订单评价【开发中 @wang171776704
- [ ] 会员管理
- [ ] 会员资料 20%【待认领】
- [ ] 会员等级
- [ ] 会员积分
- [ ] 用户标签
- TODO 需要补充
- [ ] 营销管理
- [x] 首页广告
- [x] 商品推荐
- [x] 优惠劵
- [ ] 优惠码【待认领
- [ ] 优惠码【开发中 @native8623 2019-05-17
- [ ] 满减送 20% 【待认领】
- [ ] 限制折扣 20% 【待认领】
- [ ] 多人拼团【待认领】
- [ ] 积分商城
- [ ] 问卷调查
- [ ] 幸运大转盘
- [ ] 分销管理
- [ ] 分销设置
- [ ] 分销员管理
- [ ] 提现管理
- [ ] 系统管理
- [x] 员工管理
- [x] 角色管理 <!--【前端页面需要细化下】-->
- [ ] 权限管理
- [ ] 短信管理
- [x] 权限管理 <!--【前端页面需要细化下】-->
- [ ] 部门管理【待认领】
- [x] 数据字典
- [ ] 短信管理【开发中 @小范
- [ ] 短信模板
- [ ] 发送日志
- [ ] 员工操作日志

View File

@ -229,11 +229,11 @@ service.interceptors.response.use(
// TODO token 过期
// TODO 需要拿 refresh token 置换
if (code === 1001001011 // 访问令牌不存在
|| code === 1001001013 // 访问令牌已失效
|| code === 1001001021 // 刷新令牌不存在
|| code === 1001001022 // 刷新令牌已过期
|| code === 1001001023) { // 刷新令牌已失效
if (code === 1002001011 // 访问令牌不存在
|| code === 1002001013 // 访问令牌已失效
|| code === 1002001017 // 刷新令牌不存在
|| code === 1002001018 // 刷新令牌已过期
|| code === 1002001019) { // 刷新令牌已失效
Dialog.confirm({
title: '系统提示',
message: res.message,
@ -249,7 +249,7 @@ service.interceptors.response.use(
}
}
});
} else if (code === 1001001012) { // 访问令牌已过期
} else if (code === 1002001012) { // 访问令牌已过期
return refreshToken(response);
} else {
Dialog.alert({

View File

@ -69,7 +69,7 @@ export default {
let that = this;
let response = doPassportMobileRegister(this.mobile, this.code);
response.then(data => {
setLoginToken(data.accessToken, data.refreshToken);
setLoginToken(data.token.accessToken, data.token.refreshToken);
Dialog.alert({
title: '系统提示',
message: '登陆成功',

View File

@ -50,21 +50,6 @@
<div class="category-div">
<!--<h4>热门分类</h4>-->
<ul>
<!--<li><a ><img src="//img11.360buyimg.com/focus/s140x140_jfs/t21388/146/237407622/26923/221da1b3/5b054fedN2ba90518.jpg"><span>手机</span></a></li>-->
<!--<li><a ><img src="//img20.360buyimg.com/focus/s140x140_jfs/t20128/208/216721929/9242/472993da/5b05522dNa2aae1bb.png"><span>耳机</span></a></li>-->
<!--<li><a ><img src="//img30.360buyimg.com/focus/s140x140_jfs/t21655/83/2186874549/15932/c273d29b/5b48802aN13fe73de.png"><span>剃须刀</span></a></li>-->
<!--<li><a ><img src="//img20.360buyimg.com/focus/s140x140_jfs/t21715/149/246679831/16257/ddbf2036/5b0565a7N8dbc0017.png"><span>路由器</span></a></li>-->
<!--<li><a ><img src="//img14.360buyimg.com/focus/s140x140_jfs/t1/4478/16/633/36008/5b923503E39b9dfa9/13b099f187576d8c.png"><span>月饼</span></a></li>-->
<!--<li><a ><img src="//img10.360buyimg.com/focus/s140x140_jfs/t1/1410/32/643/38009/5b9236b2Eb02fbf02/1e7de6987578dcdd.jpg" ><span>牛奶</span></a></li>-->
<!--<li><a ><img src="//img20.360buyimg.com/focus/s140x140_jfs/t1/4674/14/665/25245/5b9236bbE088d5efb/6c7c2f9857736c65.jpg"><span>男士内裤</span></a></li>-->
<!--<li><a ><img src="//img20.360buyimg.com/focus/s140x140_jfs/t1/1710/26/666/26147/5b9236c3E5fd1cd42/86c6bca8f4fe1efa.png"><span>小米8</span></a></li>-->
<!--<li><a ><img src="//img11.360buyimg.com/focus/s140x140_jfs/t1/3653/6/655/42593/5b9236caEfef6235b/9e118f12705f52bb.png"><span>大闸蟹</span></a></li>-->
<!--<li><a ><img src="//img20.360buyimg.com/focus/s140x140_jfs/t23881/349/2204372862/9923/4c62864a/5b7693eeNf6883734.png"><span>三只松鼠</span></a></li>-->
<!--<li><a ><img src="//img20.360buyimg.com/focus/s140x140_jfs/t24253/294/2182777138/4059/429945c9/5b76990bNde226fbc.png"><span>充电宝</span></a></li>-->
<!--<li><a ><img src="//img30.360buyimg.com/focus/s140x140_jfs/t22051/318/235303191/9297/c5ea2761/5b055000N410a7553.png"><span>空调</span></a></li>-->
<!--<li><a ><img src="//img10.360buyimg.com/focus/s140x140_jfs/t19960/243/653029866/38879/91bb398b/5b055555N9245f8aa.jpg"><span>电饭煲</span></a></li>-->
<!--<li><a ><img src="//img12.360buyimg.com/focus/s140x140_jfs/t1/345/33/944/5582/5b9236d2E62d8da2e/99f72d51b8f195ed.jpg"><span>电话手表</span></a></li>-->
<!--<li><a ><img src="//img30.360buyimg.com/focus/s140x140_jfs/t1/1446/14/631/8500/5b9237e5E0d1f9e16/b1a627b92323b5ed.png"><span>华为</span></a></li>-->
<li v-for="category in childCategories">
<router-link :to="'/products/list?title=' + activeCategory.name + '&cidFirst=' + activeCategory.id + '&cidSecond=' + category.id">
<img :src="category.picUrl" />

View File

@ -1,7 +1,7 @@
<template>
<div>
<headerNav title="个人信息"/>
<van-cell-group>
<van-cell-group title="基础资料">
<!--<van-cell title="修改个人信息" is-link />-->
<!--<van-cell title="修改登录密码" is-link />-->
<!--<van-cell title="修改绑定手机" is-link />-->
@ -14,6 +14,10 @@
</van-cell-group>
<van-cell-group title="密保资料">
<van-cell title="手机号" :value="user.mobile" />
</van-cell-group>
<!-- 昵称修改弹出 -->
<van-dialog
v-model="showNicknameDialog"
@ -83,4 +87,4 @@ export default {
<style>
</style>
</style>

View File

@ -9,7 +9,6 @@ import cn.iocoder.mall.order.application.convert.OrderReturnConvert;
import cn.iocoder.mall.order.application.po.admin.OrderReturnQueryPO;
import io.swagger.annotations.Api;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@ -26,8 +25,7 @@ import javax.servlet.http.HttpServletRequest;
@Api("订单退货(admins api)")
public class AdminOrderReturnController {
@Autowired
@Reference(validation = "true")
@Reference(validation = "true", version = "${dubbo.provider.OrderReturnService.version}")
private OrderReturnService orderReturnService;
@GetMapping("list")

View File

@ -5,7 +5,9 @@ import cn.iocoder.mall.order.api.OrderService;
import cn.iocoder.mall.order.api.bo.OrderItemBO;
import cn.iocoder.mall.order.api.bo.OrderPageBO;
import cn.iocoder.mall.order.api.bo.OrderRecipientBO;
import cn.iocoder.mall.order.api.dto.*;
import cn.iocoder.mall.order.api.dto.OrderItemUpdateDTO;
import cn.iocoder.mall.order.api.dto.OrderLogisticsUpdateDTO;
import cn.iocoder.mall.order.api.dto.OrderQueryDTO;
import cn.iocoder.mall.order.application.convert.OrderConvertAPP;
import cn.iocoder.mall.order.application.convert.OrderDeliveryConvert;
import cn.iocoder.mall.order.application.po.admin.OrderDeliverPO;
@ -15,7 +17,6 @@ import cn.iocoder.mall.order.application.po.admin.OrderPageQueryPO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@ -29,10 +30,10 @@ import java.util.List;
*/
@RestController
@RequestMapping("admins/order")
@Api(value = "订单API(admins)")
@Api(value = "订单 API(admins)")
public class AdminsOrderController {
@Reference(validation = "true")
@Reference(validation = "true", version = "${dubbo.provider.OrderService.version}")
private OrderService orderService;
@GetMapping("page")

View File

@ -19,6 +19,7 @@ import cn.iocoder.mall.order.application.po.user.OrderCreatePO;
import cn.iocoder.mall.order.application.vo.UsersOrderConfirmCreateVO;
import cn.iocoder.mall.promotion.api.CouponService;
import cn.iocoder.mall.promotion.api.bo.CouponCardAvailableBO;
import cn.iocoder.mall.user.sdk.annotation.RequiresLogin;
import cn.iocoder.mall.user.sdk.context.UserSecurityContextHolder;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@ -41,19 +42,23 @@ import static cn.iocoder.common.framework.vo.CommonResult.success;
*/
@RestController
@RequestMapping("users/order")
@Api(description = "用户订单")
@Api(description = "用户订单") // TODO FROM 芋艿 to 小范description 已经废弃啦
public class OrderController {
@Reference(validation = "true")
@Reference(validation = "true", version = "${dubbo.provider.OrderReturnService.version}")
private OrderService orderService;
@Reference(validation = "true", version = "${dubbo.provider.CartService.version}")
private CartService cartService;
@Reference(validation = "true", version = "${dubbo.consumer.DataDictService.version}")
private DataDictService dataDictService;
@Reference(validation = "true", version = "${dubbo.consumer.CouponService.version}")
private CouponService couponService;
@GetMapping("order_page")
@RequiresLogin
@ApiOperation("订单分页")
public CommonResult<OrderPageBO> getOrderPage(@Validated OrderQueryDTO orderQueryDTO) {
Integer userId = UserSecurityContextHolder.getContext().getUserId();
@ -62,6 +67,7 @@ public class OrderController {
}
@PostMapping("create_order")
@RequiresLogin
@ApiOperation("创建订单")
public CommonResult<OrderCreateBO> createOrder(@RequestBody @Validated OrderCreatePO orderCreatePO,
HttpServletRequest request) {
@ -72,6 +78,7 @@ public class OrderController {
}
@PostMapping("create_order_from_cart")
@RequiresLogin
@ApiOperation("创建订单购物车")
public CommonResult<OrderCreateBO> createOrderFromCart(@RequestParam("userAddressId") Integer userAddressId,
@RequestParam(value = "couponCardId", required = false) Integer couponCardId,
@ -99,6 +106,7 @@ public class OrderController {
}
@GetMapping("confirm_create_order")
@RequiresLogin
@ApiOperation("确认创建订单")
public CommonResult<UsersOrderConfirmCreateVO> getConfirmCreateOrder(@RequestParam("skuId") Integer skuId,
@RequestParam("quantity") Integer quantity,
@ -118,6 +126,7 @@ public class OrderController {
}
@PostMapping("confirm_receiving")
@RequiresLogin
@ApiOperation("确认收货")
public CommonResult confirmReceiving(@RequestParam("orderId") Integer orderId) {
Integer userId = UserSecurityContextHolder.getContext().getUserId();
@ -125,6 +134,7 @@ public class OrderController {
}
@GetMapping("info")
@RequiresLogin
@ApiOperation("订单详情")
public CommonResult<OrderInfoBO> orderInfo(@RequestParam("orderId") Integer orderId) {
Integer userId = UserSecurityContextHolder.getContext().getUserId();

View File

@ -35,8 +35,9 @@ import java.util.stream.Collectors;
@Api(description = "订单物流信息")
public class OrderLogisticsController {
@Reference(validation = "true")
@Reference(validation = "true", version = "${dubbo.provider.OrderLogisticsService.version}")
private OrderLogisticsService orderLogisticsService;
@Reference(validation = "true", version = "${dubbo.consumer.DataDictService.version}")
private DataDictService dataDictService;

View File

@ -25,8 +25,9 @@ import java.util.List;
@RequestMapping("users/order_return")
public class OrderReturnController {
@Reference(validation = "true")
@Reference(validation = "true", version = "${dubbo.provider.OrderReturnService.version}")
private OrderReturnService orderReturnService;
@Reference(validation = "true", version = "${dubbo.consumer.DataDictService.version}")
private DataDictService dataDictService;

View File

@ -13,7 +13,6 @@ import cn.iocoder.mall.order.application.vo.UsersCartDetailVO;
import cn.iocoder.mall.order.application.vo.UsersOrderConfirmCreateVO;
import cn.iocoder.mall.promotion.api.CouponService;
import cn.iocoder.mall.promotion.api.bo.CouponCardAvailableBO;
import cn.iocoder.mall.user.sdk.annotation.PermitAll;
import cn.iocoder.mall.user.sdk.context.UserSecurityContextHolder;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.web.bind.annotation.*;
@ -31,8 +30,10 @@ public class UsersCartController {
@Reference(validation = "true", version = "${dubbo.provider.CartService.version}")
private CartService cartService;
@Reference(validation = "true")
@Reference(validation = "true", version = "${dubbo.provider.OrderService.version}")
private OrderService orderService;
@Reference(validation = "true", version = "${dubbo.consumer.CouponService.version}")
private CouponService couponService;
@ -125,7 +126,6 @@ public class UsersCartController {
}
@GetMapping("/calc_sku_price")
@PermitAll
public CommonResult<UsersCalcSkuPriceVO> calcSkuPrice(@RequestParam("skuId") Integer skuId) {
// 计算 sku 的价格
CalcSkuPriceBO calcSkuPrice = cartService.calcSkuPrice(skuId);

View File

@ -127,7 +127,7 @@ public interface OrderService {
CommonResult updateLogistics(OrderLogisticsUpdateDTO orderLogisticsDTO);
/**
* 删除订单
* 删除订单 // TODO FROM 芋艿 to 小范删除订单不要使用 deleted 字段对于用户是删除实际是隐藏
*
* @param id
*/

View File

@ -12,7 +12,7 @@ import lombok.experimental.Accessors;
*/
@Data
@Accessors(chain = true)
public class OrderRecipientBO extends BaseDO {
public class OrderRecipientBO extends BaseDO { // TODO FROM 芋艿 TO 小范不要继承 BaseDO
/**
* 编号

View File

@ -15,10 +15,12 @@ public class CalcOrderPriceDTO {
@NotNull(message = "用户编号不能为空")
private Integer userId;
/**
* 优惠劵编号
*/
private Integer couponCardId;
@NotNull(message = "商品数组不能为空")
private List<Item> items;

View File

@ -1,7 +0,0 @@
/**
* 订单 api
*
* @author Sin
* @time 2019-03-16 13:15
*/
package cn.iocoder.mall.order.api;

View File

@ -16,5 +16,5 @@ public class ServiceExceptionConfiguration {
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
}
}
} // TODO FROM 芋艿 to 小范这里记得配置下不然错误提示不出去呀
}

View File

@ -1,7 +0,0 @@
/**
* 定义常量以及枚举信息
*
* @author Sin
* @time 2019-03-20 21:16
*/
package cn.iocoder.mall.order.biz.constants;

View File

@ -18,7 +18,7 @@ import lombok.experimental.Accessors;
public class OrderCommentDO extends BaseDO {
/**
* 评论id
* 评论id // TODO FROM 芋艿 TO wtz 中英文之间要有空格
*/
private Integer id;
@ -103,7 +103,7 @@ public class OrderCommentDO extends BaseDO {
private Integer replayCount;
/**
* 点赞数
* 点赞数 // TODO FROM 芋艿 TO wtz collect 是收藏的意思最好换个单词噢
*/
private Integer collectCount;

View File

@ -8,6 +8,8 @@ import lombok.experimental.Accessors;
/**
* 商品评价回复表
*
* // TODO FROM 芋艿 TO wtz 商品评价回复表 =订单评论回复表
*
* @author wtz
* @time 2019-05-14 21:00
*
@ -28,7 +30,7 @@ public class OrderCommentReplayDO extends BaseDO {
private Integer commentId;
/**
* 回复的类型
* 回复的类型 // TODO FROM 芋艿 TO wtz 记得加下枚举类
*/
private Integer replyType;
@ -73,7 +75,7 @@ public class OrderCommentReplayDO extends BaseDO {
private String replyUserAvatar;
/**
* 回复用户身份
* 回复用户身份 // TODO FROM 芋艿 TO wtz 提示userType UserTypeEnum 记录保持一致
*/
private Integer replyUserType;

View File

@ -1,7 +1,6 @@
package cn.iocoder.mall.order.biz.dataobject;
import cn.iocoder.common.framework.dataobject.BaseDO;
import cn.iocoder.common.framework.dataobject.DeletableDO;
import lombok.Data;
import lombok.experimental.Accessors;
@ -17,6 +16,8 @@ import java.util.Date;
@Accessors(chain = true)
public class OrderReturnDO extends BaseDO {
// TODO FROM 芋艿 TO 小范存储下支付中心的退款单号
/**
* 编号自动增长
*/
@ -24,6 +25,7 @@ public class OrderReturnDO extends BaseDO {
/**
* 服务号
*/
// TODO FROM 芋艿 to 小范换个名字看着怪怪的 哈哈哈哈
private String serviceNumber;
/**
* 订单编号
@ -54,6 +56,7 @@ public class OrderReturnDO extends BaseDO {
/**
* 问题描述
*/
// TODO FROM 芋艿 to 小范describe 是动词换成名词 description
private String describe;
///

View File

@ -21,7 +21,7 @@ import cn.iocoder.mall.order.biz.dataobject.OrderDO;
import cn.iocoder.mall.order.biz.dataobject.OrderItemDO;
import cn.iocoder.mall.order.biz.dataobject.OrderReturnDO;
import cn.iocoder.mall.pay.api.PayRefundService;
import cn.iocoder.mall.pay.api.dto.PayRefundSubmitDTO;
import cn.iocoder.mall.pay.api.dto.refund.PayRefundSubmitDTO;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

View File

@ -14,8 +14,8 @@ import cn.iocoder.mall.order.biz.convert.*;
import cn.iocoder.mall.order.biz.dao.*;
import cn.iocoder.mall.order.biz.dataobject.*;
import cn.iocoder.mall.pay.api.PayTransactionService;
import cn.iocoder.mall.pay.api.bo.PayTransactionBO;
import cn.iocoder.mall.pay.api.dto.PayTransactionCreateDTO;
import cn.iocoder.mall.pay.api.bo.transaction.PayTransactionBO;
import cn.iocoder.mall.pay.api.dto.transaction.PayTransactionCreateDTO;
import cn.iocoder.mall.product.api.ProductSpuService;
import cn.iocoder.mall.product.api.bo.ProductSkuDetailBO;
import cn.iocoder.mall.promotion.api.CouponService;
@ -79,7 +79,7 @@ public class OrderServiceImpl implements OrderService {
public CommonResult<OrderPageBO> getOrderPage(OrderQueryDTO orderQueryDTO) {
int totalCount = orderMapper.selectPageCount(orderQueryDTO);
if (totalCount == 0) {
if (totalCount == 0) { // TODO FROM 芋艿 TO 小范 Collections.EMPTY_LIST 改成 Collections.emptyList()
return CommonResult.success(new OrderPageBO().setOrders(Collections.EMPTY_LIST).setTotal(0));
}
@ -92,7 +92,7 @@ public class OrderServiceImpl implements OrderService {
// 获取订单 id
Set<Integer> orderIds = orderDOList.stream()
.map(orderDO -> orderDO.getId())
.map(orderDO -> orderDO.getId()) // TODO FROM 芋艿 to 小范记得用 Lambda
.collect(Collectors.toSet());
// 获取配送信息
@ -231,10 +231,10 @@ public class OrderServiceImpl implements OrderService {
// 设置 orderItem
Map<Integer, ProductSkuDetailBO> productSpuBOMap = productList
.stream().collect(Collectors.toMap(ProductSkuDetailBO::getId, o -> o)); // 商品 SKU 信息的集合
Map<Integer, CalcOrderPriceBO.Item> priceItemMap = new HashMap<>();
Map<Integer, CalcOrderPriceBO.Item> priceItemMap = new HashMap<>(); // 商品 SKU 价格的映射
calcOrderPrice.getItemGroups().forEach(itemGroup ->
itemGroup.getItems().forEach(item -> priceItemMap.put(item.getId(), item)));
// 遍历 orderItemDOList 数组将商品信息商品价格设置到其中
for (OrderItemDO orderItemDO : orderItemDOList) {
ProductSkuDetailBO productSkuDetailBO = productSpuBOMap.get(orderItemDO.getSkuId());
if (productSkuDetailBO.getQuantity() <= 0) {
@ -267,6 +267,7 @@ public class OrderServiceImpl implements OrderService {
// order
// TODO: 2019-04-11 Sin 订单号需要生成规则
// TODO FROM 芋艿 to 小范可以考虑抽象成一个方法下面几个也是
String orderNo = UUID.randomUUID().toString().replace("-", "").substring(0, 16);
// Integer totalAmount = orderCommon.calculatedAmount(orderItemDOList);
// Integer totalPrice = orderCommon.calculatedPrice(orderItemDOList);
@ -323,10 +324,6 @@ public class OrderServiceImpl implements OrderService {
// 一次性插入
orderItemMapper.insert(orderItemDOList);
if (true) {
throw new RuntimeException("测试 seata 事务回滚");
}
// 创建预订单
createPayTransaction(orderDO, orderItemDOList, orderCreateDTO.getIp());
@ -358,7 +355,7 @@ public class OrderServiceImpl implements OrderService {
return cartService.calcOrderPrice(calcOrderPriceDTO);
}
private CommonResult<PayTransactionBO> createPayTransaction(OrderDO order, List<OrderItemDO> orderItems, String ip) {
private PayTransactionBO createPayTransaction(OrderDO order, List<OrderItemDO> orderItems, String ip) {
// TODO sin 支付订单 orderSubject 暂时取第一个子订单商品信息
String orderSubject = orderItems.get(0).getSkuName();
Date expireTime = DateUtil.addDate(Calendar.MINUTE, PAY_EXPIRE_TIME);
@ -441,6 +438,7 @@ public class OrderServiceImpl implements OrderService {
.setUpdateTime(null);
// 关闭订单修改状态 item
// TODO FROM 芋艿 TO 小范更新的时候where 里面带下 status 避免并发的问题
orderItemMapper.updateByOrderId(
orderId,
new OrderItemDO().setStatus(OrderStatusEnum.CLOSED.getValue())
@ -454,18 +452,18 @@ public class OrderServiceImpl implements OrderService {
}
@Override
@Transactional
@Transactional // TODO FROM 芋艿 TO 小范泛型一定要明确哈
public CommonResult orderDelivery(OrderDeliveryDTO orderDelivery) {
List<Integer> orderItemIds = orderDelivery.getOrderItemIds();
// 获取所有订单 items
// 获取所有订单 items // TODO FROM 芋艿 TO 小范deleted 是默认条件所以 by 里面可以不带哈
List<OrderItemDO> allOrderItems = orderItemMapper.selectByDeletedAndOrderId(orderDelivery.getOrderId(), DeletedStatusEnum.DELETED_NO.getValue());
// 当前需要发货订单检查 id status
List<OrderItemDO> needDeliveryOrderItems = allOrderItems.stream()
.filter(orderItemDO -> orderItemIds.contains(orderItemDO.getId())
&& OrderStatusEnum.WAIT_SHIPMENT.getValue() == orderItemDO.getStatus())
.collect(Collectors.toList());
.collect(Collectors.toList()); // TODO 芋艿如果这里只是比对数字可以用 Lambda 求和不需要弄成一个集合的
// 发货订单检查
if (needDeliveryOrderItems.size() != orderItemIds.size()) {
return ServiceExceptionUtil.error(OrderErrorCodeEnum.ORDER_DELIVERY_INCORRECT_DATA.getCode());
@ -482,6 +480,7 @@ public class OrderServiceImpl implements OrderService {
orderLogisticsMapper.insert(orderLogisticsDO);
// 关联订单item 物流信息
// TODO FROM 芋艿 TO 小范更新的时候where 里面带下 status 避免并发的问题然后判断下更新数量不对就抛出异常
orderItemMapper.updateByIds(
orderItemIds,
new OrderItemDO()
@ -495,6 +494,7 @@ public class OrderServiceImpl implements OrderService {
&& !orderItemIds.contains(orderItemDO.getId()))
.collect(Collectors.toList());
if (unShippedOrderItems.size() <= 0) {
// TODO FROM 芋艿 TO 小范更新的时候where 里面带下 status 避免并发的问题
orderMapper.updateById(
new OrderDO()
.setId(orderDelivery.getOrderId())
@ -513,7 +513,7 @@ public class OrderServiceImpl implements OrderService {
}
@Override
@Transactional
@Transactional // TODO FROM 芋艿 to 小范先不做这个功能电商一班不存在这个功能哈
public CommonResult deleteOrderItem(OrderItemDeletedDTO orderItemDeletedDTO) {
Integer orderId = orderItemDeletedDTO.getOrderId();
List<Integer> orderItemIds = orderItemDeletedDTO.getOrderItemIds();
@ -562,6 +562,7 @@ public class OrderServiceImpl implements OrderService {
return ServiceExceptionUtil.error(OrderErrorCodeEnum.ORDER_UNABLE_CONFIRM_ORDER.getCode());
}
// TODO FROM 芋艿 TO 小范更新的时候where 里面带下 status 避免并发的问题
orderMapper.updateById(
new OrderDO()
.setId(orderId)
@ -617,7 +618,7 @@ public class OrderServiceImpl implements OrderService {
if (updateCount <= 0) {
return ServiceExceptionUtil.error(OrderErrorCodeEnum.ORDER_STATUS_NOT_WAITING_PAYMENT.getCode()).getMessage();
}
// TODO 芋艿 更新 OrderItemDO
// TODO FROM 芋艿 to 小范把更新 OrderItem 给补全
return "success";
}

View File

@ -21,7 +21,6 @@ mybatis-plus:
id-type: auto
mapper-locations: classpath*:mapper/*.xml
type-aliases-package: cn.iocoder.mall.order.biz.dataobject
config-location: classpath:mybatis-config.xml
# dubbo
dubbo:

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- 使用驼峰命名法转换字段。 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<typeAliases>
<typeAlias alias="Integer" type="java.lang.Integer"/>
<typeAlias alias="Long" type="java.lang.Long"/>
<typeAlias alias="HashMap" type="java.util.HashMap"/>
<typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap"/>
<typeAlias alias="ArrayList" type="java.util.ArrayList"/>
<typeAlias alias="LinkedList" type="java.util.LinkedList"/>
</typeAliases>
</configuration>

View File

@ -3,10 +3,10 @@ package cn.iocoder.mall.pay.application.controller.admins;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.pay.api.PayRefundService;
import cn.iocoder.mall.pay.api.PayTransactionService;
import cn.iocoder.mall.pay.api.bo.PayRefundBO;
import cn.iocoder.mall.pay.api.bo.PayRefundPageBO;
import cn.iocoder.mall.pay.api.bo.PayTransactionBO;
import cn.iocoder.mall.pay.api.dto.PayRefundPageDTO;
import cn.iocoder.mall.pay.api.bo.refund.PayRefundBO;
import cn.iocoder.mall.pay.api.bo.refund.PayRefundPageBO;
import cn.iocoder.mall.pay.api.bo.transaction.PayTransactionBO;
import cn.iocoder.mall.pay.api.dto.refund.PayRefundPageDTO;
import cn.iocoder.mall.pay.application.convert.PayRefundConvert;
import cn.iocoder.mall.pay.application.vo.admins.AdminsPayRefundPageVO;
import org.apache.dubbo.config.annotation.Reference;

View File

@ -2,8 +2,8 @@ package cn.iocoder.mall.pay.application.controller.admins;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.pay.api.PayTransactionService;
import cn.iocoder.mall.pay.api.bo.PayTransactionPageBO;
import cn.iocoder.mall.pay.api.dto.PayTransactionPageDTO;
import cn.iocoder.mall.pay.api.bo.transaction.PayTransactionPageBO;
import cn.iocoder.mall.pay.api.dto.transaction.PayTransactionPageDTO;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.GetMapping;

View File

@ -3,11 +3,14 @@ package cn.iocoder.mall.pay.application.controller.users;
import cn.iocoder.common.framework.util.HttpUtil;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.pay.api.PayTransactionService;
import cn.iocoder.mall.pay.api.bo.PayTransactionBO;
import cn.iocoder.mall.pay.api.bo.PayTransactionSubmitBO;
import cn.iocoder.mall.pay.api.bo.transaction.PayTransactionBO;
import cn.iocoder.mall.pay.api.bo.transaction.PayTransactionSubmitBO;
import cn.iocoder.mall.pay.api.constant.PayChannelEnum;
import cn.iocoder.mall.pay.api.dto.PayTransactionSubmitDTO;
import cn.iocoder.mall.pay.api.dto.transaction.PayTransactionGetDTO;
import cn.iocoder.mall.pay.api.dto.transaction.PayTransactionSubmitDTO;
import cn.iocoder.mall.user.sdk.context.UserSecurityContextHolder;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.dubbo.config.annotation.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -18,8 +21,11 @@ import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import static cn.iocoder.common.framework.vo.CommonResult.success;
@RestController
@RequestMapping("users/transaction") // TODO 芋艿理论来说是用户无关的这里先酱紫先~
@RequestMapping("users/transaction")
@Api("【用户】支付交易 API")
public class UsersPayTransactionController {
private Logger logger = LoggerFactory.getLogger(getClass());
@ -28,23 +34,19 @@ public class UsersPayTransactionController {
private PayTransactionService payTransactionService;
@GetMapping("/get")
// TODO result 后面改下
public CommonResult<PayTransactionBO> get(@RequestParam("appId") String appId,
@RequestParam("orderId") String orderId) {
return payTransactionService.getTransaction(UserSecurityContextHolder.getContext().getUserId(), appId, orderId);
@ApiOperation("获得支付交易")
public CommonResult<PayTransactionBO> get(PayTransactionGetDTO payTransactionGetDTO) {
payTransactionGetDTO.setUserId(UserSecurityContextHolder.getContext().getUserId());
return success(payTransactionService.getTransaction(payTransactionGetDTO));
}
@PostMapping("/submit") // TODO api 注释
// TODO result 后面改下
@PostMapping("/submit")
@ApiOperation("提交支付交易")
public CommonResult<PayTransactionSubmitBO> submit(HttpServletRequest request,
@RequestParam("appId") String appId,
@RequestParam("orderId") String orderId,
@RequestParam("payChannel") Integer payChannel) {
PayTransactionSubmitDTO payTransactionSubmitDTO = new PayTransactionSubmitDTO()
.setAppId(appId).setOrderId(orderId).setPayChannel(payChannel)
.setCreateIp(HttpUtil.getIp(request));
PayTransactionSubmitDTO payTransactionSubmitDTO) {
payTransactionSubmitDTO.setCreateIp(HttpUtil.getIp(request));
// 提交支付提交
return payTransactionService.submitTransaction(payTransactionSubmitDTO);
return success(payTransactionService.submitTransaction(payTransactionSubmitDTO));
}
@PostMapping(value = "pingxx_pay_success", consumes = MediaType.APPLICATION_JSON_VALUE)
@ -63,11 +65,7 @@ public class UsersPayTransactionController {
// JSONObject bodyObj = JSON.parseObject(sb.toString());
// bodyObj.put("webhookId", bodyObj.remove("id"));
// String body = bodyObj.toString();
CommonResult<Boolean> result = payTransactionService.updateTransactionPaySuccess(PayChannelEnum.PINGXX.getId(), sb.toString());
if (result.isError()) {
logger.error("[pingxxPaySuccess][message({}) result({})]", sb, result);
return "failure";
}
payTransactionService.updateTransactionPaySuccess(PayChannelEnum.PINGXX.getId(), sb.toString());
return "success";
}

View File

@ -1,6 +1,6 @@
package cn.iocoder.mall.pay.application.convert;
import cn.iocoder.mall.pay.api.bo.PayRefundBO;
import cn.iocoder.mall.pay.api.bo.refund.PayRefundBO;
import cn.iocoder.mall.pay.application.vo.admins.AdminsPayRefundDetailVO;
import org.mapstruct.Mapper;
import org.mapstruct.Mappings;

View File

@ -1,7 +1,7 @@
package cn.iocoder.mall.pay.application.vo.admins;
import cn.iocoder.mall.pay.api.bo.PayRefundBO;
import cn.iocoder.mall.pay.api.bo.PayTransactionBO;
import cn.iocoder.mall.pay.api.bo.refund.PayRefundBO;
import cn.iocoder.mall.pay.api.bo.transaction.PayTransactionBO;
import lombok.Data;
import lombok.experimental.Accessors;

View File

@ -1,10 +1,10 @@
package cn.iocoder.mall.pay.api;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.pay.api.bo.PayRefundPageBO;
import cn.iocoder.mall.pay.api.bo.PayRefundSubmitBO;
import cn.iocoder.mall.pay.api.dto.PayRefundPageDTO;
import cn.iocoder.mall.pay.api.dto.PayRefundSubmitDTO;
import cn.iocoder.mall.pay.api.bo.refund.PayRefundPageBO;
import cn.iocoder.mall.pay.api.bo.refund.PayRefundSubmitBO;
import cn.iocoder.mall.pay.api.dto.refund.PayRefundPageDTO;
import cn.iocoder.mall.pay.api.dto.refund.PayRefundSubmitDTO;
public interface PayRefundService {

View File

@ -1,23 +1,24 @@
package cn.iocoder.mall.pay.api;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.pay.api.bo.PayTransactionBO;
import cn.iocoder.mall.pay.api.bo.PayTransactionPageBO;
import cn.iocoder.mall.pay.api.bo.PayTransactionSubmitBO;
import cn.iocoder.mall.pay.api.dto.PayTransactionCreateDTO;
import cn.iocoder.mall.pay.api.dto.PayTransactionPageDTO;
import cn.iocoder.mall.pay.api.dto.PayTransactionSubmitDTO;
import cn.iocoder.mall.pay.api.bo.transaction.PayTransactionBO;
import cn.iocoder.mall.pay.api.bo.transaction.PayTransactionPageBO;
import cn.iocoder.mall.pay.api.bo.transaction.PayTransactionSubmitBO;
import cn.iocoder.mall.pay.api.dto.transaction.PayTransactionCreateDTO;
import cn.iocoder.mall.pay.api.dto.transaction.PayTransactionGetDTO;
import cn.iocoder.mall.pay.api.dto.transaction.PayTransactionPageDTO;
import cn.iocoder.mall.pay.api.dto.transaction.PayTransactionSubmitDTO;
import java.util.Collection;
import java.util.List;
public interface PayTransactionService {
CommonResult<PayTransactionBO> getTransaction(Integer userId, String appId, String orderId);
PayTransactionBO getTransaction(PayTransactionGetDTO payTransactionGetDTO);
CommonResult<PayTransactionBO> createTransaction(PayTransactionCreateDTO payTransactionCreateDTO);
PayTransactionBO createTransaction(PayTransactionCreateDTO payTransactionCreateDTO);
CommonResult<PayTransactionSubmitBO> submitTransaction(PayTransactionSubmitDTO payTransactionSubmitDTO);
PayTransactionSubmitBO submitTransaction(PayTransactionSubmitDTO payTransactionSubmitDTO);
/**
* 更新交易支付成功
@ -29,7 +30,7 @@ public interface PayTransactionService {
* 因为不同平台能够提供的参数不同所以使用 String 类型统一接收然后在使用不同的 AbstractPaySDK 进行处理
* @return 是否支付成功
*/
CommonResult<Boolean> updateTransactionPaySuccess(Integer payChannel, String params);
Boolean updateTransactionPaySuccess(Integer payChannel, String params);
List<PayTransactionBO> getTransactionList(Collection<Integer> ids);

View File

@ -1,24 +0,0 @@
package cn.iocoder.mall.pay.api.bo;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* 支付交易提交结果 BO
*/
@Data
@Accessors(chain = true)
public class PayTransactionSubmitBO implements Serializable {
/**
* 支付交易拓展单编号
*/
private Integer id;
/**
* 调用三方平台的响应结果
*/
private String invokeResponse;
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.mall.pay.api.bo;
package cn.iocoder.mall.pay.api.bo.refund;
import lombok.Data;
import lombok.experimental.Accessors;

View File

@ -1,4 +1,4 @@
package cn.iocoder.mall.pay.api.bo;
package cn.iocoder.mall.pay.api.bo.refund;
import lombok.Data;
import lombok.experimental.Accessors;

View File

@ -1,4 +1,4 @@
package cn.iocoder.mall.pay.api.bo;
package cn.iocoder.mall.pay.api.bo.refund;
import lombok.Data;
import lombok.experimental.Accessors;

View File

@ -1,63 +1,48 @@
package cn.iocoder.mall.pay.api.bo;
package cn.iocoder.mall.pay.api.bo.transaction;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
/**
* 支付交易 BO
*/
@ApiModel("支付交易 BO")
@Data
@Accessors(chain = true)
public class PayTransactionBO implements Serializable {
/**
* 编号自增
*/
@ApiModelProperty(value = "交易编号", required = true, example = "POd4RC6a")
private Integer id;
/**
* 应用编号
*/
@ApiModelProperty(value = "应用编号", required = true, example = "POd4RC6a")
private String appId;
/**
* 发起交易的 IP
*/
@ApiModelProperty(value = "发起交易的 IP", required = true, example = "192.168.10.1")
private String createIp;
/**
* 业务线的订单编号
*
* 1. 使用 String 的原因是业务线可能使用 String 做为编号
* 2. 每个 appId orderId 唯一
*/
@ApiModelProperty(value = "订单号不能为空", required = true, example = "1024")
private String orderId;
/**
* 订单商品名
*/
@ApiModelProperty(value = "商品名", required = true, example = "芋道源码")
private String orderSubject;
/**
* 订单商品描述
*/
@ApiModelProperty(value = "订单商品描述", required = true, example = "绵啾啾的")
private String orderDescription;
/**
* 订单备注
*/
@ApiModelProperty(value = "订单商品备注", example = "绵啾啾的")
private String orderMemo;
/**
* 支付金额单位
*/
@ApiModelProperty(value = "支付金额,单位:分。", required = true, example = "10")
private Integer price;
/**
* 订单状态
*
* @see cn.iocoder.mall.pay.api.constant.PayTransactionStatusEnum
*/
@ApiModelProperty(value = "订单状态", required = true, example = "1", notes = "参见 PayTransactionStatusEnum 枚举")
private Integer status;
/**
* 交易过期时间
*/
@ApiModelProperty(value = "交易过期时间", required = true)
private Date expireTime;
/**
* 回调业务线完成时间
*/

View File

@ -1,4 +1,4 @@
package cn.iocoder.mall.pay.api.bo;
package cn.iocoder.mall.pay.api.bo.transaction;
import lombok.Data;
import lombok.experimental.Accessors;

View File

@ -0,0 +1,21 @@
package cn.iocoder.mall.pay.api.bo.transaction;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
@ApiModel("支付交易提交结果 BO")
@Data
@Accessors(chain = true)
public class PayTransactionSubmitBO implements Serializable {
@ApiModelProperty(value = "支付交易拓展单编号", required = true, example = "1")
private Integer id;
@ApiModelProperty(value = "调用三方平台的响应结果", required = true)
private String invokeResponse;
}

View File

@ -1,9 +1,13 @@
package cn.iocoder.mall.pay.api.constant;
import cn.iocoder.common.framework.core.IntArrayValuable;
import java.util.Arrays;
/**
* 支付通道
*/
public enum PayChannelEnum {
public enum PayChannelEnum implements IntArrayValuable {
WEIXIN_APP(100, "wx", "微信 App 支付"),
WEIXIN_PUB(101, "wxjs", "微信 JS API 支付"),
@ -13,6 +17,8 @@ public enum PayChannelEnum {
PINGXX(9999, "ping++", "ping++ 支付"),
;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PayChannelEnum::getId).toArray();
/**
* 渠道编号
*/
@ -44,4 +50,9 @@ public enum PayChannelEnum {
return name;
}
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -1,37 +0,0 @@
package cn.iocoder.mall.pay.api.dto;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* 支付交易提交 DTO
*/
@Data
@Accessors(chain = true)
public class PayTransactionSubmitDTO {
/**
* 应用编号
*/
@NotEmpty(message = "应用编号不能为空")
private String appId;
/**
* 发起交易的 IP
*/
@NotEmpty(message = "IP 不能为空")
private String createIp;
/**
* 业务线的订单编号
*/
@NotEmpty(message = "订单号不能为空")
private String orderId;
/**
* 支付渠道
*/
@NotNull(message = "支付渠道")
private Integer payChannel;
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.mall.pay.api.dto;
package cn.iocoder.mall.pay.api.dto.refund;
import lombok.Data;
import lombok.experimental.Accessors;

View File

@ -1,4 +1,4 @@
package cn.iocoder.mall.pay.api.dto;
package cn.iocoder.mall.pay.api.dto.refund;
import lombok.Data;
import lombok.experimental.Accessors;

View File

@ -1,5 +1,7 @@
package cn.iocoder.mall.pay.api.dto;
package cn.iocoder.mall.pay.api.dto.transaction;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import org.hibernate.validator.constraints.Length;
@ -10,54 +12,43 @@ import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;
/**
* 支付交易创建 DTO
*/
@ApiModel("支付交易创建 DTO")
@Data
@Accessors(chain = true)
public class PayTransactionCreateDTO implements Serializable {
/**
* 应用编号
*/
@ApiModelProperty(value = "应用编号", required = true, example = "POd4RC6a")
@NotEmpty(message = "应用编号不能为空")
private String appId;
/**
* 发起交易的 IP
*/
@ApiModelProperty(value = "发起交易的 IP", required = true, example = "192.168.10.1")
@NotEmpty(message = "IP 不能为空")
private String createIp;
/**
* 业务线的订单编号
*/
@ApiModelProperty(value = "订单号不能为空", required = true, example = "1024")
@NotEmpty(message = "订单号不能为空")
private String orderId;
/**
* 订单商品名
*/
@ApiModelProperty(value = "商品名", required = true, example = "芋道源码")
@NotEmpty(message = "商品名不能为空")
@Length(max = 32, message = "商品名不能超过32")
private String orderSubject;
/**
* 订单商品描述
*/
@ApiModelProperty(value = "订单商品描述", required = true, example = "绵啾啾的")
@NotEmpty(message = "商品描述不能为空")
@Length(max = 128, message = "商品描述长度不能超过128")
private String orderDescription;
/**
* 订单备注
*/
@Length(max = 256, message = "商品描述长度不能超过256")
@ApiModelProperty(value = "订单商品备注", example = "绵啾啾的")
@Length(max = 256, message = "商品备注长度不能超过256")
private String orderMemo;
/**
* 支付金额单位
*/
@ApiModelProperty(value = "支付金额,单位:分。", required = true, example = "10")
@NotNull(message = "金额不能为空")
@DecimalMin(value = "0", inclusive = false, message = "金额必须大于零")
private Integer price;
/**
* 交易过期时间
*/
@ApiModelProperty(value = "交易过期时间", required = true)
@NotNull(message = "交易过期时间不能为空")
private Date expireTime;

View File

@ -0,0 +1,28 @@
package cn.iocoder.mall.pay.api.dto.transaction;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@ApiModel("支付交易获得 DTO")
@Data
@Accessors(chain = true)
public class PayTransactionGetDTO {
@ApiModelProperty(value = "用户编号", required = true, example = "1", hidden = true) // hidden 的原因是Service DTO 自己传入无需暴露的 Controller API
@NotNull(message = "用户编号不能为空")
private Integer userId;
@ApiModelProperty(value = "应用编号", required = true, example = "POd4RC6a")
@NotEmpty(message = "应用编号不能为空")
private String appId;
@ApiModelProperty(value = "订单号不能为空", required = true, example = "1024")
@NotEmpty(message = "订单号不能为空")
private String orderId;
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.mall.pay.api.dto;
package cn.iocoder.mall.pay.api.dto.transaction;
import lombok.Data;
import lombok.experimental.Accessors;

View File

@ -0,0 +1,35 @@
package cn.iocoder.mall.pay.api.dto.transaction;
import cn.iocoder.common.framework.validator.InEnum;
import cn.iocoder.mall.pay.api.constant.PayChannelEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@ApiModel("支付交易提交 DTO")
@Data
@Accessors(chain = true)
public class PayTransactionSubmitDTO {
@ApiModelProperty(value = "应用编号", required = true, example = "POd4RC6a")
@NotEmpty(message = "应用编号不能为空")
private String appId;
@ApiModelProperty(value = "发起交易的 IP", required = true, example = "192.168.10.1", hidden = true) // hidden 的原因是Service DTO 自己传入无需暴露的 Controller API
@NotEmpty(message = "IP 不能为空")
private String createIp;
@ApiModelProperty(value = "订单号", required = true, example = "1024")
@NotEmpty(message = "订单号不能为空")
private String orderId;
@ApiModelProperty(value = "支付渠道", required = true, example = "1", notes = "参见 PayChannelEnum 枚举")
@InEnum(value = PayChannelEnum.class, message = "支付渠道必须是 {value}")
@NotNull(message = "支付渠道")
private Integer payChannel;
}

View File

@ -1,17 +1,20 @@
package cn.iocoder.mall.pay.biz.component;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ReferenceConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.rpc.service.GenericService;
import cn.iocoder.common.framework.util.StringUtil;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.Data;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ReferenceConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.rpc.service.GenericService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import java.util.List;
@Component
public class DubboReferencePool {
@ -44,7 +47,8 @@ public class DubboReferencePool {
private String dubboApplicationName;
private ReferenceMeta createGenericService(String notifyUrl) {
String[] notifyUrlParts = notifyUrl.split("#");
// 使用 # 号分隔格式为 服务名#方法名#版本号
List<String> notifyUrlParts = StringUtil.split(notifyUrl, "#");
// 创建 ApplicationConfig 对象
ApplicationConfig application = new ApplicationConfig();
application.setName(dubboApplicationName);
@ -55,14 +59,14 @@ public class DubboReferencePool {
application.setRegistry(registry);
// 创建 ReferenceConfig 对象
ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
reference.setInterface(notifyUrlParts[0]); // 弱类型接口名
reference.setInterface(notifyUrlParts.get(0)); // 弱类型接口名
reference.setGeneric(true); // 声明为泛化接口
reference.setApplication(application);
// reference.setVersion("*"); // TODO 芋艿后面要优化下
reference.setVersion(notifyUrlParts.size() > 2 ? notifyUrlParts.get(2) : "1.0.0"); // 如果未配置服务的版本号则默认使用 1.0.0
// 获得 GenericService 对象
GenericService genericService = reference.get();
// 构建最终的 ReferenceMeta 对象
return new ReferenceMeta(reference, genericService, notifyUrlParts[1]);
return new ReferenceMeta(reference, genericService, notifyUrlParts.get(1));
}
public ReferenceMeta getReferenceMeta(String notifyUrl) {

View File

@ -1,7 +1,7 @@
package cn.iocoder.mall.pay.biz.convert;
import cn.iocoder.mall.pay.api.bo.PayRefundBO;
import cn.iocoder.mall.pay.api.dto.PayRefundSubmitDTO;
import cn.iocoder.mall.pay.api.bo.refund.PayRefundBO;
import cn.iocoder.mall.pay.api.dto.refund.PayRefundSubmitDTO;
import cn.iocoder.mall.pay.biz.dataobject.PayRefundDO;
import org.mapstruct.Mapper;
import org.mapstruct.Mappings;

View File

@ -1,8 +1,8 @@
package cn.iocoder.mall.pay.biz.convert;
import cn.iocoder.mall.pay.api.bo.PayTransactionBO;
import cn.iocoder.mall.pay.api.dto.PayTransactionCreateDTO;
import cn.iocoder.mall.pay.api.dto.PayTransactionSubmitDTO;
import cn.iocoder.mall.pay.api.bo.transaction.PayTransactionBO;
import cn.iocoder.mall.pay.api.dto.transaction.PayTransactionCreateDTO;
import cn.iocoder.mall.pay.api.dto.transaction.PayTransactionSubmitDTO;
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionDO;
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionExtensionDO;
import org.mapstruct.Mapper;

View File

@ -18,6 +18,7 @@ import java.util.Date;
topic = PayRefundSuccessMessage.TOPIC,
consumerGroup = "pay-consumer-group-" + PayRefundSuccessMessage.TOPIC
)
@Deprecated // 艿艿突然发现业务方实际无需回调参考了 https://help.youzan.com/displaylist/detail_4_998 的文章业务方只要记录下退款单号进行关联即可
public class PayRefundSuccessConsumer extends AbstractPayNotifySuccessConsumer<PayRefundSuccessMessage>
implements RocketMQListener<PayRefundSuccessMessage> {

View File

@ -2,7 +2,6 @@ package cn.iocoder.mall.pay.biz.service;
import cn.iocoder.common.framework.constant.CommonStatusEnum;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.pay.api.constant.PayErrorCodeEnum;
import cn.iocoder.mall.pay.biz.dao.PayAppMapper;
import cn.iocoder.mall.pay.biz.dataobject.PayAppDO;
@ -15,17 +14,17 @@ public class PayAppServiceImpl {
@Autowired
private PayAppMapper payAppMapper;
public CommonResult<PayAppDO> validPayApp(String appId) {
public PayAppDO validPayApp(String appId) {
PayAppDO payAppDO = payAppMapper.selectById(appId);
// 校验是否存在
if (payAppDO == null) {
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_APP_NOT_FOUND.getCode());
throw ServiceExceptionUtil.exception(PayErrorCodeEnum.PAY_APP_NOT_FOUND.getCode());
}
// 校验是否禁用
if (CommonStatusEnum.DISABLE.getValue().equals(payAppDO.getStatus())) {
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_APP_IS_DISABLE.getCode());
throw ServiceExceptionUtil.exception(PayErrorCodeEnum.PAY_APP_IS_DISABLE.getCode());
}
return CommonResult.success(payAppDO);
return payAppDO;
}
}
}

View File

@ -27,6 +27,7 @@ public class PayNotifyServiceImpl {
@Resource
private RocketMQTemplate rocketMQTemplate;
@Deprecated // 参见 PayRefundSuccessConsumer 类的说明
public void addRefundNotifyTask(PayRefundDO refund) {
PayNotifyTaskDO payTransactionNotifyTask = this.createBasePayNotifyTaskDO(refund.getAppId(), refund.getNotifyUrl())
.setType(PayNotifyType.REFUND.getValue());

View File

@ -5,13 +5,13 @@ import cn.iocoder.common.framework.util.MathUtil;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.pay.api.PayRefundService;
import cn.iocoder.mall.pay.api.bo.PayRefundPageBO;
import cn.iocoder.mall.pay.api.bo.PayRefundSubmitBO;
import cn.iocoder.mall.pay.api.bo.refund.PayRefundPageBO;
import cn.iocoder.mall.pay.api.bo.refund.PayRefundSubmitBO;
import cn.iocoder.mall.pay.api.constant.PayErrorCodeEnum;
import cn.iocoder.mall.pay.api.constant.PayRefundStatus;
import cn.iocoder.mall.pay.api.constant.PayTransactionStatusEnum;
import cn.iocoder.mall.pay.api.dto.PayRefundPageDTO;
import cn.iocoder.mall.pay.api.dto.PayRefundSubmitDTO;
import cn.iocoder.mall.pay.api.dto.refund.PayRefundPageDTO;
import cn.iocoder.mall.pay.api.dto.refund.PayRefundSubmitDTO;
import cn.iocoder.mall.pay.biz.client.AbstractPaySDK;
import cn.iocoder.mall.pay.biz.client.PaySDKFactory;
import cn.iocoder.mall.pay.biz.client.RefundSuccessBO;
@ -51,13 +51,9 @@ public class PayRefundServiceImpl implements PayRefundService {
private RocketMQTemplate rocketMQTemplate;
@Override
@SuppressWarnings("Duplicates")
public CommonResult<PayRefundSubmitBO> submitRefund(PayRefundSubmitDTO payRefundSubmitDTO) {
// 校验 App 是否有效
CommonResult<PayAppDO> appResult = payAppService.validPayApp(payRefundSubmitDTO.getAppId());
if (appResult.isError()) {
return CommonResult.error(appResult);
}
PayAppDO payAppDO = payAppService.validPayApp(payRefundSubmitDTO.getAppId());
// 获得 PayTransactionDO 并校验其是否存在
PayTransactionDO payTransaction = payTransactionService.getTransaction(payRefundSubmitDTO.getAppId(), payRefundSubmitDTO.getOrderId());
if (payTransaction == null) { // 是否存在
@ -82,7 +78,7 @@ public class PayRefundServiceImpl implements PayRefundService {
.setTransactionId(payTransaction.getId())
.setRefundCode(generateTransactionCode()) // TODO 芋艿后续调整
.setStatus(PayRefundStatus.WAITING.getValue())
.setNotifyUrl(appResult.getData().getRefundNotifyUrl())
.setNotifyUrl(payAppDO.getRefundNotifyUrl())
.setRefundChannel(payTransaction.getPayChannel());
payRefundDO.setCreateTime(new Date());
payRefundMapper.insert(payRefundDO);

View File

@ -5,14 +5,15 @@ import cn.iocoder.common.framework.util.MathUtil;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.pay.api.PayTransactionService;
import cn.iocoder.mall.pay.api.bo.PayTransactionBO;
import cn.iocoder.mall.pay.api.bo.PayTransactionPageBO;
import cn.iocoder.mall.pay.api.bo.PayTransactionSubmitBO;
import cn.iocoder.mall.pay.api.bo.transaction.PayTransactionBO;
import cn.iocoder.mall.pay.api.bo.transaction.PayTransactionPageBO;
import cn.iocoder.mall.pay.api.bo.transaction.PayTransactionSubmitBO;
import cn.iocoder.mall.pay.api.constant.PayErrorCodeEnum;
import cn.iocoder.mall.pay.api.constant.PayTransactionStatusEnum;
import cn.iocoder.mall.pay.api.dto.PayTransactionCreateDTO;
import cn.iocoder.mall.pay.api.dto.PayTransactionPageDTO;
import cn.iocoder.mall.pay.api.dto.PayTransactionSubmitDTO;
import cn.iocoder.mall.pay.api.dto.transaction.PayTransactionCreateDTO;
import cn.iocoder.mall.pay.api.dto.transaction.PayTransactionGetDTO;
import cn.iocoder.mall.pay.api.dto.transaction.PayTransactionPageDTO;
import cn.iocoder.mall.pay.api.dto.transaction.PayTransactionSubmitDTO;
import cn.iocoder.mall.pay.biz.client.AbstractPaySDK;
import cn.iocoder.mall.pay.biz.client.PaySDKFactory;
import cn.iocoder.mall.pay.biz.client.TransactionSuccessBO;
@ -68,23 +69,21 @@ public class PayTransactionServiceImpl implements PayTransactionService {
}
@Override
public CommonResult<PayTransactionBO> getTransaction(Integer userId, String appId, String orderId) {
PayTransactionDO payTransaction = payTransactionMapper.selectByAppIdAndOrderId(appId, orderId);
public PayTransactionBO getTransaction(PayTransactionGetDTO payTransactionGetDTO) {
PayTransactionDO payTransaction = payTransactionMapper.selectByAppIdAndOrderId(payTransactionGetDTO.getAppId(),
payTransactionGetDTO.getOrderId());
if (payTransaction == null) {
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_NOT_FOUND.getCode());
throw ServiceExceptionUtil.exception(PayErrorCodeEnum.PAY_TRANSACTION_NOT_FOUND.getCode());
}
// TODO 芋艿 userId 的校验
return CommonResult.success(PayTransactionConvert.INSTANCE.convert(payTransaction));
return PayTransactionConvert.INSTANCE.convert(payTransaction);
}
@Override
@SuppressWarnings("Duplicates")
public CommonResult<PayTransactionBO> createTransaction(PayTransactionCreateDTO payTransactionCreateDTO) {
public PayTransactionBO createTransaction(PayTransactionCreateDTO payTransactionCreateDTO) {
// 校验 App
CommonResult<PayAppDO> appResult = payAppService.validPayApp(payTransactionCreateDTO.getAppId());
if (appResult.isError()) {
return CommonResult.error(appResult);
}
PayAppDO payAppDO = payAppService.validPayApp(payTransactionCreateDTO.getAppId());
// 插入 PayTransactionDO
PayTransactionDO payTransaction = payTransactionMapper.selectByAppIdAndOrderId(
payTransactionCreateDTO.getAppId(), payTransactionCreateDTO.getOrderId());
@ -95,31 +94,28 @@ public class PayTransactionServiceImpl implements PayTransactionService {
} else {
payTransaction = PayTransactionConvert.INSTANCE.convert(payTransactionCreateDTO);
payTransaction.setStatus(PayTransactionStatusEnum.WAITING.getValue())
.setNotifyUrl(appResult.getData().getNotifyUrl());
.setNotifyUrl(payAppDO.getNotifyUrl());
payTransaction.setCreateTime(new Date());
payTransactionMapper.insert(payTransaction);
}
// 返回成功
return CommonResult.success(PayTransactionConvert.INSTANCE.convert(payTransaction));
return PayTransactionConvert.INSTANCE.convert(payTransaction);
}
@Override
@SuppressWarnings("Duplicates")
public CommonResult<PayTransactionSubmitBO> submitTransaction(PayTransactionSubmitDTO payTransactionSubmitDTO) {
public PayTransactionSubmitBO submitTransaction(PayTransactionSubmitDTO payTransactionSubmitDTO) {
// TODO 校验支付渠道是否有效
// 校验 App 是否有效
CommonResult<PayAppDO> appResult = payAppService.validPayApp(payTransactionSubmitDTO.getAppId());
if (appResult.isError()) {
return CommonResult.error(appResult);
}
payAppService.validPayApp(payTransactionSubmitDTO.getAppId());
// 获得 PayTransactionDO 并校验其是否存在
PayTransactionDO payTransaction = payTransactionMapper.selectByAppIdAndOrderId(
payTransactionSubmitDTO.getAppId(), payTransactionSubmitDTO.getOrderId());
if (payTransaction == null) { // 是否存在
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_NOT_FOUND.getCode());
throw ServiceExceptionUtil.exception(PayErrorCodeEnum.PAY_TRANSACTION_NOT_FOUND.getCode());
}
if (!PayTransactionStatusEnum.WAITING.getValue().equals(payTransaction.getStatus())) { // 校验状态必须是待支付
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_STATUS_IS_NOT_WAITING.getCode());
throw ServiceExceptionUtil.exception(PayErrorCodeEnum.PAY_TRANSACTION_STATUS_IS_NOT_WAITING.getCode());
}
// 插入 PayTransactionExtensionDO
PayTransactionExtensionDO payTransactionExtensionDO = PayTransactionConvert.INSTANCE.convert(payTransactionSubmitDTO)
@ -131,33 +127,32 @@ public class PayTransactionServiceImpl implements PayTransactionService {
AbstractPaySDK paySDK = PaySDKFactory.getSDK(payTransactionSubmitDTO.getPayChannel());
CommonResult<String> invokeResult = paySDK.submitTransaction(payTransaction, payTransactionExtensionDO, null); // TODO 暂时传入 extra = null
if (invokeResult.isError()) {
return CommonResult.error(invokeResult);
throw ServiceExceptionUtil.exception(invokeResult.getCode(), invokeResult.getMessage());
}
// TODO 轮询三方接口是否已经支付的任务
// 返回成功
PayTransactionSubmitBO payTransactionSubmitBO = new PayTransactionSubmitBO()
.setId(payTransactionExtensionDO.getId()).setInvokeResponse(invokeResult.getData());
return CommonResult.success(payTransactionSubmitBO);
return new PayTransactionSubmitBO().setId(payTransactionExtensionDO.getId())
.setInvokeResponse(invokeResult.getData());
}
@Override
@Transactional
public CommonResult<Boolean> updateTransactionPaySuccess(Integer payChannel, String params) {
public Boolean updateTransactionPaySuccess(Integer payChannel, String params) {
// TODO 芋艿记录回调日志
// 解析传入的参数 TransactionSuccessBO 对象
AbstractPaySDK paySDK = PaySDKFactory.getSDK(payChannel);
CommonResult<TransactionSuccessBO> paySuccessResult = paySDK.parseTransactionSuccessParams(params);
if (paySuccessResult.isError()) {
return CommonResult.error(paySuccessResult);
throw ServiceExceptionUtil.exception(paySuccessResult.getCode(), paySuccessResult.getMessage());
}
// TODO 芋艿先最严格的校验即使调用方重复调用实际哪个订单已经被重复回调的支付也返回 false 也没问题因为实际已经回调成功了
// 1.1 查询 PayTransactionExtensionDO
PayTransactionExtensionDO extension = payTransactionExtensionMapper.selectByTransactionCode(paySuccessResult.getData().getTransactionCode());
if (extension == null) {
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_EXTENSION_NOT_FOUND.getCode());
throw ServiceExceptionUtil.exception(PayErrorCodeEnum.PAY_TRANSACTION_EXTENSION_NOT_FOUND.getCode());
}
if (!PayTransactionStatusEnum.WAITING.getValue().equals(extension.getStatus())) { // 校验状态必须是待支付
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_EXTENSION_STATUS_IS_NOT_WAITING.getCode());
throw ServiceExceptionUtil.exception(PayErrorCodeEnum.PAY_TRANSACTION_EXTENSION_STATUS_IS_NOT_WAITING.getCode());
}
// 1.2 更新 PayTransactionExtensionDO
PayTransactionExtensionDO updatePayTransactionExtension = new PayTransactionExtensionDO()
@ -172,7 +167,7 @@ public class PayTransactionServiceImpl implements PayTransactionService {
// 2.1 判断 PayTransactionDO 是否处于待支付
PayTransactionDO transaction = payTransactionMapper.selectById(extension.getTransactionId());
if (transaction == null) {
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_NOT_FOUND.getCode());
throw ServiceExceptionUtil.exception(PayErrorCodeEnum.PAY_TRANSACTION_NOT_FOUND.getCode());
}
if (!PayTransactionStatusEnum.WAITING.getValue().equals(transaction.getStatus())) { // 校验状态必须是待支付
throw ServiceExceptionUtil.exception(PayErrorCodeEnum.PAY_TRANSACTION_STATUS_IS_NOT_WAITING.getCode());
@ -191,10 +186,10 @@ public class PayTransactionServiceImpl implements PayTransactionService {
throw ServiceExceptionUtil.exception(PayErrorCodeEnum.PAY_TRANSACTION_STATUS_IS_NOT_WAITING.getCode());
}
logger.info("[updateTransactionPaySuccess][PayTransactionDO({}) 更新为已支付]", transaction.getId());
// 3 新增 PayNotifyTaskDO
payNotifyService.addTransactionNotifyTask(transaction, extension);
// 3 新增 PayNotifyTaskDO 注释原因参见 PayRefundSuccessConsumer
// payNotifyService.addTransactionNotifyTask(transaction, extension);
// 返回结果
return CommonResult.success(true);
return true;
}
@Override

View File

@ -1,7 +1,7 @@
package cn.iocoder.mall.pay.biz.service;
import cn.iocoder.mall.pay.api.PayRefundService;
import cn.iocoder.mall.pay.api.dto.PayRefundSubmitDTO;
import cn.iocoder.mall.pay.api.dto.refund.PayRefundSubmitDTO;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;

View File

@ -1,10 +1,6 @@
package cn.iocoder.mall.pay.biz.service;
import cn.iocoder.mall.pay.api.dto.PayRefundSubmitDTO;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

View File

@ -5,11 +5,10 @@ import cn.iocoder.mall.product.api.ProductCategoryService;
import cn.iocoder.mall.product.api.bo.ProductCategoryBO;
import cn.iocoder.mall.product.application.convert.ProductCategoryConvert;
import cn.iocoder.mall.product.application.vo.users.UsersProductCategoryVO;
import cn.iocoder.mall.user.sdk.annotation.PermitAll;
import org.apache.dubbo.config.annotation.Reference;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@ -30,7 +29,6 @@ public class UsersProductCategoryController {
@GetMapping("/list")
@ApiOperation("获得指定编号下的子分类的数组")
@ApiImplicitParam(name = "pid", value = "指定分类编号", required = true, example = "0")
@PermitAll
public CommonResult<List<UsersProductCategoryVO>> list(@RequestParam("pid") Integer pid) {
List<ProductCategoryBO> result = productCategoryService.getListByPid(pid);
return CommonResult.success(ProductCategoryConvert.Users.INSTANCE.convertToVO(result));

View File

@ -7,7 +7,6 @@ import cn.iocoder.mall.product.api.dto.ProductSpuPageDTO;
import cn.iocoder.mall.product.application.convert.ProductSpuConvert;
import cn.iocoder.mall.product.application.vo.users.UsersProductSpuDetailVO;
import cn.iocoder.mall.product.application.vo.users.UsersProductSpuPageVO;
import cn.iocoder.mall.user.sdk.annotation.PermitAll;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
@ -31,7 +30,6 @@ public class UsersProductSpuController {
@GetMapping("/info")
@ApiOperation("商品 SPU 明细")
@ApiImplicitParam(name = "id", value = "SPU 编号", required = true, example = "100")
@PermitAll
public CommonResult<UsersProductSpuDetailVO> info(@RequestParam("id") Integer id) {
return success(ProductSpuConvert.INSTANCE.convert4(productSpuService.getProductSpuDetail(id)));
}
@ -43,7 +41,6 @@ public class UsersProductSpuController {
@ApiImplicitParam(name = "pageNo", value = "页码,从 1 开始", example = "1"),
@ApiImplicitParam(name = "pageSize", value = "每页条数", required = true, example = "10"),
})
@PermitAll
@Deprecated // 使用商品搜索接口
public CommonResult<UsersProductSpuPageVO> page(@RequestParam(value = "cid", required = false) Integer cid,
@RequestParam(value = "pageNo", defaultValue = "0") Integer pageNo,

View File

@ -6,7 +6,6 @@ import cn.iocoder.mall.promotion.api.BannerService;
import cn.iocoder.mall.promotion.api.bo.BannerBO;
import cn.iocoder.mall.promotion.application.convert.BannerConvert;
import cn.iocoder.mall.promotion.application.vo.users.UsersBannerVO;
import cn.iocoder.mall.user.sdk.annotation.PermitAll;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.dubbo.config.annotation.Reference;
@ -27,7 +26,6 @@ public class UsersBannerController {
@GetMapping("/list")
@ApiOperation("获得所有 Banner 列表")
@PermitAll
public CommonResult<List<UsersBannerVO>> list() {
// 查询 Banner 列表
List<BannerBO> result = bannerService.getBannerListByStatus(CommonStatusEnum.ENABLE.getValue());

View File

@ -11,7 +11,6 @@ import cn.iocoder.mall.promotion.application.convert.CouponTemplateConvert;
import cn.iocoder.mall.promotion.application.vo.users.UsersCouponCardPageVO;
import cn.iocoder.mall.promotion.application.vo.users.UsersCouponCardVO;
import cn.iocoder.mall.promotion.application.vo.users.UsersCouponTemplateVO;
import cn.iocoder.mall.user.sdk.annotation.PermitAll;
import cn.iocoder.mall.user.sdk.context.UserSecurityContextHolder;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
@ -35,7 +34,6 @@ public class UsersCouponController {
@GetMapping("/template/get")
@ApiOperation(value = "优惠劵(码)模板信息")
@ApiImplicitParam(name = "id", value = "优惠劵(码)模板编号", required = true, example = "10")
@PermitAll
public CommonResult<UsersCouponTemplateVO> templateGet(@RequestParam("id") Integer id) {
CouponTemplateBO template = couponService.getCouponTemplate(id);
return success(CouponTemplateConvert.USERS.convert2(template));

View File

@ -8,7 +8,6 @@ import cn.iocoder.mall.promotion.api.ProductRecommendService;
import cn.iocoder.mall.promotion.api.bo.ProductRecommendBO;
import cn.iocoder.mall.promotion.application.convert.ProductRecommendConvert;
import cn.iocoder.mall.promotion.application.vo.users.UsersProductRecommendVO;
import cn.iocoder.mall.user.sdk.annotation.PermitAll;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import io.swagger.annotations.Api;
@ -36,7 +35,6 @@ public class UsersProductRecommendController {
@GetMapping("/list")
@ApiOperation("获得所有 Banner 列表")
@PermitAll
public CommonResult<Map<Integer, Collection<UsersProductRecommendVO>>> list() {
// 查询商品推荐列表
List<ProductRecommendBO> productRecommends = productRecommendService.getProductRecommendList(

View File

@ -12,11 +12,6 @@ spring:
max-active: 5
max-wait: 10000
# mybatis
#mybatis:
# config-location: classpath:mybatis-config.xml
# mapper-locations: classpath:mapper/*.xml
# type-aliases-package: cn.iocoder.mall.promotion.biz.dataobject
# mybatis-plus
mybatis-plus:
configuration:
@ -26,7 +21,7 @@ mybatis-plus:
id-type: auto
mapper-locations: classpath*:mapper/*.xml
type-aliases-package: cn.iocoder.mall.promotion.biz.dataobject
config-location: classpath:mybatis-config.xml
# dubbo
dubbo:
application:

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- 使用驼峰命名法转换字段。 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<typeAliases>
<typeAlias alias="Integer" type="java.lang.Integer"/>
<typeAlias alias="Long" type="java.lang.Long"/>
<typeAlias alias="HashMap" type="java.util.HashMap"/>
<typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap"/>
<typeAlias alias="ArrayList" type="java.util.ArrayList"/>
<typeAlias alias="LinkedList" type="java.util.LinkedList"/>
</typeAliases>
</configuration>

BIN
sessionStore/root.data Normal file

Binary file not shown.

View File

@ -12,7 +12,17 @@ import java.util.Set;
@Accessors(chain = true)
public class AdminSecurityContext {
/**
* 管理员编号
*/
private Integer adminId;
/**
* 管理员账号
*/
private String username;
/**
* 拥有的角色编号
*/
private Set<Integer> roleIds;
}

View File

@ -0,0 +1,31 @@
package cn.iocoder.mall.admin.sdk.interceptor;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.mall.admin.api.constant.AdminConstants;
import cn.iocoder.mall.admin.api.constant.AdminErrorCodeEnum;
import cn.iocoder.mall.admin.sdk.context.AdminSecurityContextHolder;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Admin 演示拦截器
*
* 这是个比较奇怪的拦截器用于演示的管理员账号禁止使用 POST 请求从而实现即达到阉割版的演示的效果又避免影响了数据
*/
@Component
public class AdminDemoInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (AdminConstants.USERNAME_DEMO.equals(AdminSecurityContextHolder.getContext().getUsername())
&& request.getMethod().equalsIgnoreCase(HttpMethod.POST.toString())) {
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.ADMIN_DEMO_CAN_NOT_WRITE.getCode());
}
return true;
}
}

View File

@ -89,6 +89,7 @@ public class AdminSecurityInterceptor extends HandlerInterceptorAdapter {
context.setAdminId(authentication.getUserId());
MallUtil.setUserId(request, authentication.getUserId()); // 记录到 request 避免 AdminSecurityContext 后续清理掉后其它地方需要用到 userId
if (authorization != null) {
context.setUsername(authorization.getUsername());
context.setRoleIds(authorization.getRoleIds());
}
}
@ -113,8 +114,4 @@ public class AdminSecurityInterceptor extends HandlerInterceptorAdapter {
requiresPermissions != null ? Arrays.asList(requiresPermissions.value()) : null);
}
private void checkPermission() {
}
}

View File

@ -1,6 +1,6 @@
/**
* 提供 SDK 给其它服务使用如下功能
*
* 1. 通过 {@link cn.iocoder.mall.admin.sdk.interceptor.UserSecurityInterceptor} 拦截器实现需要登陆 URL 的鉴权
* 1. 通过 {@link cn.iocoder.mall.admin.sdk.interceptor.AdminSecurityInterceptor} 拦截器实现需要登陆 URL 的鉴权
*/
package cn.iocoder.mall.admin.sdk;
package cn.iocoder.mall.admin.sdk;

View File

@ -17,7 +17,7 @@ import java.util.Map;
public interface AdminService {
/**
* 用户认证认证成功后返回认证信息
* 管理员认证认证成功后返回认证信息
*
* 实际上就是用户名 + 密码登陆
*

View File

@ -4,6 +4,8 @@ import cn.iocoder.mall.admin.api.bo.oauth2.OAuth2AccessTokenBO;
import cn.iocoder.mall.admin.api.bo.oauth2.OAuth2AuthenticationBO;
import cn.iocoder.mall.admin.api.dto.oauth2.OAuth2CreateTokenDTO;
import cn.iocoder.mall.admin.api.dto.oauth2.OAuth2GetTokenDTO;
import cn.iocoder.mall.admin.api.dto.oauth2.OAuth2RefreshTokenDTO;
import cn.iocoder.mall.admin.api.dto.oauth2.OAuth2RemoveTokenByUserDTO;
/**
* Oauth2 服务接口
@ -18,7 +20,20 @@ public interface OAuth2Service {
*/
OAuth2AccessTokenBO createToken(OAuth2CreateTokenDTO oauth2CreateTokenDTO);
// TODO @see 刷新 token
/**
* 基于用户移除 accessToken
*
* @param oauth2RemoveTokenDTO accessToken 信息
*/
void removeToken(OAuth2RemoveTokenByUserDTO oauth2RemoveTokenDTO);
/**
* 刷新令牌获得新的 accessToken 信息
*
* @param oauth2RefreshTokenDTO refreshToken 信息
* @return accessToken 信息
*/
OAuth2AccessTokenBO refreshToken(OAuth2RefreshTokenDTO oauth2RefreshTokenDTO);
/**
* 通过 accessToken 获得身份信息

View File

@ -5,16 +5,20 @@ import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Set;
@ApiModel("管理员授权 BO")
@Data
@Accessors(chain = true)
public class AdminAuthorizationBO {
public class AdminAuthorizationBO implements Serializable {
@ApiModelProperty(value = "管理员编号", required = true, example = "1")
private Integer id;
@ApiModelProperty(value = "登陆账号", required = true, example = "1")
private String username;
@ApiModelProperty(value = "角色编号数组", required = true, example = "1")
private Set<Integer> roleIds;

View File

@ -5,10 +5,12 @@ import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
@ApiModel("OAUTH2 认证 BO")
@Data
@Accessors(chain = true)
public class OAuth2AuthenticationBO {
public class OAuth2AuthenticationBO implements Serializable {
@ApiModelProperty(value = "用户编号", required = true, example = "1")
private Integer userId;

View File

@ -1,27 +0,0 @@
package cn.iocoder.mall.admin.api.bo.oauth2;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Set;
/**
* OAUTH2 认证 BO
*/
@Data
@Accessors(chain = true)
public class OAuth2AuthenticationOldBO implements Serializable {
/**
* 管理员编号
*/
private Integer adminId;
/**
* 角色编号数组
*/
private Set<Integer> roleIds;
}

View File

@ -2,6 +2,14 @@ package cn.iocoder.mall.admin.api.constant;
public class AdminConstants {
/**
* 账号 - 管理员
*/
public static final String USERNAME_ADMIN = "admin";
}
/**
* 账号 - 演示账号
*/
public static final String USERNAME_DEMO = "yudaoyuanma";
}

View File

@ -17,8 +17,9 @@ public enum AdminErrorCodeEnum {
OAUTH2_INVALID_TOKEN_INVALID(1002001013, "访问令牌已失效"),
OAUTH2_NOT_LOGIN(1002001015, "账号未登陆"),
OAUTH2_INVALID_TOKEN_ERROR_USER_TYPE(1002001016, "访问令牌用户类型不正确"),
OAUTH_INVALID_TOKEN(1002001020, ""), // 预留
OAUTH_INVALID_REFRESH_TOKEN_NOT_FOUND(1002001017, "刷新令牌不存在"),
OAUTH_INVALID_REFRESH_TOKEN_EXPIRED(1002001018, "访问令牌已过期"),
OAUTH_INVALID_REFRESH_TOKEN_INVALID(1002001019, "刷新令牌已失效"),
// ========== 管理员模块 1002002000 ==========
ADMIN_USERNAME_NOT_REGISTERED(1002002000, "账号不存在"),
@ -30,6 +31,8 @@ public enum AdminErrorCodeEnum {
ADMIN_ADMIN_STATUS_CAN_NOT_UPDATE(1002002005, "管理员的账号状态不允许变更"),
ADMIN_ASSIGN_ROLE_NOT_EXISTS(1002002006, "分配员工角色时,有角色不存在"),
ADMIN_INVALID_PERMISSION(1002002007, "没有该操作权限"),
ADMIN_ADMIN_CAN_NOT_UPDATE(1002002008, "管理员的账号不允许变更"),
ADMIN_DEMO_CAN_NOT_WRITE(1002002009, "演示账号暂不允许写操作。欢迎加入我们的交流群http://t.cn/EKEr5WE"),
// ========== 资源模块 1002003000 ==========
RESOURCE_NAME_DUPLICATE(1002003000, "已经存在该名字的资源"),

View File

@ -8,11 +8,12 @@ import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
@ApiModel("OAuth2 创建 Token DTO")
@Data
@Accessors(chain = true)
public class OAuth2CreateTokenDTO {
public class OAuth2CreateTokenDTO implements Serializable {
@ApiModelProperty(value = "用户编号", required = true, example = "1")
@NotNull(message = "用户编号不能为空")

View File

@ -9,11 +9,12 @@ import lombok.experimental.Accessors;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
@ApiModel("OAuth2 身份验证 DTO")
@Data
@Accessors(chain = true)
public class OAuth2GetTokenDTO {
public class OAuth2GetTokenDTO implements Serializable {
@ApiModelProperty(value = "accessToken", required = true, example = "001e8f49b20e47f7b3a2de774497cd50")
@NotEmpty(message = "accessToken 不能为空")

View File

@ -0,0 +1,28 @@
package cn.iocoder.mall.admin.api.dto.oauth2;
import cn.iocoder.common.framework.validator.InEnum;
import cn.iocoder.mall.admin.api.constant.ResourceTypeEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
@ApiModel("OAuth2 刷新 Token DTO")
@Data
@Accessors(chain = true)
public class OAuth2RefreshTokenDTO implements Serializable {
@ApiModelProperty(value = "refreshToken", required = true, example = "001e8f49b20e47f7b3a2de774497cd50")
@NotEmpty(message = "refreshToken 不能为空")
private String refreshToken;
@ApiModelProperty(value = "用户类型", required = true, example = "1", notes = "参见 ResourceTypeEnum 枚举")
@NotNull(message = "用户类型不能为空")
@InEnum(value = ResourceTypeEnum.class, message = "用户类型必须是 {value}")
private Integer userType;
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.mall.admin.api.dto.oauth2;
import cn.iocoder.common.framework.validator.InEnum;
import cn.iocoder.mall.admin.api.constant.ResourceTypeEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
@ApiModel("OAuth2 移除 Token DTO")
@Data
@Accessors(chain = true)
public class OAuth2RemoveTokenByUserDTO implements Serializable {
@ApiModelProperty(value = "用户编号", required = true, example = "1")
@NotNull(message = "用户编号不能为空")
private Integer userId;
@ApiModelProperty(value = "用户类型", required = true, example = "1", notes = "参见 ResourceTypeEnum 枚举")
@NotNull(message = "用户类型不能为空")
@InEnum(value = ResourceTypeEnum.class, message = "用户类型必须是 {value}")
private Integer userType;
}

View File

@ -2,17 +2,12 @@ package cn.iocoder.mall.admin.convert;
import cn.iocoder.mall.admin.api.bo.oauth2.OAuth2AccessTokenBO;
import cn.iocoder.mall.admin.api.bo.oauth2.OAuth2AuthenticationBO;
import cn.iocoder.mall.admin.api.bo.oauth2.OAuth2AuthenticationOldBO;
import cn.iocoder.mall.admin.dataobject.AdminRoleDO;
import cn.iocoder.mall.admin.dataobject.OAuth2AccessTokenDO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.stream.Collectors;
@Mapper
public interface OAuth2Convert {
@ -28,15 +23,8 @@ public interface OAuth2Convert {
.setExpiresIn(Math.max((int) ((oauth2AccessTokenDO.getExpiresTime().getTime() - System.currentTimeMillis()) / 1000), 0));
}
@Mappings({})
OAuth2AuthenticationOldBO convertToAuthenticationOld(OAuth2AccessTokenDO oauth2AccessTokenDO);
@Mappings({})
OAuth2AuthenticationBO convertToAuthentication(OAuth2AccessTokenDO oauth2AccessTokenDO);
default OAuth2AuthenticationOldBO convertToAuthenticationOld(OAuth2AccessTokenDO oauth2AccessTokenDO, List<AdminRoleDO> adminRoleDOs) {
return convertToAuthenticationOld(oauth2AccessTokenDO)
.setRoleIds(adminRoleDOs.stream().map(AdminRoleDO::getRoleId).collect(Collectors.toSet()));
}
}

View File

@ -8,9 +8,16 @@ import org.springframework.stereotype.Repository;
@Repository
public interface OAuth2AccessTokenMapper extends BaseMapper<OAuth2AccessTokenDO> {
default int updateToInvalidByAdminId(Integer adminId) {
default int updateToInvalid(Integer userId, Integer userType) {
QueryWrapper<OAuth2AccessTokenDO> query = new QueryWrapper<OAuth2AccessTokenDO>()
.eq("admin_id", adminId).eq("valid", true);
.eq("user_id", userId).eq("user_type", userType)
.eq("valid", true);
return update(new OAuth2AccessTokenDO().setValid(false), query);
}
default int updateToInvalidByRefreshToken(String refreshToken) {
QueryWrapper<OAuth2AccessTokenDO> query = new QueryWrapper<OAuth2AccessTokenDO>()
.eq("refresh_token", refreshToken).eq("valid", true);
return update(new OAuth2AccessTokenDO().setValid(false), query);
}

View File

@ -8,9 +8,10 @@ import org.springframework.stereotype.Repository;
@Repository
public interface OAuth2RefreshTokenMapper extends BaseMapper<OAuth2RefreshTokenDO> {
default int updateToInvalidByAdminId(Integer adminId) {
default int updateToInvalid(Integer userId, Integer userType) {
QueryWrapper<OAuth2RefreshTokenDO> query = new QueryWrapper<OAuth2RefreshTokenDO>()
.eq("admin_id", adminId).eq("valid", true);
.eq("user_id", userId).eq("user_type", userType)
.eq("valid", true);
return update(new OAuth2RefreshTokenDO().setValid(false), query);
}

View File

@ -16,6 +16,7 @@ import cn.iocoder.mall.admin.api.constant.AdminConstants;
import cn.iocoder.mall.admin.api.constant.AdminErrorCodeEnum;
import cn.iocoder.mall.admin.api.dto.admin.*;
import cn.iocoder.mall.admin.api.dto.oauth2.OAuth2CreateTokenDTO;
import cn.iocoder.mall.admin.api.dto.oauth2.OAuth2RemoveTokenByUserDTO;
import cn.iocoder.mall.admin.convert.AdminConvert;
import cn.iocoder.mall.admin.dao.AdminMapper;
import cn.iocoder.mall.admin.dao.AdminRoleMapper;
@ -96,9 +97,14 @@ public class AdminServiceImpl implements AdminService {
@Override
public Boolean updateAdmin(Integer adminId, AdminUpdateDTO adminUpdateDTO) {
// 校验账号存在
if (adminMapper.selectById(adminUpdateDTO.getId()) == null) {
AdminDO admin = adminMapper.selectById(adminUpdateDTO.getId());
if (admin == null) {
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.ADMIN_USERNAME_NOT_REGISTERED.getCode());
}
if (AdminConstants.USERNAME_ADMIN.equals(admin.getUsername())
|| AdminConstants.USERNAME_DEMO.equals(admin.getUsername())) { // 特殊账号不允许编辑
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.ADMIN_ADMIN_CAN_NOT_UPDATE.getCode());
}
// 校验账号唯一
AdminDO usernameAdmin = adminMapper.selectByUsername(adminUpdateDTO.getUsername());
if (usernameAdmin != null && !usernameAdmin.getId().equals(adminUpdateDTO.getId())) {
@ -120,7 +126,8 @@ public class AdminServiceImpl implements AdminService {
if (admin == null) {
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.ADMIN_USERNAME_NOT_REGISTERED.getCode());
}
if (AdminConstants.USERNAME_ADMIN.equals(admin.getUsername())) {
if (AdminConstants.USERNAME_ADMIN.equals(admin.getUsername())
|| AdminConstants.USERNAME_DEMO.equals(admin.getUsername())) { // 特殊账号不允许编辑
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.ADMIN_ADMIN_STATUS_CAN_NOT_UPDATE.getCode());
}
// 如果状态相同则返回错误
@ -132,7 +139,7 @@ public class AdminServiceImpl implements AdminService {
adminMapper.updateById(updateAdmin);
// 如果是关闭管理员则标记 token 失效否则管理员还可以继续蹦跶
if (CommonStatusEnum.DISABLE.getValue().equals(adminUpdateStatusDTO.getStatus())) {
oauth2Service.removeToken(adminUpdateStatusDTO.getId());
oauth2Service.removeToken(new OAuth2RemoveTokenByUserDTO().setUserId(adminId).setUserType(UserTypeEnum.ADMIN.getValue()));
}
// TODO 插入操作日志
// 返回成功
@ -234,8 +241,11 @@ public class AdminServiceImpl implements AdminService {
}
}
}
// 获得用户
AdminDO admin = adminMapper.selectById(adminId);
// 返回成功
return new AdminAuthorizationBO().setId(adminId).setRoleIds(adminRoleIds);
return new AdminAuthorizationBO().setId(adminId).setUsername(admin.getUsername())
.setRoleIds(adminRoleIds);
}
private String encodePassword(String password) {

View File

@ -7,6 +7,8 @@ import cn.iocoder.mall.admin.api.bo.oauth2.OAuth2AuthenticationBO;
import cn.iocoder.mall.admin.api.constant.AdminErrorCodeEnum;
import cn.iocoder.mall.admin.api.dto.oauth2.OAuth2CreateTokenDTO;
import cn.iocoder.mall.admin.api.dto.oauth2.OAuth2GetTokenDTO;
import cn.iocoder.mall.admin.api.dto.oauth2.OAuth2RefreshTokenDTO;
import cn.iocoder.mall.admin.api.dto.oauth2.OAuth2RemoveTokenByUserDTO;
import cn.iocoder.mall.admin.convert.OAuth2Convert;
import cn.iocoder.mall.admin.dao.OAuth2AccessTokenMapper;
import cn.iocoder.mall.admin.dao.OAuth2RefreshTokenMapper;
@ -59,17 +61,37 @@ public class OAuth2ServiceImpl implements OAuth2Service {
return OAuth2Convert.INSTANCE.convertToAccessTokenWithExpiresIn(oauth2AccessTokenDO);
}
/**
* 移除管理员对应的 Token
*
* @param adminId 管理员编号
*/
@Override
@Transactional
public void removeToken(Integer adminId) {
public void removeToken(OAuth2RemoveTokenByUserDTO oauth2RemoveTokenByUserDTO) {
Integer userId = oauth2RemoveTokenByUserDTO.getUserId();
Integer userType = oauth2RemoveTokenByUserDTO.getUserType();
// 设置 access token 失效
oauth2AccessTokenMapper.updateToInvalidByAdminId(adminId);
oauth2AccessTokenMapper.updateToInvalid(userId, userType);
// 设置 refresh token 失效
oauth2RefreshTokenMapper.updateToInvalidByAdminId(adminId);
oauth2RefreshTokenMapper.updateToInvalid(userId, userType);
}
@Override
public OAuth2AccessTokenBO refreshToken(OAuth2RefreshTokenDTO oauth2RefreshTokenDTO) {
OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectById(oauth2RefreshTokenDTO.getRefreshToken());
// 校验刷新令牌是否合法
if (refreshTokenDO == null) { // 不存在
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.OAUTH_INVALID_REFRESH_TOKEN_NOT_FOUND.getCode());
}
if (refreshTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.OAUTH_INVALID_REFRESH_TOKEN_EXPIRED.getCode());
}
if (!refreshTokenDO.getValid()) { // 无效
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.OAUTH_INVALID_REFRESH_TOKEN_INVALID.getCode());
}
// 标记 refreshToken 对应的 accessToken 都不合法
oauth2AccessTokenMapper.updateToInvalidByRefreshToken(oauth2RefreshTokenDTO.getRefreshToken());
// 创建访问令牌
OAuth2AccessTokenDO oauth2AccessTokenDO = createOAuth2AccessToken(refreshTokenDO.getUserId(), refreshTokenDO.getUserType(),
refreshTokenDO.getId());
// 转换返回
return OAuth2Convert.INSTANCE.convertToAccessTokenWithExpiresIn(oauth2AccessTokenDO);
}
@Override

View File

@ -1,5 +0,0 @@
/**
* @author Sin
* @time 2019/5/16 10:52 AM
*/
package cn.iocoder.mall.admin;

View File

@ -1,17 +1,16 @@
package cn.iocoder.mall.user.application.controller.users;
import cn.iocoder.common.framework.constant.UserTypeEnum;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.admin.api.OAuth2Service;
import cn.iocoder.mall.admin.api.bo.oauth2.OAuth2AccessTokenBO;
import cn.iocoder.mall.admin.api.dto.oauth2.OAuth2RefreshTokenDTO;
import cn.iocoder.mall.user.api.MobileCodeService;
import cn.iocoder.mall.user.api.OAuth2Service;
import cn.iocoder.mall.user.api.UserService;
import cn.iocoder.mall.user.api.bo.OAuth2AccessTokenBO;
import cn.iocoder.mall.user.application.convert.PassportConvert;
import cn.iocoder.mall.user.application.vo.users.UsersAccessTokenVO;
import cn.iocoder.mall.user.application.vo.users.UsersMobileRegisterVO;
import cn.iocoder.mall.user.sdk.annotation.PermitAll;
import cn.iocoder.mall.user.api.bo.user.UserAuthenticationBO;
import cn.iocoder.mall.user.api.dto.user.UserAuthenticationByMobileCodeDTO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.web.bind.annotation.PostMapping;
@ -26,7 +25,7 @@ import static cn.iocoder.common.framework.vo.CommonResult.success;
@Api("Passport 模块")
public class PassportController {
@Reference(validation = "true", version = "${dubbo.provider.OAuth2Service.version}")
@Reference(validation = "true", version = "${dubbo.consumer.OAuth2Service.version}")
private OAuth2Service oauth2Service;
@Reference(validation = "true", version = "${dubbo.provider.UserService.version}")
private UserService userService;
@ -40,20 +39,12 @@ public class PassportController {
// return oauth2Service.getAccessToken(clientId, clientSecret, mobile, password);
// }
@PermitAll
@PostMapping("/mobile/register")
@ApiOperation(value = "手机号 + 验证码登陆(注册)", notes = "如果手机对应的账号不存在,则会自动创建")
@ApiImplicitParams({
@ApiImplicitParam(name = "mobile", value = "手机号", required = true, example = "15601691300"),
@ApiImplicitParam(name = "code", value = "验证码", required = true, example = "9999")
})
public CommonResult<UsersMobileRegisterVO> mobileRegister(@RequestParam("mobile") String mobile,
@RequestParam("code") String code) {
OAuth2AccessTokenBO result = oauth2Service.getAccessToken(mobile, code);
return success(PassportConvert.INSTANCE.convert(result));
public CommonResult<UserAuthenticationBO> mobileRegister(UserAuthenticationByMobileCodeDTO userAuthenticationByMobileCodeDTO) {
return success(userService.authenticationByMobileCode(userAuthenticationByMobileCodeDTO));
}
@PermitAll
@PostMapping("mobile/send_register_code")
@ApiOperation(value = "发送手机验证码")
@ApiImplicitParam(name = "mobile", value = "手机号", required = true, example = "15601691300")
@ -65,24 +56,21 @@ public class PassportController {
// TODO 芋艿改绑手机号
// TODO 功能qq 登陆
@PermitAll
@PostMapping("/qq/login")
public String qqLogin() {
return null;
}
// TODO 功能qq 绑定
@PermitAll
@PostMapping("/qq/bind")
public String qqBind() {
return null;
}
@PermitAll
@PostMapping("/refresh_token") // TODO 功能刷新 token
public CommonResult<UsersAccessTokenVO> refreshToken(@RequestParam("refreshToken") String refreshToken) {
OAuth2AccessTokenBO result = oauth2Service.refreshToken(refreshToken);
return success(PassportConvert.INSTANCE.convert2(result));
public CommonResult<OAuth2AccessTokenBO> refreshToken(@RequestParam("refreshToken") String refreshToken) {
return success(oauth2Service.refreshToken(new OAuth2RefreshTokenDTO().setRefreshToken(refreshToken)
.setUserType(UserTypeEnum.USER.getValue())));
}
// TODO 功能退出销毁 token

View File

@ -6,6 +6,7 @@ import cn.iocoder.mall.user.api.bo.UserBO;
import cn.iocoder.mall.user.api.dto.UserUpdateDTO;
import cn.iocoder.mall.user.application.convert.UserConvert;
import cn.iocoder.mall.user.application.vo.users.UsersUserVO;
import cn.iocoder.mall.user.sdk.annotation.RequiresLogin;
import cn.iocoder.mall.user.sdk.context.UserSecurityContextHolder;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@ -23,6 +24,7 @@ public class UserController {
private UserService userService;
@GetMapping("/info")
@RequiresLogin
@ApiOperation(value = "用户信息")
public CommonResult<UsersUserVO> info() {
UserBO userResult = userService.getUser(UserSecurityContextHolder.getContext().getUserId());
@ -30,6 +32,7 @@ public class UserController {
}
@PostMapping("/update_avatar")
@RequiresLogin
@ApiOperation(value = "更新头像")
public CommonResult<Boolean> updateAvatar(@RequestParam("avatar") String avatar) {
// 创建
@ -40,6 +43,7 @@ public class UserController {
}
@PostMapping("/update_nickname")
@RequiresLogin
@ApiOperation(value = "更新昵称")
public CommonResult<Boolean> updateNickname(@RequestParam("nickname") String nickname) {
// 创建

View File

@ -1,21 +0,0 @@
package cn.iocoder.mall.user.application.convert;
import cn.iocoder.mall.user.api.bo.OAuth2AccessTokenBO;
import cn.iocoder.mall.user.application.vo.users.UsersAccessTokenVO;
import cn.iocoder.mall.user.application.vo.users.UsersMobileRegisterVO;
import org.mapstruct.Mapper;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
@Mapper
public interface PassportConvert {
PassportConvert INSTANCE = Mappers.getMapper(PassportConvert.class);
@Mappings({})
UsersMobileRegisterVO convert(OAuth2AccessTokenBO oauth2AccessTokenBO);
@Mappings({})
UsersAccessTokenVO convert2(OAuth2AccessTokenBO result);
}

View File

@ -4,7 +4,9 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
@ -39,13 +41,15 @@ public class UserAddressAddPO implements Serializable {
@NotNull(message = "手机号为不能为空!")
@Size(min = 11, max = 11, message = "手机号为 11 位!")
private String mobile;
/**
* 收件详细地址
*/
@ApiModelProperty("收件详细地址")
@NotNull(message = "详细地址不能为空")
@Size(min = 10, max = 100, message = "地址在 10 ~ 100 字之间!")
@NotEmpty(message = "详细地址不能为空")
@Length(min = 10, max = 100, message = "地址在 10 ~ 100 字之间!")
private String address;
/**
* 收件详细地址
*/

View File

@ -1,14 +0,0 @@
package cn.iocoder.mall.user.sdk.annotation;
import java.lang.annotation.*;
/**
* URL 是否允许所有都可访问即用户不登陆就可以访问指定 URL
*
* 例如说注册接口用户是不需要登陆就可以访问的
*/
@Documented
@Target({ElementType.METHOD}) // ElementType.TYPE 暂时不支持类级别为了减少判断略微提升性能
@Retention(RetentionPolicy.RUNTIME)
public @interface PermitAll {
}

View File

@ -0,0 +1,16 @@
package cn.iocoder.mall.user.sdk.annotation;
import java.lang.annotation.*;
/**
* 要求用户登录注解通过将该注解添加到 Controller 会自动校验用户是否登陆
*
* 默认请求下用户访问的 API 接口无需登陆主要的考虑是
* 1. 需要用户登陆的接口本身会获取在线用户的编号如果不添加 @RequiresLogin 注解就会报错
* 2. 大多数情况下用户的 API 接口无需登陆
*/
@Documented
@Target({ElementType.METHOD}) // 暂时不支持 ElementType.TYPE 因为没有场景
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresLogin {
}

View File

@ -1,18 +1,18 @@
package cn.iocoder.mall.user.sdk.context;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* User Security 上下文
*/
@Data
@Accessors(chain = true)
public class UserSecurityContext {
private final Integer userId;
/**
* 用户编号
*/
private Integer userId;
public UserSecurityContext(Integer userId) {
this.userId = userId;
}
public Integer getUserId() {
return userId;
}
}
}

View File

@ -17,7 +17,7 @@ public class UserSecurityContextHolder {
UserSecurityContext ctx = SECURITY_CONTEXT.get();
// 为空时设置一个空的进去
if (ctx == null) {
ctx = new UserSecurityContext(null);
ctx = new UserSecurityContext();
SECURITY_CONTEXT.set(ctx);
}
return ctx;

View File

@ -1,12 +1,15 @@
package cn.iocoder.mall.user.sdk.interceptor;
import cn.iocoder.common.framework.constant.MallConstants;
import cn.iocoder.common.framework.constant.UserTypeEnum;
import cn.iocoder.common.framework.exception.ServiceException;
import cn.iocoder.common.framework.util.HttpUtil;
import cn.iocoder.common.framework.util.MallUtil;
import cn.iocoder.mall.user.api.OAuth2Service;
import cn.iocoder.mall.user.api.bo.OAuth2AuthenticationBO;
import cn.iocoder.mall.user.sdk.annotation.PermitAll;
import cn.iocoder.common.framework.util.StringUtil;
import cn.iocoder.mall.admin.api.OAuth2Service;
import cn.iocoder.mall.admin.api.bo.oauth2.OAuth2AuthenticationBO;
import cn.iocoder.mall.admin.api.constant.AdminErrorCodeEnum;
import cn.iocoder.mall.admin.api.dto.oauth2.OAuth2GetTokenDTO;
import cn.iocoder.mall.user.sdk.annotation.RequiresLogin;
import cn.iocoder.mall.user.sdk.context.UserSecurityContext;
import cn.iocoder.mall.user.sdk.context.UserSecurityContextHolder;
import org.apache.dubbo.config.annotation.Reference;
@ -18,40 +21,55 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 安全拦截器
* User 安全拦截器
*/
@Component
public class UserSecurityInterceptor extends HandlerInterceptorAdapter {
@Reference(validation = "true", version = "${dubbo.provider.OAuth2Service.version:1.0.0}")
@Reference(validation = "true", version = "${dubbo.consumer.OAuth2Service.version:1.0.0}")
private OAuth2Service oauth2Service;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 设置当前访问的用户类型注意即使未登陆我们也认为是用户
MallUtil.setUserType(request, MallConstants.USER_TYPE_USER);
// 校验访问令牌是否正确若正确返回授权信息
MallUtil.setUserType(request, UserTypeEnum.USER.getValue());
// 根据 accessToken 获得认证信息判断是谁
String accessToken = HttpUtil.obtainAuthorization(request);
OAuth2AuthenticationBO authentication = null;
if (accessToken != null) {
authentication = oauth2Service.checkToken(accessToken); // TODO 芋艿如果访问的地址无需登录这里也不用抛异常
// 添加到 SecurityContext
UserSecurityContext context = new UserSecurityContext(authentication.getUserId());
UserSecurityContextHolder.setContext(context);
// 同时也记录管理员编号到 AdminAccessLogInterceptor 因为
// AdminAccessLogInterceptor 需要在 AdminSecurityInterceptor 之前执行这样记录的访问日志才健全
// AdminSecurityInterceptor 执行后会移除 AdminSecurityContext 信息这就导致 AdminAccessLogInterceptor 无法获得管理员编号
// 因此这里需要进行记录
if (authentication.getUserId() != null) {
MallUtil.setUserId(request, authentication.getUserId());
ServiceException serviceException = null;
if (StringUtil.hasText(accessToken)) {
try {
authentication = oauth2Service.getAuthentication(new OAuth2GetTokenDTO().setAccessToken(accessToken)
.setUserType(UserTypeEnum.USER.getValue()));
} catch (ServiceException e) {
serviceException = e;
}
}
// 校验是否需要已授权
// 进行鉴权
HandlerMethod method = (HandlerMethod) handler;
boolean isPermitAll = method.hasMethodAnnotation(PermitAll.class);
if (!isPermitAll && authentication == null) {
throw new ServiceException(-1, "未授权"); // TODO 这里要改下
boolean requiresLogin = method.hasMethodAnnotation(RequiresLogin.class);
if (requiresLogin) { // 如果需要鉴权
if (serviceException != null) { // 认证失败抛出上面认证失败的 ServiceException 异常
throw serviceException;
}
if (authentication == null) { // 无认证信息抛出未登陆 ServiceException 异常
throw new ServiceException(AdminErrorCodeEnum.OAUTH2_NOT_LOGIN.getCode(), AdminErrorCodeEnum.OAUTH2_NOT_LOGIN.getMessage());
}
// TODO 芋艿后续拓展读取用户信息
}
// 鉴权完成初始化 AdminSecurityContext 上下文
UserSecurityContext context = new UserSecurityContext();
UserSecurityContextHolder.setContext(context);
if (authentication != null) {
context.setUserId(authentication.getUserId());
MallUtil.setUserId(request, authentication.getUserId()); // 记录到 request 避免 AdminSecurityContext 后续清理掉后其它地方需要用到 userId
// TODO 芋艿后续拓展读取用户信息
}
// 返回成功
return super.preHandle(request, response, handler);
}

View File

@ -17,6 +17,11 @@
<artifactId>common-framework</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>system-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 工具类相关 -->
<dependency>

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