插件在登录注册业务中的应用

一、手机号登录注册原有业务

 

1、实际开发过程

按照上面这个流程图,开发人员可以很容易的开发出手机号登录/注册的功能,甚至直接就可以开始编码了,一般我们都会这么去写接口:

@RequestMapping("login")

    @ResponseBody

    public JSONObject login(

            @RequestParam(value = "mobile") String mobile,

            @RequestParam(value = "mobileCode") String mobileCode) {

//TODO 校验验证码mobileCode是否正确

//TODO 查找有没有该用户 没有则自动注册为新用户

//TODO 登录

}

这样接口就写完了,调试运行一点问题都没有,下班走人。。。。。。

二、手机号登录注册业务变更

过了一个月,需求方要求原来手机号登录要校验用户是否在黑名单、新用户送优惠券礼包。

 

于是开发人员拿着上面的需求,马上又开干了,不是说好了只给半个小时搞定嘛,我分分钟在原来的代码上加完,测试运行无异常,OK,下班走人。。。。。。

@RequestMapping("login")

    @ResponseBody

    public JSONObject login(

            @RequestParam(value = "mobile") String mobile,

            @RequestParam(value = "mobileCode") String mobileCode) {

//TODO 校验验证码mobileCode是否正确

//TODO 用户受否为黑名单 不是则往下走,是则返回错误信息

//TODO 查找有没有该用户 没有则自动注册为新用户

//TODO 新用户则送新人大礼包

//TODO 登录

}

三、手机号登录注册业务又要变更

过了一个月618购物节到了,需求方要求新用户注册登录送1500商城积分,并且获得3次商城618抽奖机会,老用户推荐他人注册送1000商城积分。

尼玛。。。。。。那个登录注册的代码已经越来越臃肿了,开发人员已经改不下了,源源不断的需求,迫于压力硬着头皮往里塞,反正后面有人接盘!!!

  • 改进一下

首先是换一种思路,登录注册就看做是个简单的事务,不考虑内部要做什么具体的事情,那么问题就简单了:

如上图所示,我们把登录注册要做的事情一件件分开,独立开发,最后放到一个个插槽中,这样组合起来就是我们之前的登录注册功能,设计之后的效果应该是这样:

 

 

 

 

 

按照图上设计,以后有新的功能,我们直接做新的插件就可以了,当然假如我们后期业务调整要取消新用户注册送礼、送积分,那么我们也可以把插件拿掉就可以了,不会破坏主体代码,可以说是没有侵入性的方式。

四、改进后的代码实现

以上只是我的想法,那么如何实现呢,其实目前开源的框架中,已经有类似的实现,比如大名鼎鼎的OSGI,但是这个东西门槛比较高。

Swagger想必很多web后台开发者都用过,它里面就是插件系统实现的,而且它的插件系统号称全球最小的插件系统,使用基本无门槛,跟spring框架完美契合,所以接下来我们就用这个插件系统来实现。

1、定义业务插件接口

public interface MobileLoginPlugin extends Plugin<MobileLoginForm> {

    /**

     * 移动端登陆

     * @param mobileLoginForm

     */

    void login(MobileLoginForm mobileLoginForm,R r);

    int compareTo(MobileLoginPlugin o2);

}

定义一个抽象类,抽离公共代码,抽象方法延迟到具体的实现中

public abstract class AbstractMobileLoginPluginImpl implements MobileLoginPlugin {



    @Autowired

    private ShopMemberService shopMemberService;



    /**

     * 业务排序,order越小,plugin载入越靠前

     */

    protected int order;



    public AbstractMobileLoginPluginImpl(int order) {

        this.order = order;

    }



    protected void setOrder(int order) {

        this.order = order;

    }



    public int getOrder() {

        return order;

    }



    @Override

    public void login(MobileLoginForm mobileLoginForm, R r) {

        if (r == null) {

            r.put(R.KEY_CODE, 500);

            r.put(R.KEY_MSG, "传递R为空");

            return;

        }

        // 传递结果为true则继续登陆

        if ((int) r.get(R.KEY_CODE) > 0) {

            return;

        }

        if (supports(mobileLoginForm)) {

            excute(shopMemberService, mobileLoginForm, r);

        }

    }



