重构上帝类/God Class

2026-09-07

背景

这是一个Saas系统,其中有一个MerchantService包含了很多业务,使用到了30+Bean。

    private final CouponUsageRecordService couponUsageRecordService;
    private final AuthenticationComponent authenticationComponent;
    private final AmazonS3Service amazonS3Service;
    private final MerchantAdminRepository merchantAdminRepository;
    private final PasswordEncoder passwordEncoder;
    private final CampaignMemberRepository campaignMemberRepository;
    private final MerchantCampaignRepository merchantCampaignRepository;
    private final MerchantKeyRepository merchantKeyRepository;
    private final MemberChopLogRepository memberChopLogRepository;
    private final MerchantRoleMenuRepository merchantRoleMenuRepository;
    private final MerchantRoleRepository merchantRoleRepository;
    private final MerchantRoleActionRepository merchantRoleActionRepository;
    private final ApplicationContext applicationContext;
    private final AppleIdentifierRepository appleIdentifierRepository;
    private final AdminMerchantFeatureMenuRepository adminMerchantFeatureMenuRepository;
    private final AdminMerchantFeatureActionRepository adminMerchantFeatureActionRepository;
    private final AdminMerchantRepository adminMerchantRepository;
    private final ServicePlanRepository servicePlanRepository;
    private final SystemCommonProperties systemCommonProperties;
    private final MerchantRepository merchantRepository;
    private final JwtTokenUtil jwtTokenUtil;
    private final FollowUpRepository followUpRepository;
    private final CampaignNotificationRepository campaignNotificationRepository;
    private final CampaignMemberMessageRepository campaignMemberMessageRepository;
    private final VoucherRecordRepository voucherRecordRepository;
    private final MerchantVoucherRepository merchantVoucherRepository;
    private final CampaignMemberBalanceService campaignMemberBalanceService;
    private final MerchantAdminLoginLogRepository merchantAdminLoginLogRepository;
    private final CampaignMemberService campaignMemberService;
    private final EmailAddressService emailAddressService;
    private final MerchantCouponRepository merchantCouponRepository;

    // Public方法签名
    MerchantFormVo getMerchant(Long merchantId);

    void addNewMerchant(NewMerchantParam param);

    void updateMerchant(Long id, UpdateMerchantParam param);

    MerchantSummaryVo getMerchantSummary();

    MerchantDataVo getRedeemInfo(Long targetId);

    MerchantDataVo getCameraScanData(Long targetId);

    CampaignMemberDataVo getMemberForEarnOrAttend(Long memberId);

    void adjustMemberChops(UpdateChopParam updateChopParam, Long memberId);

    void adjustMemberChops(UpdateChopParam updateChopParam, Long memberId, MerchantAdmin operator);

    void approvePrepaidMember(Long memberId);

    Map<String, String> getUploadPresignedUrl(String filename);

    void memberChopRedeem(Long memberId);

    void memberChopRedeem(Long memberId, MerchantAdmin admin);

    void updateMemberRemark(UpdateMemberRemarkParam param);

    void updateMemberAdditionalInfo(UpdateMemberAdditionalInfoParam param);

    List<MemberNewJoinedVO> getDailyNewMember(SelectDateParam param, Long merchantId);

    List<MemberNewJoinedVO> getMonthlyNewMember(Long merchantId);

    MerchantKeyVo saveMerchantCidLicense(MerchantKeyParam merchantKeyParam);

    MerchantKeyVo getMerchantCidLicense(Long merchantId);

    void grantWelcomeGift(UpdateMemberOtherInfo param);

    MerchantAdminSummaryVO getMerchantAdminSummary();

    InactiveMemberResponse inactiveMember(Long memberId);

    AccessToken merchantRegistration(MerchantRegistrationParam merchantRegistrationParam);

    BillingServicePlanVo getBillingServicePlan();

    List<TopOperatorListVo> getTopOperators(Long merchantId);

    List<ExpirationMerchantReminderVo> getExpirationReminderMerchants(LocalDate targetDate);

    List<DailyEarnBurnVo> getDailyChopCount(Long merchantId, StartEndDateParam param);

    void updateMerchantNameAndPassword(Long merchantAdminId, MerchantEditProfileParam merchantEditProfileParam);

    MemberChopLogVo memberRedeemReward(MemberRedeemRewardParam param);

    void pushMemberNotification(PushNotificationParam param);

    PageInfo<CampaignNotificationListVO> getCampaignNotifications(PageParam pageParam);

    void updateMerchantConfiguration(Long merchantId, MerchantConfigurationParam merchantConfigurationParam);

    PageInfo<MerchantAdminLoginLogVO> getMerchantAdminLoginHistory(Long merchantId, Long merchantAdminId, PageParam pageParam);

    void updateIntegrationChop(IntegrationMemberChopParam param);

    void updateIntegrationChops(Long campaignId, List<IntegrationMemberChopParam> param);

    CampaignMemberDataVo integrationGetMemberChop(IntegrationMemberChopParam param);

