一篇搞定 SpringBoot+Mybatis+Shiro 实现多角色权限管理
初衷:我在网上想找整合springboot+mybatis+shiro并且多角色认证的博客,发现找了好久也没有找到想到的,现在自己会了,就打算写个博客分享出去,希望能帮到你。
原创不易,请点赞支持!
该项目不会将过多基础,直接实战,比较使用于有一点基础的, 并且想整合springboot+mybatis+shiro的朋友们。
代码和数据库sql都放在github,链接如下:https://github.com/zhiyuwyu/springboot-mybatis-shiro.git
一篇搞定 SpringBoot+Mybatis+Shiro 实现多角色权限管理
- 1、了解需求
- 1.1、了解页面
- 1.2、需求
- 2、准备数据库环境
- 3、编写代码
- 3.1、新建SpringBoot 工程项目
- 3.2、添加如下依赖
- 3.3、编写代码连接数据库并测试
- 3.3.1、配置数据库信息
- 3.3.2、编写实体类 entity
- 3.3.3、mapper
- 3.3.4、service
- 3.4、编写页面
- 3.5、编写 shiro 的有关配置
- 3.6、编写Controller层代码
- 4、测试
- 4.0、游客访问
- 4.1、测试无用户登录
- 4.2、测试密码不正确登录
- 4.3、测试 rootA用户正确登录
- 4.4、测试adminA用户正确登录
- 4.5、测试userA用户正确登录
- 5、结语
1、了解需求
1.1、了解页面
登录页面如下
首页页面如下
分别点击添加、删除、查询、测试超链接,展示的内容如下
1.2、需求
- 首页页面必须登录成功之后才能访问
- 所有用户、游客等都可访问登录页面、测试页面,无需登录
- 拥有 root 角色的用户可以访问所有页面,包括添加页面、删除页面、查询页面、测试页面等
- 拥有admin 角色的用户可以访问添加页面,查询页面、测试页面,除了删除页面不能访问
- 拥有 user 角色的用户可以访问 查询页面、测试页面,除了添加页面、删除页面不能访问
2、准备数据库环境
新建一个test
数据库,创建两个表(role、user)并插入数据,sql 如下
/*Navicat Premium Data TransferSource Server : LocalHostSource Server Type : MySQLSource Server Version : 50731Source Host : localhost:3306Source Schema : testTarget Server Type : MySQLTarget Server Version : 50731File Encoding : 65001Date: 12/07/2021 21:08:51
*/SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (`id` int(2) NOT NULL AUTO_INCREMENT,`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,`remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 'root', '超级管理员');
INSERT INTO `role` VALUES (2, 'admin', '管理员');
INSERT INTO `role` VALUES (3, 'user', '普通用户');-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (`id` int(10) NOT NULL AUTO_INCREMENT,`username` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,`role_id` int(3) NOT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'rootA', '123456', 1);
INSERT INTO `user` VALUES (2, 'adminA', '123456', 2);
INSERT INTO `user` VALUES (3, 'userA', '123456', 3);
INSERT INTO `user` VALUES (4, 'userB', '123456', 3);SET FOREIGN_KEY_CHECKS = 1;
role 表数据如下:
user 表数据如下:
3、编写代码
3.1、新建SpringBoot 工程项目
3.2、添加如下依赖
全部依赖如下
<!-- thymeleaf -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- web -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mysql-->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-->
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.0</version>
</dependency>
<!-- Spring对Shiro支持 -->
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.7.1</version>
</dependency>
<!--test单元测试-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.14</version>
</dependency>
3.3、编写代码连接数据库并测试
3.3.1、配置数据库信息
把 application.properties 文件修改成 application.yml,并添加如下内容
spring:datasource:username: root password: 123456 url: jdbc:mysql://localhost:3306/test
# 打印sql语句
logging:level:com:huang:shiro1:mapper: debug
3.3.2、编写实体类 entity
新建一个entity 包,分别添加下面两个实体类
User.java
package com.huang.springbootmybatisshiro.entity;
import lombok.Data;
import java.io.Serializable;
/*** (User)实体类*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {private static final long serialVersionUID = 227751358530931042L;private Integer id;private String username;private String password;private Integer roleId;private Role role;
}
Role.java
package com.huang.springbootmybatisshiro.entity;
import lombok.Data;
import java.io.Serializable;
/*** (Role)实体类*/
@Data
public class Role implements Serializable {private static final long serialVersionUID = -76407922564857637L;private Integer id;private String name;private String remark;}
3.3.3、mapper
新建一个 mapper 包,分别创建下面两个文件
UserMapper.java
public interface UserMapper {/*** 根据 Username 查询单条数据** @param username* @return*/User queryByUsername(@Param("username") String username);}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.huang.springbootmybatisshiro.mapper.UserMapper"><resultMap type="com.huang.springbootmybatisshiro.entity.User" id="UserMap"><result property="id" column="id" jdbcType="INTEGER"/><result property="username" column="username" jdbcType="VARCHAR"/><result property="password" column="password" jdbcType="VARCHAR"/><result property="roleId" column="role_id" jdbcType="INTEGER"/></resultMap><resultMap id="UserMapWithRole" type="com.huang.springbootmybatisshiro.entity.User" extends="UserMap"><collection property="role" ofType="com.huang.springbootmybatisshiro.entity.Role"><id property="id" column="rid"></id><result property="name" column="rname"></result><result property="remark" column="rremark"></result></collection></resultMap><select id="queryByUsername" resultMap="UserMapWithRole">selectu.*,r.id rid,r.name rname,r.remark rremarkfrom test.user u,test.role rwhere u.role_id=r.id<if test="username != null and username != ''">and username = #{username}</if></select></mapper>
注意:
UserMapper.xml 和UserMapper.java 文件写在同一目录下,需在pom.xml文件添加如下内容
<build><resources><resource><directory>src/main/java</directory><includes><include>**/*.xml</include></includes></resource><resource><directory>src/main/resources</directory></resource></resources><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins>
</build>
并且在启动类中添加注解@mapperscan 全局扫描 mapper 文件
@SpringBootApplication
@MapperScan(basePackages = "com.huang.springbootmybatisshiro.mapper")
public class SpringbootMybatisShiroApplication {public static void main(String[] args) {SpringApplication.run(SpringbootMybatisShiroApplication.class, args);}
}
3.3.4、service
新建一个包service,并添加 Userservice.java 文件如下
@Service
public class UserService {@AutowiredUserMapper userMapper;public User queryByUsername(String username) {return userMapper.queryByUsername(username);}
}
3.3.4 测试是否可以正常获取数据库信息
@SpringBootTest
class SpringbootMybatisShiroApplicationTests {@AutowiredUserService userService;@Testvoid contextLoads() {User rootA = userService.queryByUsername("rootA");System.out.println("=====================================");System.out.println("rootA = " + rootA);}}
如果输入如下数据则成功,就可以进行下一步了
3.4、编写页面
在resources目录下新建文件夹templates(有就不用了),在templates 下添加如下页面
- add.html 添加页面
- delete.html 删除页面
- index.html 首页页面
- login.html 登录页面
- query.html 查询页面
- test.html 测试页面
- unauthorized.html 未授权页面
add.html
<!DOCTYPE html >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>add</title>
</head>
<body>
<h1>添加页面</h1>
</body>
</html>
delete.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>update</title>
</head>
<body>
<h1>删除页面</h1>
</body>
</html>
index.html
<!DOCTYPE html >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>首页</title>
</head>
<body>
<h1>首页</h1>
进入用户添加页面: <a href="add">添加页面</a><br/>
进入用户删除页面: <a href="delete">删除页面</a><br/>
进入用户查询页面: <a href="query">查询页面</a><br/>
进入用户测试页面: <a href="test">测试页面</a><br/>
</body>
</html>
login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>login</title>
</head>
<body>
<h1>登录页面</h1>
<h3 th:text="${msg}" style="color: red"></h3>
<form action="login" method="post">用户名:<input type="text" name="username"><br>密码:<input type="text" name="password"><br><input type="submit" value="submit">
</form>
</body>
</html>
query.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>query</title>
</head>
<body>
<h1>查询页面</h1>
</body>
</html>
test.html
<!DOCTYPE html >
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>测试页面</title>
</head>
<body>
<h1>测试页面</h1>
</body>
</html>
unauthorized.html。当访问不够权限的页面时会跳转到该页面
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>unauthorized</title>
</head>
<body>
<h1>你未授权,请联系管理员</h1>
</body>
</html>
3.5、编写 shiro 的有关配置
新建一个config 包,添加以下文件
CustomRealm.java
/*** @Author: Zhiyu* @Date: 2021/7/13 10:40* @Description: 主要用于用户数据和shiro的交互工作*/
public class CustomRealm extends AuthorizingRealm {@AutowiredUserService userService;/*** 授权:给当前用户授权,以便能访问** @param principals* @return*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {// principal 就是下面的方法 doGetAuthenticationInfo 中的return new SimpleAuthenticationInfo(user, user.getPassword(), "") 中的第一个参数 user 赋值的User principal = (User) principals.getPrimaryPrincipal();System.out.println("principal = " + principal);// 给资源进行授权SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();// 授权该用户的本身角色的权限info.addRole(principal.getRole().getName());return info;}/*** 认证:能不能登录等认证** @param authenticationToken* @return* @throws AuthenticationException*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {System.out.println("-------------进入了认证逻辑---------------");// token 中存储着 subject.login(token) 中传过来的数据UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;String username = token.getUsername();// 这里假设,数据库中的 username 字段是唯一字段,可以作为唯一标识,实际开发中可以适当修改,换汤不换药// 根据 username 去数据库查询用户信息// 数据库查询回来的数据User user = userService.queryByUsername(username);if (user == null) {// 用户名不存在// return null; shiro 底层会抛出UnknowAccountExceptionthrow new UnknownAccountException();}// 第一个参数 user: 代表传值,及保存用户的信息,后面会用到// 第二个参数 填真正的密码,shiro 会帮我们做密码验证,无需我们自己做密码验证逻辑return new SimpleAuthenticationInfo(user, user.getPassword(), "");}
}
ShiroConfig.java
/*** @Author: Zhiyu* @Date: 2021/7/13 10:40* @Description: shiro 的配置* 完成下面三件事* 1.创建 ShiroFilterFactoryBean* 2.DefaultWebSecurityManager* 3.创建Realm并关联*/
@Configuration
public class ShiroConfig {@Bean(name = "customRealm")public CustomRealm customRealm() {return new CustomRealm();}@Bean(name = "securityManager")public SecurityManager securityManager(CustomRealm customRealm) {DefaultWebSecurityManager manager = new DefaultWebSecurityManager();manager.setRealm(customRealm);return manager;}/*** 过滤器配置** @param securityManager* @return*/@Beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(securityManager);/*** Shiro内置过滤器,可以实现权限相关的拦截器* 常用的过滤器:* anon: 无需认证(登录)可以访问* authc: 必须认证才可以访问* user: 如果使用rememberMe的功能可以直接访问* perms: 该资源必须得到资源权限才可以访问* roles: 该资源必须得到角色权限才可以访问*/// 1. 权限相关的拦截器(什么路径需要什么权限)LinkedHashMap<String, String> filterMap = new LinkedHashMap<>();filterMap.put("/delete", "roles[root]"); //roles[root] 意思是访问/delete 需要角色 root// roles[admin,root]意思是访问/add 需要角色 admin或者root。// 如果不配置 RoleFilter,解决多角色and关系,则roles[admin,root]意思就是访问/add 需要 admin和root两个角色同时。filterMap.put("/add", "roles[admin,root]");// anon,意识是/test、/login / 无需认证(登录)可以访问filterMap.put("/test", "anon");filterMap.put("/login", "anon");filterMap.put("/", "anon");// authc 其余的访问都必须认证才可以访问,filterMap.put("/*", "authc");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);// 添加shiro 权限过滤器// 2. 配置自定义 or 角色 认证,把自定义过滤器配置进去即可LinkedHashMap<String, Filter> filters = new LinkedHashMap<>();filters.put("roles", new RoleFilter());shiroFilterFactoryBean.setFilters(filters);// 3. 修改默认的登录页面和未授权页面// 即访问需要登录有页面时会跳转到 /toLogin 请求// 即访问需要不够权限的时候页面时会跳转到 /toLogin 请求shiroFilterFactoryBean.setLoginUrl("/unauthorized");// 修改调整的登录页面shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");// 设置未授权提示页面return shiroFilterFactoryBean;}
}
RoleFilter.java
/*** @Author: Zhiyu* @Date: 2021/7/13 11:08* @Description: 重写Shiro自带角色权限过滤器* shiro自带的方法同一权限只能分配一个角色,默认所个角色的时候是 and 关系,不是 or 关系* 所以重写 重写Shiro自带角色权限过滤器 解决多角色 的时候是 or 关系*/
public class RoleFilter extends RolesAuthorizationFilter {@Overridepublic boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)throws IOException {final Subject subject = getSubject(request, response);final String[] rolesArray = (String[]) mappedValue;if (rolesArray == null || rolesArray.length == 0) {// 无指定角色时,无需检查,允许访问return true;}for (String roleName : rolesArray) {if (subject.hasRole(roleName)) {return true;}}return false;}
}
3.6、编写Controller层代码
新建一个controller包,添加 UserController.java 文件,内容如下
@Controller
public class UserController {/*** 使用 shiro 编写认证(登录)逻辑* 1. 获取 Subject* 2. 封装用户数据* 3. 执行登录方法*/@PostMapping("/login")public String login(String username, String password, Model model) {System.out.println("username = " + username);System.out.println("password = " + password);// 1.获取 SubjectSubject subject = SecurityUtils.getSubject();// 2. 封装用户数据UsernamePasswordToken token = new UsernamePasswordToken(username, password);String msg = "登录成功";try {// 3. 执行登录方法, 到 CustomRealm 类的doGetAuthenticationInfo中去执行认证逻辑subject.login(token);} catch (UnknownAccountException uae) {msg = "未知账户";} catch (IncorrectCredentialsException ice) {msg = "密码不正确";} catch (LockedAccountException lae) {msg = "账户已锁定";} catch (ExcessiveAttemptsException eae) {msg = "用户名或密码错误次数过多";} catch (AuthenticationException ae) {msg = "用户名或密码不正确!";}model.addAttribute("msg", msg);if (subject.isAuthenticated()) {// 登录成功,跳转到 index.htmlreturn "redirect:/index";} else {token.clear();return "login";}}
}
7、编写 WebMvcConfig, 配置无逻辑的访问页面跳转
在config 包下新建 WebMvcConfig.java ,代码如下
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {// 这里配置一些无逻辑处理的页面请求@Overridepublic void addViewControllers(ViewControllerRegistry registry) {/*** registry.addViewController("/add").setViewName("add");* 的意识等价于 在controller 层的* @RequestMapping("/add")* public String add() {* return "add"; // 跳转到 add.html 页面* }*** 所以registry.addViewController("/add").setViewName("add");* 意思是:访问 /add 就会跳转到 add.html 页面* 下面的以此类推**/registry.addViewController("/add").setViewName("add");registry.addViewController("/delete").setViewName("delete");registry.addViewController("/query").setViewName("query");registry.addViewController("/toLogin").setViewName("login");registry.addViewController("/").setViewName("login");registry.addViewController("/unauthorized").setViewName("unauthorized");registry.addViewController("/index").setViewName("index");registry.addViewController("/test").setViewName("test");}
}
启动项目进行测试
4、测试
User 表数据如下
4.0、游客访问
游客直接在浏览器输入下面的地址
http://localhost:8080/add
http://localhost:8080/delete
http://localhost:8080/query
都是下面会跳转到登录页面
除了访问http://localhost:8080/test
可以正确跳转
4.1、测试无用户登录
访问 http://localhost:8080/
, 输入如下信息
点击提交,然后显示的页面如下
4.2、测试密码不正确登录
访问 http://localhost:8080/
, 输入如下信息
点击提交,然后显示的页面如下
4.3、测试 rootA用户正确登录
访问 http://localhost:8080/
, 输入如下信息
点击提交,然后显示的页面如下
分别点击添加、删除、查询、测试页面,分别显示的页面如下
结果:可以看到全部页面都可以正常访问。
4.4、测试adminA用户正确登录
访问 http://localhost:8080/
, 输入如下信息
点击提交,然后显示的页面如下
分别点击添加、删除、查询、测试页面,分别显示的页面如下
结果:拥有admin 角色的用户可以访问添加页面,查询页面、测试页面,除了删除页面不能访问
4.5、测试userA用户正确登录
访问 http://localhost:8080/
, 输入如下信息
点击提交,然后显示的页面如下
分别点击添加、删除、查询、测试页面,分别显示的页面如下
结果:拥有 user 角色的用户可以访问 查询页面、测试页面,除了添加页面、删除页面不能访问
5、结语
- 该项目不会将过多基础,直接实战,比较使用有一点基础的。
- 该项目中没有使用密码加密,如果多人浏览并反馈需要,我可以再写篇密码加密认证的
- 都是原创,希望看到这的能够点个赞支持一些。
- 代码和数据库sql都放在github,链接如下:https://github.com/JiuRiYunYue/springboot-mybatis-shiro.git
一篇搞定 SpringBoot+Mybatis+Shiro 实现多角色权限管理相关推荐
- 3分钟搞定SpringBoot+Mybatis+druid多数据源和分布式事务
在一些复杂的应用开发中,一个应用可能会涉及到连接多个数据源,所谓多数据源这里就定义为至少连接两个及以上的数据库了. 下面列举两种常用的场景: 一种是读写分离的数据源,例如一个读库和一个写库,读库负责各 ...
- SpringBoot的Web开发支持【超详细【一篇搞定】果断收藏系列】
SpringBoot的Web开发支持 常用的服务器配置 使用Jetty服务器替换Tomcat 排除Tomcat的启动器,引入Jetty application.yml 编写入口程序 编写Control ...
- java 不重启部署_一篇文章带你搞定SpringBoot不重启项目实现修改静态资源
一.通过配置文件控制静态资源的热部署 在配置文件 application.properties 中添加: #表示从这个默认不触发重启的目录中除去static目录 spring.devtools.res ...
- vlan配置实例详解_网工知识角|MUXVLAN技术详解,基本原理一篇搞定
学网络,就在IE-LAB 国内高端网络工程师培养基地 MUX VLAN(Multiplex VLAN )提供了一种通过VLAN进行网络资源控制的机制.通过MUX VLAN提供的二层流量隔离的机制可以实 ...
- CentOS7搭建LNMP+WordPress一篇搞定
零.关于本文 本文首次完成于2019年5月12日,经历多次修改.本文所有的参考文献,均以超链接的形式给出.考虑到网上的部分教程不够完整,有的已经过时,我将我搭建环境的方法记录下来. 这篇文章适合: 希 ...
- shiro、基于url权限管理、超详细
如果需要本篇博客内容的代码!请到我的博客下载中心去下载 https://download.csdn.net/download/qq_36125138/10719559 项目运行图: 权限管理原理知 ...
- springboot整合security,mybatisPlus,thymeleaf实现登录认证及用户,菜单,角色权限管理
介绍 本系统为springboot整合security,mybatisPlus,thymeleaf实现登录认证及用户,菜单,角色权限管理.页面为极简模式,没有任何渲染. 源码:https://gite ...
- SpringBoot+MyBatis+Shiro 搭建杂谈
点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:用好Java中的枚举,真的没有那么简单!个人原创+1博客:点击前往,查看更多 链接:https://www.cn ...
- Linux常用命令大全,一篇搞定
今日推荐 扔掉 Postman,一个工具全部搞定,真香!为啥查询那么慢?还在直接用JWT做鉴权?JJWT真香推荐 15 款常用开发工具干掉 navicat:这款 DB 管理工具才是y(永)y(远)d( ...
最新文章
- 计算机应用基础教材编写建议,【计算机应用论文】计算机应用基础校本教材编写研究(共3136字)...
- 正则匹配:Email 密码强度 身份证 手机号 日期 数字每4个字空一格等
- [内部项目]i前端如何增加一个页面
- 帮朋友配置的一台主机,配置发出来看看
- 【Emacs】Emacs for windows基本配置文件【转载】
- 《深度学习Python实践》第18章——持久化加载模型
- JavaScript的事件绑定及深入
- DELPHI基础教程 第二章 Delphi面向对象的编程方法
- GD32创建工程与启动文件选择
- hihocoder 1257 Snake Carpet
- 一张让android死机图片,导致安卓手机死机的照片拍摄者表示这张照片是无意之举...
- Linux内核设计与实现 第17章 设备与模块
- java查看内存信息
- Spacy分词php,spaCy 第二篇:语言模型
- 【游戏逆向】某某明月刀_技能冷却分析
- springboot自带的线程池ThreadPoolTaskExecutor、ThreadPoolTaskScheduler的深入应用——异步任务监听回调,任务中断案例
- 360手机官方刷机教程(N5系列通用)
- Android中复制到剪切板
- 【传感器sensor】机器人/无人驾驶常用传感器模型、选型与安装
- 大数据教程【05.01】--Python 数据分析简介
热门文章
- Jenkins使用FTP上传文件报错问题处理
- 微信公众号新变动!你都发现了吗?
- 时代变了,互联网与房产开发商能否找到真爱
- 【知识产权之专利权】论述题题库
- 升级IOS百度人脸SDK4.0采坑记录
- Command CompileAssetCatalog emitted errors but did not return a nonzero exit code to indicate failur
- 从TOP100summit看产品设计和运营创新的“B”计划和“C”计划
- JavaScript效率PK——统计特定字符在字符串中出现的次数
- route和bridge是什么意思_请问ROUTE 和 BRIDGE 是怎么分别的!
- 一起来学linux:磁盘与文件系统: