提交 17614d42 authored 作者: wangtongzhou's avatar wangtongzhou

Merge remote-tracking branch 'origin/master'

......@@ -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)
## 其它演示
......
......@@ -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());
}
}
......@@ -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
......
......@@ -29,4 +29,6 @@
- 用户相关
- [x] 登陆
- [x] 注册
- [ ] 个人信息
- [x] 个人信息
- [ ] 手机改绑
- [ ] 微信登陆
......@@ -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] 数据字典
- [ ] 短信管理【开发中 @小范】
- [ ] 短信模板
- [ ] 发送日志
- [ ] 员工操作日志
......
......@@ -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({
......
......@@ -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: '登陆成功',
......
......@@ -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" />
......
<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>
\ No newline at end of file
</style>
......@@ -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")
......
......@@ -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")
......
......@@ -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();
......
......@@ -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;
......
......@@ -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;
......
......@@ -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);
......
......@@ -127,7 +127,7 @@ public interface OrderService {
CommonResult updateLogistics(OrderLogisticsUpdateDTO orderLogisticsDTO);
/**
* 删除订单
* 删除订单 // TODO FROM 芋艿 to 小范。删除订单,不要使用 deleted 字段,对于用户是删除,实际是隐藏。
*
* @param id
*/
......
......@@ -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
/**
* 编号
......
......@@ -15,10 +15,12 @@ public class CalcOrderPriceDTO {
@NotNull(message = "用户编号不能为空")
private Integer userId;
/**
* 优惠劵编号
*/
private Integer couponCardId;
@NotNull(message = "商品数组不能为空")
private List<Item> items;
......
/**
* 订单 api
*
* @author Sin
* @time 2019-03-16 13:15
*/
package cn.iocoder.mall.order.api;
\ No newline at end of file
......@@ -16,5 +16,5 @@ public class ServiceExceptionConfiguration {
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
}
}
\ No newline at end of file
} // TODO FROM 芋艿 to 小范,这里记得配置下,不然错误提示不出去呀。
}
/**
* 定义常量,以及枚举信息
*
* @author Sin
* @time 2019-03-20 21:16
*/
package cn.iocoder.mall.order.biz.constants;
\ No newline at end of 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;
......
......@@ -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;
......
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;
///
......
......@@ -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;
......
......@@ -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";
}
......
......@@ -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:
......
<?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>
\ No newline at end of file
......@@ -3,8 +3,6 @@ package cn.iocoder.mall.order.biz.mapper;
import cn.iocoder.mall.order.biz.OrderApplicationTest;
import cn.iocoder.mall.order.biz.dao.OrderMapper;
import cn.iocoder.mall.order.biz.dataobject.OrderDO;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
......
......@@ -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;
......
......@@ -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;
......
......@@ -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";
}
......
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;
......
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;
......
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 {
......
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);
......
package cn.iocoder.mall.pay.api.bo;
package cn.iocoder.mall.pay.api.bo.refund;
import lombok.Data;
import lombok.experimental.Accessors;
......
package cn.iocoder.mall.pay.api.bo;
package cn.iocoder.mall.pay.api.bo.refund;
import lombok.Data;
import lombok.experimental.Accessors;
......
package cn.iocoder.mall.pay.api.bo;
package cn.iocoder.mall.pay.api.bo.refund;
import lombok.Data;
import lombok.experimental.Accessors;
......
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;
/**
* 回调业务线完成时间
*/
......
package cn.iocoder.mall.pay.api.bo;
package cn.iocoder.mall.pay.api.bo.transaction;
import lombok.Data;
import lombok.experimental.Accessors;
......
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;
/**
* 支付交易提交结果 BO
*/
@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;
}
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;
}
}
package cn.iocoder.mall.pay.api.dto;
package cn.iocoder.mall.pay.api.dto.refund;
import lombok.Data;
import lombok.experimental.Accessors;
......
package cn.iocoder.mall.pay.api.dto;
package cn.iocoder.mall.pay.api.dto.refund;
import lombok.Data;
import lombok.experimental.Accessors;
......
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;
......
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;
}
package cn.iocoder.mall.pay.api.dto;
package cn.iocoder.mall.pay.api.dto.transaction;
import lombok.Data;
import lombok.experimental.Accessors;
......
package cn.iocoder.mall.pay.api.dto;
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;
/**
* 支付交易提交 DTO
*/
@ApiModel("支付交易提交 DTO")
@Data
@Accessors(chain = true)
public class PayTransactionSubmitDTO {
/**
* 应用编号
*/
@ApiModelProperty(value = "应用编号", required = true, example = "POd4RC6a")
@NotEmpty(message = "应用编号不能为空")
private String appId;
/**
* 发起交易的 IP
*/
@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;
......
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) {
......
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;
......
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;
......
......@@ -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> {
......
......@@ -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;
}
}
\ No newline at end of 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());
......
......@@ -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);
......
......@@ -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
......
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;
......
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;
......
......@@ -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));
......
......@@ -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,
......
......@@ -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());
......
......@@ -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));
......
......@@ -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(
......
......@@ -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:
......
<?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>
......@@ -2,7 +2,6 @@ package cn.iocoder.mall.admin.application;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication(scanBasePackages = {"cn.iocoder.mall.admin"})
......@@ -10,14 +9,7 @@ import org.springframework.scheduling.annotation.EnableAsync;
public class SystemApplication {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(SystemApplication.class, args);
// Object bean = ctx.getBean("test");
// System.out.println(AopUtils.getTargetClass(bean));
// System.out.println(bean);
// ConfigurableApplicationContext ctx =
// System.out.println(); // TODO 后面去掉,这里是临时的
SpringApplication.run(SystemApplication.class, args);
}
}
......@@ -7,14 +7,12 @@ import cn.iocoder.mall.admin.api.dto.datadict.DataDictAddDTO;
import cn.iocoder.mall.admin.api.dto.datadict.DataDictUpdateDTO;
import cn.iocoder.mall.admin.application.convert.DataDictConvert;
import cn.iocoder.mall.admin.application.vo.datadict.DataDictEnumVO;
import cn.iocoder.mall.admin.application.vo.datadict.DataDictVO;
import cn.iocoder.mall.admin.sdk.annotation.RequiresPermissions;
import cn.iocoder.mall.admin.sdk.context.AdminSecurityContextHolder;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.Multimaps;
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.*;
......@@ -22,6 +20,8 @@ import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
import static cn.iocoder.common.framework.vo.CommonResult.success;
@RestController
@RequestMapping("admins/data_dict")
@Api("数据字典模块")
......@@ -33,9 +33,8 @@ public class DataDictController {
@GetMapping("/list")
@ApiOperation(value = "数据字典全列表")
@RequiresPermissions("system.dataDict.list")
public CommonResult<List<DataDictVO>> list() {
CommonResult<List<DataDictBO>> result = dataDictService.selectDataDictList();
return DataDictConvert.INSTANCE.convert(result);
public CommonResult<List<DataDictBO>> list() {
return success( dataDictService.selectDataDictList());
}
@GetMapping("/tree")
......@@ -43,12 +42,9 @@ public class DataDictController {
@ApiOperation(value = "数据字典树结构", notes = "该接口返回的信息更为精简。一般用于前端缓存数据字典到本地。")
public CommonResult<List<DataDictEnumVO>> tree() {
// 查询数据字典全列表
CommonResult<List<DataDictBO>> result = dataDictService.selectDataDictList();
if (result.isError()) {
return CommonResult.error(result);
}
List<DataDictBO> dataDicts = dataDictService.selectDataDictList();
// 构建基于 enumValue 聚合的 Multimap
ImmutableListMultimap<String, DataDictBO> dataDictMap = Multimaps.index(result.getData(), DataDictBO::getEnumValue); // KEY 是 enumValue ,VALUE 是 DataDictBO 数组
ImmutableListMultimap<String, DataDictBO> dataDictMap = Multimaps.index(dataDicts, DataDictBO::getEnumValue); // KEY 是 enumValue ,VALUE 是 DataDictBO 数组
// 构建返回结果
List<DataDictEnumVO> dataDictEnumVOs = new ArrayList<>(dataDictMap.size());
dataDictMap.keys().forEach(enumValue -> {
......@@ -56,53 +52,21 @@ public class DataDictController {
.setValues(DataDictConvert.INSTANCE.convert2(dataDictMap.get(enumValue)));
dataDictEnumVOs.add(dataDictEnumVO);
});
return CommonResult.success(dataDictEnumVOs);
return success(dataDictEnumVOs);
}
@PostMapping("/add")
@RequiresPermissions("system.dataDict.add")
@ApiOperation(value = "创建数据字典")
@ApiImplicitParams({
@ApiImplicitParam(name = "enumValue", value = "大类枚举值", required = true, example = "gender"),
@ApiImplicitParam(name = "value", value = "小类数值", required = true, example = "1"),
@ApiImplicitParam(name = "displayName", value = "展示名", required = true, example = "男"),
@ApiImplicitParam(name = "sort", required = true, value = "排序值", defaultValue = "10"),
@ApiImplicitParam(name = "memo", value = "备注", example = "你猜我猜不猜"),
})
public CommonResult<DataDictVO> add(@RequestParam("enumValue") String enumValue,
@RequestParam("value") String value,
@RequestParam("displayName") String displayName,
@RequestParam("sort") Integer sort,
@RequestParam(value = "memo", required = false) String memo) {
// 创建 DataDictAddDTO 对象
DataDictAddDTO dataDictAddDTO = new DataDictAddDTO().setEnumValue(enumValue).setValue(value).setDisplayName(displayName)
.setSort(sort).setMemo(memo);
// 保存数据字典
CommonResult<DataDictBO> result = dataDictService.addDataDict(AdminSecurityContextHolder.getContext().getAdminId(), dataDictAddDTO);
// 返回结果
return DataDictConvert.INSTANCE.convert2(result);
public CommonResult<DataDictBO> add(DataDictAddDTO dataDictAddDTO) {
return success(dataDictService.addDataDict(AdminSecurityContextHolder.getContext().getAdminId(), dataDictAddDTO));
}
@PostMapping("/update")
@RequiresPermissions("system.dataDict.update")
@ApiOperation(value = "更新数据字典")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "100"),
@ApiImplicitParam(name = "value", value = "小类数值", required = true, example = "1"),
@ApiImplicitParam(name = "displayName", value = "展示名", required = true, example = "男"),
@ApiImplicitParam(name = "sort", required = true, value = "排序值", defaultValue = "10"),
@ApiImplicitParam(name = "memo", value = "备注", example = "你猜我猜不猜"),
})
public CommonResult<Boolean> update(@RequestParam("id") Integer id,
@RequestParam("value") String value,
@RequestParam("displayName") String displayName,
@RequestParam("sort") Integer sort,
@RequestParam(value = "memo", required = false) String memo) {
// 创建 DataDictAddDTO 对象
DataDictUpdateDTO dataDictUpdateDTO = new DataDictUpdateDTO().setId(id).setValue(value).setDisplayName(displayName)
.setSort(sort).setMemo(memo);
// 更新数据字典
return dataDictService.updateDataDict(AdminSecurityContextHolder.getContext().getAdminId(), dataDictUpdateDTO);
public CommonResult<Boolean> update(DataDictUpdateDTO dataDictUpdateDTO) {
return success(dataDictService.updateDataDict(AdminSecurityContextHolder.getContext().getAdminId(), dataDictUpdateDTO));
}
@PostMapping("/delete")
......@@ -110,7 +74,7 @@ public class DataDictController {
@ApiOperation(value = "删除数据字典")
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "100")
public CommonResult<Boolean> delete(@RequestParam("id") Integer id) {
return dataDictService.deleteDataDict(AdminSecurityContextHolder.getContext().getAdminId(), id);
return success(dataDictService.deleteDataDict(AdminSecurityContextHolder.getContext().getAdminId(), id));
}
}
package cn.iocoder.mall.admin.application.convert;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.admin.api.bo.datadict.DataDictBO;
import cn.iocoder.mall.admin.application.vo.datadict.DataDictVO;
import cn.iocoder.mall.admin.application.vo.datadict.DataDictValueVO;
import cn.iocoder.mall.admin.application.vo.datadict.DataDictEnumVO;
import org.mapstruct.Mapper;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
......@@ -16,18 +14,6 @@ public interface DataDictConvert {
DataDictConvert INSTANCE = Mappers.getMapper(DataDictConvert.class);
@Mappings({})
DataDictVO convert(DataDictBO dataDictBO);
@Mappings({})
List<DataDictVO> convert(List<DataDictBO> dataDictBOs);
@Mappings({})
CommonResult<List<DataDictVO>> convert(CommonResult<List<DataDictBO>> result);
@Mappings({})
CommonResult<DataDictVO> convert2(CommonResult<DataDictBO> result);
@Mappings({})
List<DataDictValueVO> convert2(List<DataDictBO> dataDictBOs);
List<DataDictEnumVO.Value> convert2(List<DataDictBO> dataDictBOs);
}
......@@ -14,7 +14,21 @@ public class DataDictEnumVO {
@ApiModelProperty(value = "大类枚举值", required = true, example = "gender")
private String enumValue;
@ApiModelProperty(value = "小类数值数组", required = true)
private List<DataDictValueVO> values;
private List<Value> values;
@ApiModel("数据字典枚举值 VO")
@Data
@Accessors(chain = true)
public static class Value {
@ApiModelProperty(value = "小类数值", required = true, example = "1")
private String value;
@ApiModelProperty(value = "展示名", required = true, example = "男")
private String displayName;
}
}
package cn.iocoder.mall.admin.application.vo.datadict;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
@ApiModel("数据字典 VO")
@Data
@Accessors(chain = true)
public class DataDictVO {
@ApiModelProperty(value = "编号", required = true, example = "1")
private Integer id;
@ApiModelProperty(value = "大类枚举值", required = true, example = "gender")
private String enumValue;
@ApiModelProperty(value = "小类数值", required = true, example = "1")
private String value;
@ApiModelProperty(value = "展示名", required = true, example = "男")
private String displayName;
@ApiModelProperty(value = "排序值", required = true, example = "10")
private Integer sort;
@ApiModelProperty(value = "备注", example = "你猜")
private String memo;
}
......@@ -12,7 +12,17 @@ import java.util.Set;
@Accessors(chain = true)
public class AdminSecurityContext {
/**
* 管理员编号
*/
private Integer adminId;
/**
* 管理员账号
*/
private String username;
/**
* 拥有的角色编号
*/
private Set<Integer> roleIds;
}
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;
}
}
......@@ -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() {
}
}
/**
* 提供 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;
\ No newline at end of file
package cn.iocoder.mall.admin.sdk;
......@@ -17,7 +17,7 @@ import java.util.Map;
public interface AdminService {
/**
* 用户认证。认证成功后,返回认证信息
* 管理员认证。认证成功后,返回认证信息
*
* 实际上,就是用户名 + 密码登陆
*
......
......@@ -10,13 +10,13 @@ import java.util.List;
public interface DataDictService {
CommonResult<List<DataDictBO>> selectDataDictList();
List<DataDictBO> selectDataDictList();
CommonResult<DataDictBO> addDataDict(Integer adminId, DataDictAddDTO dataDictAddDTO);
DataDictBO addDataDict(Integer adminId, DataDictAddDTO dataDictAddDTO);
CommonResult<Boolean> updateDataDict(Integer adminId, DataDictUpdateDTO dataDictUpdateDTO);
Boolean updateDataDict(Integer adminId, DataDictUpdateDTO dataDictUpdateDTO);
CommonResult<Boolean> deleteDataDict(Integer adminId, Integer dataDictId);
Boolean deleteDataDict(Integer adminId, Integer dataDictId);
/**
* 获取字典值 - 单个
......@@ -28,6 +28,7 @@ public interface DataDictService {
* @return
*/
CommonResult<DataDictBO> getDataDict(String dictKey, Object dictValue);
CommonResult<List<DataDictBO>> getDataDict(String dictKey);
/**
......
......@@ -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 获得身份信息
......
package cn.iocoder.mall.admin.api;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 短信平台
*
* @author Sin
* @time 2019/5/16 6:33 PM
*/
public interface SmsPlatform {
@Data
@Accessors(chain = true)
class Result {
/**
* 编号
*/
private String id;
/**
* 审核状态
*/
private Integer applyStatus;
/**
* 审核内容
*/
private String applyMessage;
}
/**
* 签名 - 创建
*
* @param sign
*/
Result createSign(String sign);
/**
* 签名 - 获取
*
* @param sign
*/
Result getSign(String sign);
/**
* 签名 - 更新
*
* @param oldSign
* @param sign
*/
Result updateSign(String oldSign, String sign);
/**
* 模板 - 创建
*
* @param template 模板内容
* @param tplType 1 为验证码类型,其他为 null
*/
Result createTemplate(String template, Integer tplType);
/**
* 模板 - 获取
*
* @param tplId
*/
Result getTemplate(String tplId);
/**
* 模板 - 更新
*
* @param tplId 选用的哪个签名
* @param template 模板内容
* @param tplType 1 为验证码类型,其他为 null
*/
Result updateTemplate(String tplId, String template, Integer tplType);
/**
* 模板 - 删除
*
* @param tplId
* @return
*/
Result deleteTemplate(String tplId);
}
package cn.iocoder.mall.admin.api;
import cn.iocoder.mall.admin.api.bo.sms.SmsSignBO;
import cn.iocoder.mall.admin.api.bo.sms.SmsTemplateBO;
/**
* 短信服务
*
* @author Sin
* @time 2019/5/16 9:54 AM
*/
public interface SmsService {
/**
* 签名 - 创建
*
* @param sign
*/
void createSign(String sign);
/**
* 签名 - 获取
*
* @param sign
*/
SmsSignBO getSign(String sign);
/**
* 签名 - 更新
*
* @param oldSign
* @param sign
*/
void updateSign(String oldSign, String sign);
/**
* 模板 - 创建
*
* @param smsSignId 选用的哪个签名
* @param template 模板内容
* @param tplType 1 为验证码类型,其他为 null
*/
void createTemplate(Integer smsSignId, String template, Integer tplType);
/**
* 模板 - 获取
*
* @param id
*/
SmsTemplateBO getTemplate(Integer id);
/**
* 模板 - 更新
*
* @param id 模板id
* @param template 模板内容
* @param tplType 1 为验证码类型,其他为 null
*/
void updateTemplate(Integer id, String template, Integer tplType);
/**
* 模板 - 删除
*
* @param id
*/
void deleteTemplate(Integer id);
}
......@@ -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;
......
......@@ -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;
......
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;
}
package cn.iocoder.mall.admin.api.bo.sms;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 短信签名
*
* @author Sin
* @time 2019/5/16 6:30 PM
*/
@Data
@Accessors(chain = true)
public class SmsSignBO {
/**
* 编号
*/
private Integer id;
/**
* 签名id 这个是第三方的
*/
private Integer signId;
/**
* 签名名称
*/
private String sign;
/**
* 审核状态
*
* - 1、审核中
* - 2、审核成功
* - 3、审核失败
*/
private Integer applyStatus;
/**
* 审核信息
*/
private String applyMessage;
}
package cn.iocoder.mall.admin.api.bo.sms;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 短信 template
*
* @author Sin
* @time 2019/5/16 7:41 PM
*/
@Data
@Accessors(chain = true)
public class SmsTemplateBO {
/**
* 编号
*/
private Integer id;
/**
* 模板编号 (第三方的)
*/
private Integer smsSignId;
/**
* 短信签名 id
*/
private String platformId;
/**
* 短信模板
*/
private String template;
/**
* 审核状态
*
* 1、审核中
* 2、审核成功
* 3、审核失败
*/
private Integer applyStatus;
/**
* 审核信息
*/
private String applyMessage;
}
......@@ -2,6 +2,14 @@ package cn.iocoder.mall.admin.api.constant;
public class AdminConstants {
/**
* 账号 - 管理员
*/
public static final String USERNAME_ADMIN = "admin";
}
\ No newline at end of file
/**
* 账号 - 演示账号
*/
public static final String USERNAME_DEMO = "yudaoyuanma";
}
......@@ -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, "已经存在该名字的资源"),
......@@ -47,6 +50,12 @@ public enum AdminErrorCodeEnum {
DATA_DICT_EXISTS(1002005000, "该数据字典已经存在"),
DATA_DICT_NOT_EXISTS(1002005001, "该数据字典不存在"),
// ========== 短信模板 1002006000 ==========
SMS_PLATFORM_FAIL(1002006000, "短信模板添加失败"),
SMS_SIGN_NOT_EXISTENT(1002006001, "短信签名不存在"),
SMS_SIGN_IS_EXISTENT(1002006002, "短信签名已存在"),
SMS_TEMPLATE_NOT_EXISTENT(1002006020, "短信签名不存在"),
SMS_TEMPLATE_IS_EXISTENT(1002006021, "短信签名不存在"),
;
private final int code;
......
package cn.iocoder.mall.admin.api.constant;
/**
* 短信审核状态
*
* @author Sin
* @time 2019/5/16 12:48 PM
*/
public enum SmsApplyStatusEnum {
CHECKING(1, "审核中"),
SUCCESS(2, "审核成功"),
FAIL(3, "审核失败"),
;
private final int code;
private final String message;
SmsApplyStatusEnum(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
package cn.iocoder.mall.admin.api.dto.datadict;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
......@@ -7,36 +9,28 @@ import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
/**
* 数据字典添加 DTO
*/
@ApiModel("数据字典添加 DTO")
@Data
@Accessors(chain = true)
public class DataDictAddDTO implements Serializable {
/**
* 大类枚举值
*/
@ApiModelProperty(value = "大类枚举值", required = true, example = "gender")
@NotEmpty(message = "大类枚举值不能为空")
private String enumValue;
/**
* 小类数值
*/
@ApiModelProperty(value = "小类数值", required = true, example = "1")
@NotEmpty(message = "小类数值不能为空")
private String value;
/**
* 展示名
*/
@ApiModelProperty(value = "展示名", required = true, example = "男")
@NotEmpty(message = "展示名不能为空")
private String displayName;
/**
* 排序值
*/
@ApiModelProperty(required = true, value = "排序值", example = "123")
@NotNull(message = "排序值不能为空")
private Integer sort;
/**
* 备注
*/
@ApiModelProperty(value = "备注", example = "你猜我猜不猜")
private String memo;
}
package cn.iocoder.mall.admin.api.dto.datadict;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
......@@ -14,29 +15,27 @@ import java.io.Serializable;
@Accessors(chain = true)
public class DataDictUpdateDTO implements Serializable {
/**
* 编号
*/
@NotNull(message = "编号不能为空")
@ApiModelProperty(value = "数据字典编号", required = true, example = "1")
@NotNull(message = "数据字典编号不能为空")
private Integer id;
/**
* 小类数值
*/
@ApiModelProperty(value = "大类枚举值", required = true, example = "gender")
@NotEmpty(message = "大类枚举值不能为空")
private String enumValue;
@ApiModelProperty(value = "小类数值", required = true, example = "1")
@NotEmpty(message = "小类数值不能为空")
private String value;
/**
* 展示名
*/
@ApiModelProperty(value = "展示名", required = true, example = "男")
@NotEmpty(message = "展示名不能为空")
private String displayName;
/**
* 排序值
*/
@ApiModelProperty(required = true, value = "排序值", example = "123")
@NotNull(message = "排序值不能为空")
private Integer sort;
/**
* 备注
*/
@ApiModelProperty(value = "备注", example = "你猜我猜不猜")
private String memo;
}
......@@ -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 = "用户编号不能为空")
......
......@@ -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 不能为空")
......
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;
}
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;
}
......@@ -67,7 +67,17 @@
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.yunpian.sdk</groupId>
<artifactId>yunpian-java-sdk</artifactId>
<version>1.2.7</version>
</dependency>
<!-- test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
<build>
......
......@@ -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()));
}
}
package cn.iocoder.mall.admin.convert;
import cn.iocoder.mall.admin.api.bo.sms.SmsSignBO;
import cn.iocoder.mall.admin.dataobject.SmsSignDO;
import org.mapstruct.Mapper;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
/**
* 短信 签名
*
* @author Sin
* @time 2019/5/16 6:31 PM
*/
@Mapper
public interface SmsSignConvert {
SmsSignConvert INSTANCE = Mappers.getMapper(SmsSignConvert.class);
@Mappings({})
SmsSignBO convert(SmsSignDO smsSignDO);
}
package cn.iocoder.mall.admin.convert;
import cn.iocoder.mall.admin.api.bo.sms.SmsTemplateBO;
import cn.iocoder.mall.admin.dataobject.SmsTemplateDO;
import org.mapstruct.Mapper;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
/**
* 短信 template
*
* @author Sin
* @time 2019/5/16 7:43 PM
*/
@Mapper
public interface SmsTemplateConvert {
SmsTemplateConvert INSTANCE = Mappers.getMapper(SmsTemplateConvert.class);
@Mappings({})
SmsTemplateBO convert(SmsTemplateDO smsTemplateDO);
}
package cn.iocoder.mall.admin.dao;
import cn.iocoder.mall.admin.dataobject.DataDictDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
......@@ -8,9 +10,7 @@ import java.util.Collection;
import java.util.List;
@Repository
public interface DataDictMapper {
DataDictDO selectById(@Param("id") Integer id);
public interface DataDictMapper extends BaseMapper<DataDictDO> {
DataDictDO selectByEnumValueAndValue(
@Param("enumValue") String enumValue,
......@@ -26,10 +26,9 @@ public interface DataDictMapper {
@Param("enumValue") String enumValue
);
List<DataDictDO> selectList();
void insert(DataDictDO dataDict);
default List<DataDictDO> selectList() {
return selectList(new QueryWrapper<>());
}
int update(DataDictDO dataDict);
}
package cn.iocoder.mall.admin.dao;
import cn.iocoder.mall.admin.dataobject.OAuth2AccessTokenDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface OAuth2AccessTokenMapper extends BaseMapper<OAuth2AccessTokenDO> {
int updateToInvalidByAdminId(@Param("adminId") Integer adminId);
default int updateToInvalid(Integer userId, Integer userType) {
QueryWrapper<OAuth2AccessTokenDO> query = new QueryWrapper<OAuth2AccessTokenDO>()
.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);
}
}
package cn.iocoder.mall.admin.dao;
import cn.iocoder.mall.admin.dataobject.OAuth2RefreshTokenDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface OAuth2RefreshTokenMapper extends BaseMapper<OAuth2RefreshTokenDO> {
int updateToInvalidByAdminId(@Param("adminId") Integer adminId);
default int updateToInvalid(Integer userId, Integer userType) {
QueryWrapper<OAuth2RefreshTokenDO> query = new QueryWrapper<OAuth2RefreshTokenDO>()
.eq("user_id", userId).eq("user_type", userType)
.eq("valid", true);
return update(new OAuth2RefreshTokenDO().setValid(false), query);
}
}
......@@ -13,7 +13,7 @@ import java.util.Set;
@Repository
public interface ResourceMapper extends BaseMapper<ResourceDO> {
@Deprecated
// TODO 芋艿,后续改造。
List<ResourceDO> selectListByTypeAndRoleIds(@Param("type") Integer type,
@Param("roleIds") Set<Integer> roleIds);
......
package cn.iocoder.mall.admin.dao;
import cn.iocoder.mall.admin.dataobject.SmsSignDO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
/**
* 短信
*
* @author Sin
* @time 2019/5/16 6:18 PM
*/
@Repository
public interface SmsSignMapper extends BaseMapper<SmsSignDO> {
}
package cn.iocoder.mall.admin.dao;
import cn.iocoder.common.framework.dataobject.BaseDO;
import cn.iocoder.mall.admin.dataobject.SmsTemplateDO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
/**
* 短信 template
*
* @author Sin
* @time 2019/5/16 6:18 PM
*/
@Repository
public interface SmsTemplateMapper extends BaseMapper<SmsTemplateDO> {
}
package cn.iocoder.mall.admin.dataobject;
import cn.iocoder.common.framework.dataobject.DeletableDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
......@@ -13,6 +14,7 @@ import lombok.experimental.Accessors;
* value:1 男
* value:2 女
*/
@TableName("data_dict")
@Data
@Accessors(chain = true)
public class DataDictDO extends DeletableDO {
......
package cn.iocoder.mall.admin.dataobject;
import cn.iocoder.common.framework.dataobject.DeletableDO;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 短信签名
*
* 签名是短信发送前缀 如:【阿里云】、【小红书】
*
* @author Sin
* @time 2019/5/16 12:28 PM
*/
@Data
@Accessors(chain = true)
@TableName("sms_sign")
public class SmsSignDO extends DeletableDO {
/**
* 编号
*/
@TableId("id")
private Integer id;
/**
* 签名id 这个是第三方的
*/
private String platformId;
/**
* 签名名称
*/
private String sign;
/**
* 审核状态
*
* - 1、审核中
* - 2、审核成功
* - 3、审核失败
*/
private Integer applyStatus;
/**
* 审核信息
*/
private String applyMessage;
}
package cn.iocoder.mall.admin.dataobject;
import cn.iocoder.common.framework.dataobject.DeletableDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 短信 模板
*
* @author Sin
* @time 2019/5/16 12:31 PM
*/
@Data
@Accessors(chain = true)
@TableName("sms_template")
public class SmsTemplateDO extends DeletableDO {
/**
* 编号
*/
private Integer id;
/**
* 模板编号 (第三方的)
*/
private Integer smsSignId;
/**
* 短信签名 id
*/
private String platformId;
/**
* 短信模板
*/
private String template;
/**
* 审核状态
*
* 1、审核中
* 2、审核成功
* 3、审核失败
*/
private Integer applyStatus;
/**
* 审核信息
*/
private String applyMessage;
}
......@@ -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) {
......
......@@ -31,16 +31,16 @@ public class DataDictServiceImpl implements DataDictService {
private DataDictMapper dataDictMapper;
@Override
public CommonResult<List<DataDictBO>> selectDataDictList() {
public List<DataDictBO> selectDataDictList() {
List<DataDictDO> dataDicts = dataDictMapper.selectList();
return CommonResult.success(DataDictConvert.INSTANCE.convert(dataDicts));
return DataDictConvert.INSTANCE.convert(dataDicts);
}
@Override
public CommonResult<DataDictBO> addDataDict(Integer adminId, DataDictAddDTO dataDictAddDTO) {
public DataDictBO addDataDict(Integer adminId, DataDictAddDTO dataDictAddDTO) {
// 校验数据字典重复
if (dataDictMapper.selectByEnumValueAndValue(dataDictAddDTO.getEnumValue(), dataDictAddDTO.getValue()) != null) {
return ServiceExceptionUtil.error(AdminErrorCodeEnum.DATA_DICT_EXISTS.getCode());
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.DATA_DICT_EXISTS.getCode());
}
// 保存到数据库
DataDictDO dataDict = DataDictConvert.INSTANCE.convert(dataDictAddDTO);
......@@ -49,45 +49,43 @@ public class DataDictServiceImpl implements DataDictService {
dataDictMapper.insert(dataDict);
// TODO 插入操作日志
// 返回成功
return CommonResult.success(DataDictConvert.INSTANCE.convert(dataDict));
return DataDictConvert.INSTANCE.convert(dataDict);
}
@Override
public CommonResult<Boolean> updateDataDict(Integer adminId, DataDictUpdateDTO dataDictUpdateDTO) {
public Boolean updateDataDict(Integer adminId, DataDictUpdateDTO dataDictUpdateDTO) {
// 校验数据字典不存在
DataDictDO existsDataDict = dataDictMapper.selectById(dataDictUpdateDTO.getId());
if (existsDataDict == null) {
return ServiceExceptionUtil.error(AdminErrorCodeEnum.DATA_DICT_NOT_EXISTS.getCode());
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.DATA_DICT_NOT_EXISTS.getCode());
}
// 校验数据字典重复
DataDictDO duplicateDataDict = dataDictMapper.selectByEnumValueAndValue(existsDataDict.getEnumValue(), dataDictUpdateDTO.getValue());
if (duplicateDataDict != null && !duplicateDataDict.getId().equals(dataDictUpdateDTO.getId())) {
return ServiceExceptionUtil.error(AdminErrorCodeEnum.DATA_DICT_EXISTS.getCode());
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.DATA_DICT_EXISTS.getCode());
}
// 更新到数据库
DataDictDO updateDataDict = DataDictConvert.INSTANCE.convert(dataDictUpdateDTO);
dataDictMapper.update(updateDataDict);
dataDictMapper.updateById(updateDataDict);
// TODO 插入操作日志
// 返回成功
return CommonResult.success(true);
return true;
}
// 一般情况下,不要删除数据字典。
// 因为,业务数据正在使用该数据字典,删除后,可能有不可预知的问题。
@Override
public CommonResult<Boolean> deleteDataDict(Integer adminId, Integer dataDictId) {
public Boolean deleteDataDict(Integer adminId, Integer dataDictId) {
// 校验数据字典不存在
DataDictDO existsDataDict = dataDictMapper.selectById(dataDictId);
if (existsDataDict == null) {
return ServiceExceptionUtil.error(AdminErrorCodeEnum.DATA_DICT_NOT_EXISTS.getCode());
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.DATA_DICT_NOT_EXISTS.getCode());
}
// 更新到数据库
DataDictDO updateDataDict = new DataDictDO().setId(dataDictId);
updateDataDict.setDeleted(DeletedStatusEnum.DELETED_YES.getValue());
dataDictMapper.update(updateDataDict);
// 标记删除
dataDictMapper.deleteById(dataDictId);
// TODO 插入操作日志
// 返回成功
return CommonResult.success(true);
return true;
}
@Override
......@@ -106,7 +104,7 @@ public class DataDictServiceImpl implements DataDictService {
@Override
public CommonResult<List<DataDictBO>> getDataDictList(String dictKey, Collection<?> dictValueList) {
Set<String> convertDictValueList = dictValueList.stream().map(o -> String.valueOf(o)).collect(Collectors.toSet());
Set<String> convertDictValueList = dictValueList.stream().map(String::valueOf).collect(Collectors.toSet());
List<DataDictDO> dataDictDOList = dataDictMapper.selectByEnumValueAndValues(dictKey, convertDictValueList);
List<DataDictBO> dataDictBOList = DataDictConvert.INSTANCE.convert(dataDictDOList);
return CommonResult.success(dataDictBOList);
......
......@@ -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
......
package cn.iocoder.mall.admin.service;
import cn.iocoder.common.framework.constant.DeletedStatusEnum;
import cn.iocoder.common.framework.exception.ServiceException;
import cn.iocoder.mall.admin.api.SmsPlatform;
import cn.iocoder.mall.admin.api.SmsService;
import cn.iocoder.mall.admin.api.bo.sms.SmsSignBO;
import cn.iocoder.mall.admin.api.bo.sms.SmsTemplateBO;
import cn.iocoder.mall.admin.api.constant.AdminErrorCodeEnum;
import cn.iocoder.mall.admin.convert.SmsSignConvert;
import cn.iocoder.mall.admin.convert.SmsTemplateConvert;
import cn.iocoder.mall.admin.dao.SmsSignMapper;
import cn.iocoder.mall.admin.dao.SmsTemplateMapper;
import cn.iocoder.mall.admin.dataobject.SmsSignDO;
import cn.iocoder.mall.admin.dataobject.SmsTemplateDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
/**
* 短信
*
* @author Sin
* @time 2019/5/16 10:30 AM
*/
@Service
@org.apache.dubbo.config.annotation.Service(validation = "true", version = "${dubbo.provider.SmsService.version}")
public class SmsServiceImpl implements SmsService {
private static final String SMS_TEMPLATE = "【%s】%s";
@Autowired
private SmsSignMapper smsSignMapper;
@Autowired
private SmsTemplateMapper smsTemplateMapper;
@Autowired
@Qualifier("smsYunPianPlatform")
private SmsPlatform smsPlatform;
@Override
@Transactional
public void createSign(String sign) {
// 避免重复
SmsSignDO smsSignDO = smsSignMapper.selectOne(
new QueryWrapper<SmsSignDO>().eq("sign", sign));
if (smsSignDO != null) {
throw new ServiceException(AdminErrorCodeEnum.SMS_SIGN_IS_EXISTENT.getCode(),
AdminErrorCodeEnum.SMS_SIGN_IS_EXISTENT.getMessage());
}
// 创建平台 sign
SmsPlatform.Result result = smsPlatform.createSign(sign);
// 保存数据库
smsSignMapper.insert(
(SmsSignDO) new SmsSignDO()
.setSign(sign)
.setPlatformId(result.getId())
.setApplyStatus(result.getApplyStatus())
.setDeleted(DeletedStatusEnum.DELETED_NO.getValue())
.setUpdateTime(new Date())
.setCreateTime(new Date())
);
}
@Override
public SmsSignBO getSign(String sign) {
SmsSignDO smsSignDO = smsSignMapper.selectOne(
new QueryWrapper<SmsSignDO>().eq("sign", sign));
if (smsSignDO == null) {
throw new ServiceException(AdminErrorCodeEnum.SMS_SIGN_NOT_EXISTENT.getCode(),
AdminErrorCodeEnum.SMS_SIGN_NOT_EXISTENT.getMessage());
}
return SmsSignConvert.INSTANCE.convert(smsSignDO);
}
@Override
@Transactional
public void updateSign(String oldSign, String sign) {
// 避免重复
SmsSignDO smsSignDO = smsSignMapper.selectOne(
new QueryWrapper<SmsSignDO>().eq("sign", oldSign));
if (smsSignDO == null) {
throw new ServiceException(AdminErrorCodeEnum.SMS_SIGN_NOT_EXISTENT.getCode(),
AdminErrorCodeEnum.SMS_SIGN_NOT_EXISTENT.getMessage());
}
// 更新平台
SmsPlatform.Result result = smsPlatform.updateSign(oldSign, sign);
// 更新
smsSignMapper.updateById(
(SmsSignDO) new SmsSignDO()
.setId(smsSignDO.getId())
.setPlatformId(result.getId())
.setSign(sign)
.setApplyStatus(result.getApplyStatus())
.setUpdateTime(new Date())
);
}
@Override
@Transactional
public void createTemplate(Integer smsSignId, String template, Integer tplType) {
SmsSignDO smsSignDO = smsSignMapper.selectOne(
new QueryWrapper<SmsSignDO>().eq("id", smsSignId));
if (smsSignDO == null) {
throw new ServiceException(AdminErrorCodeEnum.SMS_SIGN_NOT_EXISTENT.getCode(),
AdminErrorCodeEnum.SMS_SIGN_NOT_EXISTENT.getMessage());
}
// 调用平台
SmsPlatform.Result result = smsPlatform
.createTemplate(String.format(SMS_TEMPLATE, smsSignDO.getSign(), template), tplType);
// 保存数据库
smsTemplateMapper.insert(
(SmsTemplateDO) new SmsTemplateDO()
.setId(null)
.setSmsSignId(smsSignId)
.setPlatformId(result.getId())
.setTemplate(template)
.setApplyStatus(result.getApplyStatus())
.setApplyMessage(result.getApplyMessage())
.setDeleted(DeletedStatusEnum.DELETED_NO.getValue())
.setCreateTime(new Date())
);
}
@Override
public SmsTemplateBO getTemplate(Integer id) {
SmsTemplateDO smsTemplateDO = smsTemplateMapper.selectOne(
new QueryWrapper<SmsTemplateDO>().eq("id", id));
if (smsTemplateDO == null) {
throw new ServiceException(AdminErrorCodeEnum.SMS_TEMPLATE_NOT_EXISTENT.getCode(),
AdminErrorCodeEnum.SMS_TEMPLATE_NOT_EXISTENT.getMessage());
}
return SmsTemplateConvert.INSTANCE.convert(smsTemplateDO);
}
@Override
@Transactional
public void updateTemplate(Integer id, String template, Integer tplType) {
SmsTemplateDO smsTemplateDO = smsTemplateMapper.selectOne(
new QueryWrapper<SmsTemplateDO>().eq("id", id));
if (smsTemplateDO == null) {
throw new ServiceException(AdminErrorCodeEnum.SMS_TEMPLATE_NOT_EXISTENT.getCode(),
AdminErrorCodeEnum.SMS_TEMPLATE_NOT_EXISTENT.getMessage());
}
SmsSignDO smsSignDO = smsSignMapper.selectOne(
new QueryWrapper<SmsSignDO>().eq("id", smsTemplateDO.getSmsSignId()));
SmsPlatform.Result result = smsPlatform.updateTemplate(
smsTemplateDO.getPlatformId(),
String.format(SMS_TEMPLATE, smsSignDO.getSign(), template), tplType);
smsTemplateMapper.update(
(SmsTemplateDO) new SmsTemplateDO()
.setTemplate(template)
.setApplyStatus(result.getApplyStatus())
.setApplyMessage(result.getApplyMessage())
.setUpdateTime(new Date()),
new QueryWrapper<SmsTemplateDO>().eq("id", id)
);
}
@Override
@Transactional
public void deleteTemplate(Integer id) {
SmsTemplateDO smsTemplateDO = smsTemplateMapper.selectOne(
new QueryWrapper<SmsTemplateDO>().eq("id", id));
if (smsTemplateDO == null
|| smsTemplateDO.getDeleted().equals(DeletedStatusEnum.DELETED_YES.getValue())) {
throw new ServiceException(AdminErrorCodeEnum.SMS_TEMPLATE_NOT_EXISTENT.getCode(),
AdminErrorCodeEnum.SMS_TEMPLATE_NOT_EXISTENT.getMessage());
}
// 删除 平台的模板
smsPlatform.deleteTemplate(smsTemplateDO.getPlatformId());
// 删除 数据库模板
SmsTemplateDO updateTemplate =new SmsTemplateDO();
updateTemplate.setDeleted(DeletedStatusEnum.DELETED_YES.getValue());
smsTemplateMapper.delete(new UpdateWrapper<SmsTemplateDO>()
.set("deleted", DeletedStatusEnum.DELETED_YES).eq("id", id));
}
}
package cn.iocoder.mall.admin.service;
import cn.iocoder.common.framework.exception.ServiceException;
import cn.iocoder.mall.admin.api.SmsPlatform;
import cn.iocoder.mall.admin.api.constant.AdminErrorCodeEnum;
import cn.iocoder.mall.admin.api.constant.SmsApplyStatusEnum;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 云片 短信平台
*
* @author Sin
* @time 2019/5/16 6:34 PM
*/
@Service
public class SmsYunPianPlatform implements SmsPlatform {
protected static final Logger LOGGER = LoggerFactory.getLogger(SmsPlatform.class);
private static final int SUCCESS_CODE = 0;
//查账户信息的http地址
private static final String URI_GET_USER_INFO =
"https://sms.yunpian.com/v2/user/get.json";
//智能匹配模板发送接口的http地址
private static final String URI_SEND_SMS =
"https://sms.yunpian.com/v2/sms/single_send.json";
//模板发送接口的http地址
private static final String URI_TPL_SEND_SMS =
"https://sms.yunpian.com/v2/sms/tpl_single_send.json";
//发送语音验证码接口的http地址
private static final String URI_SEND_VOICE =
"https://voice.yunpian.com/v2/voice/send.json";
//绑定主叫、被叫关系的接口http地址
private static final String URI_SEND_BIND =
"https://call.yunpian.com/v2/call/bind.json";
//解绑主叫、被叫关系的接口http地址
private static final String URI_SEND_UNBIND =
"https://call.yunpian.com/v2/call/unbind.json";
/**
* 签名 - 添加
*/
private static final String URL_SIGN_ADD = "https://sms.yunpian.com/v2/sign/add.json";
/**
* 签名 - 获取
*/
private static final String URL_SIGN_GET = "https://sms.yunpian.com/v2/sign/get.json";
/**
* 签名 - 更新
*/
private static final String URL_SIGN_UPDATE = "https://sms.yunpian.com/v2/sign/update.json";
/**
* 模板 - 添加
*/
private static final String URL_TEMPLATE_ADD = "https://sms.yunpian.com/v2/tpl/add.json";
/**
* 模板 - 获取
*/
private static final String URL_TEMPLATE_GET = "https://sms.yunpian.com/v2/tpl/get.json";
/**
* 模板 - 更新
*/
private static final String URL_TEMPLATE_UPDATE = "https://sms.yunpian.com/v2/tpl/update.json";
/**
* 模板 - 删除
*/
private static final String URL_TEMPLATE_DELETE = "https://sms.yunpian.com/v2/tpl/del.json";
//编码格式。发送编码格式统一用UTF-8
private static String ENCODING = "UTF-8";
@Value("${sms.apiKey}")
private String apiKey;
@Override
public Result createSign(String sign) {
try {
// 存在直接 return 相当于,创建了
return getSign(sign);
} catch (ServiceException e) {
// skip 不存在会进这里,不错任何操作
}
// 调用 短信平台
Map<String, String> params = new LinkedHashMap<>();
params.put("apikey", apiKey);
params.put("sign", sign);
params.put("notify", "true");
String result = post(URL_SIGN_ADD, params);
JSONObject jsonObject = JSON.parseObject(result);
if (jsonObject.containsKey("code")
&& !(jsonObject.getInteger("code") == SUCCESS_CODE)) {
throw new ServiceException(AdminErrorCodeEnum.SMS_PLATFORM_FAIL.getCode(),
jsonObject.getString("detail"));
}
JSONObject signJSONObject = (JSONObject) jsonObject.get("sign");
Integer applyState = smsStatusMapping(signJSONObject.getString("apply_state"));
return new Result().setId(null).setApplyStatus(applyState).setApplyMessage(null);
}
@Override
public Result getSign(String sign) {
Map<String, String> params = new LinkedHashMap<>();
params.put("apikey", apiKey);
params.put("sign", sign);
params.put("page_num", "1");
params.put("page_size", "20");
String result = post(URL_SIGN_GET, params);
JSONObject jsonObject = JSON.parseObject(result);
if (jsonObject.containsKey("code")
&& !(jsonObject.getInteger("code") == SUCCESS_CODE)) {
throw new ServiceException(AdminErrorCodeEnum.SMS_PLATFORM_FAIL.getCode(),
jsonObject.getString("detail"));
}
JSONArray jsonArray = jsonObject.getJSONArray("sign");
if (jsonArray.size() <= 0) {
throw new ServiceException(AdminErrorCodeEnum.SMS_SIGN_NOT_EXISTENT.getCode(),
AdminErrorCodeEnum.SMS_SIGN_NOT_EXISTENT.getMessage());
}
JSONObject signJSONObject = (JSONObject) jsonArray.get(0);
String checkStatus = signJSONObject.getString("check_status");
String applyMessage = signJSONObject.getString("remark");
Integer applyStatus = smsStatusMapping(checkStatus);
return new Result().setId(null).setApplyStatus(applyStatus).setApplyMessage(applyMessage);
}
@Override
public Result updateSign(String oldSign, String sign) {
Map<String, String> params = new LinkedHashMap<>();
params.put("apikey", apiKey);
params.put("old_sign", oldSign);
params.put("sign", sign);
String result = post(URL_SIGN_UPDATE, params);
JSONObject jsonObject = JSON.parseObject(result);
if (jsonObject.containsKey("code")
&& !(jsonObject.getInteger("code") == SUCCESS_CODE)) {
throw new ServiceException(AdminErrorCodeEnum.SMS_PLATFORM_FAIL.getCode(),
jsonObject.getString("detail"));
}
JSONObject signJSONObject = (JSONObject) jsonObject.get("sign");
Integer applyState = smsStatusMapping(signJSONObject.getString("apply_state"));
return new Result().setId(null).setApplyStatus(applyState).setApplyMessage(null);
}
@Override
public Result createTemplate(String template, Integer tplType) {
Map<String, String> params = new LinkedHashMap<>();
params.put("apikey", apiKey);
params.put("tpl_content", template);
if (tplType != null) {
params.put("tplType", String.valueOf(tplType));
}
String result = post(URL_TEMPLATE_ADD, params);
JSONObject jsonObject = JSON.parseObject(result);
if (jsonObject.containsKey("code")
&& !(jsonObject.getInteger("code") == SUCCESS_CODE)) {
throw new ServiceException(AdminErrorCodeEnum.SMS_PLATFORM_FAIL.getCode(),
jsonObject.getString("detail"));
}
String tipId = jsonObject.getString("tpl_id");
String checkStatus = jsonObject.getString("check_status");
String reason = jsonObject.getString("reason");
Integer applyStatus = smsStatusMapping(checkStatus);
return new Result().setId(tipId).setApplyStatus(applyStatus).setApplyMessage(reason);
}
@Override
public Result getTemplate(String tplId) {
Map<String, String> params = new LinkedHashMap<>();
params.put("apikey", apiKey);
params.put("tpl_id", tplId);
String result = post(URL_TEMPLATE_GET, params);
JSONObject jsonObject = JSON.parseObject(result);
if (jsonObject.containsKey("code")
&& !(jsonObject.getInteger("code") == SUCCESS_CODE)) {
throw new ServiceException(AdminErrorCodeEnum.SMS_PLATFORM_FAIL.getCode(),
jsonObject.getString("detail"));
}
String checkStatus = jsonObject.getString("check_status");
Integer applyStatus = smsStatusMapping(checkStatus);
String reason = jsonObject.getString("reason");
return new Result().setId(tplId).setApplyStatus(applyStatus).setApplyMessage(reason);
}
@Override
public Result updateTemplate(String tplId, String template, Integer tplType) {
Map<String, String> params = new LinkedHashMap<>();
params.put("apikey", apiKey);
params.put("tpl_id", tplId);
params.put("tpl_content", template);
String result = post(URL_TEMPLATE_UPDATE, params);
JSONObject jsonObject = JSON.parseObject(result);
if (jsonObject.containsKey("code")
&& !(jsonObject.getInteger("code") == SUCCESS_CODE)) {
throw new ServiceException(AdminErrorCodeEnum.SMS_PLATFORM_FAIL.getCode(),
jsonObject.getString("detail"));
}
JSONObject templateJSONObject = (JSONObject) jsonObject.get("template");
String checkStatus = templateJSONObject.getString("check_status");
Integer applyStatus = smsStatusMapping(checkStatus);
String reason = jsonObject.getString("reason");
return new Result().setId(tplId).setApplyStatus(applyStatus).setApplyMessage(reason);
}
@Override
public Result deleteTemplate(String tplId) {
// 如果不存在/已删除,直接返回
try {
getTemplate(tplId);
} catch (ServiceException e) {
// skip
return new Result().setId(tplId).setApplyStatus(null).setApplyMessage(null);
}
Map<String, String> params = new LinkedHashMap<>();
params.put("apikey", apiKey);
params.put("tpl_id", tplId);
String result = post(URL_TEMPLATE_DELETE, params);
JSONObject jsonObject = JSON.parseObject(result);
if (jsonObject.containsKey("code")
&& !(jsonObject.getInteger("code") == SUCCESS_CODE)) {
throw new ServiceException(AdminErrorCodeEnum.SMS_PLATFORM_FAIL.getCode(),
jsonObject.getString("detail"));
}
return new Result().setId(tplId).setApplyStatus(null).setApplyMessage(null);
}
/**
* 短信 status 和 云片状态 映射关系
*
* @param checkStatus
* @return
*/
private Integer smsStatusMapping(String checkStatus) {
Integer applyStatus;
switch (checkStatus) {
case "SUCCESS":
applyStatus = SmsApplyStatusEnum.SUCCESS.getCode();
break;
case "FAIL":
applyStatus = SmsApplyStatusEnum.FAIL.getCode();
break;
default:
applyStatus = SmsApplyStatusEnum.CHECKING.getCode();
break;
}
return applyStatus;
}
/**
* 基于HttpClient 4.3的通用POST方法
*
* @param url 提交的URL
* @param paramsMap 提交<参数,值>Map
* @return 提交响应
*/
public static String post(String url, Map<String, String> paramsMap) {
CloseableHttpClient client = HttpClients.createDefault();
String responseText = "";
CloseableHttpResponse response = null;
try {
HttpPost method = new HttpPost(url);
if (paramsMap != null) {
List<NameValuePair> paramList = new ArrayList<>();
for (Map.Entry<String, String> param : paramsMap.entrySet()) {
NameValuePair pair = new BasicNameValuePair(param.getKey(),
param.getValue());
paramList.add(pair);
}
method.setEntity(new UrlEncodedFormEntity(paramList, ENCODING));
}
response = client.execute(method);
HttpEntity entity = response.getEntity();
if (entity != null) {
responseText = EntityUtils.toString(entity, ENCODING);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (Exception e) {
e.printStackTrace();
}
}
LOGGER.debug("云片短信平台 res: {}", responseText);
return responseText;
}
}
......@@ -24,6 +24,10 @@ mybatis-plus:
mapper-locations: classpath*:mapper/*.xml
type-aliases-package: cn.iocoder.mall.admin.dataobject
# sms
sms:
apiKey: d4705399e71e822fe3a90f801ed95bd9
# dubbo
dubbo:
application:
......@@ -49,6 +53,8 @@ dubbo:
version: 1.0.0
RoleService:
version: 1.0.0
SmsService:
version: 1.0.0
# logging
logging:
......
......@@ -29,21 +29,6 @@
</foreach>
</select>
<select id="selectById" resultType="DataDictDO">
SELECT
<include refid="FIELDS"/>
FROM data_dict
WHERE id = #{id}
AND deleted = 0
</select>
<select id="selectList" resultType="DataDictDO">
SELECT
<include refid="FIELDS"/>
FROM data_dict
WHERE deleted = 0
</select>
<select id="selectByEnumValue" resultType="cn.iocoder.mall.admin.dataobject.DataDictDO">
SELECT
<include refid="FIELDS"/>
......@@ -52,39 +37,4 @@
AND enum_value = #{enumValue}
</select>
<insert id="insert" parameterType="DataDictDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
INSERT INTO data_dict (
id, enum_value, value, display_name, sort,
memo, create_time, deleted
) VALUES (
#{id}, #{enumValue}, #{value}, #{displayName}, #{sort},
#{memo}, #{createTime}, #{deleted}
)
</insert>
<update id="update" parameterType="DataDictDO">
UPDATE data_dict
<set>
<if test="enumValue != null">
enum_value = #{enumValue},
</if>
<if test="value != null">
value = #{value},
</if>
<if test="displayName != null">
display_name = #{displayName},
</if>
<if test="sort != null">
sort = #{sort},
</if>
<if test="memo != null">
memo = #{memo},
</if>
<if test="deleted != null">
deleted = #{deleted}
</if>
</set>
WHERE id = #{id}
</update>
</mapper>
\ No newline at end of file
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.mall.admin.dao.OAuth2AccessTokenMapper">
<update id="updateToInvalidByAdminId" parameterType="Integer">
UPDATE oauth2_access_token
SET valid = 0
WHERE admin_id = #{adminId}
AND valid = 1
</update>
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.mall.admin.dao.OAuth2RefreshTokenMapper">
<update id="updateToInvalidByAdminId" parameterType="Integer">
UPDATE oauth2_refresh_token
SET valid = 0
WHERE admin_id = #{adminId}
AND valid = 1
</update>
</mapper>
package cn.iocoder.mall.admin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
/**
* 短信 application (test)
*
* @author Sin
* @time 2019/5/16 10:53 AM
*/
@SpringBootApplication(scanBasePackages = {"cn.iocoder.mall.admin"})
@EnableAsync(proxyTargetClass = true)
public class SystemApplicationTest {
public static void main(String[] args) {
SpringApplication.run(SystemApplicationTest.class);
}
}
package cn.iocoder.mall.admin.service;
import cn.iocoder.mall.admin.SystemApplicationTest;
import cn.iocoder.mall.admin.api.bo.sms.SmsSignBO;
import cn.iocoder.mall.admin.api.bo.sms.SmsTemplateBO;
import org.junit.Assert;
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.SpringRunner;
/**
* 短信 test
*
* @author Sin
* @time 2019/5/16 10:52 AM
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SystemApplicationTest.class)
public class SmsServiceImplTest {
@Autowired
private SmsServiceImpl smsService;
@Test
public void createSignTest() {
smsService.createSign("测试签名1");
// smsService.createSign("悦跑会");
}
@Test
public void getSignTest() {
SmsSignBO smsSignBO = smsService.getSign("悦跑会");
Assert.assertNotNull(smsSignBO);
}
@Test
public void updateSignTest() {
smsService.updateSign("测试签名2", "测试签名3");
SmsSignBO newSmsSignBO = smsService.getSign("测试签名3");
Assert.assertNotNull(newSmsSignBO);
}
///
/// template
@Test
public void createTemplateTest() {
smsService.createTemplate(1, "打死也不要告诉别人哦002 #code# ", 1);
}
@Test
public void getTemplateTest() {
SmsTemplateBO smsTemplateBO = smsService.getTemplate(3);
Assert.assertNotNull(smsTemplateBO);
}
@Test
public void updateTemplateTest() {
smsService.updateTemplate(3, "打死也不要告诉别人哦444 #code# ", 1);
}
@Test
public void deleteTemplateTest() {
smsService.deleteTemplate(3);
}
}
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
......
......@@ -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) {
// 创建
......
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);
}
......@@ -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;
/**
* 收件详细地址
*/
......
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 {
}
\ No newline at end of file
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 {
}
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;
public UserSecurityContext(Integer userId) {
this.userId = userId;
}
public Integer getUserId() {
return userId;
}
/**
* 用户编号
*/
private Integer userId;
}
\ No newline at end of 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;
......
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);
}
......
......@@ -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>
......
package cn.iocoder.mall.user.api;
import cn.iocoder.mall.user.api.bo.OAuth2AccessTokenBO;
import cn.iocoder.mall.user.api.bo.OAuth2AuthenticationBO;
public interface OAuth2Service {
OAuth2AccessTokenBO getAccessToken(String mobile, String code);
/**
* 校验访问令牌,获取身份信息( 不包括 accessToken 等等 )
*
* @param accessToken 访问令牌
* @return 授权信息
*/
OAuth2AuthenticationBO checkToken(String accessToken);
OAuth2AccessTokenBO refreshToken(String refreshToken);
// TODO @see 移除 token
}
package cn.iocoder.mall.user.api;
import cn.iocoder.mall.user.api.dto.UserAccessLogAddDTO;
public interface UserAccessLogService {
void addUserAccessLog(UserAccessLogAddDTO userAccessLogAddDTO);
}
......@@ -2,13 +2,17 @@ package cn.iocoder.mall.user.api;
import cn.iocoder.common.framework.constant.CommonStatusEnum;
import cn.iocoder.common.framework.validator.InEnum;
import cn.iocoder.mall.user.api.bo.user.UserAuthenticationBO;
import cn.iocoder.mall.user.api.bo.UserBO;
import cn.iocoder.mall.user.api.bo.UserPageBO;
import cn.iocoder.mall.user.api.dto.UserPageDTO;
import cn.iocoder.mall.user.api.dto.UserUpdateDTO;
import cn.iocoder.mall.user.api.dto.user.UserAuthenticationByMobileCodeDTO;
public interface UserService {
UserAuthenticationBO authenticationByMobileCode(UserAuthenticationByMobileCodeDTO userAuthenticationByMobileCodeDTO);
UserPageBO getUserPage(UserPageDTO userPageDTO);
UserBO getUser(Integer userId);
......
package cn.iocoder.mall.user.api.bo;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@Accessors(chain = true)
public class OAuth2AccessTokenBO implements Serializable {
/**
* 访问令牌
*/
private String accessToken;
/**
* 刷新令牌
*/
private String refreshToken;
/**
* 过期时间,单位:秒。
*/
private Integer expiresIn;
}
package cn.iocoder.mall.user.api.bo;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@Accessors(chain = true)
public class OAuth2AuthenticationBO implements Serializable {
/**
* 用户编号
*/
private Integer userId;
}
package cn.iocoder.mall.admin.application.vo.datadict;
package cn.iocoder.mall.user.api.bo.user;
import cn.iocoder.mall.admin.api.bo.oauth2.OAuth2AccessTokenBO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
@ApiModel("数据字典枚举值 VO")
@ApiModel("用户认证 BO")
@Data
@Accessors(chain = true)
public class DataDictValueVO {
public class UserAuthenticationBO {
@ApiModelProperty(value = "小类数值", required = true, example = "1")
private String value;
@ApiModelProperty(value = "展示名", required = true, example = "男")
private String displayName;
@ApiModelProperty(value = "用户编号", required = true, example = "1")
private Integer id;
@ApiModelProperty(value = "昵称", required = true, example = "小王")
private String nickname;
private OAuth2AccessTokenBO token;
}
package cn.iocoder.mall.user.api.dto;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;
/**
* 用户访问日志添加 DTO
*/
@Data
@Accessors(chain = true)
public class UserAccessLogAddDTO implements Serializable {
/**
* 用户编号 - 空
*/
public static final Integer USER_ID_NULL = 0;
/**
* 用户编号.
*
* 当用户为空时,该值为0
*/
@NotNull(message = "用户编号不能为空")
private Integer userId;
/**
* 访问地址
*/
@NotNull(message = "访问地址不能为空")
private String uri;
/**
* 参数
*/
@NotNull(message = "请求参数不能为空")
private String queryString;
/**
* http 方法
*/
@NotNull(message = "http 请求方法不能为空")
private String method;
/**
* User Agent
*/
@NotNull(message = "User-Agent 不能为空")
private String userAgent;
/**
* ip
*/
@NotNull(message = "ip 不能为空")
private String ip;
/**
* 请求时间
*/
@NotNull(message = "请求时间不能为空")
private Date startTime;
/**
* 响应时长 -- 毫秒级
*/
@NotNull(message = "响应时长不能为空")
private Integer responseTime;
}
package cn.iocoder.mall.user.api.dto.user;
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.Pattern;
@ApiModel("用户认证 DTO")
@Data
@Accessors(chain = true)
public class UserAuthenticationByMobileCodeDTO {
@ApiModelProperty(value = "手机号", required = true, example = "15601691300")
@NotEmpty(message = "手机号不能为空")
@Length(min = 11, max = 11, message = "账号长度为 11 位")
@Pattern(regexp = "^[0-9]+$", message = "手机号必须都是数字")
private String mobile;
@ApiModelProperty(value = "手机验证码", required = true, example = "1024")
@NotEmpty(message = "手机验证码不能为空")
@Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
@Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
private String code;
}
package cn.iocoder.mall.user.biz.convert;
import cn.iocoder.mall.user.biz.dataobject.OAuth2AccessTokenDO;
import cn.iocoder.mall.user.api.bo.OAuth2AccessTokenBO;
import cn.iocoder.mall.user.api.bo.OAuth2AuthenticationBO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
@Mapper
public interface OAuth2Convert {
OAuth2Convert INSTANCE = Mappers.getMapper(OAuth2Convert.class);
@Mappings({
@Mapping(source = "id", target = "accessToken")
})
OAuth2AccessTokenBO convertToAccessToken(OAuth2AccessTokenDO oauth2AccessTokenDO);
default OAuth2AccessTokenBO convertToAccessTokenWithExpiresIn(OAuth2AccessTokenDO oauth2AccessTokenDO) {
return this.convertToAccessToken(oauth2AccessTokenDO)
.setExpiresIn(Math.max((int) ((oauth2AccessTokenDO.getExpiresTime().getTime() - System.currentTimeMillis()) / 1000), 0));
}
@Mappings({})
OAuth2AuthenticationBO convertToAuthentication(OAuth2AccessTokenDO oauth2AccessTokenDO);
}
\ No newline at end of file
package cn.iocoder.mall.user.biz.convert;
import cn.iocoder.mall.user.biz.dataobject.UserAccessLogDO;
import cn.iocoder.mall.user.api.dto.UserAccessLogAddDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
@Mapper
public interface UserAccessLogConvert {
UserAccessLogConvert INSTANCE = Mappers.getMapper(UserAccessLogConvert.class);
@Mappings({})
UserAccessLogDO convert(UserAccessLogAddDTO adminAccessLogAddDTO);
}
\ No newline at end of file
package cn.iocoder.mall.user.biz.convert;
import cn.iocoder.mall.user.biz.dataobject.UserDO;
import cn.iocoder.mall.user.api.bo.user.UserAuthenticationBO;
import cn.iocoder.mall.user.api.bo.UserBO;
import cn.iocoder.mall.user.api.dto.UserUpdateDTO;
import cn.iocoder.mall.user.biz.dataobject.UserDO;
import org.mapstruct.Mapper;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
......@@ -17,10 +18,13 @@ public interface UserConvert {
@Mappings({})
UserBO convert(UserDO userDO);
@Mappings({})
UserAuthenticationBO convert2(UserDO userDO);
@Mappings({})
UserDO convert(UserUpdateDTO userUpdateDTO);
@Mappings({})
List<UserBO> convert(List<UserDO> userDOs);
}
\ No newline at end of file
}
package cn.iocoder.mall.user.biz.dao;
import cn.iocoder.mall.user.biz.dataobject.MobileCodeDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
@Repository // 实际不加也没问entity,就是不想 IDEA 那看到有个报错
public interface MobileCodeMapper {
void insert(MobileCodeDO entity);
/**
* 更新手机验证码
*
* @param entity 更新信息
*/
void update(MobileCodeDO entity);
public interface MobileCodeMapper extends BaseMapper<MobileCodeDO> {
/**
* 获得手机号的最后一个手机验证码
......@@ -21,6 +14,12 @@ public interface MobileCodeMapper {
* @param mobile 手机号
* @return 手机验证码
*/
MobileCodeDO selectLast1ByMobile(String mobile);
default MobileCodeDO selectLast1ByMobile(String mobile) {
QueryWrapper<MobileCodeDO> query = new QueryWrapper<MobileCodeDO>()
.eq("mobile", mobile)
.orderByDesc("id")
.last("limit 1");
return selectOne(query);
}
}
\ No newline at end of file
}
package cn.iocoder.mall.user.biz.dao;
import cn.iocoder.mall.user.biz.dataobject.OAuth2AccessTokenDO;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface OAuth2AccessTokenMapper {
void insert(OAuth2AccessTokenDO entity);
OAuth2AccessTokenDO selectByTokenId(String tokenId);
void updateToInvalidByUserId(@Param("userId") Integer userId);
void updateToInvalidByRefreshToken(@Param("refreshToken") String refreshToken);
}
package cn.iocoder.mall.user.biz.dao;
import cn.iocoder.mall.user.biz.dataobject.OAuth2RefreshTokenDO;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface OAuth2RefreshTokenMapper {
void insert(OAuth2RefreshTokenDO entity);
void updateToInvalidByUserId(@Param("userId") Integer userId);
OAuth2RefreshTokenDO selectById(@Param("id") String id);
}
package cn.iocoder.mall.user.biz.dao;
import cn.iocoder.mall.user.biz.dataobject.UserAccessLogDO;
import org.springframework.stereotype.Repository;
@Repository
public interface UserAccessLogMapper {
void insert(UserAccessLogDO entity);
}
package cn.iocoder.mall.user.biz.dataobject;
import cn.iocoder.common.framework.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
// TODO 优化,IP
@TableName("mobile_code")
@Data
@Accessors(chain = true)
public class MobileCodeDO {
public class MobileCodeDO extends BaseDO {
/**
* 编号
......@@ -34,10 +37,6 @@ public class MobileCodeDO {
* 注册的用户编号
*/
private Integer usedUserId;
/**
* 创建时间
*/
private Date createTime;
/**
* 使用时间
*/
......
......@@ -50,21 +50,21 @@ public class MobileCodeServiceImpl implements MobileCodeService {
*/
public MobileCodeDO validLastMobileCode(String mobile, String code) {
// TODO: 2019-04-09 Sin 暂时先忽略掉验证码校验
return new MobileCodeDO().setCode(code).setCreateTime(new Date()).setId(1);
// MobileCodeDO mobileCodePO = mobileCodeMapper.selectLast1ByMobile(mobile);
// if (mobileCodePO == null) { // 若验证码不存在,抛出异常
// throw ServiceExceptionUtil.exception(UserErrorCodeEnum.MOBILE_CODE_NOT_FOUND.getCode());
// }
// if (System.currentTimeMillis() - mobileCodePO.getCreateTime().getTime() >= codeExpireTimes) { // 验证码已过期
// throw ServiceExceptionUtil.exception(UserErrorCodeEnum.MOBILE_CODE_EXPIRED.getCode());
// }
// if (mobileCodePO.getUsed()) { // 验证码已使用
// throw ServiceExceptionUtil.exception(UserErrorCodeEnum.MOBILE_CODE_USED.getCode());
// }
// if (!mobileCodePO.getCode().equals(code)) {
// throw ServiceExceptionUtil.exception(UserErrorCodeEnum.MOBILE_CODE_NOT_CORRECT.getCode());
// }
// return mobileCodePO;
// return new MobileCodeDO().setCode(code).setCreateTime(new Date()).setId(1);
MobileCodeDO mobileCodePO = mobileCodeMapper.selectLast1ByMobile(mobile);
if (mobileCodePO == null) { // 若验证码不存在,抛出异常
throw ServiceExceptionUtil.exception(UserErrorCodeEnum.MOBILE_CODE_NOT_FOUND.getCode());
}
if (System.currentTimeMillis() - mobileCodePO.getCreateTime().getTime() >= codeExpireTimes) { // 验证码已过期
throw ServiceExceptionUtil.exception(UserErrorCodeEnum.MOBILE_CODE_EXPIRED.getCode());
}
if (mobileCodePO.getUsed()) { // 验证码已使用
throw ServiceExceptionUtil.exception(UserErrorCodeEnum.MOBILE_CODE_USED.getCode());
}
if (!mobileCodePO.getCode().equals(code)) {
throw ServiceExceptionUtil.exception(UserErrorCodeEnum.MOBILE_CODE_NOT_CORRECT.getCode());
}
return mobileCodePO;
}
/**
......@@ -75,7 +75,7 @@ public class MobileCodeServiceImpl implements MobileCodeService {
*/
public void useMobileCode(Integer id, Integer userId) {
MobileCodeDO update = new MobileCodeDO().setId(id).setUsed(true).setUsedUserId(userId).setUsedTime(new Date());
mobileCodeMapper.update(update);
mobileCodeMapper.updateById(update);
}
// TODO 芋艿,后面要返回有效时间
......@@ -99,7 +99,8 @@ public class MobileCodeServiceImpl implements MobileCodeService {
MobileCodeDO newMobileCodePO = new MobileCodeDO().setMobile(mobile)
.setCode("9999") // TODO 芋艿,随机 4 位验证码 or 6 位验证码
.setTodayIndex(lastMobileCodePO != null ? lastMobileCodePO.getTodayIndex() : 1)
.setUsed(false).setCreateTime(new Date());
.setUsed(false);
newMobileCodePO.setCreateTime(new Date());
mobileCodeMapper.insert(newMobileCodePO);
// TODO 发送验证码短信
}
......
package cn.iocoder.mall.user.biz.service;
import cn.iocoder.common.framework.exception.ServiceException;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.mall.user.api.OAuth2Service;
import cn.iocoder.mall.user.api.bo.OAuth2AccessTokenBO;
import cn.iocoder.mall.user.api.bo.OAuth2AuthenticationBO;
import cn.iocoder.mall.user.api.constant.UserErrorCodeEnum;
import cn.iocoder.mall.user.biz.convert.OAuth2Convert;
import cn.iocoder.mall.user.biz.dao.OAuth2AccessTokenMapper;
import cn.iocoder.mall.user.biz.dao.OAuth2RefreshTokenMapper;
import cn.iocoder.mall.user.biz.dataobject.MobileCodeDO;
import cn.iocoder.mall.user.biz.dataobject.OAuth2AccessTokenDO;
import cn.iocoder.mall.user.biz.dataobject.OAuth2RefreshTokenDO;
import cn.iocoder.mall.user.biz.dataobject.UserDO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import java.util.Date;
import java.util.UUID;
/**
* OAuth2Service ,实现用户授权相关的逻辑
*/
@Service
@org.apache.dubbo.config.annotation.Service(validation = "true", version = "${dubbo.provider.OAuth2Service.version}")
public class OAuth2ServiceImpl implements OAuth2Service {
/**
* 访问令牌过期时间,单位:毫秒
*/
@Value("${modules.oauth2-code-service.access-token-expire-time-millis}")
private int accessTokenExpireTimeMillis;
/**
* 刷新令牌过期时间,单位:毫秒
*/
@Value("${modules.oauth2-code-service.refresh-token-expire-time-millis}")
private int refreshTokenExpireTimeMillis;
@Autowired
private UserServiceImpl userService;
@Autowired
private MobileCodeServiceImpl mobileCodeService;
@Autowired
private OAuth2AccessTokenMapper oauth2AccessTokenMapper;
@Autowired
private OAuth2RefreshTokenMapper oauth2RefreshTokenMapper;
@Override
@Transactional
public OAuth2AccessTokenBO getAccessToken(String mobile, String code) {
// 校验传入的 mobile 和 code 是否合法
MobileCodeDO mobileCodeDO = mobileCodeService.validLastMobileCode(mobile, code);
// 获取用户
UserDO userDO = userService.getUser(mobile);
if (userDO == null) { // 用户不存在,则进行创建用户
userDO = userService.createUser(mobile);
Assert.notNull(userDO, "创建用户必然成功");
}
// 创建刷新令牌
OAuth2RefreshTokenDO oauth2RefreshTokenDO = createOAuth2RefreshToken(userDO.getId());
// 创建访问令牌
OAuth2AccessTokenDO oauth2AccessTokenDO = createOAuth2AccessToken(userDO.getId(), oauth2RefreshTokenDO.getId());
// 标记已使用
mobileCodeService.useMobileCode(mobileCodeDO.getId(), userDO.getId());
// 转换返回
return OAuth2Convert.INSTANCE.convertToAccessTokenWithExpiresIn(oauth2AccessTokenDO);
}
@Override
public OAuth2AuthenticationBO checkToken(String accessToken) throws ServiceException {
OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectByTokenId(accessToken);
if (accessTokenDO == null) { // 不存在
throw ServiceExceptionUtil.exception(UserErrorCodeEnum.OAUTH_INVALID_ACCESS_TOKEN_NOT_FOUND.getCode());
}
if (accessTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期
throw ServiceExceptionUtil.exception(UserErrorCodeEnum.OAUTH_INVALID_ACCESS_TOKEN_EXPIRED.getCode());
}
if (!accessTokenDO.getValid()) { // 无效
throw ServiceExceptionUtil.exception(UserErrorCodeEnum.OAUTH_INVALID_ACCESS_TOKEN_INVALID.getCode());
}
// 转换返回
return OAuth2Convert.INSTANCE.convertToAuthentication(accessTokenDO);
}
@Override
public OAuth2AccessTokenBO refreshToken(String refreshToken) {
OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectById(refreshToken);
// 校验刷新令牌是否合法
if (refreshTokenDO == null) { // 不存在
throw ServiceExceptionUtil.exception(UserErrorCodeEnum.OAUTH_INVALID_REFRESH_TOKEN_NOT_FOUND.getCode());
}
if (refreshTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期
throw ServiceExceptionUtil.exception(UserErrorCodeEnum.OAUTH_INVALID_REFRESH_TOKEN_EXPIRED.getCode());
}
if (!refreshTokenDO.getValid()) { // 无效
throw ServiceExceptionUtil.exception(UserErrorCodeEnum.OAUTH_INVALID_REFRESH_TOKEN_INVALID.getCode());
}
// 标记 refreshToken 对应的 accessToken 都不合法
oauth2AccessTokenMapper.updateToInvalidByRefreshToken(refreshToken);
// 创建访问令牌
OAuth2AccessTokenDO oauth2AccessTokenDO = createOAuth2AccessToken(refreshTokenDO.getUserId(), refreshTokenDO.getId());
// 转换返回
return OAuth2Convert.INSTANCE.convertToAccessTokenWithExpiresIn(oauth2AccessTokenDO);
}
/**
* 移除用户对应的 Token
*
* @param userId 管理员编号
*/
@Transactional
public void removeToken(Integer userId) {
// 设置 access token 失效
oauth2AccessTokenMapper.updateToInvalidByUserId(userId);
// 设置 refresh token 失效
oauth2RefreshTokenMapper.updateToInvalidByUserId(userId);
}
private OAuth2AccessTokenDO createOAuth2AccessToken(Integer uid, String refreshToken) {
OAuth2AccessTokenDO accessToken = new OAuth2AccessTokenDO().setId(generateAccessToken())
.setRefreshToken(refreshToken)
.setUserId(uid)
.setExpiresTime(new Date(System.currentTimeMillis() + accessTokenExpireTimeMillis))
.setValid(true);
oauth2AccessTokenMapper.insert(accessToken);
return accessToken;
}
private OAuth2RefreshTokenDO createOAuth2RefreshToken(Integer uid) {
OAuth2RefreshTokenDO refreshToken = new OAuth2RefreshTokenDO().setId(generateRefreshToken())
.setUserId(uid)
.setExpiresTime(new Date(System.currentTimeMillis() + refreshTokenExpireTimeMillis))
.setValid(true);
oauth2RefreshTokenMapper.insert(refreshToken);
return refreshToken;
}
private String generateAccessToken() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
private String generateRefreshToken() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
}
package cn.iocoder.mall.user.biz.service;
import cn.iocoder.common.framework.util.StringUtil;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.user.biz.dao.UserAccessLogMapper;
import cn.iocoder.mall.user.biz.dataobject.UserAccessLogDO;
import cn.iocoder.mall.user.api.UserAccessLogService;
import cn.iocoder.mall.user.api.dto.UserAccessLogAddDTO;
import cn.iocoder.mall.user.biz.convert.UserAccessLogConvert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
@org.apache.dubbo.config.annotation.Service(validation = "true", version = "${dubbo.provider.UserAccessLogService.version}")
public class UserAccessLogServiceImpl implements UserAccessLogService {
/**
* 请求参数最大长度。
*/
private static final Integer QUERY_STRING_MAX_LENGTH = 4096;
/**
* 请求地址最大长度。
*/
private static final Integer URI_MAX_LENGTH = 4096;
/**
* User-Agent 最大长度。
*/
private static final Integer USER_AGENT_MAX_LENGTH = 1024;
@Autowired
private UserAccessLogMapper userAccessLogMapper;
@Override
public void addUserAccessLog(UserAccessLogAddDTO userAccessLogAddDTO) {
// 创建 UserAccessLogDO
UserAccessLogDO accessLog = UserAccessLogConvert.INSTANCE.convert(userAccessLogAddDTO);
accessLog.setCreateTime(new Date());
// 截取最大长度
if (accessLog.getUri().length() > URI_MAX_LENGTH) {
accessLog.setUri(StringUtil.substring(accessLog.getUri(), URI_MAX_LENGTH));
}
if (accessLog.getQueryString().length() > QUERY_STRING_MAX_LENGTH) {
accessLog.setQueryString(StringUtil.substring(accessLog.getQueryString(), QUERY_STRING_MAX_LENGTH));
}
if (accessLog.getUserAgent().length() > USER_AGENT_MAX_LENGTH) {
accessLog.setUserAgent(StringUtil.substring(accessLog.getUserAgent(), USER_AGENT_MAX_LENGTH));
}
// 插入
userAccessLogMapper.insert(accessLog);
}
}
......@@ -3,20 +3,29 @@ package cn.iocoder.mall.user.biz.service;
import cn.iocoder.common.framework.constant.CommonStatusEnum;
import cn.iocoder.common.framework.constant.DeletedStatusEnum;
import cn.iocoder.common.framework.constant.SysErrorCodeEnum;
import cn.iocoder.common.framework.constant.UserTypeEnum;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.common.framework.util.ValidationUtil;
import cn.iocoder.mall.admin.api.OAuth2Service;
import cn.iocoder.mall.admin.api.bo.oauth2.OAuth2AccessTokenBO;
import cn.iocoder.mall.admin.api.dto.oauth2.OAuth2CreateTokenDTO;
import cn.iocoder.mall.admin.api.dto.oauth2.OAuth2RemoveTokenByUserDTO;
import cn.iocoder.mall.user.api.UserService;
import cn.iocoder.mall.user.api.bo.user.UserAuthenticationBO;
import cn.iocoder.mall.user.api.bo.UserBO;
import cn.iocoder.mall.user.api.bo.UserPageBO;
import cn.iocoder.mall.user.api.constant.UserConstants;
import cn.iocoder.mall.user.api.constant.UserErrorCodeEnum;
import cn.iocoder.mall.user.api.dto.UserPageDTO;
import cn.iocoder.mall.user.api.dto.UserUpdateDTO;
import cn.iocoder.mall.user.api.dto.user.UserAuthenticationByMobileCodeDTO;
import cn.iocoder.mall.user.biz.convert.UserConvert;
import cn.iocoder.mall.user.biz.dao.UserMapper;
import cn.iocoder.mall.user.biz.dao.UserRegisterMapper;
import cn.iocoder.mall.user.biz.dataobject.MobileCodeDO;
import cn.iocoder.mall.user.biz.dataobject.UserDO;
import cn.iocoder.mall.user.biz.dataobject.UserRegisterDO;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
......@@ -35,7 +44,10 @@ public class UserServiceImpl implements UserService {
@Autowired
private UserRegisterMapper userRegisterMapper;
@Autowired
private OAuth2ServiceImpl oAuth2Service;
private MobileCodeServiceImpl mobileCodeService;
@Reference(validation = "true", version = "${dubbo.consumer.OAuth2Service.version}")
private OAuth2Service oAuth2Service;
public UserDO getUser(String mobile) {
return userMapper.selectByMobile(mobile);
......@@ -67,6 +79,36 @@ public class UserServiceImpl implements UserService {
userRegisterMapper.insert(userRegisterDO);
}
@Override
@Transactional
public UserAuthenticationBO authenticationByMobileCode(UserAuthenticationByMobileCodeDTO userAuthenticationByMobileCodeDTO) {
String mobile = userAuthenticationByMobileCodeDTO.getMobile();
String code = userAuthenticationByMobileCodeDTO.getCode();
// 校验手机格式
if (!ValidationUtil.isMobile(mobile)) {
throw ServiceExceptionUtil.exception(SysErrorCodeEnum.VALIDATION_REQUEST_PARAM_ERROR.getCode(), "手机格式不正确"); // TODO 有点搓
}
// 校验验证码是否正确
MobileCodeDO mobileCodeDO = mobileCodeService.validLastMobileCode(mobile, code);
// 获得用户
UserDO user = userMapper.selectByMobile(mobile);
if (user == null) { // 用户不存在,则进行创建
user = new UserDO().setMobile(mobile).setStatus(UserConstants.STATUS_ENABLE);
user.setCreateTime(new Date());
user.setDeleted(DeletedStatusEnum.DELETED_NO.getValue());
userMapper.insert(user);
// 插入注册信息 TODO 芋艿 后续完善,记录 ip、ua 等等
createUserRegister(user);
}
// 更新验证码已使用
mobileCodeService.useMobileCode(mobileCodeDO.getId(), user.getId());
// 创建 accessToken
OAuth2AccessTokenBO accessTokenBO = oAuth2Service.createToken(new OAuth2CreateTokenDTO().setUserId(user.getId())
.setUserType(UserTypeEnum.USER.getValue()));
// 转换返回
return UserConvert.INSTANCE.convert2(user).setToken(accessTokenBO);
}
@Override
public UserPageBO getUserPage(UserPageDTO userPageDTO) {
UserPageBO userPageBO = new UserPageBO();
......@@ -114,7 +156,7 @@ public class UserServiceImpl implements UserService {
userMapper.update(updateUser);
// 如果是关闭管理员,则标记 token 失效。否则,管理员还可以继续蹦跶
if (CommonStatusEnum.DISABLE.getValue().equals(status)) {
oAuth2Service.removeToken(userId);
oAuth2Service.removeToken(new OAuth2RemoveTokenByUserDTO().setUserId(userId).setUserType(UserTypeEnum.USER.getValue()));
}
// 返回成功
return true;
......
......@@ -3,6 +3,3 @@
modules.mobile-code-service.code-expire-time-millis = 600000
modules.mobile-code-service.send-maximum-quantity-per-day = 10
modules.mobile-code-service.send-frequency = 60000
## OAuth2CodeService
modules.oauth2-code-service.access-token-expire-time-millis = 2880000
modules.oauth2-code-service.refresh-token-expire-time-millis = 43200000
\ No newline at end of file
......@@ -6,19 +6,17 @@ spring:
username: root
password: ${MALL_MYSQL_PASSWORD}
# mybatis
#mybatis:
# config-location: classpath:mybatis-config.xml
# mapper-locations: classpath:mapper/*.xml
# type-aliases-package: cn.iocoder.mall.user.biz.dataobject
# mybatis-plus
mybatis-plus:
configuration:
mapUnderscoreToCamelCase: true # 虽然默认为 true ,但是还是显示去指定下。
map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
global-config:
db-config:
id-type: auto
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
mapperLocations: classpath*:mapper/*.xml
typeAliasesPackage: cn.iocoder.mall.user.biz.dataobject
config-location: classpath:mybatis-config.xml
# dubbo
dubbo:
......@@ -35,11 +33,12 @@ dubbo:
filter: -exception
MobileCodeService:
version: 1.0.0
OAuth2Service:
version: 1.0.0
UserAccessLogService:
version: 1.0.0
UserAddressService:
version: 1.0.0
UserService:
version: 1.0.0
consumer:
OAuth2Service:
version: 1.0.0
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.mall.user.biz.dao.MobileCodeMapper">
<insert id="insert" parameterType="MobileCodeDO">
INSERT INTO mobile_code (
id, mobile, code, today_index, used,
userd_user_id, used_time, create_time
) VALUES (
#{id}, #{mobile}, #{code}, #{todayIndex}, #{used},
#{usedUserId}, #{usedTime}, #{createTime}
)
</insert>
<update id="update" parameterType="MobileCodeDO">
UPDATE mobile_code
<set>
<if test="used != null"> used = #{used}, </if>
<if test="usedUserId != null"> userd_user_id = #{usedUserId}, </if>
<if test="usedTime != null"> used_time = #{usedTime}, </if>
</set>
WHERE id = #{id}
</update>
<select id="selectLast1ByMobile" parameterType="String" resultType="MobileCodeDO">
SELECT
id, mobile, code, today_index, used,
userd_user_id, used_time, create_time
FROM mobile_code
WHERE mobile = #{mobile}
ORDER BY id DESC
LIMIT 1
</select>
</mapper>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.mall.user.biz.dao.OAuth2AccessTokenMapper">
<insert id="insert" parameterType="OAuth2AccessTokenDO">
INSERT INTO oauth2_access_token (
id, refresh_token, user_id, valid, expires_time,
create_time
) VALUES (
#{id}, #{refreshToken}, #{userId}, #{valid}, #{expiresTime},
#{createTime}
)
</insert>
<select id="selectByTokenId" parameterType="String" resultType="OAuth2AccessTokenDO">
SELECT
id, user_id, valid, expires_time
FROM oauth2_access_token
WHERE id = #{id}
</select>
<update id="updateToInvalidByUserId" parameterType="Integer">
UPDATE oauth2_access_token
SET valid = 0
WHERE user_id = #{userId}
AND valid = 1
</update>
<update id="updateToInvalidByRefreshToken" parameterType="String">
UPDATE oauth2_access_token
SET valid = 0
WHERE refresh_token = #{refreshToken}
AND valid = 1
</update>
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.mall.user.biz.dao.OAuth2RefreshTokenMapper">
<insert id="insert" parameterType="OAuth2RefreshTokenDO">
INSERT INTO oauth2_refresh_token (
id, user_id, valid, expires_time, create_time
) VALUES (
#{id}, #{userId}, #{valid}, #{expiresTime}, #{createTime}
)
</insert>
<update id="updateToInvalidByUserId" parameterType="Integer">
UPDATE oauth2_refresh_token
SET valid = 0
WHERE user_id = #{userId}
AND valid = 1
</update>
<select id="selectById" parameterType="string" resultType="cn.iocoder.mall.user.biz.dataobject.OAuth2RefreshTokenDO">
SELECT
id, user_id, valid, expires_time, create_time
FROM oauth2_refresh_token
WHERE id = #{id}
</select>
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.mall.user.biz.dao.UserAccessLogMapper">
<!--<sql id="FIELDS">-->
<!--id, username, nickname, password, status,-->
<!--create_time-->
<!--</sql>-->
<insert id="insert" parameterType="UserAccessLogDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
INSERT INTO user_access_log (
user_id, uri, query_string, method, user_agent,
ip, start_time, response_time, create_time
) VALUES (
#{userId}, #{uri}, #{queryString}, #{method}, #{userAgent},
#{ip}, #{startTime}, #{responseTime}, #{createTime}
)
</insert>
</mapper>
\ No newline at end of file
<?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>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论