一、LDAP概念

LDAP的全称为Lightweight Directory Access Protocol(轻量级目录访问协议), 基于X.500标准, 支持 TCP/IP。

LDAP目录为数据库,通过LDAP服务器(相当于DBMS)处理查询和更新, 以树状的层次结构来存储数据,相对关系型数据库, LDAP主要是优化数据读取的性能,适用于比较少改变、跨平台的信息。

二、Softerra LDAP Administrator

下载安装软件,并配置LDAP

DN:Distinguished Name 唯一标识一条记录的路径,Base DN为基准DN,指定LDAP search的起始DN,即从哪个DN下开始搜索,RDN为叶子结点本身的名字。

DC:Domain Component 一条记录所属区域

OU:Organization Unit 组织单元

CN/UID:一条记录的名字/ID

三、在JAVA中应用LDAP

1、spring配置文件

<bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
          <constructor-arg ref="ldapcontextSource" />
    </bean>

<bean id="ldapcontextSource" class="org.springframework.ldap.core.support.LdapContextSource">

<property name="url" value="${LdapConnHost}" /> <!—例: LdapConnHost=ldap://10.190.123.123 -->

<property name="userDn" value="${LdapConnUser}" /><!—例: LdapConnUser=cn=root -->

<property name="password" value="${LdapConnPwd}" /><!—例: LdapConnPwd=111111 -->

<property name="base" value="${LdapBaseDn}" /> <!—Base DN 例: LdapBaseDn=DC=FJTIC -->

<property name="pooled" value="false" />

</bean>

2、JAVA代码

 package com.test.dao;

 import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import javax.naming.Name;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.ModificationItem;
