Spring Boot集成Security快速入门Demo

1.什么是Security?

Spring Security是一个Java框架,用于保护应用程序的安全性。它提供了一套全面的安全解决方案,包括身份验证、授权、防止攻击等功能。Spring Security基于过滤器链的概念,可以轻松地集成到任何基于Spring的应用程序中。它支持多种身份验证选项和授权策略,开发人员可以根据需要选择适合的方式。此外,Spring Security还提供了一些附加功能,如集成第三方身份验证提供商和单点登录,以及会话管理和密码编码等。总之,Spring Security是一个强大且易于使用的框架,可以帮助开发人员提高应用程序的安全性和可靠性。

how it works?

22

  • Filter

拦截Http请求,获取用户名和秘密等认证信息 关键方法:

public abstract Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)throws AuthenticationException, IOException, ServletException;
  • AuthenticationManager

从filter中获取认证信息,然后查找合适的AuthenticationProvider来发起认证流程 关键方法:

Authentication authenticate(Authentication authentication) throws AuthenticationException;
  • AuthenticationProvider

调用UserDetailsService来查询已经保存的用户信息并与从http请求中获取的认证信息比对。如果成功则返回,否则则抛出异常。 关键方法:

protected abstract UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)throws AuthenticationException;
  • UserDetailsService

负责获取用户保存的认证信息,例如查询数据库。 关键方法:

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

这些组件都是抽象的,每个都可以有不同的实现,换句话说都是可以定制,特别灵活,所以就特别复杂。具体到我们这个默认的例子中,使用的都是默认实现:

  • Filter: UsernamePasswordAuthenticationFilter
  • AuthenticationManager: ProviderManager
  • AuthenticationProvider: DaoAuthenticationProvider
  • UserDetailsService: InMemoryUserDetailsManager

业务流程是不是很清晰,之所以感觉复杂是因为经过框架的一顿设计,拉长了调用链。虽然设计上复杂了,但是如果理解了这套设计流程,终端用户使用就会简单很多,不理解的话,感觉特别复杂

2.环境搭建

mysql database

 

docker run --name docker-mysql-5.7 -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -d mysql:5.7

init data

