增加商城用户的相关功能
商城用户模块介绍
新蜂商城包含后台管理系统和商城端。在后台管理系统中可以对各个功能模块进行配置和操作,如轮播图管理、商品管理、订单管理等,对应的用户是管理员用户。管理员用户在后台管理系统的登录页面登录后,才能够在对应的页面进行操作。商城端则是用于完成整个购物流程的系统,该系统包含商城类系统的主要功能,如商品搜索、添加购物车、下单、支付等,对应的用户是商城用户。商城用户需要在商城端自行注册,登录之后就可以完成购物流程了。
新蜂商城有两种用户,分别是后台管理系统的管理员用户和商城端的商城用户,如图 6-1 所示。前面实战章节中讲解的都是后台管理系统相关的功能实现,如商品管理、轮播图管理等,代码中用到的用户是管理员用户。后续章节中将继续完善该实战项目,主要涉及商城端的功能模块,因此需要实现与商城用户相关的代码。

笔者的做法是将两种用户的相关编码实现都放在用户微服务中,编码是直接在 newbee-mall-cloud-user-service
模块中进行的。这是一种实现形式,如果有读者想把用户微服务拆分得更细一些,完全可以拆分为商城用户微服务和管理员用户微服务。
商城用户功能模块编码
本节的源代码是在 newbee-mall-cloud-dev-step09
工程的基础上改造的,名称为 newbee-mall-cloud-dev-step10
。打开用户微服务 newbee-mall-cloud-user-web
的工程目录,将原单体 API
项目中与商城用户相关的业务代码和 Mapper
文件依次复制过来,如图 6-2 所示。