import org.apache.commons.beanutils.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.ldap.core.ContextMapper;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.AbstractContextMapper;
import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.filter.OrFilter;
import org.springframework.stereotype.Component;
import com.surekam.model.Person;
import com.surekam.utils.DateCl;
import com.surekam.utils.StringUtil; @Component
public class UserDAOL {
@Autowired
private LdapTemplate ldapTemplate; /**
*
* @Description: 添加
*
*/
public boolean addPerson(Person person) {
boolean flag = false;
Name dn = buildUDn(person.getUid());
Attributes buildAddAttributes = buildAddAttributes(person);
ldapTemplate.bind(dn, null, buildAddAttributes);
flag = true;
return flag;
} /**
*
* @Description: 修改
*
*/
public boolean updatePerson(Person person) {
boolean flag = false;
Name dn = buildUDn(person.getUid());
ModificationItem[] modificationItem = buildModifyAttributes(person);
ldapTemplate.modifyAttributes(dn, modificationItem);
flag = true;
return flag;
} /**
*
* 多条件获取用户
*
*/
public List<Person> getPersonByOu(String ou, String level) {
AndFilter filter = new AndFilter();
OrFilter orFilter1 = new OrFilter();
if ("处级干部".equals(level)) {
orFilter1.or(new EqualsFilter("cuadministrativelevels", "aa"));
orFilter1.or(new EqualsFilter("cuadministrativelevels", "bb"));
orFilter1.or(new EqualsFilter("cuadministrativelevels", "cc"));
orFilter1.or(new EqualsFilter("cuadministrativelevels", "dd"));
}
if ("普通员工".equals(level)) {
orFilter1.or(new EqualsFilter("cuadministrativelevels", "ee"));
orFilter1.or(new EqualsFilter("cuadministrativelevels", "ff"));
orFilter1.or(new EqualsFilter("cuadministrativelevels", "gg"));
}
OrFilter orFilter2 = new OrFilter();
orFilter2.or(new EqualsFilter("departmentnumber", ou));
orFilter2.or(new EqualsFilter("cutransferdnumber", ou));
filter.and(orFilter2);
filter.and(orFilter1);
System.out.println(filter.toString());
List<Person> list = ldapTemplate.search("cn=users,dc=hq", filter
.encode(), new PersonContextMapper());
return list;
} /**
*
* 生成用户DN
*
* @return uid=123455,cn=users,dc=hq
*/
private Name buildUDn(String urdn) {
DistinguishedName dn = new DistinguishedName("");
dn.add("dc", "hq");
dn.add("cn", "users");
dn.add("uid", urdn);
return dn;
} /**
*
* 组织查询结果
*
*/
public static class PersonContextMapper implements ContextMapper {
public Object mapFromContext(Object ctx) {
if (ctx == null)
return new Person();
DirContextAdapter context = (DirContextAdapter) ctx;
Person per = new Person();
Attributes attrs = context.getAttributes();
NamingEnumeration results = attrs.getAll();
Class c = per.getClass();
while (results.hasMoreElements()) {
try {
Attribute attr = (Attribute) results.next();
String value = attr.get().toString();
if (StringUtil.isNotEmpty(value)) {
String fieldName = attr.getID();
if ("objectclass".equals(fieldName.toLowerCase())) {
continue;
}
Field field = c.getDeclaredField(fieldName
.toLowerCase());
Class fieldClazz = field.getType();
/*
* 如果属性条数大于1,那就是多值属性 attr.getAttributeDefinition()
* 获取多值属性的方法没找到
*/
if (fieldClazz.isAssignableFrom(List.class)) { // 属性值数大于1
NamingEnumeration values = attr.getAll(); // 获取所有值
// LDAP中的多值属性只会是String类型
List<String> list = new ArrayList<String>();
while (values.hasMoreElements()) {
list.add(values.next().toString());
}
BeanUtils.setProperty(per, fieldName.toLowerCase(),
list);
} else {
// 普通属性
BeanUtils.setProperty(per, fieldName.toLowerCase(),
value);
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NamingException e) {
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
per.setDn(context.getNameInNamespace());
return per;
}
} /**
*
* 组织添加数据数据
*
*/
@SuppressWarnings("unchecked")
private Attributes buildAddAttributes(Person p) {
Attributes attrs = new BasicAttributes();
BasicAttribute ocattr = new BasicAttribute("objectclass");
ocattr.add("top");
ocattr.add("person");
ocattr.add("organizationalPerson");
ocattr.add("inetOrgPerson");
ocattr.add("FJTicPerson");
attrs.put(ocattr);
Class c = p.getClass();
Field[] fields = c.getDeclaredFields();
for (int i = ; i < fields.length; i++) {
try {
Class fieldClazz = fields[i].getType();
String fieldName = fields[i].getName(); // 获得属性名
String fieldVlue = BeanUtils.getProperty(p, fieldName); // 获得属性值
/*
* 判断属性是否要过滤,例如修改时间之类的字段LDAP是没有的 判断属性值是否为空,在这里过滤了所有null和""
* 增加操作中不存在主动设置某个值为空的情况 所以只需要处理有值属性
*/
if (checkfieldName(fieldName) || StringUtil.isEmpty(fieldVlue))
continue;
/*
* 多值属性的处理 如果多值属性为空,那么增加的时候就不会增加值进去
*/
if (fieldClazz.isAssignableFrom(List.class)) { // 集合属性
BasicAttribute ocattr1 = new BasicAttribute(fieldName);
PropertyDescriptor pd = new PropertyDescriptor(fieldName, c);
Method getMethod = pd.getReadMethod();// 获得get方法
List list = (List) getMethod.invoke(p);// 执行get方法返回一个Object
for (Object object : list) {
ocattr1.add(object);
}
attrs.put(ocattr1);
} else {
attrs.put(fieldName, fieldVlue);
}
} catch (Exception e) {
e.printStackTrace();
}
}
return attrs; } /**
*
* 组织修改数据
*
*/
@SuppressWarnings("unchecked")
private ModificationItem[] buildModifyAttributes(Person p) {
ArrayList<ModificationItem> attrs = new ArrayList<ModificationItem>();
Class c = p.getClass();
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
try {
Class fieldClazz = field.getType();
String fieldName = field.getName(); // 获得属性名
String fieldValue = BeanUtils.getProperty(p, fieldName);
/*
* 判断属性是否要过滤,例如修改时间之类的字段LDAP是没有的 判断属性值是否为空,在这里过滤了所有null和""
* 要置空的属性通过识别特殊属性值:delAtr 在后面做重新置空操作
*/
if (checkfieldName(fieldName) || StringUtil.isEmpty(fieldValue))
continue;
BasicAttribute basicAttr = new BasicAttribute(fieldName);
/*
* 多值属性的处理 如果传递一个空的list,那么修改的时候就会清空多值属性 (new ArrayList<String>())
*/
if (fieldClazz.isAssignableFrom(List.class)) { // 如果是集合属性
PropertyDescriptor pd = new PropertyDescriptor(fieldName, c);
Method getMethod = pd.getReadMethod();// 获得get方法
List list = (List) getMethod.invoke(p);// 执行get方法返回一个Object
for (Object object : list) {
basicAttr.add(object);
}
} else {
/*
* 判断删除标记来对值进行置空 传递过来的对象中有些属性没有做修改就传递了"" 有些是要修改为 ""
* 所以在上面要过滤所有 "" 属性,避免将不修改的参数全都置空了 然后通过识别要修改参数的特有值来判断是否主动置空
* 如果add一个""进去,那么在LDAP中依然会显示
* 如果不给值,由BasicAttribute自动授予空值,那么在LDAP中就不显示了
*/
if ("delAtr".equals(fieldValue)) {
basicAttr.add(""); // 置空属性
} else {
basicAttr.add(fieldValue);// 有值属性
}
}
// 替换条目
attrs.add(new ModificationItem(
DirContextAdapter.REPLACE_ATTRIBUTE, basicAttr));
} catch (Exception e) {
e.printStackTrace();
}
}
return attrs.toArray(new ModificationItem[attrs.size()]);
} /**
*
* 过滤默认值字段
*
*/
private static boolean checkfieldName(String fieldName) {
String[] check = new String[] { "id", "status", "createtime",
"updatetime", "dn" };
for (int i = ; i < check.length; i++) {
if (check[i].equalsIgnoreCase(fieldName))
return true;
}
return false;
}
}

spring版

 package com.smnpc.util;

 import java.util.Hashtable;
import java.util.Vector; import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.LdapContext; /**
* Java通过Ldap操作AD的增删该查询
*
* @author guob
*/ public class LdapbyUser {
DirContext dc = null;
String root = "dc=example,dc=com"; // LDAP的根节点的DC /**
*
* @param dn类似于"CN=RyanHanson,dc=example,dc=com"
* @param employeeID是Ad的一个员工号属性
*/
public LdapbyUser(String dn, String employeeID) {
init();
// add();//添加节点
// delete("ou=hi,dc=example,dc=com");//删除"ou=hi,dc=example,dc=com"节点
// renameEntry("ou=new,o=neworganization,dc=example,dc=com","ou=neworganizationalUnit,o=neworganization,dc=example,dc=com");//重命名节点"ou=new,o=neworganization,dc=example,dc=com"
// searchInformation("dc=example,dc=com", "",
// "sAMAccountName=guob");//遍历所有根节点
modifyInformation(dn, employeeID);// 修改
// Ldapbyuserinfo("guob");//遍历指定节点的分节点
close();
} /**
*
* Ldap连接
*
* @return LdapContext
*/
public void init() {
Hashtable env = new Hashtable();
String LDAP_URL = "ldap://xxxx:389"; // LDAP访问地址
String adminName = "example\\user"; // 注意用户名的写法:domain\User或
String adminPassword = "userpassword"; // 密码
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, LDAP_URL);
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, adminName);
env.put(Context.SECURITY_CREDENTIALS, adminPassword);
try {
dc = new InitialDirContext(env);// 初始化上下文
System.out.println("认证成功");// 这里可以改成异常抛出。
} catch (javax.naming.AuthenticationException e) {
System.out.println("认证失败");
} catch (Exception e) {
System.out.println("认证出错:" + e);
}
} /**
* 添加
*/
public void add(String newUserName) {
try {
BasicAttributes attrs = new BasicAttributes();
BasicAttribute objclassSet = new BasicAttribute("objectClass");
objclassSet.add("sAMAccountName");
objclassSet.add("employeeID");
attrs.put(objclassSet);
attrs.put("ou", newUserName);
dc.createSubcontext("ou=" + newUserName + "," + root, attrs);
} catch (Exception e) {
e.printStackTrace();
System.out.println("Exception in add():" + e);
}
} /**
* 删除
*
* @param dn
*/
public void delete(String dn) {
try {
dc.destroySubcontext(dn);
} catch (Exception e) {
e.printStackTrace();
System.out.println("Exception in delete():" + e);
}
} /**
* 重命名节点
*
* @param oldDN
* @param newDN
* @return
*/
public boolean renameEntry(String oldDN, String newDN) {
try {
dc.rename(oldDN, newDN);
return true;
} catch (NamingException ne) {
System.err.println("Error: " + ne.getMessage());
return false;
}
} /**
* 修改
*
* @return
*/
public boolean modifyInformation(String dn, String employeeID) {
try {
System.out.println("updating...\n");
ModificationItem[] mods = new ModificationItem[];
/* 修改属性 */
// Attribute attr0 = new BasicAttribute("employeeID", "W20110972");
// mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
// attr0);
/* 删除属性 */
// Attribute attr0 = new BasicAttribute("description",
// "陈轶");
// mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE,
// attr0);
/* 添加属性 */
Attribute attr0 = new BasicAttribute("employeeID", employeeID);
mods[] = new ModificationItem(DirContext.ADD_ATTRIBUTE, attr0);
/* 修改属性 */
dc.modifyAttributes(dn + ",dc=example,dc=com", mods);
return true;
} catch (NamingException e) {
e.printStackTrace();
System.err.println("Error: " + e.getMessage());
return false;
}
} /**
* 关闭Ldap连接
*/
public void close() {
if (dc != null) {
try {
dc.close();
} catch (NamingException e) {
System.out.println("NamingException in close():" + e);
}
}
} /**
* @param base
* :根节点(在这里是"dc=example,dc=com")
* @param scope
* :搜索范围,分为"base"(本节点),"one"(单层),""(遍历)
* @param filter
* :指定子节点(格式为"(objectclass=*)",*是指全部,你也可以指定某一特定类型的树节点)
*/
public void searchInformation(String base, String scope, String filter) {
SearchControls sc = new SearchControls();
if (scope.equals("base")) {
sc.setSearchScope(SearchControls.OBJECT_SCOPE);
} else if (scope.equals("one")) {
sc.setSearchScope(SearchControls.ONELEVEL_SCOPE);
} else {
sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
}
NamingEnumeration ne = null;
try {
ne = dc.search(base, filter, sc);
// Use the NamingEnumeration object to cycle through
// the result set.
while (ne.hasMore()) {
System.out.println();
SearchResult sr = (SearchResult) ne.next();
String name = sr.getName();
if (base != null && !base.equals("")) {
System.out.println("entry: " + name + "," + base);
} else {
System.out.println("entry: " + name);
} Attributes at = sr.getAttributes();
NamingEnumeration ane = at.getAll();
while (ane.hasMore()) {
Attribute attr = (Attribute) ane.next();
String attrType = attr.getID();
NamingEnumeration values = attr.getAll();
Vector vals = new Vector();
// Another NamingEnumeration object, this time
// to iterate through attribute values.
while (values.hasMore()) {
Object oneVal = values.nextElement();
if (oneVal instanceof String) {
System.out.println(attrType + ": "
+ (String) oneVal);
} else {
System.out.println(attrType + ": "
+ new String((byte[]) oneVal));
}
}
}
}
} catch (Exception nex) {
System.err.println("Error: " + nex.getMessage());
nex.printStackTrace();
}
} /**
* 查询
*
* @throws NamingException
*/
public void Ldapbyuserinfo(String userName) {
// Create the search controls
SearchControls searchCtls = new SearchControls();
// Specify the search scope
searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
// specify the LDAP search filter
String searchFilter = "sAMAccountName=" + userName;
// Specify the Base for the search 搜索域节点
String searchBase = "DC=example,DC=COM";
int totalResults = ;
String returnedAtts[] = { "url", "whenChanged", "employeeID", "name",
"userPrincipalName", "physicalDeliveryOfficeName",
"departmentNumber", "telephoneNumber", "homePhone", "mobile",
"department", "sAMAccountName", "whenChanged", "mail" }; // 定制返回属性 searchCtls.setReturningAttributes(returnedAtts); // 设置返回属性集 // searchCtls.setReturningAttributes(null); // 不定制属性,将返回所有的属性集 try {
NamingEnumeration answer = dc.search(searchBase, searchFilter,
searchCtls);
if (answer == null || answer.equals(null)) {
System.out.println("answer is null");
} else {
System.out.println("answer not null");
}
while (answer.hasMoreElements()) {
SearchResult sr = (SearchResult) answer.next();
System.out
.println("************************************************");
System.out.println("getname=" + sr.getName());
Attributes Attrs = sr.getAttributes();
if (Attrs != null) {
try { for (NamingEnumeration ne = Attrs.getAll(); ne
.hasMore();) {
Attribute Attr = (Attribute) ne.next();
System.out.println("AttributeID="
+ Attr.getID().toString());
// 读取属性值
for (NamingEnumeration e = Attr.getAll(); e
.hasMore(); totalResults++) {
String user = e.next().toString(); // 接受循环遍历读取的userPrincipalName用户属性
System.out.println(user);
}
// System.out.println(" ---------------");
// // 读取属性值
// Enumeration values = Attr.getAll();
// if (values != null) { // 迭代
// while (values.hasMoreElements()) {
// System.out.println(" 2AttributeValues="
// + values.nextElement());
// }
// }
// System.out.println(" ---------------");
}
} catch (NamingException e) {
System.err.println("Throw Exception : " + e);
}
}
}
System.out.println("Number: " + totalResults);
} catch (Exception e) {
e.printStackTrace();
System.err.println("Throw Exception : " + e);
}
} /**
* 主函数用于测试
*
* @param args
*/
public static void main(String[] args) {
new LdapbyUser("CN=RyanHanson", "bbs.it-home.org");
}
}