create database demo;DROP TABLE IF EXISTS `jwt_user`; 
CREATE TABLE `jwt_user`(`id` varchar(32) CHARACTER SET utf8 NOT NULL COMMENT '用户ID',`username` varchar(100) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '登录账号',`password` varchar(255) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '密码'
)ENGINE = InnoDB CHARACTER SET = utf8 COMMENT = '用户表' ROW_FORMAT = Compact;INSERT INTO jwt_user VALUES('1','admin','123');-- ----------------------------
-- Table structure for menu
-- ----------------------------
CREATE TABLE `menu` (`id` int(11) NOT NULL AUTO_INCREMENT,`pattern` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;-- ----------------------------
-- Records of menu
-- ----------------------------
INSERT INTO `menu` VALUES ('1', '/db/**');
INSERT INTO `menu` VALUES ('2', '/admin/**');
INSERT INTO `menu` VALUES ('3', '/user/**');-- ----------------------------
-- Table structure for menu_role
-- ----------------------------
CREATE TABLE `menu_role` (`id` int(11) NOT NULL AUTO_INCREMENT,`mid` int(11) DEFAULT NULL,`rid` int(11) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;-- ----------------------------
-- Records of menu_role
-- ----------------------------
INSERT INTO `menu_role` VALUES ('1', '1', '1');
INSERT INTO `menu_role` VALUES ('2', '2', '2');
INSERT INTO `menu_role` VALUES ('3', '3', '3');-- ----------------------------
-- Table structure for role
-- ----------------------------
CREATE TABLE `role` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(32) DEFAULT NULL,`nameZh` varchar(32) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES ('1', 'dba', '数据库管理员');
INSERT INTO `role` VALUES ('2', 'admin', '系统管理员');
INSERT INTO `role` VALUES ('3', 'user', '用户');-- ----------------------------
-- Table structure for user
-- ----------------------------
CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT,`username` varchar(32) DEFAULT NULL,`password` varchar(255) DEFAULT NULL,`enabled` tinyint(1) DEFAULT NULL,`locked` tinyint(1) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of user
-- password is 123
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'root', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq', '1', '0');
INSERT INTO `user` VALUES ('2', 'admin', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq', '1', '0');
INSERT INTO `user` VALUES ('3', 'sang', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq', '1', '0');-- ----------------------------
-- Table structure for user_role
-- ----------------------------
CREATE TABLE `user_role` (`id` int(11) NOT NULL AUTO_INCREMENT,`uid` int(11) DEFAULT NULL,`rid` int(11) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES ('1', '1', '1');
INSERT INTO `user_role` VALUES ('2', '1', '2');
INSERT INTO `user_role` VALUES ('3', '2', '2');
INSERT INTO `user_role` VALUES ('4', '3', '3');

remark

msyql account:root
mysql password:123456

3.代码工程

实验目的:实现基于mysql来控制权限

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>springboot-demo</artifactId><groupId>com.et</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>security</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoconfigure</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.2</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.10</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies><!-- <build><plugins><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.4.0</version><dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.28</version></dependency></dependencies><executions><execution><id>Generate MyBatis Artifacts</id><phase>package</phase><goals><goal>generate</goal></goals></execution></executions><configuration><verbose>true</verbose><overwrite>true</overwrite><configurationFile>src/main/resources/mybatis-generator.xml</configurationFile></configuration></plugin></plugins></build>-->
</project>

config

package com.et.security.config;import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;import java.util.Collection;/*** Comparing role information in the AccessDecisionManager class*/
@Component
public class CustomAccessDecisionManager implements AccessDecisionManager {/*** Override the decide method, in which it is judged whether the currently logged in user has the role information required for the current request URL. If not, an AccessDeniedException is thrown, otherwise nothing is done.** @param auth   Information about the currently logged in user* @param object FilterInvocationObject, you can get the current request object* @param ca     FilterInvocationSecurityMetadataSource The return value of the getAttributes method in is the role required by the current request URL*/@Overridepublic void decide(Authentication auth, Object object, Collection<ConfigAttribute> ca) {Collection<? extends GrantedAuthority> auths = auth.getAuthorities();for (ConfigAttribute configAttribute : ca) {/** If the required role is ROLE_LOGIN, it means that the currently requested URL can be accessed after the user logs in.* If auth is an instance of UsernamePasswordAuthenticationToken, it means that the current user has logged in and the method ends here, otherwise it enters the normal judgment process.*/if ("ROLE_LOGIN".equals(configAttribute.getAttribute()) && auth instanceof UsernamePasswordAuthenticationToken) {return;}for (GrantedAuthority authority : auths) {// If the current user has the role required by the current request, the method endsif (configAttribute.getAttribute().equals(authority.getAuthority())) {return;}}}throw new AccessDeniedException("no permission");}@Overridepublic boolean supports(ConfigAttribute attribute) {return true;}@Overridepublic boolean supports(Class<?> clazz) {return true;}
}
package com.et.security.config;import com.et.security.entity.Menu;
import com.et.security.entity.Role;
import com.et.security.mapper.MenuMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;@Component
public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {AntPathMatcher antPathMatcher = new AntPathMatcher(); // AntPathMatcher is mainly used to implement ant-style URL matching.@ResourceMenuMapper menuMapper;// Spring Security uses the getAttributes method in the FilterInvocationSecurityMetadataSource interface to determine which roles are required for a request.    @Overridepublic Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {/** The parameter of this method is a FilterInvocation. Developers can extract the currently requested URL from the FilterInvocation.* The return value is Collection<ConfigAttribute>, indicating the role required by the current request URL.*/String requestUrl = ((FilterInvocation) object).getRequestUrl();/** Obtain all resource information from the database, that is, the menu table in this case and the role corresponding to the menu,* In a real project environment, developers can cache resource information in Redis or other cache databases.*/List<Menu> allMenus = menuMapper.getAllMenus();// Traverse the resource information. During the traversal process, obtain the role information required for the currently requested URL and return it.for (Menu menu : allMenus) {if (antPathMatcher.match(menu.getPattern(), requestUrl)) {List<Role> roles = menu.getRoles();String[] roleArr = new String[roles.size()];for (int i = 0; i < roleArr.length; i++) {roleArr[i] = roles.get(i).getName();}return SecurityConfig.createList(roleArr);}}// If the currently requested URL does not have a corresponding pattern in the resource table, it is assumed that the request can be accessed after logging in, that is, ROLE_LOGIN is returned directly.return SecurityConfig.createList("ROLE_LOGIN");}/*** The getAllConfigAttributes method is used to return all defined permission resources. SpringSecurity will verify whether the relevant configuration is correct when starting.* If verification is not required, this method can directly return null.*/@Overridepublic Collection<ConfigAttribute> getAllConfigAttributes() {return null;}/*** The supports method returns whether the class object supports verification.*/@Overridepublic boolean supports(Class<?> clazz) {return FilterInvocation.class.isAssignableFrom(clazz);}
}
package com.et.security.config;import com.et.security.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredUserService userService;@BeanRoleHierarchy roleHierarchy() {RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();String hierarchy = "ROLE_dba > ROLE_admin ROLE_admin > ROLE_user";roleHierarchy.setHierarchy(hierarchy);return roleHierarchy;}@BeanPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// The memory user is not configured, but the UserService just created is configured into the AuthenticationManagerBuilder.auth.userDetailsService(userService);}@Order@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()/*.antMatchers("/admin/**").hasRole("ADMIN")//Indicates that the user accessing the URL in the "/admin/**" mode must have the role of ADMIN.antMatchers("/user/**").access("hasAnyRole('ADMIN','USER')").antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')").anyRequest().authenticated()//Indicates that in addition to the URL pattern defined previously, users must access other URLs after authentication (access after logging in) */.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {@Overridepublic <O extends FilterSecurityInterceptor> O postProcess(O object) {object.setSecurityMetadataSource(cfisms());object.setAccessDecisionManager(cadm());return object;}}).and().formLogin().loginProcessingUrl("/login").permitAll().and().csrf().disable();}@BeanCustomFilterInvocationSecurityMetadataSource cfisms() {return new CustomFilterInvocationSecurityMetadataSource();}@BeanCustomAccessDecisionManager cadm() {return new CustomAccessDecisionManager();}
}

代码生成

配置文件在resource目录下,mybatis-generator.xml

如何生成代码?

可以参见这篇文章?Spring Boot集成Druid快速入门Demo

DemoApplication.java

package com.et.security;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
@MapperScan(value = "com.et.security.mapper")
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}
}

application.yaml

server:port: 8088
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourceurl: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456mybatis:mapper-locations:- classpath:mapper/**/*.xml

以上只是一些关键代码,所有代码请参见下面代码仓库

代码仓库

  • GitHub - Harries/springboot-demo: a simple springboot demo with some components for example: redis,solr,rockmq and so on.

4.测试

  • 启动Spring Boot应用
  • 访问地址http://127.0.0.1:8088/hello
  • 此时会要求登陆,输入数据库不同角色的用户,验证不同的权限(密码都是加密的123)

5.引用

  • Spring Boot集成Security快速入门Demo | Harries Blog™
  • Spring Boot Security Auto-Configuration | Baeldung
  • 先决条件 :: Spring Security Reference

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/13288.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

月薪3万,沉迷“薅羊毛”

在网购江湖中&#xff0c;蟹老板是一位拥有十年经验的资深“羊毛党”。 他不仅是位精明的数学家&#xff0c;更是一位高效的“生产线”工人&#xff0c;专注于各大网购平台的优惠机制。每逢618大促&#xff0c;他总能凭借超凡的洞察力和手速&#xff0c;轻松斩获丰厚的“羊毛”…

17.高并发场景下CAS效率的优化

文章目录 高并发场景下CAS效率的优化1.空间换时间&#xff08;LongAdder&#xff09;2.对比LongAdder和AtomicLong执行效率2.1.AtmoictLong2.2.LongAdder2.3.比对 3.LongAdder原理3.1.基类Striped64内部的三个重要成员3.2.LongAdder.add()方法3.3.LongAdder中longAccumulate()方…

搜索二维矩阵 - LeetCode 热题 64

大家好&#xff01;我是曾续缘&#x1f9e1; 今天是《LeetCode 热题 100》系列 发车第 64 天 二分查找第 2 题 ❤️点赞 &#x1f44d; 收藏 ⭐再看&#xff0c;养成习惯 搜索二维矩阵 给你一个满足下述两条属性的 m x n 整数矩阵&#xff1a; 每行中的整数从左到右按非严格递增…

六西格玛绿带培训:解锁质量工程师的职场新篇章

在质量管理这条道路上&#xff0c;我们或许都曾有过这样的疑问&#xff1a;为何付出了同样的努力&#xff0c;却未能获得预期的回报&#xff1f;当我们看到身边的同行们逐渐步入高薪的行列&#xff0c;而自己却似乎陷入了职业的泥沼&#xff0c;这种对比无疑令人倍感焦虑。然而…

【STM32-MX_GPIO_Init分析】

MX_GPIO_Init分析源码如下&#xff1a; __HAL_RCC_GPIOE_CLK_ENABLE源码如下&#xff1a; #define RCC ((RCC_TypeDef *) RCC_BASE) #define RCC_BASE (AHB1PERIPH_BASE 0x3800UL) #define AHB1PERIPH_BASE (PERIPH_BASE 0x00020000U…

Android Studio kotlin 转 Java

一. 随笔记录 java代码可以转化成kotlin代码&#xff0c;当然 Kotlin 反过来也可以转java 在Android Studio中 可以很方便的操作 AS 环境&#xff1a;Android Studio Iguana | 2023.2.1 二. 操作步骤 1.步骤 顶部Tools ----->Kotlin ------>Show Kotlin Bytecode 步…

springcloud+nocos从零开始

首先是去nacos官网下载最新的包&#xff1a;Nacos 快速开始 | Nacos win下启动命令&#xff1a;startup.cmd -m standalone 这样就可以访问你的nacos 了。 添加一个配置&#xff0c;记住你的 DataId,和Group名字。 创建一个pom项目&#xff0c;引入springCloud <?xml ve…

邦注科技 电解式超声波清洗机的原理介绍

电解式超声波去除模具表面油污锈迹的原理结合了电解和超声波技术的优势。 首先&#xff0c;电解作用是通过在特定的电解槽中&#xff0c;将模具作为阴极&#xff08;放入清洗框即可&#xff09;&#xff0c;并将有制式电极棒作为阳极。在电解过程中&#xff0c;电流如同魔法师…

Cache基本原理--以TC3xx为例(1)

目录 1.为什么要使用Cache 2.Memory与Cache如何映射 2.1 地址映射概设 3.小结 为什么要使用Cache&#xff1f;为什么在多核工程里要谨慎使用DCache&#xff1f;Cache里的数据、指令是如何与Memory映射&#xff1f; 灵魂三连后&#xff0c;软件工程师应该都会有模糊的回答&…

【虚拟仿真】Unity3D中实现对大疆无人机遥控器手柄按键响应

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址QQ群:398291828大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。 一、前言 最近项目中需要用到大疆无人机遥控器对程序中无人机进行控制,遥控器是下图这一款: 博主发…

微信小程序之九宫格抽奖

1.实现效果 2. 实现步骤 话不多说&#xff0c;直接上代码 /**index.wxml*/ <view class"table-list flex fcc fwrap"><block wx:for"{{tableList}}" wx:key"id"><view class"table-item btn fcc {{isTurnOver?:grayscale…

基于springboot+vue+Mysql的交流互动系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

java入门详细教程之集合的理解与应用

一、Collenction集合 数组和集合的区别 长度 数组的长度是不可变的,集合的长度是可变的 数据类型 数组可以存基本数据类型和引用数据类型 集合只能存引用数据类型,如果要存基本数据类型,需要存对应的包装类 Collection 集合概述和使用 Collection集合概述​&#xff1a; 是单…

【漏洞复现】泛微OA E-Cology GetLabelByModule SQL注入漏洞

漏洞描述&#xff1a; 泛微OA E-Cology是一款面向中大型组织的数字化办公产品&#xff0c;它基于全新的设计理念和管理思想&#xff0c;旨在为中大型组织创建一个全新的高效协同办公环境。泛微OA E-Cology getLabelByModule存在SQL注入漏洞&#xff0c;允许攻击者非法访问和操…

[数据集][目标检测]结直肠息肉内镜图像病变检测数据集13524张2类别

数据集共分为2个版本&#xff0c;即A版和B版&#xff0c;两个版本图片数一样&#xff0c;数据集图片不存在重叠文件名也不存在重复&#xff0c;可以合并训练&#xff0c;也可以单独训练。 下面是信息介绍&#xff1a; 结直肠息肉内镜图像病变检测数据集13524张2类别A版 数据…

Elasticsearch的并发控制策略

文章目录 利用external对版本号进行外部控制利用if_seq_no和if_primary_term作为唯一标识来避免版本冲突 ES中的文档是不可变更的。如果你更新一个文档,会将就文档标记为删除,同时增加一个全新的文档。同时文是的version字段加1内部版本控制 If_seq_no If_primary_term 使用外…

使用Xterm实现终端构建

————html篇———— // 需要使用Xterm Xterm的官网&#xff1a; Xterm.js 新建项目 增加基本文件 下载 框架 npm init -y Xterm依赖 npm install xterm/xterm 参考文档写的代码 贴入代码 <html><head><link rel"stylesheet" href"nod…

免费思维13招之十三:种群型思维

免费思维13招之十三&#xff1a;种群型思维 免费思维的最后一个思维——族群思维 人&#xff0c;都是群居性的动物&#xff0c;在人群中的一部分人群对于另一部分人群来说&#xff0c;具有强大的吸引力。那么&#xff0c;我们就从这一点出发&#xff0c;通过对其中一部分人群进…

2万字实操入门案例之在Springboot框架下用Mybatis简化JDBC开发实现基础的操作MySQL之预编译SQL主键返回增删改查

环境准备 准备数据库表 use mybatis;-- 部门管理 create table dept(id int unsigned primary key auto_increment comment 主键ID,name varchar(10) not null unique comment 部门名称,create_time datetime not null comment 创建时间,update_time datetime not null comme…

Idea + maven 搭建 SSH (struts2 +hibernate5 + spring5) 环境

org.apache.struts struts2-core 2.3.35 org.apache.struts struts2-spring-plugin 2.3.35 org.apache.struts struts2-json-plugin 2.3.8 1.4 配置Java EE 坐标依赖 这里可以引入 servlet api&#xff0c;jstl 标签库等一系列工具 javax.servlet javax.servlet-api …