一、手机号登录注册原有业务
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,代码的维护和扩展性已经提高很多很多。