Spring Boot(十一):Spring Security 实现权限控制
xsobi 2024-12-15 17:31 1 浏览
大家好,我是 杰哥
我们知道,Web 应用的安全一般关注用户认证(authentication)以及用户授权(authorization)两个部分,即确定你是谁以及你能干什么
Spring Security 是基于 Spring 框架,用于解决 Web 应用安全性的一种方案,它是一款优秀的权限管理框架
虽然相对于 shiro (是 Apache 的一个轻量级权限控制框架,不与任何框架捆绑)来说,它比较重(依赖的东西较多),但是 Spring Boot 提供了自动化配置方案,通过较少的配置就可以使用 Spring Security 来进行权限控制。因此,若项目使用的是 Spring Boot 框架,使用 Spring Security 将会是一个更优的选择
Spring Boot 集成 Security 实战
Spring Security 采用的是责任链的设计模式,它有一条很长的过滤器链来实现各个环节的控制逻辑。要使用它,只需要自定义类继承自 WebSecurityConfigurerAdapter 来配置。我们主要配置三个地方
- 配置可登录用户,包括用户名、密码和权限/角色
configure(AuthenticationManagerBuilder auth)
- 配置权限关系(url、authority的所有对应关系)
configure(HttpSecurity http)
- 配置放行规则
configure(WebSecurity web)
具体怎样使用呢?我们一起来看看具体步骤
一 公共配置
1 pom 依赖
首先引入 security 的 pom 依赖,直接引入 spring-boot-starter-security 依赖
<!--security 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--lombok 工具 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
其他两个依赖 spring-boot-starter-web 和 lombok 则分别用于构建 Web 应用、采用 lombok 的依赖包进行快捷开发
2 定义 User 实体类
@Data
public class User implements UserDetails {
private Integer id;
private String username;
private String password;
// 用户所有权限
private List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
由于后续的配置需要使用到用户的用户名、密码以及所拥有的权限这三个信息,因此需要为原 User 实体类,添加一个 authorities 属性,表示用户所拥有的的权限列表
3 定义测试 API
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/common")
public String common() {
return "hello~ common";
}
@GetMapping("/admin")
public String admin() {
return "hello~ admin";
}
}
分别定义 /user/common 和 /user/admin 两个 API,作为用户的权限,用于测试不同用户的权限
二 核心权限代码配置
好了,做好了准备工作之后,接下来就进入到我们的重点部分。为了让初学者有一个理解过程,我们先通过手动配置的方式,让大家对于 Security 的使用方式有个大致的概念
(一) 方式一:手动配置
1 权限配置
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 将用户配置在内存中(可登录用户,包括用户名、密码和权限)
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 方式一:写到内存
auth.inMemoryAuthentication()
.withUser("admin").password(passwordEncoder().encode("admin")).authorities("admin")
.and()
.withUser("user").password(passwordEncoder().encode("123456")).authorities("common");
}
/**
* 登录处理-配置对应用户所拥有的权限(url、authority对应的所有关系)
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 方式一:写死的方式
http.authorizeRequests().
antMatchers("/user/common").hasAnyAuthority("admin")
.antMatchers("/user/admin").hasAnyAuthority("admin")
.antMatchers("/user/common").hasAnyAuthority("common")
.antMatchers("/**").fullyAuthenticated()
.anyRequest().authenticated()
.and().formLogin();
}
/**
* 放行规则(忽略拦截配置)
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
// 设置拦截忽略url - 会直接过滤该url - 将不会经过Spring Security过滤器链
web.ignoring().antMatchers("/hello");
// 设置拦截忽略文件夹,可以对静态资源放行
web.ignoring().antMatchers("/css/**", "/js/**");
}
/**
* 密码加密类
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
也就是说,我们将 Web 应用中的所有用户:admin 和 user 以及对应的权限,通过实现 configure(AuthenticationManagerBuilder auth) 方法,直接手动写入内存中,这里需要指定用户的用户名、密码以及用户所拥有的权限
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 方式一:写到内存
auth.inMemoryAuthentication()
.withUser("admin").password(passwordEncoder().encode("admin")).authorities("admin")
.and()
.withUser("user").password(passwordEncoder().encode("123456")).authorities("common");
}
通过实现 configure(HttpSecurity http) 方法,来配置不同资源与权限的对应关系。这里表示 资源 /user/common 能够分别被具有 admin 和 common 权限的用户访问,而 /user/admin 则 只能够被具有 admin 权限的用户访问
@Override
protected void configure(HttpSecurity http) throws Exception {
// 方式一:写死的方式
http.authorizeRequests().
antMatchers("/user/common").hasAnyAuthority("admin")
.antMatchers("/user/admin").hasAnyAuthority("admin")
.antMatchers("/user/common").hasAnyAuthority("common")
.antMatchers("/**").fullyAuthenticated()
.anyRequest().authenticated()
.and().formLogin();
}
通过实现 configure(WebSecurity web) 方法,来配置 Web 应用的放行规则。这里表示资源 /hello 和 /css/、/js/ 资源是不用做权限校验的,也就是说任何用户都有访问他们的权限
@Override
public void configure(WebSecurity web) throws Exception {
// 设置拦截忽略url - 会直接过滤该url - 将不会经过Spring Security过滤器链
web.ignoring().antMatchers("/hello");
// 设置拦截忽略文件夹,可以对静态资源放行
web.ignoring().antMatchers("/css/**", "/js/**");
}
2)验证 启动项目,进行访问验证
输入 /user/common,便自动跳转到了 login 登录页面。使用 user 进行登录
用户名密码输入正确之后,点击登录,则可以自动重定向至 /user/common 接口
我们修改浏览器的访问地址为 localhost:8082/user/admin,点击回车
由于当前登录用户 user ,并没有 admin 权限,因此被禁止访问 /user/admin 接口,返回码为 403
那么,也就是说,user 用户可以访问 /user/common 接口,但并没有权限访问 /user/adimin 接口
同样地,清掉 cookie 缓存之后,再试试采用 admin 用户重新登录,分别访问 /user/common 和 /user/admin 接口,均可以正常请求
这两个用户的访问效果,均符合我们的预期,说明我们的权限功能已经成功实现啦
(二) 数据库方式
Spring Boot Security 提供了基于用户的权限控制以及基于角色来进行权限控制这两种方式,即通过分别配置用户对应的权限和资源对应的权限(hasAuthority)、分别配置用户对应的角色和资源对应的角色(hasRole)这两种方式,这里使用的是直接指定用户的权限的方式,即 hasAuthority 方法,不过两者其实都是采用 RBAC -基于角色的权限访问控制(Role-Based Access Control) 进行权限管理的。即:
所以,若需要采用数据库的方式进行权限逻辑控制,我们需要创建 5 张表:user 表、role 表、permission 表、user_role 表以及 role_permission 表
1)建表语句
-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`url` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`description` varchar(255) DEFAULT NULL,
`pid` bigint(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of permission
-- ----------------------------
BEGIN;
INSERT INTO `permission` VALUES (1, '/user/common', 'common', NULL, 0);
INSERT INTO `permission` VALUES (2, '/user/admin', 'admin', NULL, 0);
COMMIT;
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of role
-- ----------------------------
BEGIN;
INSERT INTO `role` VALUES (1, 'USER');
INSERT INTO `role` VALUES (2, 'ADMIN');
COMMIT;
-- ----------------------------
-- Table structure for role_permission
-- ----------------------------
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`role_id` bigint(11) NOT NULL,
`permission_id` bigint(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of role_permission
-- ----------------------------
BEGIN;
INSERT INTO `role_permission` VALUES (1, 1, 1);
INSERT INTO `role_permission` VALUES (2, 2, 1);
INSERT INTO `role_permission` VALUES (3, 2, 2);
COMMIT;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
BEGIN;
INSERT INTO `user` VALUES (1, 'user', '$2a$10$4zd/aj2BNJhuM5PIs5BupO8tiN2yikzP7JMzNaq1fXhcXUefWCOF2');
INSERT INTO `user` VALUES (2, 'admin', '$2a$10$4zd/aj2BNJhuM5PIs5BupO8tiN2yikzP7JMzNaq1fXhcXUefWCOF2');
COMMIT;
-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` bigint(11) NOT NULL,
`role_id` bigint(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user_role
-- ----------------------------
BEGIN;
INSERT INTO `user_role` VALUES (1, 1, 1);
INSERT INTO `user_role` VALUES (3, 2, 2);
COMMIT;
其中,user 表中的两个用户的密码,均为 123456,是提前采用代码new BCryptPasswordEncoder().encode("123456")加密之后所得的结果
2)引入依赖
再引入数据库连接相关依赖
<!-- mysql 连接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- jpa 连接池-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<exclusion>
<artifactId>com.zaxxer</artifactId>
<groupId>HikariCP</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.9</version>
</dependency>
3)数据库配置
在配置文件 application.yml 中,添加数据库配置
server:
port: 8082
spring:
datasource:
url: jdbc:mysql://localhost:3306/security_demo?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
username: root
password: 123456
4)定义 UserMapper 接口
public interface UserMapper {
/**
* 根据用户名称查询
*
* @param userName
* @return
*/
@Select(" select * from user where username = #{userName}")
User findByUsername(@Param("userName") String userName);
/**
* 查询用户的权限根据用户查询权限
*
* @param userName
* @return
*/
@Select(" SELECT d.*\n" +
"from user a,user_role b,role_permission c,permission d\n" +
"WHERE \n" +
"a.id = b.user_id\n" +
"and b.role_id = c.role_id\n" +
"and c.permission_id = d.id\n" +
"and \n" +
"a.username= #{userName};")
List<Permission> findPermissionByUsername(@Param("userName") String userName);
}
5)定义 UserService 接口
该接口继承自接口 UserDetaileService 接口
public interface UserService<T extends User> extends UserDetailsService {
}
6) 接口实现类
@Slf4j
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1.根据用户名称查询到user用户
User userDetails = userMapper.findByUsername(username);
if (userDetails == null) {
return null;
}
// 2.查询该用户对应的权限
List<Permission> permissionList = userMapper.findPermissionByUsername(username);
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
permissionList.forEach((a) -> grantedAuthorities.add(new SimpleGrantedAuthority(a.getName())));
log.info(">>permissionList:{}<<", permissionList);
// 设置权限
userDetails.setAuthorities(grantedAuthorities);
return userDetails;
}
}
该实现类,主要实现 UserDetailsService 接口的 loadUserByUsername(String username) 方法,根据用户名,从数据库中获取到用户的信息,包括用户名、密码以及权限列表,用户后续的配置
7) 配置类
接下来,则进入具体代码逻辑
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private UserService<User> userService;
@Resource
private PermissionMapper permissionMapper;
/**
* 将用户配置在内存或者mysql中(可登录用户,包括用户名、密码和角色)
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 方式二:来源于数据库
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
/**
* 登录处理-配置对应用户所拥有的权限(url、authority对应的所有关系)
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 方式二:配置来源于数据库
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry
authorizeRequests = http.csrf().disable().authorizeRequests();
// 1.查询到所有的权限
List<Permission> allPermission = permissionMapper.findAllPermission();
// 2.分别添加权限规则
allPermission.forEach((p -> {
authorizeRequests.antMatchers(p.getUrl()).hasAnyAuthority(p.getName()) ;
}));
authorizeRequests.antMatchers("/**").fullyAuthenticated()
.anyRequest().authenticated().and().formLogin();
}
/**
* 忽略拦截处理
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
// 设置拦截忽略url - 会直接过滤该url - 将不会经过Spring Security过滤器链
web.ignoring().antMatchers("/getUserInfo");
// 设置拦截忽略文件夹,可以对静态资源放行
web.ignoring().antMatchers("/css/**", "/js/**");
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
其实只需要将 configure(AuthenticationManagerBuilder auth) 和 configure(HttpSecurity http) 中写死的部分改写为从数据库中获取的方式即可
完成之后,只需要重新启动项目,再次进行验证,发现效果与方式一的一样
项目中,一般也就是采用数据库配置的方式进行权限配置,当然根据不同的需要,只需要进行不同的数据库更换即可,底层逻辑并未发生变化。那么,这样我们就轻松实现了采用 Spring Boot 集成 Security 进行权限控制的效果
但是,你可能还没有发现,到了 Spring Boot 2.7 以后,WebSecurityConfigurerAdapter 被标记为了过期
这么重要的一个类,突然被标记为过期,那么,我们的权限配置逻辑要如何实现呢?
别慌,车到山前必有路,既然这个类不建议再使用了,那么Spring Security 的开发大佬们一定为我们提供了一个更为方便快捷的方式 我们一起来看看~
(三) Spring Security 2.7 以后的配置方式
根据 WebSecurityConfigurerAdapter 类的注释,我们可以清楚地看到:如果想要配置过滤器链,可以通过自定义 SecurityFilterChain Bean 来实现
而如果想要配置 WebSecurity,可以通过 WebSecurityCustomizer Bean 来实现
我们再来采用最新的方式,实现一下上述例子中的效果:
1 定义一个配置类
@Configuration
@Slf4j
public class SecurityConfig {
@Resource
private PermissionMapper permissionMapper;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry
authorizeRequests = http.csrf().disable().authorizeRequests();
// 方式二:配置来源于数据库
// 1.查询到所有的权限
List<Permission> allPermission = permissionMapper.findAllPermission();
// 2.分别添加权限规则
allPermission.forEach((p -> {
authorizeRequests.antMatchers(p.getUrl()).hasAnyAuthority(p.getName()) ;
}));
authorizeRequests.antMatchers("/**").fullyAuthenticated()
.anyRequest().authenticated().and().formLogin();
return http.build();
}
@Bean
WebSecurityCustomizer webSecurityCustomizer() {
return new WebSecurityCustomizer() {
@Override
public void customize(WebSecurity web) {
web.ignoring().antMatchers("/hello");
web.ignoring().antMatchers("/css/**", "/js/**");
}
};
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
也就是说,最新的方案:
- 只需要自定义 SecurityFilterChain(HttpSecurity http) Bean,将原先 configure(HttpSecurity http) 方法中的逻辑,转移到 SecurityFilterChain(HttpSecurity http) Bean 中实现即可
- 放行规则,则可以将原先 configure(WebSecurity web) 方法中的逻辑转移到 WebSecurityCustomizer Bean 中进行实现即可
- 而对于当前系统的用户信息,包括用户名、密码以及所拥有的权限列表这些信息,只需要实现 UserDetailsService 接口,并通过重写 loadUserByUsername() 方法进行获取即可(采用最新方案,这里不需要做任何修改)
总结
权限管理是每个 Web 应用都需要考虑的,而 SpringBoot 为 Security 提供了自动化配置方案,基于 RBAC 权限模型,通过较少的配置就可以实现较为复杂的权限管理逻辑
本文章,介绍了 Spring Security,并通过实例演示,让大家很容易理解 Security 的用法。需要注意的是,Spring Boot 2.7 以后,配置类WebSecurityConfigurerAdapter 已经被标记为过期,需要参考文章中的最新的方案,进行配置使用
文章演示代码地址:
https://github.com/helemile/Spring-Boot-Notes/tree/master/springSecurity
嗯,就这样。每天学习一点,时间会见证你的强大~
欢迎大家关注我们的公众号,一起持续性学习吧~
往期精彩回顾
总结复盘
架构设计读书笔记与感悟总结
带领新人团队的沉淀总结
复盘篇:问题解决经验总结复盘
网络篇
网络篇(四):《图解 TCP/IP》读书笔记
网络篇(一):《趣谈网络协议》读书笔记(一)
事务篇章
事务篇(四):Spring事务并发问题解决
事务篇(三):分享一个隐性事务失效场景
事务篇(一):毕业三年,你真的学会事务了吗?
Docker篇章
Docker篇(六):Docker Compose如何管理多个容器?
Docker篇(二):Docker实战,命令解析
Docker篇(一):为什么要用Docker?
..........
SpringCloud篇章
Spring Cloud(十三):Feign居然这么强大?
Spring Cloud(十):消息中心篇-Kafka经典面试题,你都会吗?
Spring Cloud(九):注册中心选型篇-四种注册中心特点超全总结
Spring Cloud(四):公司内部,关于Eureka和zookeeper的一场辩论赛
..........
Spring Boot篇章
Spring Boot(七):你不能不知道的Mybatis缓存机制!
Spring Boot(六):那些好用的数据库连接池们
Spring Boot(四):让人又爱又恨的JPA
SpringBoot(一):特性概览
..........
翻译
[译]用 Mint 这门强大的语言来创建一个 Web 应用
【译】基于 50 万个浏览器指纹的新发现
使用 CSS 提升页面渲染速度
WebTransport 会在不久的将来取代 WebRTC 吗?
.........
职业、生活感悟
你有没有想过,旅行的意义是什么?
程序员的职业规划
灵魂拷问:人生最重要的是什么?
如何高效学习一个新技术?
如何让自己更坦然地度过一天?
..........
相关推荐
- js向对象中添加元素(对象,数组) js对象里面添加元素
-
一、添加一个元素对象名["属性名"]=值(值:可以是一个值,可以是一个对象,也可以是一个数组)这样添加进去的元素,就是一个值或对象或数组...
- JS小技巧,如何去重对象数组?(一)
-
大家好,关于数组对象去重的业务场景,想必大家都遇到过类似的需求吧,这对这样的需求你是怎么做的呢。下面我就先和大家分享下如果是基于对象的1个属性是怎么去重实现的。方法一:使用.filter()和....
- 「C/C++」之数组、vector对象和array对象的比较
-
数组学习过C语言的,对数组应该都不会陌生,于是这里就不再对数组进行展开介绍。模板类vector模板类vector类似于string,也是一种动态数组。能够在运行阶段设置vector对象的长度,可以在末...
- 如何用sessionStorage保存对象和数组
-
背景:在工作中,我将[{},{}]对象数组形式,存储到sessionStorage,然后ta变成了我看不懂的形式,然后我想取之用之,发现不可能了~记录这次深刻的教训。$clickCouponIndex...
- JavaScript Array 对象 javascript的array对象
-
Array对象Array对象用于在变量中存储多个值:varcars=["Saab","Volvo","BMW"];第一个数组元素的索引值为0,第二个索引值为1,以此类推。更多有...
- JavaScript中的数组Array(对象) js array数组
-
1:数组Array:-数组也是一个对象-数组也是用来存储数据的-和object不同,数组中可以存储一组有序的数据,-数组中存储的数据我们称其为元素(element)-数组中的每一个元素都有一...
- 数组和对象方法&数组去重 数组去重的5种方法前端
-
列举一下JavaScript数组和对象有哪些原生方法?数组:arr.concat(arr1,arr2,arrn);--合并两个或多个数组。此方法不会修改原有数组,而是返回一个新数组...
- C++ 类如何定义对象数组?初始化数组?linux C++第43讲
-
对象数组学过C语言的读者对数组的概念应该很熟悉了。数组的元素可以是int类型的变量,例如int...
- ElasticSearch第六篇:复合数据类型-数组,对象
-
在ElasticSearch中,使用JSON结构来存储数据,一个Key/Value对是JSON的一个字段,而Value可以是基础数据类型,也可以是数组,文档(也叫对象),或文档数组,因此,每个JSON...
- 第58条:区分数组对象和类数组对象
-
示例设想有两个不同类的API。第一个是位向量:有序的位集合varbits=newBitVector;bits.enable(4);bits.enable([1,3,8,17]);b...
- 八皇后问题解法(Common Lisp实现)
-
如何才能在一张国际象棋的棋盘上摆上八个皇后而不致使她们互相威胁呢?这个著名的问题可以方便地通过一种树搜索方法来解决。首先,我们需要写一个函数来判断棋盘上的两个皇后是否互相威协。在国际象棋中,皇后可以沿...
- visual lisp修改颜色的模板函数 怎么更改visual studio的配色
-
(defunBF-yansemokuai(tuyuanyanse/ss)...
- 用中望CAD加载LISP程序技巧 中望cad2015怎么加载燕秀
-
1、首先请加载lisp程序,加载方法如下:在菜单栏选择工具——加载应用程序——添加,选择lisp程序然后加载,然后选择添加到启动组。2、然后是添加自定义栏以及图标,方法如下(以...
- 图的深度优先搜索和广度优先搜索(Common Lisp实现)
-
为了便于描述,本文中的图指的是下图所示的无向图。搜索指:搜索从S到F的一条路径。若存在,则以表的形式返回路径;若不存在,则返回nil。...
- 两个有助于理解Common Lisp宏的例子
-
在Lisp中,函数和数据具有相同的形式。这是Lisp语言的一个重大特色。一个Lisp函数可以分析另一个Lisp函数;甚至可以和另一个Lisp函数组成一个整体,并加以利用。Lisp的宏,是实现上述特色的...
- 一周热门
- 最近发表
- 标签列表
-
- grid 设置 (58)
- 移位运算 (48)
- not specified (45)
- patch补丁 (31)
- strcat (25)
- 导航栏 (58)
- context xml (46)
- scroll (43)
- element style (30)
- dedecms模版 (53)
- vs打不开 (29)
- nmap (30)
- webgl开发 (24)
- parse (24)
- c 视频教程下载 (33)
- android 开发环境 (24)
- paddleocr (28)
- listview排序 (33)
- firebug 使用 (31)
- transactionmanager (30)
- characterencodingfilter (33)
- getmonth (34)
- commandtimeout (30)
- hibernate教程 (31)
- label换行 (33)