場景:
公司上線的權限系統涉及的有一個功能:
- 使用其它平臺的認證,如果通過。我們的平臺也算認證通過。
- 公司同一個產品可能有多家公司在用,每家的認證平臺也不一樣,認證方式不一樣,數據格式也不一樣。
- 在以上兩種情況出現的時候,還要保證公司產品保持標準化,以便客戶後面升級。
這樣就比較麻煩了。按理講,這種情況就屬於客戶定製版本。升級也是按照自制版本升級。標準產品裏的新功能要去每一個定製產品裏做合併。
在這種各個客戶要各種功能的情況越來越多,標準版本要合併到各定製版本的工作量越來越大。所以思考出以下解決方案。
- 統一功能的接口;
- 爲各現場做不同的實現;
- 做一個配置,表明是哪個實現;
- 把實現類注入到Spring中,以便使用.
下面給出一個目前做出來效果比較好的具體例子:
- 登錄這個功能的通用接口
public interface AdLoginService {
public boolean adLogin(String userName, String password, String url);
}
- 這個接口功能的不同實現類:
a. 通用實現
@Service
public class AdLoginServiceGeneralImpl implements AdLoginService{
@Autowired
private MConfig mConfig;
@Override
public boolean adLogin(String userName, String password, String url) {
// String adPath = String.format(config.getADPathtmpl(), username);
String adPath = String.format(mConfig.get("sys.config.ADPathtmpl"), userName);
Hashtable<String, String> HashEnv = new Hashtable<String, String>();
HashEnv.put(Context.SECURITY_AUTHENTICATION, "simple");
HashEnv.put(Context.SECURITY_PRINCIPAL, adPath);
HashEnv.put(Context.SECURITY_CREDENTIALS, password);
HashEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
HashEnv.put("com.sun.jndi.ldap.connect.timeout", "3000");
// HashEnv.put(Context.PROVIDER_URL, config.getADUrl());
HashEnv.put(Context.PROVIDER_URL, url);
try {
DirContext ctx = new InitialDirContext(HashEnv);
ctx.close();
return true;
} catch (NamingException e) {
e.printStackTrace();
}
return false;
}
}
b. 某個現場的特殊實現:
public class AdLoginServiceTuyinImpl implements AdLoginService {
private static Logger log = LoggerFactory.getLogger(AdLoginServiceTuyinImpl.class);
@Override
public boolean adLogin(String userName, String password, String url) {
String envelope = createEnvelope(userName, password);
return idVerify(url, envelope);
}
private static String createEnvelope(String id, String password) {
StringBuffer buffer = new StringBuffer();
buffer.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
buffer.append("<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
buffer.append(
" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">");
buffer.append("<soap:Body>");
buffer.append("<IdVerify xmlns=\"http://microsoft.com/taiwan/mcs\">");
buffer.append("<id>" + id + "</id>");
buffer.append("<password>" + password + "</password>");
buffer.append("</IdVerify>");
buffer.append("</soap:Body>");
buffer.append("</soap:Envelope>");
System.out.println(buffer.toString());
return buffer.toString();
}
private boolean idVerify(String requestUrl, String outputStr) {
try {
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "text/xml;charset=UTF-8");
headers.put("SOAPAction", "http://microsoft.com/taiwan/mcs/IdVerify");
String rst = NetUtils.httpsPost(requestUrl, headers, outputStr.getBytes("utf-8"), 50000);
log.debug("adlogin detail:{}", rst);
return (StringUtils.isNotBlank(rst) && rst.contains("<StatusCode>0</StatusCode>")) ? true : false;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
這樣,一個接口兩個實現就都有了。
然後,我們需要一個配置,這個配置就可以各個現場不一樣。
key | doc | value |
---|---|---|
sys.config.ADEnable | ad login config(true-開啓, false-關閉)(默認值false) | true |
sys.config.ADLoginClass | AD域登錄實現 | net.riking.core.OAuth.service.impl.AdLoginServiceTuyinImpl |
sys.config.ADUrl | ad login config | ldap://172.16.32.1:389 |
這裏只需要配置不同的實現和平臺登錄URL就可以了。
當然這樣還不行。必須在程序裏動態使用。如果實現類裏使用了Spring注入一些資源。我們就必須把這個實現類初始化爲SpringBean。
Spring注入方法:
@Component("OAuthSpringContextBeanUtil")
public class SpringContextBeanUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext) {
SpringContextBeanUtil.applicationContext = applicationContext;
}
/**
* @return ApplicationContext
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static Object getBean(String name) throws BeansException {
return applicationContext.getBean(name);
}
public static Object getBean(Class<?> clazz) {
return applicationContext.getBean(clazz);
}
public static void addNewBean(Object bean) throws Exception {
ConfigurableListableBeanFactory beanFactory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
beanFactory.registerSingleton(bean.getClass().getCanonicalName(), bean);
}
}
根據配置注入bean:
//其它代碼
if (!adLogin(user.getUserName(), user.getPassword())) {
return new Resp("#{login.info.userOrPasswordError}").setCode(CodeDef.UNAUTHORIZED);
}
//其它代碼
private boolean adLogin(String username, String password) throws Exception {
String className = mConfig.get("sys.config.ADLoginClass");
AdLoginService adLoginService;
if(StringUtils.isBlank(className)) {
adLoginService = (AdLoginService) SpringContextBeanUtil.getBean(AdLoginServiceGeneralImpl.class);
}else {
//以下是關鍵代碼
Class<?> clz = Class.forName(className);
try {
adLoginService = (AdLoginService) SpringContextBeanUtil.getBean(clz);
}catch(Exception e) {
Object newInstance = clz.newInstance();
SpringContextBeanUtil.addNewBean(newInstance);
adLoginService = (AdLoginService) SpringContextBeanUtil.getBean(clz);
}
if(null == adLoginService) {
return false;
}
}
return adLoginService.adLogin(username, password, mConfig.get("sys.config.ADUrl"));
}