插件在登錄註冊業務中的應用

一、手機號登錄註冊原有業務

 

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,代碼的維護和擴展性已經提高很多很多。

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