前端:整理首页

前端:修复订单列表和详情价格展示错误
前端:H5 页面的登陆拦截补充
后端 + 前端:增加 refreshToken 刷新 accessToken
This commit is contained in:
YunaiV 2019-04-22 19:11:23 +08:00
parent 3e156b18ee
commit f46a4f7010
28 changed files with 292 additions and 94 deletions

View File

@ -2,7 +2,7 @@
export default { export default {
'/admin-api/': { '/admin-api/': {
target: 'http://180.167.213.26:18083/', target: 'http://127.0.0.1:18083/',
// target: 'http://180.167.213.26:18083/', // target: 'http://180.167.213.26:18083/',
changeOrigin: true, changeOrigin: true,
pathRewrite: {}, pathRewrite: {},

View File

@ -3,8 +3,8 @@
// localStorage 操作 // localStorage 操作
const cacheKeys = { const cacheKeys = {
accessTokenKey: 'accessToken', ACCESS_TOKEN: 'accessToken',
refreshTokenKey: 'refreshToken', REFRESH_TOKEN: 'refreshToken',
}; };
/// ///

View File

@ -1,8 +1,9 @@
import axios from 'axios' import axios from 'axios'
import {baseUrl, dataSources} from './env'; import {baseUrl, dataSources} from './env';
import datas from '../data/data'; import datas from '../data/data';
import { getAccessToken } from '../utils/cache.js'; import { getAccessToken, getRefreshToken } from '../utils/cache.js';
import { Dialog } from 'vant'; import { Dialog } from 'vant';
import {setLoginToken} from "../utils/cache";
const serviceRouter = function(requestUrl) { const serviceRouter = function(requestUrl) {
function getConfig() { function getConfig() {
@ -75,8 +76,9 @@ const serviceRouter = function(requestUrl) {
// }); // });
// } // }
// } // }
const config = getConfig(); const config = getConfig();
// TODO 芋艿,临时加下。
// const createServer = doCreateServer(config); // const createServer = doCreateServer(config);
const indexOf = requestUrl.indexOf("/", 1); const indexOf = requestUrl.indexOf("/", 1);
const _urlPrefix = requestUrl.substring(0, indexOf); const _urlPrefix = requestUrl.substring(0, indexOf);
@ -130,9 +132,10 @@ const servicef = function (parameter) {
return service(parameter); return service(parameter);
}; };
service.interceptors.request.use( service.interceptors.request.use(
config => { config => {
// 记录下原始请求的地址
config.originUrl = config.url;
// Do something before request is sent // Do something before request is sent
// if (store.getters.token) { // if (store.getters.token) {
// // 让每个请求携带token-- ['X-Token']为自定义key 请根据实际情况自行修改 // // 让每个请求携带token-- ['X-Token']为自定义key 请根据实际情况自行修改
@ -144,7 +147,8 @@ service.interceptors.request.use(
let url = config.url = config.url.replace(`${prefix}`, target); let url = config.url = config.url.replace(`${prefix}`, target);
// TODO 芋艿,这些 url 不用增加认证 token 。可能这么写,有点脏,后面看看咋优化下。 // TODO 芋艿,这些 url 不用增加认证 token 。可能这么写,有点脏,后面看看咋优化下。
if (url.indexOf('user-api/users/passport/mobile/send_register_code') !== -1 if (url.indexOf('user-api/users/passport/mobile/send_register_code') !== -1
|| url.indexOf('user-api/users/passport/mobile/register') !== -1) { || url.indexOf('user-api/users/passport/mobile/register') !== -1
|| url.indexOf('user-api/users/passport/refresh_token') !== -1) {
return config; return config;
} }
@ -152,7 +156,6 @@ service.interceptors.request.use(
if (getAccessToken()) { if (getAccessToken()) {
config.headers['Authorization'] = `Bearer ${getAccessToken()}`; config.headers['Authorization'] = `Bearer ${getAccessToken()}`;
} }
return config return config
}, },
error => { error => {
@ -162,6 +165,30 @@ service.interceptors.request.use(
} }
); );
function refreshToken(lastResponse) {
// TODO 芋艿,可能会存在多个异步 callback 的情况。
let refreshToken = getRefreshToken();
return servicef({
url: '/user-api/users/passport/refresh_token',
method: 'post',
params: {
refreshToken
}
}).then(data => {
// 设置新的 accessToken
setLoginToken(data.accessToken, data.refreshToken);
// 重新发起请求
let config = lastResponse.config;
return servicef({
url: config.originUrl,
method: config.method,
params: {
...config.params,
}
});
});
}
// response interceptor // response interceptor
service.interceptors.response.use( service.interceptors.response.use(
//response => response, //response => response,
@ -194,7 +221,11 @@ service.interceptors.response.use(
// TODO token 过期 // TODO token 过期
// TODO 需要拿 refresh token 置换 // TODO 需要拿 refresh token 置换
if (code === 1001001012) { if (code === 1001001011 // 访问令牌不存在
|| code === 1001001013 // 访问令牌已失效
|| code === 1001001021 // 刷新令牌不存在
|| code === 1001001022 // 刷新令牌已过期
|| code === 1001001023) { // 刷新令牌已失效
Dialog.confirm({ Dialog.confirm({
title: '系统提示', title: '系统提示',
message: res.message, message: res.message,
@ -210,6 +241,8 @@ service.interceptors.response.use(
} }
} }
}); });
} else if (code === 1001001012) { // 访问令牌已过期
return refreshToken(response);
} else { } else {
Dialog.alert({ Dialog.alert({
title: '系统提示', title: '系统提示',

View File

@ -66,21 +66,24 @@ const routes = [
path: '/user/address', path: '/user/address',
component: () => import('../page/user/address/list'), component: () => import('../page/user/address/list'),
meta: { meta: {
title: '我的地址' title: '我的地址',
requireAuth: true,
} }
}, },
{ {
path: '/user/address/edit', path: '/user/address/edit',
component: () => import('../page/user/address/edit'), component: () => import('../page/user/address/edit'),
meta: { meta: {
title: '修改地址' title: '修改地址',
requireAuth: true,
} }
}, },
{ {
path: '/user/favorite', path: '/user/favorite',
component: () => import('../page/user/favorite/list'), component: () => import('../page/user/favorite/list'),
meta: { meta: {
title: '我的收藏' title: '我的收藏',
requireAuth: true,
} }
}, },
{ {
@ -102,21 +105,24 @@ const routes = [
path: '/user/order/:id', path: '/user/order/:id',
component: () => import('../page/user/order/list'), component: () => import('../page/user/order/list'),
meta: { meta: {
title: '我的订单' title: '我的订单',
requireAuth: true,
} }
}, },
{ {
path: '/user/order/info/:id', path: '/user/order/info/:id',
component: () => import('../page/user/order/info'), component: () => import('../page/user/order/info'),
meta: { meta: {
title: '我的订单' title: '我的订单',
requireAuth: true,
} }
}, },
{ {
path: '/user/order/logistics/:id', path: '/user/order/logistics/:id',
component: () => import('../page/user/order/logistics'), component: () => import('../page/user/order/logistics'),
meta: { meta: {
title: '订单追踪' title: '订单追踪',
requireAuth: true,
} }
}, },
{ {
@ -172,7 +178,8 @@ const routes = [
name: 'cart', name: 'cart',
component: () => import('../page/cart/index'), component: () => import('../page/cart/index'),
meta: { meta: {
title: '购物车' title: '购物车',
requireAuth: true,
} }
}, },
{ {
@ -187,7 +194,8 @@ const routes = [
path: '/order/success', path: '/order/success',
component: () => import('../page/shipping/order-success'), component: () => import('../page/shipping/order-success'),
meta: { meta: {
title: '确认订单' title: '确认订单',
requireAuth: true,
} }
}, },
{ {
@ -208,7 +216,8 @@ const routes = [
path: '/pay', path: '/pay',
component: () => import('../page/pay/index'), component: () => import('../page/pay/index'),
meta: { meta: {
title: '收银台' title: '收银台',
requireAuth: true,
} }
} }
]; ];

View File

@ -12,15 +12,37 @@
</a> </a>
</van-swipe-item> </van-swipe-item>
</van-swipe> </van-swipe>
<van-row style="text-align: center">
<van-col span="8">
<router-link to="/category">
<van-icon name="http://static.iocoder.cn/icons8-medium-priority-45.png"/>
<div style="font-size:12px;margin-top: -10px;">分类</div>
</router-link>
</van-col>
<van-col span="8">
<router-link to="/category">
<van-icon name="http://static.iocoder.cn/icons8-sun-45.png" />
<div style="font-size:12px;margin-top: -10px;">热卖</div>
</router-link>
</van-col>
<van-col span="8">
<router-link to="/category">
<van-icon name="http://static.iocoder.cn/icons8-new-45.png" />
<div style="font-size:12px;margin-top: -10px;">新品</div>
</router-link>
</van-col>
</van-row>
<van-panel title="新品推荐" > <van-panel title="新品推荐" >
<!-- <product :data="productRecommends['1']" ></product>--> <!-- <product :data="productRecommends['1']" ></product>-->
<div v-for="(product,i) in productRecommends['1']" :key="i"> <div style="height: 70px;" v-for="(product,i) in productRecommends['1']" :key="i">
<product-card :product='product' @click="showProduct(product)" /> <product-card :product='product' @click="showProduct(product)" />
</div> </div>
</van-panel> </van-panel>
<van-panel title="热卖推荐"> <van-panel title="热卖推荐">
<div v-for="(product,i) in productRecommends['2']" :key="i"> <div style="height: 70px;" v-for="(product,i) in productRecommends['2']" :key="i">
<product-card :product='product' @click="showProduct(product)" /> <product-card :product='product' @click="showProduct(product)" />
</div> </div>
</van-panel> </van-panel>
@ -39,7 +61,6 @@ import cube from "../../components/page/cube.vue";
import imageAd from "../../components/page/imageAd.vue"; import imageAd from "../../components/page/imageAd.vue";
import imageText from "../../components/page/imageText.vue"; import imageText from "../../components/page/imageText.vue";
import product from "../../components/page/product.vue"; import product from "../../components/page/product.vue";
import { GetPage } from "../../api/page.js";
import {getBannerList, getProductRecommendList} from '../../api/promotion.js'; import {getBannerList, getProductRecommendList} from '../../api/promotion.js';
export default { export default {

View File

@ -174,6 +174,7 @@
import {getProductSpuInfo} from '../../api/product'; import {getProductSpuInfo} from '../../api/product';
import {addCart, countCart, getCartCalcSkuPrice} from '../../api/order'; import {addCart, countCart, getCartCalcSkuPrice} from '../../api/order';
import {Dialog} from 'vant'; import {Dialog} from 'vant';
import {checkLogin} from "../../utils/cache";
export default { export default {
components: {}, components: {},
@ -316,6 +317,13 @@
}); });
}, },
onAddCartClicked(data) { onAddCartClicked(data) {
if (!checkLogin()) {
Dialog.alert({
title: '系统提示',
message: '未登陆用户,暂时不支持使用购物车',
});
return;
}
const { selectedNum } = data; const { selectedNum } = data;
// debugger; // debugger;
addCart(data.selectedSkuComb.id,selectedNum).then(data => { addCart(data.selectedSkuComb.id,selectedNum).then(data => {
@ -398,10 +406,12 @@
this.doCalcSkuPrice(this.initialSku.id); this.doCalcSkuPrice(this.initialSku.id);
}); });
// //
if (checkLogin()) {
countCart().then(data => { countCart().then(data => {
this.cartCount = data; this.cartCount = data;
}) })
} }
}
}; };
</script> </script>

View File

@ -39,15 +39,16 @@
</van-cell-group> </van-cell-group>
<div style="height:15px;"></div> <div style="height:15px;"></div>
<van-cell-group class="total"> <van-cell-group class="total">
<van-cell title="商品总额" :value="orderInfo.price"/> <van-cell title="商品总额" :value="orderInfo.buyPrice / 100.0"/>
<van-cell title="运费" :value="'+' + orderInfo.logisticsPrice / 100"/> <van-cell title="运费" :value="'+' + orderInfo.logisticsPrice / 100.0"/>
<van-cell title="实付金额" :value="orderInfo.payAmount" style="font-weight: 700;"/> <van-cell title="折扣" :value="- orderInfo.discountPrice / 100.0"/>
<van-cell title="实付金额" :value="orderInfo.presentPrice / 100.0" style="font-weight: 700;"/>
</van-cell-group> </van-cell-group>
<div class="footer"> <div class="footer">
<div class="munu"> <div class="munu">
<van-button v-if="orderInfo.status === 3 " size="small">退货</van-button> <van-button v-if="orderInfo.status === 3 " size="small">退货</van-button>
<van-button v-if="orderInfo.status === 3 " size="small" v-on:click="clickConfirmReceiving(orderId)">确认收货</van-button> <van-button v-if="orderInfo.status === 3 " size="small" v-on:click="clickConfirmReceiving(orderId)">确认收货</van-button>
<van-button v-if="orderInfo.status === 1 " size="small" type="danger">支付</van-button> <van-button v-if="orderInfo.status === 1 " size="small" type="danger" @click="goPay(orderInfo.id)">支付</van-button>
</div> </div>
</div> </div>
</div> </div>
@ -88,6 +89,9 @@
this.queryOrderPage(this.queryParams) this.queryOrderPage(this.queryParams)
}) })
}, },
goPay(itemId) {
this.$router.push('/pay?appId=POd4RC6a&orderId=' + itemId + '&returnUrl=' + encodeURI('/user/order/info/' + itemId));
},
}, },
mounted() { mounted() {
const { id } = this.$route.params; const { id } = this.$route.params;

View File

@ -33,7 +33,7 @@
</router-link> </router-link>
</div> </div>
<div slot="footer" class="footer"> <div slot="footer" class="footer">
<span class="total">总价{{item.payAmount / 100}} </span> <span class="total">总价{{item.presentPrice / 100}} </span>
<router-link :to="'/user/order/logistics/'+item.orderid"> <router-link :to="'/user/order/logistics/'+item.orderid">
<van-button v-if="[3,4,5].indexOf(item.status) != -1" size="small">查看物流</van-button> <van-button v-if="[3,4,5].indexOf(item.status) != -1" size="small">查看物流</van-button>
</router-link> </router-link>
@ -114,7 +114,7 @@
state: `${statusArray[order.status]}`, state: `${statusArray[order.status]}`,
status: order.status, status: order.status,
products, products,
payAmount: order.payAmount, presentPrice: order.presentPrice,
}; };
}); });

View File

@ -3,32 +3,41 @@
// localStorage 操作 // localStorage 操作
const cacheKeys = { const cacheKeys = {
accessTokenKey: 'accessToken', ACCESS_TOKEN: 'accessToken',
refreshTokenKey: 'refreshToken', REFRESH_TOKEN: 'refreshToken',
}; };
/// ///
/// 设置 loginToken分为 accessToken 和 refreshToken /// 设置 loginToken分为 accessToken 和 refreshToken
export function checkLogin() {
let accessToken = getAccessToken();
return accessToken && accessToken.length > 0;
}
export function setLoginToken(accessToken, refreshToken) { export function setLoginToken(accessToken, refreshToken) {
setLocalStorage(cacheKeys.accessTokenKey, accessToken); setLocalStorage(cacheKeys.ACCESS_TOKEN, accessToken);
setLocalStorage(cacheKeys.refreshTokenKey, refreshToken); setLocalStorage(cacheKeys.REFRESH_TOKEN, refreshToken);
} }
export function getLoginToken() { export function getLoginToken() {
const res = {}; const res = {};
res[cacheKeys.accessTokenKey] = getLocalStorage(cacheKeys.accessTokenKey); res[cacheKeys.ACCESS_TOKEN] = getLocalStorage(cacheKeys.ACCESS_TOKEN);
res[cacheKeys.refreshTokenKey] = getLocalStorage(cacheKeys.refreshTokenKey); res[cacheKeys.REFRESH_TOKEN] = getLocalStorage(cacheKeys.REFRESH_TOKEN);
return res; return res;
} }
export function clearLoginToken() { export function clearLoginToken() {
removeLocalStorage(cacheKeys.accessTokenKey); removeLocalStorage(cacheKeys.ACCESS_TOKEN);
removeLocalStorage(cacheKeys.refreshTokenKey); removeLocalStorage(cacheKeys.REFRESH_TOKEN);
} }
export function getAccessToken() { export function getAccessToken() {
return getLocalStorage(cacheKeys.accessTokenKey); return getLocalStorage(cacheKeys.ACCESS_TOKEN);
}
export function getRefreshToken() {
return getLocalStorage(cacheKeys.REFRESH_TOKEN);
} }
/// ///

View File

@ -13,6 +13,7 @@ import cn.iocoder.mall.order.application.vo.UsersCartDetailVO;
import cn.iocoder.mall.order.application.vo.UsersOrderConfirmCreateVO; import cn.iocoder.mall.order.application.vo.UsersOrderConfirmCreateVO;
import cn.iocoder.mall.promotion.api.CouponService; import cn.iocoder.mall.promotion.api.CouponService;
import cn.iocoder.mall.promotion.api.bo.CouponCardAvailableBO; 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 cn.iocoder.mall.user.sdk.context.UserSecurityContextHolder;
import com.alibaba.dubbo.config.annotation.Reference; import com.alibaba.dubbo.config.annotation.Reference;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -144,6 +145,7 @@ public class UsersCartController {
} }
@GetMapping("/calc_sku_price") @GetMapping("/calc_sku_price")
@PermitAll
public CommonResult<UsersCalcSkuPriceVO> calcSkuPrice(@RequestParam("skuId") Integer skuId) { public CommonResult<UsersCalcSkuPriceVO> calcSkuPrice(@RequestParam("skuId") Integer skuId) {
// 计算 sku 的价格 // 计算 sku 的价格
CommonResult<CalcSkuPriceBO> calcSkuPriceResult = cartService.calcSkuPrice(skuId); CommonResult<CalcSkuPriceBO> calcSkuPriceResult = cartService.calcSkuPrice(skuId);

View File

@ -25,16 +25,32 @@ public class OrderBO implements Serializable {
* 用户编号 * 用户编号
*/ */
private Integer userId; private Integer userId;
/**
* 物流id
*/
private Integer orderLogisticsId;
/** /**
* 订单编号 * 订单编号
*/ */
private String orderNo; private String orderNo;
/** /**
* 交易金额 * 购买商品总金额单位
*/
private Integer buyPrice;
/**
* 优惠总金额单位
*/
private Integer discountPrice;
/**
* 物流金额 ()
*/
private Integer logisticsPrice;
/**
* 最终金额单位
*
* buyPrice + logisticsPrice - discountPrice = presentPrice
*/
private Integer presentPrice;
/**
* 实际已支付金额单位
*
* 初始时金额为 0 等到支付成功后会进行更新
*/ */
private Integer payAmount; private Integer payAmount;

View File

@ -16,22 +16,39 @@ import java.util.Date;
@Accessors(chain = true) @Accessors(chain = true)
public class OrderInfoBO implements Serializable { public class OrderInfoBO implements Serializable {
/**
* id
*/
private Integer id;
/** /**
* 订单编号 * 订单编号
*/ */
private String orderNo; private String orderNo;
/** /**
* 价格() * 购买商品总金额单位
*/ */
private Integer price; private Integer buyPrice;
/** /**
* 交易金额 * 优惠总金额单位
*/ */
private Integer payAmount; private Integer discountPrice;
/** /**
* 物流金额 () * 物流金额 ()
*/ */
private Integer logisticsPrice; private Integer logisticsPrice;
/**
* 最终金额单位
*
* buyPrice + logisticsPrice - discountPrice = presentPrice
*/
private Integer presentPrice;
/**
* 实际已支付金额单位
*
* 初始时金额为 0 等到支付成功后会进行更新
*/
private Integer payAmount;
/** /**
* 付款时间待发货 * 付款时间待发货
*/ */

View File

@ -85,6 +85,12 @@
<groupId>io.springfox</groupId> <groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId> <artifactId>springfox-swagger-ui</artifactId>
</dependency> </dependency>
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>user-sdk</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>

View File

@ -5,6 +5,7 @@ import cn.iocoder.mall.product.api.ProductCategoryService;
import cn.iocoder.mall.product.api.bo.ProductCategoryBO; import cn.iocoder.mall.product.api.bo.ProductCategoryBO;
import cn.iocoder.mall.product.application.convert.ProductCategoryConvert; import cn.iocoder.mall.product.application.convert.ProductCategoryConvert;
import cn.iocoder.mall.product.application.vo.users.UsersProductCategoryVO; import cn.iocoder.mall.product.application.vo.users.UsersProductCategoryVO;
import cn.iocoder.mall.user.sdk.annotation.PermitAll;
import com.alibaba.dubbo.config.annotation.Reference; import com.alibaba.dubbo.config.annotation.Reference;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParam;
@ -14,7 +15,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.annotation.security.PermitAll;
import java.util.List; import java.util.List;
@RestController @RestController

View File

@ -7,6 +7,7 @@ import cn.iocoder.mall.product.api.dto.ProductSpuPageDTO;
import cn.iocoder.mall.product.application.convert.ProductSpuConvert; 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.UsersProductSpuDetailVO;
import cn.iocoder.mall.product.application.vo.users.UsersProductSpuPageVO; import cn.iocoder.mall.product.application.vo.users.UsersProductSpuPageVO;
import cn.iocoder.mall.user.sdk.annotation.PermitAll;
import com.alibaba.dubbo.config.annotation.Reference; import com.alibaba.dubbo.config.annotation.Reference;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParam;
@ -17,8 +18,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.annotation.security.PermitAll;
@RestController @RestController
@RequestMapping("users/spu") @RequestMapping("users/spu")
@Api("商品 SPU + SKU") @Api("商品 SPU + SKU")

View File

@ -6,6 +6,7 @@ import cn.iocoder.mall.promotion.api.BannerService;
import cn.iocoder.mall.promotion.api.bo.BannerBO; import cn.iocoder.mall.promotion.api.bo.BannerBO;
import cn.iocoder.mall.promotion.application.convert.BannerConvert; import cn.iocoder.mall.promotion.application.convert.BannerConvert;
import cn.iocoder.mall.promotion.application.vo.users.UsersBannerVO; import cn.iocoder.mall.promotion.application.vo.users.UsersBannerVO;
import cn.iocoder.mall.user.sdk.annotation.PermitAll;
import com.alibaba.dubbo.config.annotation.Reference; import com.alibaba.dubbo.config.annotation.Reference;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
@ -13,7 +14,6 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.annotation.security.PermitAll;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;

View File

@ -8,6 +8,7 @@ import cn.iocoder.mall.promotion.api.ProductRecommendService;
import cn.iocoder.mall.promotion.api.bo.ProductRecommendBO; import cn.iocoder.mall.promotion.api.bo.ProductRecommendBO;
import cn.iocoder.mall.promotion.application.convert.ProductRecommendConvert; import cn.iocoder.mall.promotion.application.convert.ProductRecommendConvert;
import cn.iocoder.mall.promotion.application.vo.users.UsersProductRecommendVO; import cn.iocoder.mall.promotion.application.vo.users.UsersProductRecommendVO;
import cn.iocoder.mall.user.sdk.annotation.PermitAll;
import com.alibaba.dubbo.config.annotation.Reference; import com.alibaba.dubbo.config.annotation.Reference;
import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
@ -17,7 +18,6 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.annotation.security.PermitAll;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;

View File

@ -2,12 +2,13 @@ package cn.iocoder.mall.user.application.controller.users;
import cn.iocoder.common.framework.vo.CommonResult; import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.user.application.convert.PassportConvert; import cn.iocoder.mall.user.application.convert.PassportConvert;
import cn.iocoder.mall.user.application.vo.users.UsersAccessTokenVO;
import cn.iocoder.mall.user.sdk.annotation.PermitAll; import cn.iocoder.mall.user.sdk.annotation.PermitAll;
import cn.iocoder.mall.user.api.MobileCodeService; import cn.iocoder.mall.user.api.MobileCodeService;
import cn.iocoder.mall.user.api.OAuth2Service; import cn.iocoder.mall.user.api.OAuth2Service;
import cn.iocoder.mall.user.api.UserService; import cn.iocoder.mall.user.api.UserService;
import cn.iocoder.mall.user.api.bo.OAuth2AccessTokenBO; import cn.iocoder.mall.user.api.bo.OAuth2AccessTokenBO;
import cn.iocoder.mall.user.application.vo.users.MobileRegisterVO; import cn.iocoder.mall.user.application.vo.users.UsersMobileRegisterVO;
import com.alibaba.dubbo.config.annotation.Reference; import com.alibaba.dubbo.config.annotation.Reference;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParam;
@ -44,7 +45,7 @@ public class PassportController {
@ApiImplicitParam(name = "mobile", value = "手机号", required = true, example = "15601691300"), @ApiImplicitParam(name = "mobile", value = "手机号", required = true, example = "15601691300"),
@ApiImplicitParam(name = "code", value = "验证码", required = true, example = "9999") @ApiImplicitParam(name = "code", value = "验证码", required = true, example = "9999")
}) })
public CommonResult<MobileRegisterVO> mobileRegister(@RequestParam("mobile") String mobile, public CommonResult<UsersMobileRegisterVO> mobileRegister(@RequestParam("mobile") String mobile,
@RequestParam("code") String code) { @RequestParam("code") String code) {
CommonResult<OAuth2AccessTokenBO> result = oauth2Service.getAccessToken(mobile, code); CommonResult<OAuth2AccessTokenBO> result = oauth2Service.getAccessToken(mobile, code);
return PassportConvert.INSTANCE.convert(result); return PassportConvert.INSTANCE.convert(result);
@ -74,7 +75,12 @@ public class PassportController {
return null; return null;
} }
// TODO 功能刷新 token @PermitAll
@PostMapping("/refresh_token") // TODO 功能刷新 token
public CommonResult<UsersAccessTokenVO> refreshToken(@RequestParam("refreshToken") String refreshToken) {
CommonResult<OAuth2AccessTokenBO> result = oauth2Service.refreshToken(refreshToken);
return PassportConvert.INSTANCE.convert2(result);
}
// TODO 功能退出销毁 token // TODO 功能退出销毁 token
} }

View File

@ -2,7 +2,8 @@ package cn.iocoder.mall.user.application.convert;
import cn.iocoder.common.framework.vo.CommonResult; import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.user.api.bo.OAuth2AccessTokenBO; import cn.iocoder.mall.user.api.bo.OAuth2AccessTokenBO;
import cn.iocoder.mall.user.application.vo.users.MobileRegisterVO; 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.Mapper;
import org.mapstruct.Mappings; import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
@ -13,9 +14,12 @@ public interface PassportConvert {
PassportConvert INSTANCE = Mappers.getMapper(PassportConvert.class); PassportConvert INSTANCE = Mappers.getMapper(PassportConvert.class);
@Mappings({}) @Mappings({})
MobileRegisterVO convert(OAuth2AccessTokenBO oauth2AccessTokenBO); UsersMobileRegisterVO convert(OAuth2AccessTokenBO oauth2AccessTokenBO);
@Mappings({}) @Mappings({})
CommonResult<MobileRegisterVO> convert(CommonResult<OAuth2AccessTokenBO> oauth2AccessTokenBO); CommonResult<UsersMobileRegisterVO> convert(CommonResult<OAuth2AccessTokenBO> oauth2AccessTokenBO);
@Mappings({})
CommonResult<UsersAccessTokenVO> convert2(CommonResult<OAuth2AccessTokenBO> result);
} }

View File

@ -0,0 +1,20 @@
package cn.iocoder.mall.user.application.vo.users;
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 UsersAccessTokenVO {
@ApiModelProperty(value = "访问令牌", required = true, example = "2e3d7635c15e47e997611707a237859f")
private String accessToken;
@ApiModelProperty(value = "刷新令牌", required = true, example = "d091e7c35bbb4313b0f557a6ef23d033")
private String refreshToken;
@ApiModelProperty(value = "过期时间,单位:秒", required = true, example = "2879")
private Integer expiresIn;
}

View File

@ -8,7 +8,7 @@ import lombok.experimental.Accessors;
@ApiModel("手机注册结果 VO") @ApiModel("手机注册结果 VO")
@Data @Data
@Accessors(chain = true) @Accessors(chain = true)
public class MobileRegisterVO { public class UsersMobileRegisterVO {
@ApiModelProperty(value = "访问令牌", required = true, example = "2e3d7635c15e47e997611707a237859f") @ApiModelProperty(value = "访问令牌", required = true, example = "2e3d7635c15e47e997611707a237859f")
private String accessToken; private String accessToken;

View File

@ -17,7 +17,7 @@ public interface OAuth2Service {
*/ */
CommonResult<OAuth2AuthenticationBO> checkToken(String accessToken); CommonResult<OAuth2AuthenticationBO> checkToken(String accessToken);
// TODO @see 刷新 token CommonResult<OAuth2AccessTokenBO> refreshToken(String refreshToken);
// TODO @see 移除 token // TODO @see 移除 token

View File

@ -12,10 +12,13 @@ public enum UserErrorCodeEnum {
OAUTH2_INVALID_GRANT_BAD_CREDENTIALS(1001001001, "密码不正确"), // 暂时没用到 OAUTH2_INVALID_GRANT_BAD_CREDENTIALS(1001001001, "密码不正确"), // 暂时没用到
OAUTH2_INVALID_GRANT_USERNAME_NOT_FOUND(1001001002, "账号不存在"), // 暂时没用到 OAUTH2_INVALID_GRANT_USERNAME_NOT_FOUND(1001001002, "账号不存在"), // 暂时没用到
OAUTH2_INVALID_GRANT(1001001010, ""), // 预留 OAUTH2_INVALID_GRANT(1001001010, ""), // 预留
OAUTH_INVALID_TOKEN_NOT_FOUND(1001001011, "访问令牌不存在"), OAUTH_INVALID_ACCESS_TOKEN_NOT_FOUND(1001001011, "访问令牌不存在"),
OAUTH_INVALID_TOKEN_EXPIRED(1001001012, "访问令牌已过期"), OAUTH_INVALID_ACCESS_TOKEN_EXPIRED(1001001012, "访问令牌已过期"),
OAUTH_INVALID_TOKEN_INVALID(1001001013, "访问令牌已失效"), OAUTH_INVALID_ACCESS_TOKEN_INVALID(1001001013, "访问令牌已失效"),
OAUTH_INVALID_TOKEN(1001001020, ""), // 预留 OAUTH_INVALID_REFRESH_TOKEN(1001001020, ""), // 预留
OAUTH_INVALID_REFRESH_TOKEN_NOT_FOUND(1001001021, "刷新令牌不存在"),
OAUTH_INVALID_REFRESH_TOKEN_EXPIRED(1001001022, "访问令牌已过期"),
OAUTH_INVALID_REFRESH_TOKEN_INVALID(1001001023, "刷新令牌已失效"),
// ========== 用户模块 ========== // ========== 用户模块 ==========
USER_MOBILE_NOT_REGISTERED(1001002000, "手机号未注册用户"), USER_MOBILE_NOT_REGISTERED(1001002000, "手机号未注册用户"),

View File

@ -13,4 +13,6 @@ public interface OAuth2AccessTokenMapper {
void updateToInvalidByUserId(@Param("userId") Integer userId); void updateToInvalidByUserId(@Param("userId") Integer userId);
void updateToInvalidByRefreshToken(@Param("refreshToken") String refreshToken);
} }

View File

@ -11,4 +11,6 @@ public interface OAuth2RefreshTokenMapper {
void updateToInvalidByUserId(@Param("userId") Integer userId); void updateToInvalidByUserId(@Param("userId") Integer userId);
OAuth2RefreshTokenDO selectById(@Param("id") String id);
} }

View File

@ -82,18 +82,39 @@ public class OAuth2ServiceImpl implements OAuth2Service {
public CommonResult<OAuth2AuthenticationBO> checkToken(String accessToken) throws ServiceException { public CommonResult<OAuth2AuthenticationBO> checkToken(String accessToken) throws ServiceException {
OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectByTokenId(accessToken); OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectByTokenId(accessToken);
if (accessTokenDO == null) { // 不存在 if (accessTokenDO == null) { // 不存在
return ServiceExceptionUtil.error(UserErrorCodeEnum.OAUTH_INVALID_TOKEN_NOT_FOUND.getCode()); return ServiceExceptionUtil.error(UserErrorCodeEnum.OAUTH_INVALID_ACCESS_TOKEN_NOT_FOUND.getCode());
} }
if (accessTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期 if (accessTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期
return ServiceExceptionUtil.error(UserErrorCodeEnum.OAUTH_INVALID_TOKEN_EXPIRED.getCode()); return ServiceExceptionUtil.error(UserErrorCodeEnum.OAUTH_INVALID_ACCESS_TOKEN_EXPIRED.getCode());
} }
if (!accessTokenDO.getValid()) { // 无效 if (!accessTokenDO.getValid()) { // 无效
return ServiceExceptionUtil.error(UserErrorCodeEnum.OAUTH_INVALID_TOKEN_INVALID.getCode()); return ServiceExceptionUtil.error(UserErrorCodeEnum.OAUTH_INVALID_ACCESS_TOKEN_INVALID.getCode());
} }
// 转换返回 // 转换返回
return CommonResult.success(OAuth2Convert.INSTANCE.convertToAuthentication(accessTokenDO)); return CommonResult.success(OAuth2Convert.INSTANCE.convertToAuthentication(accessTokenDO));
} }
@Override
public CommonResult<OAuth2AccessTokenBO> refreshToken(String refreshToken) {
OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectById(refreshToken);
// 校验刷新令牌是否合法
if (refreshTokenDO == null) { // 不存在
return ServiceExceptionUtil.error(UserErrorCodeEnum.OAUTH_INVALID_REFRESH_TOKEN_NOT_FOUND.getCode());
}
if (refreshTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期
return ServiceExceptionUtil.error(UserErrorCodeEnum.OAUTH_INVALID_REFRESH_TOKEN_EXPIRED.getCode());
}
if (!refreshTokenDO.getValid()) { // 无效
return ServiceExceptionUtil.error(UserErrorCodeEnum.OAUTH_INVALID_REFRESH_TOKEN_INVALID.getCode());
}
// 标记 refreshToken 对应的 accessToken 都不合法
oauth2AccessTokenMapper.updateToInvalidByRefreshToken(refreshToken);
// 创建访问令牌
OAuth2AccessTokenDO oauth2AccessTokenDO = createOAuth2AccessToken(refreshTokenDO.getUserId(), refreshTokenDO.getId());
// 转换返回
return CommonResult.success(OAuth2Convert.INSTANCE.convertToAccessTokenWithExpiresIn(oauth2AccessTokenDO));
}
/** /**
* 移除用户对应的 Token * 移除用户对应的 Token
* *

View File

@ -26,4 +26,11 @@
AND valid = 1 AND valid = 1
</update> </update>
<update id="updateToInvalidByRefreshToken" parameterType="String">
UPDATE oauth2_access_token
SET valid = 0
WHERE refresh_token = #{refreshToken}
AND valid = 1
</update>
</mapper> </mapper>

View File

@ -17,4 +17,11 @@
AND valid = 1 AND valid = 1
</update> </update>
<select id="selectById" parameterType="String" resultType="OAuth2RefreshTokenDO">
SELECT
id, user_id, valid, expires_time, create_time
FROM oauth2_refresh_token
WHERE id = #{id}
</select>
</mapper> </mapper>