策略模式在积分系统中的应用

347 阅读5分钟

这是我在青训营的第五篇笔记

策略模式在积分系统中的应用

前言

引言: 互联网平台积分体系是一个独立、完整的系统模块,主要用于激励和回馈用户在平台的消费行为和活动行为,通过积分体系可以激发与引导用户在平台的活跃行为,逐步形成用户对平台的依赖性和习惯性,提升用户对平台的黏度和使用率。

本次课程软件系统实践项目主要是构建一套网站积分体系,根据用户不同的行为进行积分管理。同时每个行为所对应的积分有如下特点:

  • 积分分为可兑换积分(存在失效日期)和成长积分(不存在失效日期)
  • 不同行为对应的积分分数不同
  • 不同行为积分都存在有一定的限制,也就是限制每日、每月、每年获取积分上限次数。

由于项目使用的是Spring Web进行开发,容易想到的就是在每个Controller中主要逻辑实现完成后再进行积分策略。但是此方法有如下缺

  • 项目逻辑耦合度增大,过多非主要业务会嵌入主要业务逻辑,导致后期维护难度增大,系统可维护性下降。
  • 积分体系通常作为辅助业务,其产生异常可能导致其他重要业务也发生异常,系统可靠性降低
  • 积分具体逻辑代码可能涉及到与数据库打交道,因此过多积分逻辑的织入可能造成系统总体性能下降,导致系统的QPS、TPS下降。

那么,存在其他方法来解决此问题吗,经过无数次Google,最终得到了一套可行的方案:

  • 引入策略模式来管理具体策略 ==> 实现业务代码和积分业务代码逻辑分离,增加可维护性能
  • 引入MQ ==> 实现主要业务和积分业务的系统分离,增加系统的高可用性
  • 引入Redis缓存,将静态页面、Session以及以及对象加入缓存 ==> 大大增加系统的QPS,提升系统响应速度

具体实现

对于MQ和Redis缓存实现,本文主要阐述如何使用策略模式来进行积分逻辑代码和业务主逻辑的分离,提高代码的可扩展性。读者只需要了解Spring Web中 HandlerInterceptor即可,本质上是靠一系列拦截器来实现积分逻辑的。在此系统中我们需要三个部件:

关于策略模式,读者可以查阅这篇文章 : 策略模式

  • 策略类:策略实施者,用来实现真正的积分变动业务。
  • 策略注解:和Controller中的GetMapping("url=...")一样,用来注明不同策略类对应的需要起作用的URL
  • 拦截器:策略管理类,用来注册所有策略类,简单来说,就是管理从 URL到相应策略的映射。

1. 策略注解

/**
 * @author tyf
 * @description 策略注解
 * @date 10:20 2022/5/12
 **/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UserTaskStrategyType {
    /**
     * 需要拦截的 URL
     *
     * @return uri数组
     */
    String[] uri();
}

2. 策略类实现

为了降低耦合度,采用 接口–>抽象类–>具体类的结构来降低代码耦合度。

I: 策略类接口

/**
 * @author tyf
 * @description define the strategy method
 * @date 10:23 2022/5/12
 **/
public interface IUserTaskStrategy {

    /**
     * 主要声明具体增加积分逻辑
     * @param user  用户对象
     * @param token
     */
    void finishedUserIntegralTask(HttpServletRequest request);
}

II: 策略类抽象类

/**
 * @describe: 用户逻辑抽象类
 * @author: tyf
 * @createTime: 2022/5/18 11:51
 **/
public abstract class UserTaskStrategy implements IUserTaskStrategy {
     //具体的积分记录实现逻辑
    @Autowired
    CreditTransactionService creditTransactionService;
     //对应的行为事件
    protected Event event;
    public UserTaskStrategy(Event event) {
        this.event = event;
    }
    public UserTaskStrategy() {   
    }
     /**
     * 具体实现方法
     **/
    @Override
    public void finishedUserIntegralTask(HttpServletRequest request) {
        creditTransactionService.insert(user, event);
    }
}

III: 具体策略类

这里用具体的更新用户个人信息增加积分举例

/**
 * @describe: 用户更新个人信息策略
 * @author: tyf
 * @createTime: 2022/5/18 09:48
 **/
@Component
@UserTaskStrategyType(uri = {"/u/update"})
public class UpdateInfoStrategy extends UserTaskStrategy {
     //这里也可以重写finishedUserIntegralTask方法进行重写策略
    public UpdateInfoStrategy() {
        super(Event.UPDATE_USER_DETAIL);
    }

}

3. 拦截器

/**
 * @Describe: 用户任务拦截器,主要通过userTaskStrategyMap来维护不同的积分策略
 * @Author: tyf
 * @CreateTime: 2022/5/12
 **/
@Component
public class UserTaskInterceptor implements HandlerInterceptor{

     //url映射到具体逻辑
    private Map<String, IUserTaskStrategy> userTaskStrategyMap = new HashMap<>();
    /**
     * 注册所有拦截器并且生成对应策略 Map
     * @param iUserTaskStrategies
     */
    @Autowired
    public void setUserTaskStrategyMap(List<IUserTaskStrategy> iUserTaskStrategies){
        iUserTaskStrategies.forEach(iUserTaskStrategy -> {
            String[] uris = Objects.requireNonNull(AnnotationUtils.findAnnotation(iUserTaskStrategy.getClass(),
                    UserTaskStrategyType.class).uri());
            userTaskStrategyMap.putAll(Arrays.stream(uris).collect(Collectors.toMap(uri->uri, uri-> iUserTaskStrategy)));
        });
    }

     //根据URL找到相应的策略类来实现
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        String uri = request.getRequestURI();
        IUserTaskStrategy userStrategy = userTaskStrategyMap.get(uri);
        //其他接口过滤器
        if (userStrategy != null) {
            userStrategy.finishedUserIntegralTask(request);
        }
    }

}

4. 将拦截器注册到MVC配置类中

/**
 * @Describe: web全局配置类
 * @Author: tyf
 * @CreateTime: 2022/4/17
 **/
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private UserArgumentResolver userArgumentResolver;
    @Autowired
    private AuthInterceptor authInterceptor;
    private List<String> uriList = new ArrayList<>();
    @Autowired
    private UserTaskInterceptor userTaskInterceptor;


    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        //注入自定义的解析器
        argumentResolvers.add(userArgumentResolver);
    }
     
     //提取所有需要拦截的URL
    @Autowired
    public void setUriList(List<IUserTaskStrategy> iUserTaskStrategies){
        iUserTaskStrategies.forEach(iUserTaskStrategy -> {
            String[] uris = Objects.requireNonNull(AnnotationUtils.findAnnotation(iUserTaskStrategy.getClass(),
                    UserTaskStrategyType.class).uri());
            uriList.addAll(Arrays.asList(uris));
        });
    }

     //将相应的URL配置到对应的拦截器中
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(userTaskInterceptor).addPathPatterns(uriList);
    }
}

由此可见,策略模式已经完美落地到项目中,并且后续如需要有定义其他的积分项目,只需实现新的策略类并添加相应策略注解即可。而前文所述对应其他MQ、Redis中可以在具体的策略中运用。

笔者通过这次实验学到了很多,不得不说:设计模式,真精髓也!

具体项目实操: 积分管理系统

其他文章

  1. 设计模式之装饰器模式