单体改微服务记录
最近两个月自己一个人扛住完成了单体项目改微服务架构技术经验也提升成熟了不少。回顾遇到的问题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 FallbackFactoryUserFeignClient { 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); RAdminUser userResult adminUserFeignClient.findByUserName(username,new Md5Hash(passwords, HXN, 3).toString(),SecurityConstants.INNER); // 查询用户信息 // RLoginUser userResult remoteUserService.getUserInfo(username, SecurityConstants.INNER); if (R.FAIL userResult.getCode()) { throw new ServiceException(userResult.getMsg()); } return userResult.getData(); } * 创建令牌 */ public MapString, Object createToken(LoginUser loginUser) { String userName loginUser.getAdminUser().getUsername(); loginUser.setUsername(userName); // Jwt存储信息 MapString, Object claimsMap new HashMapString, Object(); claimsMap.put(SecurityConstants.DETAILS_USERNAME, userName); String tokenJwtUtils.createToken(claimsMap); loginUser.setToken(token); refreshToken(loginUser); // 接口返回信息 MapString, Object rspMap new HashMapString, Object(); rspMap.put(Authorization, token); rspMap.put(expires_in, TOKEN_EXPIRE_TIME); return rspMap; }8.前端需要调整增加模块前缀但为了减少改动不动用前端网关做路径重写无需前端增加服务名前缀全部由后端hny-gateway.yml配置完成predicates:# 匹配前端原来调用的所有认证相关接口按需补充- Path/login,/logout/,/refresh,/registerspring: 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/**,/mi9.网关增加请求用户信息拦截放行白名单# 不校验白名单 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/receiveNotify10.服务间远程调用分页失效需要调整代码Override public PageInfoMapString, 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); ListMapString, 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?spm1001.2014.3001.550213.记得服务间内部调用需要放行14.服务间内部调用请求头参数需要塞进去15.注意拦截处理的优先顺序还有很多不记得的问题等想起来再补充