    /**

     * 真正执行的操作

     *

     * @param shopMemberService

     * @param mobileLoginForm

     */

    protected abstract void excute(ShopMemberService shopMemberService,

                                   MobileLoginForm mobileLoginForm, R r);



    @Override

    public int compareTo(MobileLoginPlugin o2) {

        int order = ((AbstractMobileLoginPluginImpl) o2).getOrder();

        return this.order > order ? 1 : -1;

    }



    @Override

    public boolean supports(MobileLoginForm mobileLoginForm) {

        return true;

    }



    public ShopMemberService getShopMemberService() {

        return shopMemberService;

    }

}

2、实现业务插件功能(只有部分代码)

public class MobileCodePluginImpl extends AbstractMobileLoginPluginImpl {

    private final Log logger = LogFactory.getLog(MobileCodePluginImpl.class);



    @Autowired

    private RedisUtils redisUtils;



    public MobileCodePluginImpl(int order) {

        super(order);

    }



    @Override

    protected void excute(ShopMemberService shopMemberService, MobileLoginForm mobileLoginForm, R r) {

        logger.info("短信验证码验证");

        //验证码验证

        String mobileCode = mobileLoginForm.getMobileCode();

        String captcha = redisUtils.get(NotifyType.CAPTCHA.getType() + mobileLoginForm.getMobile());

        if (StringUtils.isEmpty(captcha)) {

            r.put(R.KEY_CODE, 500);

            r.put(R.KEY_MSG, "验证码已过期");

            return;

        }

        if (!captcha.equals(mobileCode)) {

            r.put(R.KEY_CODE, 500);

            r.put(R.KEY_MSG, "验证码错误");

            return;

        }

        // 删掉短信验证码,防止重复使用

        redisUtils.delete(NotifyType.CAPTCHA.getType() + mobileLoginForm.getMobile());

    }

}

3、注册业务插件

@Configuration

@EnablePluginRegistries({

        MobileLoginPlugin.class

})

public class PluginConfig {

    /**

     * 手机号短信验证码验证(需要时解开注释)

     * @return

     */

//    @Bean

//    public MobileCodePluginImpl mobileCodePlugin() {

//        return new MobileCodePluginImpl(2);

//    }

    /**

     * 手机号注册插件

     *

     * @return

     */

    @Bean

    public MobileRegisterPluginImpl mobileRegister() {

        return new MobileRegisterPluginImpl(3);

    }

    /**

     * 手机号登陆插件

     *

     * @return

     */

    @Bean

    public MobileLoginPluginImpl mobileLogin() {

        return new MobileLoginPluginImpl(4);

    }

}

4、使用插件

业务排序(我的业务需要插件按顺序执行,看情况,也可以直接取插件):

@Component

public class CustomizePluginRegistry {

    @Autowired

    private PluginRegistry<MobileLoginPlugin, MobileLoginForm> mobileLoginPluginRegistry;



    public List<MobileLoginPlugin> getSortMobileLoginPlugin() {

        List<MobileLoginPlugin> plugins = mobileLoginPluginRegistry.getPlugins();

        List<MobileLoginPlugin> result = new ArrayList<>();

        for (MobileLoginPlugin plugin : plugins) {

            result.add(plugin);

        }

        Collections.sort(result, (o1, o2) -> o1.compareTo(o2));

        return result;

    }

}

使用(部分代码):

@Autowired

    private CustomizePluginRegistry customizePluginRegistry;

    @ApiOperation("通过手机号登录")

    @PostMapping("loginByMobile")

    public R loginByMobile(@RequestBody MobileLoginForm form) {

        //表单校验

        ValidatorUtils.validateEntity(form);

        //初始化结果对象

        R r = R.ok();

        customizePluginRegistry.getSortMobileLoginPlugin().forEach(m -> m.login(form, r));

        return r;

    }

至此,改造完成了,是不是很easy,代码的维护和扩展性已经提高很多很多。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章