推荐微服务编码
推荐微服务的源代码是在 newbee-mall-cloud-dev-step08
工程的基础上改造的,将工程命名为 newbee-mall-cloud-dev-step09
。
推荐微服务调用用户微服务编码实践
与商品微服务一样,在推荐微服务中也需要对请求中的 token
字段进行处理,因此需要远程调用用户微服务。
第一步,增加配置,启用 OpenFeign
并使 FeignClient
类生效。
由于已经引入 LoadBalancer
依赖和 user-api
依赖,因此这里可以直接通过 OpenFeign
来调用用户微服务中的接口用于鉴权。
打开 newbee-mall-cloud-recommend-web
工程,在项目的启动类 NewBeeMallCloudRecommendServiceApplication
上添加 @EnableFeignClients
注解,并配置相关的 FeignClient
类,代码如下:
@EnableFeignClients(basePackageClasses = {ltd.user.cloud.newbee.openfeign.NewBeeCloudAdminUserServiceFeign.class})
这里使用 basePackageClasses
配置了需要的 FeignClient
类,即 NewBeeCloudAdminUserServiceFeign
类。接下来就可以直接使用 NewBeeCloudAdminUserServiceFeign
与用户微服务进行远程通信了。
第二步,修改 token
处理类中的逻辑代码。
打开 newbee-mall-cloud-recommend-web
工程,修改 TokenToAdminUserMethodArgumentResolver
类中对 token
字段处理的逻辑代码,主要引入 NewBeeCloudAdminUserServiceFeign
类,通过调用用户微服务来获取管理员用户的数据。
修改后的代码如下:
@Component
public class TokenToAdminUserMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Autowired
private NewBeeCloudAdminUserServiceFeign newBeeCloudAdminUserService;
public TokenToAdminUserMethodArgumentResolver() {
}
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(TokenToAdminUser.class)) {
return true;
}
return false;
}
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
if (parameter.getParameterAnnotation(TokenToAdminUser.class)
instanceof TokenToAdminUser) {
String token = webRequest.getHeader("token");
if (null != token && !"".equals(token) && token.length() == 32) {
// 通过用户微服务获取用户信息
Result result = new BeesCloudAdminUserService().getAdminUserByToken(token);
if (result == null || result.getResultCode() != 200 ||
result.getData() == null) {
throw new NewBeeMallException.fail("ADMIN_NOT_LOGIN_ERROR");
}
LinkedHashMap resultData = (LinkedHashMap) result.getData();
// 将返回的对象封装到LoginAdminUser对象中
LoginAdminUser loginAdminUser = new LoginAdminUser();
loginAdminUser.setAdminUserId(Long.valueOf(resultData.get("adminUserId").toString()));
loginAdminUser.setLoginUserName((String) resultData.get("loginUserName"));
loginAdminUser.setNickName((String) resultData.get("nickName"));
loginAdminUser.setLocked(Byte.valueOf(resultData.get("locked").toString()));
return loginAdminUser;
} else {
throw new NewBeeMallException.fail("ADMIN_NOT_LOGIN_ERROR");
}
}
return null;
}
}
这里的编码和代码逻辑与商品微服务中的 token
处理类的编码和代码逻辑一致。如此,便完成了在推荐微服务中通过远程通信获取当前登录用户的功能。
推荐微服务编码
下面补充推荐微服务中的业务代码,主要把原来单体项目中的功能模块整合到推荐微服务中。
打开推荐微服务 newbee-mall-cloud-recommend-web
的工程目录,在 ltd.recommend.cloud.newbee
包下依次创建 config
包、dao
包、entity
包、service
包,在 resources
目录下新增 mapper
文件夹用于存放 Mapper
文件。直接将原单体 API
项目中与轮播图管理和商品推荐管理相关的业务代码及 Mapper
文件依次复制过来,如图 5-7 所示。

由于代码量较大,这里就不一一介绍了,按照对应的文件目录复制过来即可。上述步骤完成后,最终的目录结构如图 5-8 所示。

