Filter(过滤器)常见应用(三)——权限管理系统(一)
来源:互联网 发布:淘宝卖家扫码发货 编辑:程序博客网 时间:2024/06/07 05:01
我们要设计的权限管理系统,要使用Filter实现URL级别的权限认证。那我们不禁就要想设计出的权限管理系统应该使用在哪种情景中呢?我想应该是这样的一种情景吧:在实际开发中我们经常把一些执行敏感操作的servlet映射到一些特殊目录中,并用filter把这些特殊目录保护起来,限制只能拥有相应访问权限的用户才能访问这些目录下的资源。从而在我们系统中实现一种URL级别的权限功能。而且为使Filter具有通用性,Filter保护的资源和相应的访问权限通过filter参数的形式予以配置。
创建MVC架构的Web项目
在Eclipse中新创建一个day20的项目,导入项目所需要的开发包(jar包),创建项目所需要的包,在Java Web开发中,架构的层次是以包的形式体现出来的。
项目所需要的开发包(jar包):
项目所需要的包:
以上就是根据此项目的实际情况创建的包,可能还需要创建其他的包,这个得根据项目的需要来定了。
权限管理系统的设计和分析
我们若要设计一个这样的权限管理系统,肯定就要考虑需要设计几个对象,一般来说大概至少需要以下四个对象:
- Resource:代表系统中的每一个资源(超链接)。
- Privilege:代表访问资源的权限。
- Role:资源属于什么角色(管理员、经理、普通员工)。
- User:用户。
现在我们来思考这几个对象之间的关系,万事万物总该有那么一点联系吧!
- 权限和资源之间的关系:
若一个资源需要一个权限,这是一对一的关系;若一个权限可以控制多个资源,并且一个资源只能对应有一个控制权限,这是一对多的关系。但是在此权限管理系统中我们选择后者,即一对多的关系。 - 权限和角色之间的关系:
一个角色有多个权限,一个权限赋予多个角色,这是多对多的关系。 - 用户和角色之间的关系:
一个用户有可能拥有多个角色(多重身份),一个角色可能被赋予多个用户,这是多对多的关系。
明了上述内容之后,我们就来设计这四个对象,即开发domain层。
开发domain层
首先我们来分析和设计权限(Privilege)这个类,该类一些基本的属性,我们忽略,而来着重分析权限和资源以及权限和角色之间的关系。
- 在分析和设计权限(Privilege)这个类时,这个类可以不设置集合来记住它控制的哪些资源,由于在页面里面一般是没有这个需求的,我们在显示这个权限的时候,我们不需要说这个权限控制了哪几个资源,我们只会说在显示资源的时候,说它被哪个权限控制。
- 在分析和设计权限(Privilege)这个类时,我有必要设计一个集合来记住这个权限授予了哪些角色吗?——没有必要,一般来说,只是在显示角色的时候,我们才会说这个角色拥有哪些权限,我们是不会在页面里说在显示权限的时候,权限授予了哪些角色。
分析完了,权限(Privilege)这个类的代码实现就会一目了然,我们在cn.itcast.domain包中创建这个权限(Privilege)类。
权限(Privilege)这个类的具体代码如下:
public class Privilege { private String id; private String name; // 添加分类...的权限 private String description; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; }}
接下来我们来分析和设计资源(Resource)这个类,该类一些基本的属性,我们忽略,而来着重分析资源和权限之间的关系。
- 在分析和设计资源(Resource)这个类时,有没有必要设计一个属性记住资源被哪个权限控制呢?——显然有必要,我们在页面显示资源的时候,我们会说这个资源被哪个权限控制。
分析完了,资源(Resource)这个类的代码实现就会一目了然,我们在cn.itcast.domain包中创建这个资源(Resource)类。
资源(Resource)这个类的具体代码如下:
public class Resource { private String id; private String uri; // /day20/Servlet1 private String description; private Privilege privilege; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getUri() { return uri; } public void setUri(String uri) { this.uri = uri; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Privilege getPrivilege() { return privilege; } public void setPrivilege(Privilege privilege) { this.privilege = privilege; }}
再接下来我们来分析和设计角色(Role)这个类,该类一些基本的属性,我们忽略,而来着重分析角色和权限以及角色和用户之间的关系。
- 思考你在页面显示一个角色的时候,你需要显示什么?——我在页面里面显示一个角色的时候,这个角色应拥有哪些权限,所以说要设计一个集合来记住角色所有的权限。
- 然后思考有没有必要设计一个集合来记住这个角色授予了多少个用户呢?——一般来说没有必要,应该在显示用户的时候说这个用户拥有哪些角色,而不会说显示角色的时候,这个角色授予了哪些用户。
分析完了,角色(Role)这个类的代码实现就会一目了然,我们在cn.itcast.domain包中创建这个角色(Role)类。
角色(Role)这个类的具体代码如下:
public class Role { private String id; private String name; private String description; private Set<Privilege> privileges = new HashSet<Privilege>(); public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Set<Privilege> getPrivileges() { return privileges; } public void setPrivileges(Set<Privilege> privileges) { this.privileges = privileges; }}
最后我们来分析和设计用户(User)这个类,该类一些基本的属性,我们忽略,而来着重分析用户和角色之间的关系。
- 思考:在用户这边有没有必要设计一个集合来记住这个用户授予了哪些角色呢?——显然有必要。
分析完了,用户(User)这个类的代码实现就会一目了然,我们在cn.itcast.domain包中创建这个用户(User)类。
用户(User)这个类的具体代码如下:
public class User { private String id; private String username; private String password; private String description; private Set<Role> roles = new HashSet<Role>(); public String getId() { return id; } public void setId(String id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Set<Role> getRoles() { return roles; } public void setRoles(Set<Role> roles) { this.roles = roles; }}
至此,domain层就编写完了。如果说有人还不明了,我们就用图来表示这四个对象之间的关系,这样或许会更加清楚。
设计完这四个类之后,我们就要在数据库中创建这四个类所对应的数据库表及其中间表,完整的建表SQL语句如下:
create database day20;use day20;create table privilege( id varchar(40) primary key, name varchar(100) not null unique, description varchar(255));create table resource ( id varchar(40) primary key, uri varchar(255) not null unique, description varchar(255), privilege_id varchar(40), constraint privilege_id_FK foreign key(privilege_id) references privilege(id));create table role( id varchar(40) primary key, name varchar(100) not null unique, description varchar(255));create table role_privilege( role_id varchar(40), privilege_id varchar(40), primary key(role_id,privilege_id), constraint role_id_FK foreign key(role_id) references role(id), constraint privilege_id_FK1 foreign key(privilege_id) references privilege(id));create table user ( id varchar(40) primary key, username varchar(40) not null unique, password varchar(40) not null, description varchar(255));create table user_role( user_id varchar(40), role_id varchar(40), primary key(user_id,role_id), constraint user_id_FK foreign key(user_id) references user(id), constraint role_id_FK1 foreign key(role_id) references role(id));
开发数据访问层(dao、dao.impl)
为了提升程序的数据库访问性能,我们通常在项目开发中使用C3P0数据源。所以应在类目录下加入C3P0的配置文件:c3p0-config.xml。
c3p0-config.xml文件的内容如下:
<?xml version="1.0" encoding="UTF-8"?><c3p0-config> <default-config> <!-- C3P0的属性:driverClass --> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/day20</property> <property name="user">root</property> <property name="password">yezi</property> <property name="initialPoolSize">10</property> <property name="maxIdleTime">30</property> <!-- 最大空闲时间 --> <property name="maxPoolSize">20</property> <property name="minPoolSize">10</property> <property name="maxStatements">200</property> </default-config> <named-config name="mysql"> <property name="acquireIncrement">50</property> <property name="initialPoolSize">100</property> <property name="minPoolSize">50</property> <property name="maxPoolSize">1000</property> <!-- intergalactoApp adopts a different approach to configuring statement caching --> <property name="maxStatements">0</property> <property name="maxStatementsPerConnection">5</property> <!-- he's important, but there's only one of him --> </named-config> <named-config name="oracle"> <property name="acquireIncrement">50</property> <property name="initialPoolSize">100</property> <property name="minPoolSize">50</property> <property name="maxPoolSize">1000</property> <!-- intergalactoApp adopts a different approach to configuring statement caching --> <property name="maxStatements">0</property> <property name="maxStatementsPerConnection">5</property> <!-- he's important, but there's only one of him --> </named-config></c3p0-config>
也是为了简化JDBC的开发,我们使用Apache组织提供的一个开源JDBC工具类库——commons-dbutils-1.6.jar。然后在cn.itcast.utils包下创建一个工具类——JdbcUtils.java,用于读取C3P0的xml配置文件创建数据源。
该工具类的具体代码如下:
public class JdbcUtils { private static DataSource ds; static { ds = new ComboPooledDataSource(); } public static DataSource getDataSource() { return ds; }}
准备好以上这些工作之后,我们正式步入开发数据库访问层的阶段。
我们首先开发ResourceDao类,在cn.itcast.dao包下创建一个ResourceDao类。
ResourceDao类的具体代码如下:
public class ResourceDao { // 添加资源进数据库 public void add(Resource r) { try { QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "insert into resource(id,uri,description) values(?,?,?)"; Object[] params = {r.getId(),r.getUri(),r.getDescription()}; runner.update(sql, params); } catch (Exception e) { throw new RuntimeException(e); } } public Resource find(String uri) { // 根据uri进行查找资源 try { QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "select * from resource where uri=?"; Resource r = (Resource) runner.query(sql, uri, new BeanHandler(Resource.class)); if (r == null) { return null; } // 得到控制资源的权限(涉及多表查询) sql = "select p.* from resource r,privilege p where r.uri=? and p.id=r.privilege_id"; Privilege p = (Privilege) runner.query(sql, uri, new BeanHandler(Privilege.class)); r.setPrivilege(p); return r; } catch (Exception e) { throw new RuntimeException(e); } } public Resource findById(String id) { // 根据id进行查找资源 try { QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "select * from resource where id=?"; Resource r = (Resource) runner.query(sql, id, new BeanHandler(Resource.class)); if (r == null) { return null; } // 得到控制资源的权限(涉及多表查询) sql = "select p.* from resource r,privilege p where r.id=? and p.id=r.privilege_id"; Privilege p = (Privilege) runner.query(sql, id, new BeanHandler(Privilege.class)); r.setPrivilege(p); return r; } catch (Exception e) { throw new RuntimeException(e); } } public List getAll() { try { QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "select * from resource"; List<Resource> list = (List<Resource>) runner.query(sql, new BeanListHandler(Resource.class)); for (Resource r : list) { // 得到控制资源的权限(涉及多表查询) sql = "select p.* from resource r,privilege p where r.id=? and p.id=r.privilege_id"; Privilege p = (Privilege) runner.query(sql, r.getId(), new BeanHandler(Privilege.class)); r.setPrivilege(p); } return list; } catch (Exception e) { throw new RuntimeException(e); } } public void updatePrivilege(Resource r, Privilege p) { try { QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "update resource set privilege_id=? where id=?"; Object[] params = {p.getId(), r.getId()}; runner.update(sql, params); } catch (Exception e) { throw new RuntimeException(e); } }}
接下来我们开发PrivilegeDao类,在cn.itcast.dao包下创建一个PrivilegeDao类。
PrivilegeDao类的具体代码如下:
public class PrivilegeDao { public void add(Privilege p) { try { QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "insert into privilege(id,name,description) values(?,?,?)"; Object[] params = {p.getId(), p.getName(), p.getDescription()}; runner.update(sql, params); } catch (Exception e) { throw new RuntimeException(e); } } public Privilege find(String id) { try { QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "select * from privilege where id=?"; return (Privilege) runner.query(sql, id, new BeanHandler(Privilege.class)); } catch (Exception e) { throw new RuntimeException(e); } } public List getAll() { try { QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "select * from privilege"; return (List) runner.query(sql, new BeanListHandler(Privilege.class)); } catch (Exception e) { throw new RuntimeException(e); } }}
再接下来我们开发RoleDao类,在cn.itcast.dao包下创建一个RoleDao类。
RoleDao类的具体代码如下:
public class RoleDao { public void add(Role role) { try { QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "insert into role(id,name,description) values(?,?,?)"; Object[] params = {role.getId(),role.getName(),role.getDescription()}; runner.update(sql, params); } catch (Exception e) { throw new RuntimeException(e); } } public Role find(String id) { try { // 1. 查找角色的基本信息 QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "select * from role where id=?"; Role role = (Role) runner.query(sql, id, new BeanHandler(Role.class)); // 2. 找出角色的所有权限 sql = "select p.* from role_privilege rp,privilege p where rp.role_id=? and p.id=rp.privilege_id"; List<Privilege> list = (List<Privilege>) runner.query(sql, id, new BeanListHandler(Privilege.class)); role.getPrivileges().addAll(list); return role; } catch (Exception e) { throw new RuntimeException(e); } } public List getAll() { try { // 1. 查找角色的基本信息 QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "select * from role"; List<Role> list = (List<Role>) runner.query(sql, new BeanListHandler(Role.class)); // 2. 找出每一个角色拥有的所有权限 for (Role r : list) { sql = "select p.* from role_privilege rp,privilege p where rp.role_id=? and p.id=rp.privilege_id"; List<Privilege> listp = (List<Privilege>) runner.query(sql, r.getId(), new BeanListHandler(Privilege.class)); r.getPrivileges().addAll(listp); } return list; } catch (Exception e) { throw new RuntimeException(e); } } // 更新角色的权限 public void updateRolePrivileges(Role role, List<Privilege> privileges) { try { // 删除角色拥有的所有权限 QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "delete from role_privilege where role_id=?"; runner.update(sql, role.getId()); // 为角色赋予新的权限 for (Privilege p : privileges) { sql = "insert into role_privilege(role_id,privilege_id) values(?,?)"; Object[] params = {role.getId(), p.getId()}; runner.update(sql, params); } } catch (Exception e) { throw new RuntimeException(e); } }}
其中更新角色的权限,写起来是有些麻烦的,因为一个角色就有可能拥有多个权限。考虑到这点,我们可以采用简便的方法,即首先删除掉角色拥有的所有权限,然后为角色赋予新的权限。如果按照这种简便的方式来写代码,当更新角色的权限时,什么也不勾选的话,这就只相当于删除掉角色拥有的所有权限,如果勾选了若干权限,就能为角色赋予若干权限了。
最后我们开发UserDao类,在cn.itcast.dao包下创建一个UserDao类。
UserDao类的具体代码如下:
public class UserDao { public void add(User user) { try { QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "insert into user(id,username,password,description) values(?,?,?,?)"; Object[] params = {user.getId(), user.getUsername(), user.getPassword(), user.getDescription()}; runner.update(sql, params); } catch (Exception e) { throw new RuntimeException(e); } } public User find(String id) { try { QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "select * from user where id=?"; User user = (User) runner.query(sql, id, new BeanHandler(User.class)); if (user == null) { return null; } // 找出用户拥有的所有角色 sql = "select r.* from user_role ur,role r where ur.user_id=? and r.id=ur.role_id"; List<Role> list = (List<Role>) runner.query(sql, id, new BeanListHandler(Role.class)); user.getRoles().addAll(list); return user; } catch (Exception e) { throw new RuntimeException(e); } } // 登录 public User find(String username, String password) { try { QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "select * from user where username=? and password=?"; Object[] params = {username, password}; User user = (User) runner.query(sql, params, new BeanHandler(User.class)); if (user == null) { return null; } // 找出用户拥有的所有角色 sql = "select r.* from user_role ur,role r where ur.user_id=? and r.id=ur.role_id"; List<Role> list = (List<Role>) runner.query(sql, user.getId(), new BeanListHandler(Role.class)); user.getRoles().addAll(list); return user; } catch (Exception e) { throw new RuntimeException(e); } } public void updateUserRoles(User user, List<Role> roles) { try { QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); // 先删除用户所有的角色 String sql = "delete from user_role where user_id=?"; runner.update(sql, user.getId()); // 再为用户赋予新的角色 for (Role role : roles) { sql = "insert into user_role(user_id,role_id) values(?,?)"; Object[] params = {user.getId(), role.getId()}; runner.update(sql, params); } } catch (Exception e) { throw new RuntimeException(e); } } public List<User> getAll() { try { QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "select * from user"; List<User> list = (List<User>) runner.query(sql, new BeanListHandler(User.class)); return list; } catch (Exception e) { throw new RuntimeException(e); } }}
至此,整个数据库访问层就开发完了。既然开发完了,那么我们就来开发业务逻辑层。
开发service层(service层对web层提供所有的业务服务)
在cn.itcast.service包下创建一个SecurityService类,用来对web层提供资源、权限、角色和用户相关的服务。
SecurityService类的具体代码如下:
public class SecurityService { private ResourceDao rdao = new ResourceDao(); private PrivilegeDao pdao = new PrivilegeDao(); private RoleDao roledao = new RoleDao(); private UserDao udao = new UserDao(); /*************************************************************************************** * 提供资源相关的服务 ***************************************************************************************/ public void addResource(Resource r) { rdao.add(r); } public Resource findResource(String uri) { return rdao.find(uri); } public Resource finfResourceByID(String id) { return rdao.findById(id); } public List<Resource> getAllResource() { return rdao.getAll(); } // 更新控制资源的权限 public void updateResourcePrivilege(String resourceid, String privilegeid) { Resource r = rdao.findById(resourceid); Privilege p = pdao.find(privilegeid); rdao.updatePrivilege(r, p); } /*************************************************************************************** * 提供权限相关的服务 ***************************************************************************************/ public void addPrivilege(Privilege p) { pdao.add(p); } public Privilege findPrivilege(String id) { return pdao.find(id); } public List<Privilege> getAllPrivilege() { return pdao.getAll(); } /*************************************************************************************** * 提供角色相关的服务 ***************************************************************************************/ public void addRole(Role role) { roledao.add(role); } public Role findRole(String id) { return roledao.find(id); } public List<Role> getAllRole() { return roledao.getAll(); } // 更新角色拥有的权限 public void updateRolePrivilege(String roleid, String[] privilege_ids) { Role role = roledao.find(roleid); List<Privilege> list = new ArrayList<Privilege>(); for (int i = 0; privilege_ids != null && i < privilege_ids.length; i++) { Privilege p = pdao.find(privilege_ids[i]); list.add(p); } roledao.updateRolePrivileges(role, list); } /*************************************************************************************** * 提供用户相关的服务 ***************************************************************************************/ public void addUser(User user) { udao.add(user); } public User findUser(String id) { return udao.find(id); } public User findUser(String username, String password) { return udao.find(username, password); } public List<User> getAllUser() { return udao.getAll(); } // 更新用户拥有的角色 public void updateUserRole(String userid, String[] roleids) { User user = udao.find(userid); List<Role> list = new ArrayList<Role>(); for (int i = 0; roleids != null && i < roleids.length; i++) { Role r = roledao.find(roleids[i]); list.add(r); } udao.updateUserRoles(user, list); } // 得到某个用户拥有的所有权限 public List<Privilege> getUserAllPrivilege(String userid) { List<Privilege> allPrivilege = new ArrayList<Privilege>(); User user = udao.find(userid); // 找用户的角色时,并没有找出这个角色的所有权限,所以Set集合里面每一个角色并没有它相关的权限 Set<Role> roles = user.getRoles(); for (Role r : roles) { r = roledao.find(r.getId()); Set<Privilege> privileges = r.getPrivileges(); allPrivilege.addAll(privileges); } return allPrivilege; }}
在SecurityService类中,编写对Web层提供用户相关的服务时,尤其是在编写得到某个用户拥有的所有权限的方法——getUserAllPrivilege(String userid)时,可能要更加麻烦 ,所以我们更应该耐心一点。思考我们为什么要得到某个用户拥有的所有权限呢?要回答这个问题,就要看看我们的网站的首页了,或许网站首页——index.jsp就是这样子的:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><%@ taglib uri="/itcast" prefix="itcast" %><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>Insert title here</title></head><body> <a href="/day20/manager/Servlet1">添加分类</a> <a href="/day20/manager/Servlet2">删除分类</a> <a href="/day20/manager/Servlet3">修改分类</a> <a href="/day20/manager/Servlet4">查找分类</a></body></html>
若网站首页就是以上这个样子的,那么对于该网站来说,就有四种权限,即:
- 添加分类
- 删除分类
- 修改分类
- 查找分类
我们总结出一句话:网站有什么权限,其实就是由它对外提供的超链接来决定的!
我们不要东拉西扯太远了,还是回到这个问题上来,有人访问网站首页,他一点“添加分类”的超链接,一个过滤器就把这个请求拦截下来,拦截下来之后,检查此人有没有这个权限,即有没有添加分类的权限,那为了检查某个用户有没有添加分类权限,就要得到用户的所有权限。
我们知道原因之后,就要编码实现这个业务逻辑了,怎么去写代码实现呢?
- 得到该用户拥有的所有角色。
虽然得到该用户拥有的所有角色,但是并没有找出每个角色被赋予的所有权限,即user.getRoles()
得到的Set<Role>
集合里面每一个角色并没有与它相关的权限。 - 遍历
user.getRoles()
得到的Set<Role>
集合,找出每个角色下的所有权限。
- Filter(过滤器)常见应用(三)——权限管理系统(一)
- Filter(过滤器)常见应用(三)——权限管理系统(一)
- Filter(过滤器)常见应用(三)——权限管理系统(三)
- Filter(过滤器)常见应用(三)——权限管理系统(三)
- Filter(过滤器)常见应用(三)——权限管理系统(二)
- Filter(过滤器)常见应用(三)——权限管理系统(二)
- Filter(过滤器)常见应用(一)
- Filter(过滤器)常见应用
- Filter(过滤器)常见应用
- Filter(过滤器)常见应用
- javaweb学习总结(四十六)——Filter(过滤器)常见应用
- javaweb学习总结(四十六)——Filter(过滤器)常见应用
- javaweb学习总结(四十六)——Filter(过滤器)常见应用
- javaweb学习总结(四十六)——Filter(过滤器)常见应用
- javaweb学习总结(四十六)——Filter(过滤器)常见应用
- javaweb学习总结(四十六)——Filter(过滤器)常见应用
- javaweb学习总结(四十六)——Filter(过滤器)常见应用
- javaweb学习总结(四十六)——Filter(过滤器)常见应用
- CCF 201503-3 我30分
- 网络编程服务器端绑定ip设置
- Android Studio自动生成带系统签名的apk
- CSS-secrets 读书笔记(1)
- 基本运算符的重载(复数类)
- Filter(过滤器)常见应用(三)——权限管理系统(一)
- uicc详解-1(常识介绍)
- 使用bootstrap.css框架无法显示图标问题
- HDU1506-Largest Rectangle in a Histogram(dp)
- unity3d摄像机参数
- CC26xx之Flash Programmer 2使用
- 无线路由器说说2.4G和5G Wi-Fi的区别
- Generic design | 多重继承在设计组合上的失败以及Templates带来的曙光
- latex 做ppt,生成的pdf中目录乱码