单体改微服务记录

最近两个月自己一个人扛住,完成了单体项目改微服务架构,技术经验也提升成熟了不少。

回顾遇到的问题

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.注意拦截处理的优先顺序

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

相关推荐
chase_my_dream10 分钟前
C++ + SLAM 高频面试问题整理
开发语言·c++·面试
snow@li25 分钟前
Java:理解 Gradle / 后端项目的管家 / 打包SpringBoot 应用 / 完成编译、下载依赖、运行测试、打包 JAR/WAR / 速查表
java
Cloud_Shy61833 分钟前
解读《Effective Python 3rd Edition》:从练气到老魔(第五章 Item 30 - 32)
开发语言·人工智能·笔记·python·学习方法
云烟成雨TD37 分钟前
Spring AI 1.x 系列【57】动态工具发现:Tool Search Tool
java·人工智能·spring
zfoo-framework1 小时前
[修改代码使用]codex官方app中使用中转(不需要cc-switch) 1.config.toml 2.sk方式登录
java
天佑木枫1 小时前
15天Python入门系列 · 序
开发语言·python
逍遥德1 小时前
MQTT教程详解-05.SpringBoot集成mqtt client 性能分析
java·spring boot·spring·mt
云烟成雨TD1 小时前
Spring AI 1.x 系列【54】Retry 机制分析
java·人工智能·spring
weixin_523185321 小时前
Collections.unmodifiableMap详解:真的不可修改吗?
java·linux·前端
点燃大海1 小时前
SpringAI构建智能体
java·spring boot·spring·springai智能体