分析

业务领域

包含的依赖

问题诊断

认证与权限

(Auth & RBAC)

authenticationComponent, PasswordEncoder, JwtTokenUtil, MerchantAdminRepository, MerchantRoleRepository, MerchantRoleMenuRepository, MerchantRoleActionRepository, MerchantAdminLoginLogRepository

认证和权限是独立的领域,不应与商户基础信息耦合。

营销活动

(Campaign)

MerchantCampaignRepository, CampaignMemberRepository, CampaignMemberService, CampaignMemberBalanceService, CampaignMemberMessageRepository, CampaignNotificationRepository

营销体系非常庞大,包含会员、消息、通知等,应完全独立。

优惠券/代金券

(Coupon & Voucher)

CouponUsageRecordService, MerchantCouponRepository, VoucherRecordRepository, MerchantVoucherRepository

优惠券的发放、核销、记录属于独立的交易/营销链路。

商户基础与套餐

(Merchant Core)

MerchantRepository, AdminMerchantRepository, AdminMerchantFeatureMenuRepository, AdminMerchantFeatureActionRepository, ServicePlanRepository, MerchantKeyRepository

这才是 MerchantService 真正应该关心的核心领域。

基础设施与工具 (Infrastructure)

AmazonS3Service, EmailAddressService, ApplicationContext, SystemCommonProperties

基础设施组件不应直接暴露在核心业务编排层。

其他零散业务

MemberChopLogRepository, AppleIdentifierRepository, FollowUpRepository

这些属于边缘业务(如跟进记录、Apple标识、日志),应剥离到各自的服务中。

拆分

1. 商户基础与账号管理 (Merchant Core & Account)

MerchantService 真正应该保留的核心职责。

  • getMerchant(Long merchantId) - 获取商户详情

  • addNewMerchant(NewMerchantParam param) - 新增商户

  • updateMerchant(Long id, UpdateMerchantParam param) - 更新商户信息

  • getMerchantSummary() - 获取商户摘要信息

  • merchantRegistration(MerchantRegistrationParam) - 商户注册(返回Token)

  • updateMerchantNameAndPassword(Long, MerchantEditProfileParam) - 更新商户管理员名称和密码

  • saveMerchantCidLicense(MerchantKeyParam) - 保存商户 CID License

  • getMerchantCidLicense(Long merchantId) - 获取商户 CID License

  • getBillingServicePlan() - 获取计费服务套餐

  • updateMerchantConfiguration(Long, MerchantConfigurationParam) - 更新商户配置

  • getExpirationReminderMerchants(LocalDate) - 获取即将过期的商户提醒(偏后台/定时任务)

2. 会员与积分管理 (Member & Chop Management)

MemberManageService包含会员信息的增删改查以及积分(Chop)的调整。

  • getMemberForEarnOrAttend(Long memberId) - 获取用于赚取或参与活动的会员信息

  • adjustMemberChops(UpdateChopParam, Long) - 调整会员积分(2个重载方法)

  • approvePrepaidMember(Long memberId) - 审批预付会员

  • updateMemberRemark(UpdateMemberRemarkParam) - 更新会员备注

  • updateMemberAdditionalInfo(UpdateMemberAdditionalInfoParam) - 更新会员附加信息

  • getDailyNewMember(SelectDateParam, Long) - 获取每日新增会员

  • getMonthlyNewMember(Long merchantId) - 获取每月新增会员

  • inactiveMember(Long memberId) - 停用会员

  • getTopOperators(Long merchantId) - 获取顶级操作员列表(偏统计)

  • getDailyChopCount(Long, StartEndDateParam) - 获取每日积分增减统计(偏统计)

3. 优惠券与核销业务 (Coupon & Redemption)

CouponRedemptionService包含扫码、核销、奖励发放等交易链路。

  • getRedeemInfo(Long targetId) - 获取核销信息

  • getCameraScanData(Long targetId) - 获取扫码数据(会员或优惠券)

  • memberChopRedeem(Long memberId) - 会员积分核销(2个重载方法)

  • memberRedeemReward(MemberRedeemRewardParam) - 会员核销奖励

  • updateIntegrationChop(IntegrationMemberChopParam) - 更新集成积分

  • updateIntegrationChops(Long campaignId, List<...>) - 批量更新集成积分

  • integrationGetMemberChop(IntegrationMemberChopParam) - 集成获取会员积分

  • grantWelcomeGift(UpdateMemberOtherInfo) - 发放迎新礼物(偏营销/优惠券)

4. 营销活动与消息通知 (Campaign & Notification)

CampaignNotificationService完全属于独立的营销域。

  • pushMemberNotification(PushNotificationParam) - 推送会员通知

  • getCampaignNotifications(PageParam) - 获取活动通知列表

5. 权限、安全与基础设施 (Auth, Security & Infra)