最后,修改 newbee-mall-cloud-recommend-web
工程的 application.properties
配置文件,主要是数据库连接参数及 MyBatis
扫描配置,代码如下:
# datasource config (MySQL)
spring.datasource.name=newbee-mall-cloud-recommend-datasource
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/newbee_mall_cloud_recommend_db?useUnicode=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8&autoReconnect=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=15
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=60000
spring.datasource.hikari.pool-name=hikariCP
spring.datasource.hikari.max-lifetime=600000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.connection-test-query=SELECT 1
# mybatis config
mybatis.mapper-locations=classpath:mapper/*Mapper.xml
本步骤中的源代码涉及的数据库为 newbee_mall_cloud_recommend_db
,数据库表为 tb_newbee_mall_index_config
和 tb_newbee_mall_carousel
。
推荐微服务中虽然创建了 recommend-api
模块,但是该微服务中暂时没有需要暴露的接口,因此并没有在该模块中编码。
最后,打开 newbee-mall-cloud-gateway-admin
项目中的 application.properties
文件,新增关于推荐微服务的路由信息,配置项为 spring.cloud.gateway.routes.*
,新增内容如下:
# 推荐微服务的路由配置-1
spring.cloud.gateway.routes[3].id=carousels-admin-service-route
spring.cloud.gateway.routes[3].uri=lb://newbee-mall-cloud-recommend-service
spring.cloud.gateway.routes[3].order=1
spring.cloud.gateway.routes[3].predicates[0]=Path=/carousels/admin/**
# 推荐微服务的路由配置-2
spring.cloud.gateway.routes[4].id=indexConfigs-admin-service-route
spring.cloud.gateway.routes[4].uri=lb://newbee-mall-cloud-recommend-service
spring.cloud.gateway.routes[4].order=1
spring.cloud.gateway.routes[4].predicates[0]=Path=/indexConfigs/admin/**
这里主要配置 newbee-mall-cloud-gateway-admin
到推荐微服务的路由信息。如果访问网关项目的路径是以 /carousels/admin
或 /indexConfigs/admin
开头的,就会被路由到推荐微服务实例。
推荐微服务远程调用商品微服务编码实践
按照上述步骤对推荐微服务进行编码后,代码中依然会标红。被标红的代码及注释如下:
@Override
// 添加首页配置数据
public String saveIndexConfig(IndexConfig indexConfig) {
// 根据参数 goodsId 查询数据库中对应的商品数据是否存在
if (goodsMapper.selectByPrimaryKey(indexConfig.getGoodsId()) == null) {
// 若商品数据不存在,则直接返回错误提示
return ServiceResultEnum.GOODS_NOT_EXIST.getResult();
}
if (indexConfigMapper.selectByTypeAndGoodsId(indexConfig.getConfigType(),
indexConfig.getGoodsId()) != null) {
return ServiceResultEnum.SAME_INDEX_CONFIG_EXIST.getResult();
}
if (indexConfigMapper.insertSelective(indexConfig) > 0) {
return ServiceResultEnum.SUCCESS.getResult();
}
return ServiceResultEnum.DB_ERROR.getResult();
}
@Override
// 修改首页配置数据
public String updateIndexConfig(IndexConfig indexConfig) {
// 根据参数 goodsId 查询数据库中对应的商品数据是否存在
if (goodsMapper.selectByPrimaryKey(indexConfig.getGoodsId()) == null) {
// 若商品数据不存在,则直接返回错误提示
return ServiceResultEnum.GOODS_NOT_EXIST.getResult();
}
IndexConfig temp =
indexConfigMapper.selectByPrimaryKey(indexConfig.getConfigId());
if (temp == null) {
return ServiceResultEnum.DATA_NOT_EXIST.getResult();
}
IndexConfig temp2 =
indexConfigMapper.selectByTypeAndGoodsId(indexConfig.getConfigType(),
indexConfig.getGoodsId());
if (temp2 != null
&& !temp2.getConfigId().equals(indexConfig.getConfigId())) {
//若goodsId相同但id不同,则不能继续修改
return ServiceResultEnum.SAME_INDEX_CONFIG_EXIST.getResult();
}
indexConfig.setUpdateTime(new Date());
if (indexConfigMapper.updateByPrimaryKeySelective(indexConfig) > 0) {
return ServiceResultEnum.SUCCESS.getResult();
}
return ServiceResultEnum.DB_ERROR.getResult();
}
在新增或修改一条 IndexConfig
数据时,需要对所传送的参数 goodsId
进行验证,即判断需要入库的 IndexConfig
数据中的商品是否存在,如果不存在,则标识所传送的参数不正确。因为推荐微服务未连接商品表所在的数据库,无法直接通过 GoodsMapper
去查询对应的商品数据,所以这部分代码会被标红。
这里必须进行商品数据判断逻辑的修改,由原本直接查询商品表改为远程调用商品微服务中的接口来完成这个逻辑。推荐微服务不仅要与用户微服务通信,还要与商品微服务通信。这里的代码改造步骤如下。
第一步,引入 goods-api
依赖。
打开 newbee-mall-cloud-recommend-web
工程下的 pom.xml
文件,增加与商品微服务远程通信所需的 newbee-mall-cloud-goods-api
模块,新增依赖配置如下:
<dependency>
<groupId>ltd.goods.newbee.cloud</groupId>
<artifactId>newbee-mall-cloud-goods-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
第二步,增加关于商品微服务中 FeignClient
的配置。
打开 newbee-mall-cloud-goods-web
工程,对启动类 NewBeeMallCloudRecommendServiceApplication
中的 @EnableFeignClients
注解进行修改,增加对 NewBeeCloudGoodsServiceFeign
的声明,代码如下:
@EnableFeignClients(basePackageClasses = {
ltd.user.cloud.newbee.openfeign.NewBeeCloudAdminUserServiceFeign.class,
ltd.goods.cloud.newbee.openfeign.NewBeeCloudGoodsServiceFeign.class})
接下来就可以直接使用 NewBeeCloudGoodsServiceFeign
与商品微服务进行远程通信了。
第三步,修改商品数据判断逻辑的代码。
打开 newbee-mall-cloud-recommend-web
工程,修改 NewBeeMallIndexConfigServiceImpl
类中商品数据判断逻辑的代码。删除原本直接查询商品数据库的代码,之后注入 NewBeeCloudGoodsServiceFeign
类,并通过调用商品微服务来获取商品数据,代码如下:
@Autowired
private NewBeeCloudGoodsServiceFeign goodsService;
@Override
// 添加首页配置数据
public String saveIndexConfig(IndexConfig indexConfig) {
// 根据参数 goodsId 调用商品微服务中的接口查询对应的商品数据是否存在
Result<NewBeeMallGoodsDTO> goodsDetailResult =
goodsService.getGoodsDetail(indexConfig.getGoodsId());
if (goodsDetailResult == null || goodsDetailResult.getResultCode() != 200) {
// 若商品数据不存在,则直接返回错误提示
return ServiceResultEnum.GOODS_NOT_EXIST.getResult();
}
if (indexConfigMapper.selectByTypeAndGoodsId(
indexConfig.getConfigType(), indexConfig.getGoodsId()) != null) {
return ServiceResultEnum.SAME_INDEX_CONFIG_EXIST.getResult();
}
if (indexConfigMapper.insertSelective(indexConfig) > 0) {
return ServiceResultEnum.SUCCESS.getResult();
}
return ServiceResultEnum.DB_ERROR.getResult();
}
@Override
// 修改首页配置数据
public String updateIndexConfig(IndexConfig indexConfig) {
// 根据参数 goodsId 调用商品微服务中的接口查询对应的商品数据是否存在
Result<NewBeeMallGoodsDTO> goodsDetailResult =
goodsService.getGoodsDetail(indexConfig.getGoodsId());
if (goodsDetailResult == null || goodsDetailResult.getResultCode() != 200) {
// 若商品数据不存在,则直接返回错误提示
return ServiceResultEnum.GOODS_NOT_EXIST.getResult();
}
IndexConfig temp = indexConfigMapper.selectByPrimaryKey(indexConfig.getConfigId());
if (temp == null) {
return ServiceResultEnum.DATA_NOT_EXIST.getResult();
}
IndexConfig temp2 = indexConfigMapper.selectByTypeAndGoodsId(indexConfig.getConfigType(), indexConfig.getGoodsId());
if (temp2 != null && !temp2.getConfigId().equals(indexConfig.getConfigId())) {
//若 goodsId 相同但 id 不同,则不能继续修改
return ServiceResultEnum.SAME_INDEX_CONFIG_EXIST.getResult();
}
indexConfig.setUpdateTime(new Date());
if (indexConfigMapper.updateByPrimaryKeySelective(indexConfig) > 0) {
return ServiceResultEnum.SUCCESS.getResult();
}
return ServiceResultEnum.DB_ERROR.getResult();
}
这里调用的就是在商品微服务中所编写并暴露的 /goods/admin/goodsDetail
接口,根据参数 goodsId
查询对应的商品数据。
功能测试
代码修改完成后,测试步骤是不能漏掉的。一定要验证项目是否能正常启动、接口是否能正常调用,防止在代码移动过程中出现一些小问题,导致项目无法启动或代码报错。在项目启动前需要分别启动 Nacos Server
和 Redis Server
,之后依次启动 newbee-mall-cloud-recommend-web
工程、newbee-mall-cloud-user-web
工程、newbee-mall-cloud-goods-web
工程和 newbee-mall-cloud-gateway-admin
工程下的主类。启动成功后,就可以进行本节的功能测试。
打开用户微服务的 Swagger
页面,在浏览器中输入如下网址: http://localhost:29000/swagger-ui/index.html 。
在该页面使用登录接口获取一个 token
值用于后续的功能测试。比如,笔者在测试时获取到一个值为 “2a3d6aa70da7a1bbeb8a83ad74addb93” 的 token
字段。
接下来打开推荐微服务的 Swagger
页面,在浏览器中输入如下网址: http://localhost:29020/swagger-ui/index.html。
这时就可以在 Swagger
提供的 UI
页面进行推荐微服务的接口测试了,推荐微服务接口文档显示内容如图 5-9 所示。

下面演示新增首页配置项接口。单击 “新增首页配置项”→“Try it out” 按钮,在参数栏中输入分类名称、分类等级等字段,在登录认证 token
输入框中输入管理员用户登录接口返回的 token
值,如图 5-10 所示。

单击 “Execute” 按钮,接口的响应结果如图 5-11 所示。

若后端接口的测试结果中有 “SUCCESS”,则表示添加成功。查看推荐微服务的数据库,首页配置项表中已经新增了一条数据。笔者在测试时,输入的字段都是符合规范的。如果输入的参数没有通过基本的验证判断,就会报出对应的错误提示,如商品不存在或重复配置,提示内容如下,读者在测试时需要注意。
{
"resultCode": 500,
"message": "已存在相同的首页配置项!",
"data": null
}
{
"resultCode": 500,
"message": "商品不存在!",
"data": null
}
由于篇幅有限,笔者只演示了一个接口的测试过程,读者在测试时可以看看其他接口。
功能测试完成,并且接口响应一切正常,表示推荐微服务本身的功能编码完成,并且远程调用用户微服务、商品微服务也一切正常。在测试时,读者也可以通过 debug
模式启动项目,打上几个断点来查看接口测试时的完整过程。
不知不觉中已经基本完成 3 个微服务模块和 1 个网关模块的编码、配置和测试,在这些实战章节中,我们不断地运用所学到的微服务知识来 “添砖加瓦”,再结合一些实际的业务代码让微服务知识的应用更加生动,微服务知识加上具体的业务功能使得整个实战项目更加丰富。希望读者能够在实战中更加透彻地理解与微服务相关的知识点、组件,更加得心应手地在实战项目中应用这些知识。本章主要介绍了首页配置项管理功能接口和轮播图管理功能的编码改造,希望读者能够根据笔者提供的开发步骤顺利地完成本章的项目改造。