最近两个月自己一个人扛住,完成了单体项目改微服务架构,技术经验也提升成熟了不少。
回顾遇到的问题
1.结合微服务框架(基于官方文档)和单体需求划分8个微服务
hny-gateway 网关模块
hny-auth 认证中心
hny-system 系统模块
hny-user 用户管理服务
hny-order 订单管理
hny-payment 支付处理
hny-third-api 第三方服务(E签宝\SMS\MapAPI\阿里文件)
hny-management 后台管理模块
2.整理现有单体服务145张表,生成ER模型,划分所属服务,后续严格按照此标准执行

3.构建框架模块
使用了开源微服务源码,构建基本项目结构,如下:

4.公共模块构建
先把涉及公共代码移动hny-common通用模块

5.用户模块构建、代码移动调整
例如
新增userIdentityFeignClient
替换MiniMerchantsIncomeServiceImpl的miniUserIdentityDao;
实现基本实体类、dao、impl代码移动
@FeignClient(contextId = "UserIdentityFeignClient", value = ServiceNameConstants.USER_SERVICE, fallbackFactory = UserIdentityFeignClientFallbackFactory.class)
public interface UserIdentityFeignClient {
// ==================== 查询方法 ====================
/**
* 根据用户 ID 查询用户身份
*/
@GetMapping("/userIdentity/inner/selectByUserId")
UserIdentity selectByUserId(@RequestParam("userId") String userId, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
}
@Slf4j
@Component
public class UserFeignClientFallbackFactory implements FallbackFactory<UserFeignClient> {
@Override
public UserFeignClient create(Throwable cause) {
log.error("User 服务调用失败:{}", cause.getMessage());
return null;
}
}
@Slf4j
@RestController
@RequiredArgsConstructor
public class UserIdentityFeignController extends BaseController implements UserIdentityFeignClient{
@Resource
private MiniUserIdentityDao miniUserIdentityDao;
@Override
public UserIdentity selectByUserId(String userId, String source) {
log.info("UserIdentityFeignController - selectByUserId, userId = {}", userId);
return miniUserIdentityDao.selectByUserId(userId);
}
}
注意要在服务实现模块注入坐标

使用时要注入接口
@Autowired
private UserIdentityFeignClient userIdentityFeignClient;

其中肯定会涉及版本不一致问题,自己在网上找到合适的解决办法就行,总有行得通的一条路
6.本地nacos以及redis启动,以及排除各种循环依赖、配置等问题
可以参考文章https://mp.csdn.net/mp_blog/creation/editor/123739739
基础代码移动了,再构建登录,网关认证模块
7.hny-auth认证授权改造
hny-auth认证授权中心新增坐标,调整bootstrap.yml配置文件

改造框架登录认证模块
A.移除各模块中对权限注解的使用(@RequiresLogin,@RequiresPermissions,@RequiresRoles)
B更新依赖配置,移除security 模块引用、删除 hny-common-datascope数据权限模块
C移动原本框架的控制层代码
登录接口/auth/login代码调整,完成获取Authorization;完成放行内部feign调用
public class LoginBody
{
/**
* 用户名
*/
private String userName;
/**
* 用户密码
*/
private String password;
public String getUserName()
{
return userName;
}
public void setUserName(String userName)
{
this.userName = userName;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
}
@PostMapping("login")
public Result login(@RequestBody LoginBody form)
{
// 用户登录
AdminUser adminUser = sysLoginService.login(form.getUserName(), form.getPassword());
if (adminUser != null) {
try {
if (adminUser.getStatus() != 1){
return Result.error(StateCode.ERROR_CHANGE10);
}
// 获取登录token
return Result.success(tokenService.createToken(new LoginUser(adminUser)));
} catch (Exception e) {
e.printStackTrace();
return Result.error(StateCode.ADMIN_LOGIN_FAIL.getCode(), "密码错误");
}
} else {
return Result.error(StateCode.ADMIN_LOGIN_FAIL.getCode(), "用户名或密码错误");
}
}
public AdminUser login(String username, String password)
{
// 用户名或密码为空 错误
if (StringUtils.isAnyBlank(username, password))
{
throw new ServiceException("用户/密码必须填写");
}
// 密码如果不在指定范围内 错误
// IP黑名单校验
String blackStr = Convert.toStr(redisService.getCacheObject(CacheConstants.SYS_LOGIN_BLACKIPLIST));
if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr()))
{
// recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "很遗憾,访问IP已被列入系统黑名单");
throw new ServiceException("很遗憾,访问IP已被列入系统黑名单");
}
// 解密密码
String passwords = this.rsaUtil.buildRSADecryptByPrivateKey(password);
R<AdminUser> userResult = adminUserFeignClient.findByUserName(username,new Md5Hash(passwords, "HXN", 3).toString(),SecurityConstants.INNER);
// 查询用户信息
// R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);
if (R.FAIL == userResult.getCode())
{
throw new ServiceException(userResult.getMsg());
}
return userResult.getData();
}
* 创建令牌
*/
public Map<String, Object> createToken(LoginUser loginUser)
{
String userName = loginUser.getAdminUser().getUsername();
loginUser.setUsername(userName);
// Jwt存储信息
Map<String, Object> claimsMap = new HashMap<String, Object>();
claimsMap.put(SecurityConstants.DETAILS_USERNAME, userName);
String token=JwtUtils.createToken(claimsMap);
loginUser.setToken(token);
refreshToken(loginUser);
// 接口返回信息
Map<String, Object> rspMap = new HashMap<String, Object>();
rspMap.put("Authorization", token);
rspMap.put("expires_in", TOKEN_EXPIRE_TIME);
return rspMap;
}
8.前端需要调整,增加模块前缀
但为了减少改动,不动用前端,网关做路径重写,无需前端增加服务名前缀,全部由后端hny-gateway.yml配置完成
predicates:
匹配前端原来调用的所有认证相关接口(按需补充)
-
Path=/login,/logout/,/refresh,/register
spring:
cloud:
gateway:
discovery:
locator:
lowerCaseServiceId: true
enabled: true
routes:
# 认证中心
- id: hny-auth
uri: lb://hny-auth
predicates:
# 匹配前端原来调用的所有认证相关接口(按需补充)
- Path=/login,/logout/,/refresh,/register
filters:
# 验证码处理
- CacheRequestBody
- ValidateCodeFilter
# 系统模块
- id: hny-system
uri: lb://hny-system
predicates:
- Path=/system/notice/,/contentManager/,/agreement/,/common/
# 管理服务
- id: hny-management
uri: lb://hny-management
predicates:
- Path=/indexPage/,/businessLabel/,/BusinessArticleCategory/**
filters:
# 用户服务
- id: hny-user
uri: lb://hny-user
predicates:
- Path=/user/,/userManager/,/UserMyWalletManager/,/userAuthentication/,//userIdentity/,/userIdentityAgentUpgrade/,/userMyWallet/,/miniPartner/,/miniUser/**
filters:
# 支付服务
- id: hny-payment
uri: lb://hny-payment
predicates:
- Path=/pay/,/withdrawManager/,/wallet/*
filters:
# 订单服务
- id: hny-order
uri: lb://hny-order
predicates:
- Path=/sellerDiscountUser/**
# 第三方服务
- id: hny-third-api
uri: lb://hny-third-api
predicates:
- Path=/esign/,/file/,/pictureBed/**,/mi
9.网关增加请求用户信息拦截,放行白名单
# 不校验白名单
ignore:
whites:
# ========== 认证服务 ==========
# 认证通用
- /logout
- /login
- /user/login
- /user/newLogin
- /user/newWeixinLogin
- /user/newWeixinRegister
- /user/weiXinLogin
- /user/weiXinLogin1
- /user/loginCalf
- /admin/login
# 用户注册与验证
- /user/sendVerifyCode
- /user/UserRegister
- /user/UserRegisterCalf
- /user/resetPasswords
- /user/verificationVCode
- /user/resetPw
- /user/delPhone
- /user/getUser
# 支付回调
- /pay/receiveNotify

10.服务间远程调用,分页失效,需要调整代码

@Override
public PageInfo<Map<String, Object>> sellerList(String nickName, String userId, String userPhone, int state, String startTime, String endTime, String thisRecommendId, String twoRecommendId, String types, int pageNum, int pageSize, String source) {
log.info("UserIdentityFeignController - sellerList: nickName={}, userId={}, state={}", nickName, userId, state);
// 分页
PageHelper.startPage(pageNum, pageSize);
List<Map<String, Object>> resultList = userIdentityDao.sellerList(nickName, userId, userPhone, state, startTime, endTime, thisRecommendId, twoRecommendId, types);
return new PageInfo<>(resultList);
}

11.内部调用频繁,添加批量查询接口
这个只能先批量查询出来,再用代码去匹对
12.分布式事务是比较严重的,用seata解决,在下面链接文章
https://blog.csdn.net/weixin_38948287/article/details/161447962?spm=1001.2014.3001.5502
13.记得服务间内部调用需要放行

14.服务间,内部调用,请求头参数需要塞进去

15.注意拦截处理的优先顺序

还有很多不记得的问题,等想起来再补充