普通实现(转载)

JAVA操作LDAP总结的更多相关文章

  1. 配置OpenLDAP,Java操作LDAP,DBC-LDAP进访问

    LDAP快速入门 1. LDAP简介 LDAP(轻量级目录访问协议,Lightweight Directory Access Protocol)是实现提供被称为目录服务的信息服务.目录服务是一种特殊的 ...

  2. JAVA操作LDAP的详解(JLDAP)

    最近两周由于要学习测试LDAP,所以对于用脚本操作LDAP很感兴趣,所以就做了一些脚本,都是比较简单的脚本吧. 废话不多说了哈.直接上教程 首先声明:我使用的是JLDAP操作LDAP,所以需要从官网下 ...

  3. #JAVA操作LDAP

    package com.wisdombud.unicom.monitor.ldap; import java.util.ArrayList; import org.slf4j.Logger; impo ...

  4. JAVA使用Ldap操作AD域

    项目上遇到的需要在集成 操作域用户的信息的功能,第一次接触ad域,因为不了解而且网上其他介绍不明确,比较费时,这里记录下. 说明: (1). 特别注意:Java操作查询域用户信息获取到的数据和域管理员 ...

  5. OpenLDAP使用疑惑解答及使用Java完成LDAP身份认证

    导读 LDAP(轻量级目录访问协议,Lightweight Directory Access Protocol)是实现提供被称为目录服务的信息服务.目录服务是一种特殊的数据库系统,其专门针对读取,浏览 ...

  6. springLdap 操作ldap示例(增删改查)

    转自:http://blog.csdn.net/sundenskyqq/article/details/9002440 这部分的示例网上的确有很多,但是个人在查找的过程中还是感到不够满意,所以就自己总 ...

  7. Java操作Sqlite数据库-jdbc连接

    Java操作Sqlite数据库步骤: 1. 导入Sqlite jdbc 本文使用sqlite-jdbc-3.7.2.jar,下载地址 http://pan.baidu.com/s/1kVHAGdD 2 ...

  8. 【MongoDB for Java】Java操作MongoDB

    上一篇文章: http://www.cnblogs.com/hoojo/archive/2011/06/01/2066426.html介绍到了在MongoDB的控制台完成MongoDB的数据操作,通过 ...

  9. JAVA 通过LDAP获取AD域用户及组织信息

    因为工作需求近期做过一个从客户AD域获取数据实现单点登录的功能,在此整理分享. 前提:用户可能有很多系统的情况下,为了方便账号的统一管理使用AD域验证登录,所以不需要我们的系统登录,就需要获取用户的A ...

