SpringMVC+Spring Data JPA+Shiro+EasyUI简单权限管理系统
来源:互联网 发布:特征提取算法 编辑:程序博客网 时间:2024/06/06 02:46
概述
一直想做一个管理系统,希望它简洁,能做一个demo使用。以后在研究学习的时候,可以在此基础上增加代码。我觉得权限管理系统很值得做,因为涉及关系数据库模式的设计,能学到很多东西。万事开头难,先做个简单的,以后再慢慢完善的。任何事情关键是要做,不能停留在想。
前端
由于之前没有多少前端编程经验,所以做起前端比较吃力。之前前端使用Bootstrap,发现需要自己编写很多前端代码,虽然花费了很多时间,但是页面做起来还是很难看。后来前端选择了EasyUI,发现特别适合做管理页面,也容易上手。当然每个框架都有其局限性,后面使用多了就知道了,就是EasyUI不够灵活,但是现在先用着,以后再选择换其他的。EasyUI官网http://www.jeasyui.com/
MVC
使用主流的SpringMVC,不但功能强大,用起来比较简单方便。
SpringMVC推荐学习网站http://www.iteye.com/blogs/subjects/kaitao-springmvc
持久化
使用Spring Data JPA。这个框架真的很赞,对于简单的业务逻辑,基本不用些SQL代码。最重要的是可以根据接口名自动生成SQL代码,极大的提高了开发效率。参考http://www.ibm.com/developerworks/cn/opensource/os-cn-spring-jpa
同时也配置了Hibernate,虽然现在还没是用,但准备后期和Spring Data JPA配合是用。
权限管理
选择Shiro,简单好用,容易上手。推荐学习网站http://www.iteye.com/blogs/subjects/shiro
权限模型了解http://blog.csdn.net/painsonline/article/details/7183613/
使用预览
注册
登录
管理后台
设置角色
授权
实体层
以下只是展示了部分关键代码
用户
用户和角色是N-N的关系。
@Entity@Table(name = "sys_user")public class User implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // 编号 // 这里不能用CascadeType.ALL @ManyToMany(targetEntity = Role.class, cascade = CascadeType.MERGE, fetch = FetchType.EAGER) @JoinTable(name = "sys_user_role", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id") , inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id") ) private Set<Role> roles = new HashSet<>(); @Column(unique = true) private String username; // 用户名 private String password; // 密码 private String salt; // 加密密码的盐 private Boolean locked = Boolean.FALSE; private String email; @Column(name = "create_date") @Temporal(TemporalType.TIMESTAMP) private Date createDate = new Date();....
角色
@Entity@Table(name = "sys_role")public class Role implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // 编号 @OneToMany(targetEntity = Permission.class, cascade = CascadeType.REFRESH, fetch = FetchType.EAGER) @JoinTable(name = "sys_role_permission", joinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id") , inverseJoinColumns = @JoinColumn(name = "permission_id", referencedColumnName = "id", unique = true) ) private List<Permission> permissions = new ArrayList<>(); private String name; @Column(unique = true) private String role; // 角色标识 程序中判断使用,如"admin" private String description; // 角色描述,UI界面显示使用
权限
权限是资源和操作的组合。也就是说一个权限意味着对一个资源的多个操作。
@Entity@Table(name = "sys_permission")public class Permission implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToOne(targetEntity = Resource.class, cascade = CascadeType.REFRESH, fetch = FetchType.EAGER) @JoinColumn(name = "resource_id", referencedColumnName = "id") private Resource resource; @ManyToMany(targetEntity = Operation.class, cascade = CascadeType.REFRESH, fetch = FetchType.EAGER) @JoinTable(name = "sys_permission_operation", joinColumns = @JoinColumn(name = "permission_id", referencedColumnName = "id") , inverseJoinColumns = @JoinColumn(name = "operation_id", referencedColumnName = "id") ) private Set<Operation> operations = new HashSet<>(); private String name; private String description;
资源
@Entity@Table(name = "sys_resource")public class Resource implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // 编号 private String name; // 资源名称 @Column(unique = true) private String identity;// 资源类型
操作
@Entity@Table(name = "sys_operation")public class Operation implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // 编号 private String name; // 操作名称 @Column(unique = true) private String operation;// 操作标识 private String description;
简洁易用的Spring Data JPA
Spring Data可以通过解析方法名创建查询。比如方法是findByUsername(String username),则Spring Data会自动生成SQL语句select * from 表名 where username= username.所以你对于简单的操作,你只要按照Srping Data约定写出对应的方法名,Spring Data就能自动创建SQL语句,基本不需要你写SQL语句。
一般定义DAO层时,继承一个Spring Data的基本接口,比如JpaRepository,该接口能可以帮你实现增删查改的功能外,还可以帮你做分页查询和排序。
import org.springframework.data.jpa.repository.JpaRepository;import com.myweb.entity.User;public interface UserDao extends JpaRepository<User, Long> { User findByUsername(String username);}
使用时,直接注入就可以了
@Inject private UserDao userDao;
Shiro认证与授权
shiro比Spring Security简单,在实现认证授权的时候,继承一个Realm类然后扩展,这里使用AuthorizingRealm。
Realm在shiro中类似数据源,认证和授权的数据就依靠它。
public class UserRealm extends AuthorizingRealm { @Inject private UserService userService; @Inject private UserDao userDao; @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // 授权 String username = (String) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.setRoles(userService.getStringRoles(username)); authorizationInfo.setStringPermissions(userService.getStringPermissions(username)); return authorizationInfo; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { // 认证 String username = (String) authenticationToken.getPrincipal(); User user = userDao.findByUsername(username); // 查出是否有此用户 if (user == null) { throw new UnknownAccountException();// 没找到帐号 } if (Boolean.TRUE.equals(user.getLocked())) { throw new LockedAccountException(); // 帐号锁定 } // 交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), ByteSource.Util.bytes(user.getCredentialsSalt()), getName()); return authenticationInfo; }}
认证
需要SecurityUtils返回一个Subject,然后通过用户名和密码创建Token,使用这个Token登录验证。Subject 是 Shiro 的核心对象,基本所有身份验证、授权都是通过 Subject 完成。
@RequestMapping(value = "/login", method = RequestMethod.POST) public ModelAndView login(User user) { ModelAndView mv = new ModelAndView(); try { Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken authenticationToken = new UsernamePasswordToken(user.getUsername(), user.getPassword()); subject.login(authenticationToken); } catch (AuthenticationException e) { e.printStackTrace(); mv.setViewName("redirect:/unauthorized"); return mv; } mv.setViewName("redirect:/index"); return mv; }
授权
采用注解形式,简单方便
@RequiresPermissions("user:create") @RequestMapping(value = "/save", method = RequestMethod.POST) @ResponseBody public String saveUser(@RequestParam String username, @RequestParam String password, @RequestParam String email, @RequestParam(value = "roles", required = false) List<Long> roleIds) { User user = new User(); user.setUsername(username); user.setPassword(password); user.setEmail(email); List<Role> roles = roleDao.findAll(roleIds); user.setRoles(new HashSet<>(roles)); userService.save(user); return "OK"; }
支持注解需要配置
<!-- Enable Shiro Annotations for Spring-configured beans. Only run after --> <!-- the lifecycleBeanProcessor has run: --> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor" /> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager" /> </bean>
XML配置
项目整体配置
web.xml:
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>myweb</display-name> <welcome-file-list> <welcome-file>/WEB-INF/jsp/login.jsp</welcome-file> </welcome-file-list> <!-- 加载所有的配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:config/spring-*.xml</param-value> </context-param> <!-- 配置Spring监听 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 配置字符集 --> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 配置Session --> <filter> <filter-name>openSession</filter-name> <filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class> <init-param> <param-name>singleSession</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>flushMode</param-name> <param-value>AUTO</param-value> </init-param> </filter> <filter-mapping> <filter-name>openSession</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 配置SpringMVC --> <servlet> <description>myweb</description> <display-name>myweb</display-name> <servlet-name>springMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:config/spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 配置shiro --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping></web-app>
配置SpringMVC
spring-mvc.xml:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> <!-- 开启Spring MVC注解 --> <mvc:annotation-driven /> <!-- 静态资源映射 --> <mvc:resources mapping="/static/**" location="/WEB-INF/static/" /> <mvc:resources mapping="/html/**" location="/WEB-INF/html/" /> <!-- 打开Spring的Annotation支持 --> <context:annotation-config /> <!-- 注解扫描包 --> <context:component-scan base-package="com.myweb.controller" /> <context:component-scan base-package="com.myweb.dao.imp" /> <context:component-scan base-package="com.myweb.service.imp" /> <context:component-scan base-package="com.myweb.security.util" /> <!-- 配置SessionFactory --> <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> <!-- <property name="dataSource" ref="dataSource" /> --> <property name="configLocation" value="classpath:config/hibernate.cfg.xml" /> <property name="packagesToScan" value="com.myweb.entity" /> </bean> <!-- 配置Dao要注入的hibernateTemplate --> <bean id="hibernateTemplate" class="org.springframework.orm.hibernate4.HibernateTemplate"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <!-- 配置Spring的事务处理 --> <!-- 创建事务管理器 --> <bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <!-- 配置AOP,Spring是通过AOP来进行事务管理的 --> <aop:config> <!-- 设置pointCut表示哪些方法要加入事务处理 --> <!-- 以下的事务是声明在DAO中,但是通常都会在Service来处理多个业务对象逻辑的关系,注入删除,更新等,此时如果在执行了一个步骤之后抛出异常 就会导致数据不完整,所以事务不应该在DAO层处理,而应该在service,这也就是Spring所提供的一个非常方便的工具,声明式事务 --> <aop:pointcut id="allMethods" expression="execution(* com.myweb.service.imp.*.*(..))" /> <!-- 通过advisor来确定具体要加入事务控制的方法 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="allMethods" /> </aop:config> <!-- 配置哪些方法要加入事务控制 --> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <!-- 让所有的方法都加入事务管理,为了提高效率,可以把一些查询之类的方法设置为只读的事务 --> <tx:method name="find*" propagation="REQUIRED" read-only="true" /> <!-- 以下方法都是可能设计修改的方法,就无法设置为只读 --> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="del*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="save*" propagation="REQUIRED" /> </tx:attributes> </tx:advice> <!-- 开启事务注解 --> <tx:annotation-driven /> <!-- 默认的视图解析器ViewResolver --> <bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean> <!-- JPA --> <jpa:repositories base-package="com.myweb.dao" repository-impl-postfix="Impl" entity-manager-factory-ref="entityManagerFactory" transaction-manager-ref="transactionManager"> </jpa:repositories> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="packagesToScan" value="com.myweb.entity" /> <property name="persistenceProvider"> <bean class="org.hibernate.ejb.HibernatePersistence" /> </property> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <property name="generateDdl" value="false" /> <property name="database" value="MYSQL" /> <property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" /> <property name="showSql" value="true" /> </bean> </property> <property name="jpaDialect"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" /> </property> <property name="jpaPropertyMap"> <map> <entry key="hibernate.query.substitutions" value="true 1, false 0" /> <entry key="hibernate.default_batch_fetch_size" value="16" /> <entry key="hibernate.max_fetch_depth" value="2" /> <entry key="hibernate.generate_statistics" value="true" /> <entry key="hibernate.bytecode.use_reflection_optimizer" value="true" /> <entry key="hibernate.cache.use_second_level_cache" value="false" /> <entry key="hibernate.cache.use_query_cache" value="false" /> </map> </property> </bean> <!--事务管理器配置 --> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean> <bean name="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName"> <value>com.mysql.jdbc.Driver</value> </property> <property name="url"> <value>jdbc:mysql://localhost:3306/myweb</value> </property> <property name="username"> <value>root</value> </property> <property name="password" value="jiangyu" /> </bean> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> <property name="messageConverters"> <list> <ref bean="mappingJacksonHttpMessageConverter" /> </list> </property> </bean> <bean id="mappingJacksonHttpMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>apolication/json; charset=UTF-8</value> </list> </property> </bean> <mvc:annotation-driven> <mvc:argument-resolvers> <bean class="com.myweb.controller.CurrentUserMethodArgumentResolver" /> </mvc:argument-resolvers> </mvc:annotation-driven></beans>
shiro在Spring配置
spring-shiro-web.xml:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <!-- 凭证匹配器 --> <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="md5" /> <property name="hashIterations" value="2" /> <property name="storedCredentialsHexEncoded" value="true" /> </bean> <!-- Realm实现 --> <bean id="userRealm" class="com.myweb.security.realm.UserRealm"> <property name="credentialsMatcher" ref="credentialsMatcher" /> </bean> <!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="userRealm" /> </bean> <!-- Shiro的Web过滤器 ,id要与web.xml一致 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <property name="loginUrl" value="/api/user/login.page" /> <property name="successUrl" value="/index" /> <property name="unauthorizedUrl" value="/unauthorized" /> <property name="filterChainDefinitions"> <value> /static/** = anon /api/user/login = anon /api/user/register* = anon /unauthorized = anon /** = authc </value> </property> </bean> <!-- Shiro生命周期处理器 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" /> <!-- Enable Shiro Annotations for Spring-configured beans. Only run after --> <!-- the lifecycleBeanProcessor has run: --> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor" /> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager" /> </bean></beans>
hibernate配置
hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"><hibernate-configuration> <session-factory> <property name="hibernate.dialect"> org.hibernate.dialect.MySQLDialect </property> <property name="hibernate.connection.driver_class"> com.mysql.jdbc.Driver </property> <property name="hibernate.connection.url"> jdbc:mysql://localhost/myweb </property> <property name="hibernate.connection.username"> root </property> <property name="hibernate.connection.password"> jiangyu </property> <property name="hibernate.temp.use_jdbc_metadata_defaults">false</property> <!-- ddl语句自动建表 --> <property name="hbm2ddl.auto">update</property> <property name="show_sql">true</property> <property name="format_sql">true</property> <property name="jdbc.fetch_size">50</property> <property name="jdbc.batch_size">30</property> <property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property> <property name="hibernate.c3p0.acquireRetryAttempts">30</property> <property name="hibernate.c3p0.acquireIncrement">2</property> <property name="hibernate.c3p0.checkoutTimeout">30000</property> <property name="hibernate.c3p0.idleConnectionTestPeriod">120</property> <property name="hibernate.c3p0.maxIdleTime">180</property> <property name="hibernate.c3p0.initialPoolSize">3</property> <property name="hibernate.c3p0.maxPoolSize">50</property> <property name="hibernate.c3p0.minPoolSize">1</property> <property name="hibernate.c3p0.maxStatements">0</property> <!-- XML配置映射关系 <mapping resource="Employee.hbm.xml" /> --> <!-- 注册ORM映射文件 --> <!-- spring不起作用 <mapping package="com.myweb.entity"/> --> </session-factory></hibernate-configuration>
其他说明
超级管理员用户名/密码:admin/123456。
数据库导入文件:/myweb/src/main/resources/SQL/myweb.sql
源码:https://github.com/Jdoing/myweb
- SpringMVC+Spring Data JPA+Shiro+EasyUI简单权限管理系统
- SpringMVC+Spring Data JPA+Shiro+EasyUI简单权限管理系统
- SpringMVC+Spring Data JPA+Shiro+EasyUI简单权限管理系统
- SpringMVC+Spring Data JPA+Shiro+EasyUI简单权限管理系统
- SpringMVC+Spring Data JPA+Shiro+EasyUI简单权限管理系统
- springmvc-spring-mybatis-shiro-easyui权限系统
- spring+springmvc+mybatis shiro权限管理系统demo mysql数据库
- SpringMvc+hibernate+easyui简单的权限管理系统
- SpringMvc+hibernate+easyui简单的权限管理系统
- Spring+SpringMVC+Mybatis+shiro权限登录管理
- Spring+SpringMVC+Shiro+Redis+Maven权限管理
- SpringMVC+Shiro权限管理简单例子
- 【Shiro】SpringMVC+Shiro权限管理
- SpringMVC+Shiro权限管理
- SpringMVC+Shiro权限管理
- SpringMVC+Shiro权限管理
- SpringMVC+Shiro权限管理
- SpringMVC+Shiro权限管理
- android圆形旋转菜单,教你分分钟搞定它,CircleMenu
- π秒就是一个纳世纪
- 黑马程序员—面向对象(上)
- iOS 使用AVFoundation 扫描二维码并限定扫描区域(带代码生成蒙版)
- perl fork多进程
- SpringMVC+Spring Data JPA+Shiro+EasyUI简单权限管理系统
- Struts2中的ActionContext
- OpenGL教程翻译 第十九课 镜面高光
- iOS开发实习期间get的技巧
- 多个源代码,如何使用公共全局变量
- Display类 提供获取屏幕尺寸和分辨率的信息
- 目前流行的CSS+DIV的命名规则
- 移动应用开发必备工具盘点
- Android 获取GPS和网络定位信息