MerchantSecurityService属于系统支撑能力,不应与核心业务混杂。

  • getMerchantAdminSummary() - 获取商户管理员摘要

  • getMerchantAdminLoginHistory(Long, Long, PageParam) - 获取管理员登录日志

  • getUploadPresignedUrl(String filename) - 获取 S3 上传预签名 URL(基础设施)

新问题

在系统中,通常存在两类操作者:

  1. B端商户(Merchant):只能操作自己租户下的数据(如:我的会员、我的优惠券、我的活动)。

  2. SaaS 平台管理员(Platform Admin):拥有上帝视角,可以操作所有商户的数据(如:分配套餐、调整商户权限、查看全局统计)。

目前有些方法(数据)是两边都需要的,会出现权限边界模糊、数据越权风险极高、代码里充斥着大量的 if (isAdmin) 判断的问题。

解决方案

1. 平台管理侧(Platform Admin Services)

职责:专门服务于公司内部的后台管理系统。不受商户数据隔离的限制,负责商户的生命周期、计费和全局管控。

  • PlatformMerchantManageService(商户生命周期管理)

    • addNewMerchant (平台代商户创建账号)

    • getMerchant (后台查看任意商户详情)

    • updateMerchantConfiguration (后台修改商户配置)

    • saveMerchantCidLicense / getMerchantCidLicense (后台管理 License)

  • PlatformBillingService(计费与套餐管理)

    • getBillingServicePlan (查看/修改套餐)

    • getExpirationReminderMerchants (后台查看哪些商户快过期了)

  • PlatformSecurityService(平台级安全审计)

    • getMerchantAdminLoginHistory (后台审计商户管理员的登录日志)

2. 商户端(Merchant Tenant Services)

职责:专门服务于商户后台(App/Web)。所有方法必须强制带上 merchantId 作为数据隔离的边界,确保商户只能看到自己的数据。

  • MerchantAccountService(商户自身账号管理)

    • merchantRegistration (商户注册账号)

    • updateMerchantNameAndPassword (商户修改自己的密码)

    • getMerchantSummary (商户看自己的摘要)

  • MerchantCampaignService(商户营销管理)

    • pushMemberNotification (商户给自己的会员发通知)

    • getCampaignNotifications (商户看自己的通知列表)

3. 共享业务域(Shared Domain Services)

3.1 职责

这就是“两边都需要看”的业务。这些 Service 不关心调用者是商户还是平台管理员,它们只关心业务逻辑本身。数据隔离的工作应该交给分开请求接口(即采用 /merchant/xxx 和 /sys/xxx 的独立路径)去做。

  • MemberManageService(会员管理)

    • getDailyNewMember / getMonthlyNewMember (商户看自己的,平台看全局的)

    • adjustMemberChops / inactiveMember (调整积分、停用)

    • getMemberForEarnOrAttend

  • CouponRedemptionService(优惠券与核销)

    • getRedeemInfo / getCameraScanData (扫码核销)

    • memberChopRedeem / memberRedeemReward (核销发奖)

    • grantWelcomeGift (发放迎新礼物)

  • DataStatisticsService(数据统计)

    • getTopOperators

    • getDailyChopCount

3.2 优势

3.2.1 天然的权限与数据隔离边界
  • 商户端接口 (/merchant/xxx):Controller 层只需配置一个全局拦截器或 AOP,强制校验 JWT 中是否包含 merchantId。如果包含,则直接注入到 Service 的参数中。Service 层完全不需要关心权限,只管查数据。

  • 平台端接口 (/sys/xxx):Controller 层校验当前用户是否是 PlatformAdmin。如果是,则允许传入 merchantId(可选)进行全局或指定查询。

3.2.2 返回数据的差异化(DTO 隔离)

商户和平台管理员对同一个实体的关注点完全不同。

  • 商户看会员:只需要看到自己门店的会员信息、积分。

  • 平台看会员:除了会员信息,可能还需要看到该会员属于哪个商户、商户的套餐类型、甚至是敏感操作日志。
    分开接口后,你可以为它们定义完全不同的 VO(如 MerchantMemberVO 和 PlatformMemberVO),互不干扰。

3.2.3网关层(Gateway)的统一管控

在微服务架构下,API 网关可以根据 URL 前缀做不同的路由和鉴权策略。例如,网关可以直接拦截所有 /merchant/ 开头的请求,校验租户 Token;而拦截 /sys/ 开头的请求,校验超级管理员 Token。

未来

以后再有新需求,按照以下流程判断:

它是给谁用的?

  • 如果是平台管理员用来“管商户”的 -> 放入 PlatformMerchantManageService。

  • 如果是商户用来“管自己”的 -> 放入 MerchantAccountService。

它是具体的业务动作吗?

  • 如果是会员、优惠券、活动相关的 -> 放入对应的 共享 Domain Service(如 MemberManageService)。

数据隔离怎么做?

  • 在 Controller 层根据登录角色,决定是否把 merchantId 传给 Service。