随机推荐

  1. 远程控制篇:在DELPHI程序中拨号上网

    用MODEM拨号上网,仍是大多数个人网民选择上网的方式.如果能在我们的应用程序中启动拨号连接(如IE浏览器程序中的自动拨号功能),无疑将会方便我们的软件用户(不用再切换应用程序,运行拨号网络),提高我 ...

  2. phonegap Overview

    PhoneGap 和 Cordova的关系阐述 是PhoneGap贡献给Apache后的开源项目,是从PhoneGap中抽出的核心代码,是驱动PhoneGap的核心引擎.你可以把他想象成类似于Webk ...

  3. STL学习系列二:Vector容器

    1.Vector容器简介 vector是将元素置于一个动态数组中加以管理的容器. vector可以随机存取元素(支持索引值直接存取, 用[]操作符或at()方法,这个等下会详讲). vector尾部添 ...

  4. C#操作JSON学习

    JSON(全称为JavaScript Object Notation) 是一种轻量级的数据交换格式.它是基于JavaScript语法标准的一个子集. JSON采用完全独立于语言的文本格式,可以很容易在 ...

  5. Android的事件处理

    1 android事件处理概述 不论是桌面应用还是手机应用程序,面对最多的就是用户,经常需要处理用户的动作-------也就是需要为用户动作提供响应,这种为用户动作提供响应的机制就是事件处理.andr ...

  6. 已知有十六支男子足球队参加2008 北京奥运会。写一个程序,把这16 支球队随机分为4 个组。采用List集合和随机数

      package homework002; import java.util.ArrayList; import java.util.List; import java.util.Random; p ...

  7. PS自定义对象二_PSCustomObject

    创建自定义对象 $obj = [pscustomobject]@{a=1;b="";c=$null} % 选择属性列 $obj | gm |  % definition ( $ob ...

  8. android常用的快捷键

    Ctrl + shift + O    删除.java文件中所有未用到的引用的包的快捷键 Ctrl+D 删除光标所在行 Ctrl + shift + F   代码整体对齐:如果失效的情况下,就选中代码 ...

  9. Tricks Device (hdu 5294 最短路+最大流)

    Tricks Device Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) To ...

  10. Android 电子邮件发送成功与失败的提示

    前言          欢迎大家我分享和推荐好用的代码段~~ 声明          欢迎转载.但请保留文章原始出处:          CSDN:http://www.csdn.net        ...