一、手機號登錄註冊原有業務
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,代碼的維護和擴展性已經提高很多很多。