本步骤中的源代码涉及的数据库为 newbee_mall_cloud_user_db
,数据库表为 tb_newbee_mall_user
。
商城用户的表结构和建表语句如下:
USE 'newbee_mall_cloud_user_db';
# 创建商城用户表
CREATE TABLE 'tb_newbee_mall_user' (
'user_id' bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户主键 id',
'nick_name' varchar(50) NOT NULL DEFAULT '' COMMENT '用户昵称',
'login_name' varchar(11) NOT NULL DEFAULT '' COMMENT '登录名称(默认为手机号)',
'password_md5' varchar(32) NOT NULL DEFAULT '' COMMENT 'MD5加密后的密码',
'introduce_sign' varchar(100) NOT NULL DEFAULT '' COMMENT '个性签名',
'is_deleted' tinyint(4) NOT NULL DEFAULT '0' COMMENT '注销标识字段 (0-正常 1-已注销)',
'locked_flag' tinyint(4) NOT NULL DEFAULT '0' COMMENT '锁定标识字段 (0-未锁定 1-已锁定)',
'create_time' timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间',
PRIMARY KEY ('user_id') USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
# 新增育城用户数据
INSERT INTO `tb_newbee_mall_user` (`user_id`, `nick_name`, `login_name`, `password_md5`, `introduce_sign`, `is_deleted`, `locked_flag`, `create_time`)
VALUES
(1, '十三', '13700002703', 'e10adc3949ba59abbe56e057f20f883e', '我怕千万人阻挡,只怕自己投降', 0, 0, '2022-05-22 08:44:57'),
(6, '陈尼克', '13711113333', 'e10adc3949ba59abbe56e057f20f883e', '测试用户陈尼克', 0, 0, '2022-05-22 08:44:57');
除此之外,与用户微服务相关的还有 Redis
数据库。
不过,仅仅复制过来是不够的,还需要对代码进行一些微调。有些工具类已经被移到公用模块 newbee-mall-cloud-common
中,代码中使用这些工具类的地方也需要处理一下引用路径。Controller
类中的接口地址都做了微调,与之前单体项目中定义的 URL
略有不同。
商城用户模块代码完善
商城用户的鉴权实现逻辑也要修改。与管理员用户的处理方式类似,需要对微服务架构下用户鉴权的编码进行改造。实现思路与处理管理员用户的实现思路一致,将原来存储在 token
表中的数据改为存储在 Redis
数据库中,这样在网关层就可以通过读取 Redis
数据库中的数据实现商城用户的鉴权操作。
由于用户微服务模块中已经引入了 Redis
组件,也配置了相关内容,因此在改造商城用户相关逻辑实现时,只处理代码即可。
-
修改登录和退出登录方法
原来的登录逻辑中会生成
MallUserToken
的相关数据并被保存到MySQL
数据库中,现在把这部分数据保存到Redis
数据库中,修改后的登录方法代码如下:public String login(String loginName, String passwordMD5) { MallUser mallUser = mallUserMapper.selectByLoginNameAndPassword(loginName, passwordMD5); if (user != null) { if (user.getLockedFlag() == 1) { return ServiceResultEnum.LOGIN_USER_LOCKED_ERROR.getResult(); } //登录成功后修改 token 的值 String token = getNewToken(System.currentTimeMillis() + "", user.getUserId()); MallUserToken mallUserToken = new MallUserToken(); mallUserToken.setToken(token); mallUserToken.setUserId(user.getUserId()); ValueOperations<String, MallUserToken> setToken = redisTemplate.opsForValue(); setToken.set(token, mallUserToken, 7 * 24 * 60 * 60, TimeUnit.SECONDS); //过期时间为 7 天 return token; } return ServiceResultEnum.LOGIN_ERROR.getResult(); }
商城用户不仅有登录方法,还有退出登录方法,所以还要对
logout()
方法进行修改,调整后的代码如下:public Boolean logout(String token) { redisTemplate.delete(token); return true; }
修改的类是
newbee-mall-cloud-user-web
模块下的ltd.user.cloud.newbee.service.impl.MallUserServiceImpl
类。在
login()
方法中,删除原有保存和修改MallUserToken
数据到MySQL
数据库中的逻辑代码,之后使用RedisTemplate
对象将MallUserToken
数据存储到Redis
数据库中,存储时的key
为登录成功后生成的token
值。在
logout()
方法中,删除操作MySQL
数据库的逻辑代码,实现方式改为使用RedisTemplate
对象将Redis
数据库中对应的数据删除,使用的key
为当前的token
值。 -
修改商城用户
token
值处理的逻辑修改完登录方法的逻辑后,还要修改对应的鉴权逻辑。原来的逻辑是获取请求头中的
token
值,之后根据这个值查询MySQL
数据库是否存在及是否过期。现在不需要查询MySQL
数据库了,直接读取Redis
数据库中是否有对应的token
值即可,修改后的商城用户的鉴权代码如下:@Component public class TokenToMallUserMethodArgumentResolver implements HandlerMethodArgumentResolver { @Autowired private RedisTemplate redisTemplate; @Autowired private MallUserMapper mallUserMapper; public TokenToMallUserMethodArgumentResolver() { } public boolean supportsParameter(MethodParameter parameter) { if (parameter.hasParameterAnnotation(TokenToMallUser.class)) { return true; } return false; } public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { if (parameter.getParameterAnnotation(TokenToMallUser.class) instanceof TokenToMallUser) { String token = webRequest.getHeader("token"); if (null != token && !"".equals(token) && token.length() == 32) { ValueOperations<String, MallUserToken> opsForMallUserToken = redisTemplate.opsForValue(); MallUserToken mallUserToken = opsForMallUserToken.get(token); if (mallUserToken == null) { throw NewBeeMallException.fail(ServiceResultEnum.TOKEN_EXPIRE_ERROR.getResult()); } MallUser mallUser = mallUserMapper.selectByPrimaryKey(mallUserToken.getUserId()); if (mallUser == null) { throw NewBeeMallException.fail(ServiceResultEnum.USER_NULL.getResult()); } if (mallUser.getLockedFlag().intValue() == 1) { NewBeeMallException.fail(ServiceResultEnum.LOGIN_USER_LOCKED_ERROR.getResult()); } return mallUserToken; } else { NewBeeMallException.fail(ServiceResultEnum.NOT_LOGIN_ERROR.getResult()); } return null; } } }
修改的类是
newbee-mall-cloud-user-web
模块下的ltd.user.cloud.newbee.service.config.handler.TokenToMallUserMethodArgumentResolver
类。删除原有查询
MySQL
数据库中MallUserToken
数据的逻辑代码,之后添加从Redis
数据库中读取MallUserToken
数据的代码,后续逻辑并没有更改。接下来在
WebMVC
配置类中增加对TokenToMallUserMethodArgumentResolver
的配置并使其生效。增加商城用户功能模块后,WebMVC
配置类就不只是处理管理员用户了,所以将该类的名称由AdminUserWebMvcConfigurer
改成UserServiceWebMvcConfigurer
。最终的代码如下:public class UserServiceWebMvcConfigurer extends WebMvcConfigurationSupport { @Autowired private TokenToAdminUserMethodArgumentResolver tokenToAdminUserMethodArgumentResolver; @Autowired private TokenToMallUserMethodArgumentResolver tokenToMallUserMethodArgumentResolver; /** * @param argumentResolvers * @tip @TokenToAdminUser 注解处理方法 */ public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { argumentResolvers.add(tokenToAdminUserMethodArgumentResolver); argumentResolvers.add(tokenToMallUserMethodArgumentResolver); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry. addResourceHandler("/swagger-ui/**") .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/") .resourceChain(false); } }
OpenFeign 编码暴露远程接口
在暴露远程接口前,将需要暴露的接口代码编写完成。笔者在 NewBeeMallCloudPersonalController
类中新建了一个根据 token
值查询商城用户信息的接口,并将其暴露,代码如下:
@RequestMapping(value = "/getDetailByToken", method = RequestMethod.GET)
public Result getMallUserByToken(@RequestParam("token") String token) {
MallUser userDetailByToken =
newBeeMallUserService.getUserDetailByToken(token);
if (userDetailByToken != null) {
Result result = ResultGenerator.genSuccessResult();
result.setData(userDetailByToken);
return result;
}
return ResultGenerator.genFailResult("无此用户数据");
}
该方法对应的业务层方法实现代码如下:
public MallUser getUserDetailByToken(String token) {
ValueOperations<String, MallUser> opsForMallUserToken =
redisTemplate.opsForValue();
MallUserToken mallUserToken = opsForMallUserToken.get(token);
if (mallUserToken != null) {
MallUser mallUser = mallUserMapper.selectByPrimaryKey(mallUserToken.getUserId());
if (mallUser == null) {
NewBeeMallException.fail(ServiceResultEnum.DATA_NOT_EXIST.getResult());
}
if (mallUser.getLockedFlag().intValue() == 1) {
NewBeeMallException.fail(ServiceResultEnum.LOGIN_USER_LOCKED_ERROR.getResult());
}
return mallUser;
}
NewBeeMallException.fail(ServiceResultEnum.DATA_NOT_EXIST.getResult());
return null;
}
代码逻辑如下。
-
根据
token
值查询Redis
数据库中对应的MallUserToken
对象。 -
如果不为空,则根据获取的
MallUserToken
对象中的userId
字段查询MySQL
数据库中的商城用户信息;如果为空,则返回错误提示。 -
如果查询到正确的商城用户信息,则返回给调用端。
接下来,在 newbee-mall-cloud-user-api
模块中新增商城用户需要暴露的接口 FeignClient
,后续在改造商城端的功能模块时需要用到。
之前管理员用户中的远程接口配置在 ltd.user.cloud.newbee.openfeign.NewBeeCloudAdminUserServiceFeign
类中,增加商城用户功能模块后,NewBeeCloudAdminUserServiceFeign
类就不只处理管理员用户了,因此将该类的名称由 NewBeeCloudAdminUserServiceFeign
改为 NewBeeCloudUserServiceFeign
。注意,修改类名后,引用了该类的代码都需要同步改动。新增对 users/mall/getDetailByToken
接口的配置,最终的代码如下:
package ltd.user.cloud.newbee.openfeign;
import ltd.user.cloud.newbee.dto.MallUserDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import ltd.common.cloud.newbee.dto.Result;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "newbee-mall-cloud-user-service", path = "/users")
public interface NewBeeCloudUserServiceFeign {
@GetMapping(value = "/admin/{token}")
Result getAdminUserByToken(@PathVariable(value = "token") String token);
@GetMapping(value = "/mall/getDetailByToken")
Result<MallUserDTO> getMallUserByToken(@RequestParam(value = "token") String token);
}
这个方法的作用就是根据 token
值获取商城用户的信息,响应的实体结构为 MallUserDTO
,其中的字段都是商城用户的属性。调用的接口是 newbee-mall-cloud-user-web
模块下 NewBeeMallCloudPersonalController
类中的 getMallUserByToken()
方法。这样,如果其他服务需要通过 token
值获取商城用户数据,就可以引入 newbee-mall-cloud-user-api
模块作为依赖,并且直接调用 NewBeeCloudUserServiceFeign
类中的 getMallUserByToken()
方法。
商城用户鉴权功能测试
修改完代码后,测试步骤是不能漏掉的,一定要验证项目是否能正常启动、接口是否能正常调用,防止在代码移动过程中出现一些小问题,导致项目无法启动或代码报错。在项目启动前需要分别启动 Nacos Server
和 Redis Server
,之后依次启动 newbee-mall-cloud-user-web
工程、newbee-mall-cloud-gateway-admin
工程下的主类。启动成功后,就可以进行本节的功能测试了。
打开用户微服务的 Swagger
页面,在浏览器中输入如下网址: http://localhost:29000/swagger-ui/index.html。
加入商城用户后的用户微服务接口文档如图 6-3 所示,页面中包括管理员用户的接口文档和商城用户的接口文档,本节在测试时使用的是与商城用户相关的接口。

-
商城用户登录功能测试
依次单击 “登录接口”、“Try it out” 按钮,在参数栏中输入账号、密码字段,在这里笔者使用建表时的测试账号进行测试,如图 6-4 所示。
Figure 4. 图6-4 商城用户登录接口的测试过程当然,也可以使用商城用户的注册接口自行注册一个商城账号用于后续的功能测试。单击 “Execute” 按钮,接口的测试结果如图 6-5 所示。
Figure 5. 图6-5 商城用户登录接口的测试结果接口测试成功,使用登录接口获取的
token
值可以用于后续关于商城端的功能测试。比如,笔者在测试时获取了一个值为 “97a89ec463f28d213ca23c1707b43e95” 的token
字段。由于对
token
字段处理的逻辑做了改动,因此还需要查看Redis
数据库中是否存在刚刚获取的token
值,如图 6-6 所示。Figure 6. 图6-6 Redis 数据库中的用户登录数据此时,可以查看
Redis
数据库中索引为13
的数据。token
值被正确地存入Redis
数据库,并且设置了过期时间。 -
获取商城用户信息接口测试
接下来测试其他需要进行鉴权的接口,确认
token
字段处理的逻辑在改动后功能是否正常。依次单击 “获取用户信息”、“Try it out” 按钮,在登录认证
token
的输入框中输入管理员用户登录接口返回的token
值,如图 6-7 所示。Figure 7. 图6-7 获取商城用户信息接口的测试过程单击 “Execute” 按钮,接口的测试结果如图 6-8 所示。
Figure 8. 图6-8 获取商城用户信息接口的测试结果若后端接口的测试结果中有 “SUCCESS”,则表示信息获取成功。该接口对应到实际的项目页面中,是新蜂商城系统中的个人信息页面,如图 6-9 所示。
Figure 9. 图6-9 新蜂商城系统中的个人信息页面
笔者在这里只演示了一个接口的测试过程,读者也可以测试其他接口。