Spring-Security前后端分离权限认证
前后端分离
一般来说,我们用SpringSecurity默认的话是前后端整在一起的,比如thymeleaf或者Freemarker,SpringSecurity还自带login登录页,还让你配置登出页,错误页。
但是现在前后端分离才是正道,前后端分离的话,那就需要将返回的页面换成Json格式交给前端处理了
SpringSecurity默认的是采用Session来判断请求的用户是否登录的,但是不方便分布式的扩展,虽然SpringSecurity也支持采用SpringSession来管理分布式下的用户状态,不过现在分布式的还是无状态的Jwt比较主流。 所以怎么让SpringSecurity变成前后端分离,可以采用Jwt来做认证
什么是jwt
Json web token (JWT),是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC7519).该token被设计为紧凑且==安全==的,特别适用于==分布式站点的单点登录(SSO)场景==。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
官网: JSON Web Token Introduction - jwt.io
jwt的结构
以 . 分割 三部分


Header
Header 部分是一个JSON对象,描述JWT的元数据,通常是下面的样子。
{"alg": "HS256","typ": "JWT"}
上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256 (写成 HS256) ;typ属性表示这个令牌(token)的类型(type), JWT令牌统一写为JWT。
最后,将上面的JSON对象使用Base64URL算法转成字符串。
Payload(载荷)
Payload 部分也是一个JSON对象,==用来存放实际需要传递的数据==。JWT规定了7个官方字段,供选用。
iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (lssued At):签发时间
jti (JWT ID):编号
除了官方字段,==你还可以在这个部分定义私有字段==,下面就是一个例子。
{"sub": "1234567890","name" : "John Doe",“userid”:2"admin": true}
注意,JWT 默认是不加密的,任何人都可以读到,所以不要把==秘密信息==放在这个部分。这个JSON 对象也要使用Base64URL 算法转成字符串。
Signature
Signature部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个==密钥(secret)==。这个密钥只有==服务器才知道==,不能泄露给用户。然后,使用Header里面指定的==签名算法(默认是 HMAC SHA256)==,按照下面的公式产生签名。
HMACSHA256(base64UrlEncode(header) + ".”"+base64UrlEncode(payload),secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。
1.项目添加hutool依赖
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.18</version></dependency>
http://t.csdnimg.cn/TA0Xx基于文章中连接数据库的实例基础上进行的前后端分离设计
2.搭建好一个vue项目

所需的导入包
3.修改配置文件 main.js
全局导入引入
import Vue from 'vue' import App from './App.vue' import router from './router'Vue.config.productionTip = falseimport ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css';Vue.use(ElementUI); import axios from 'axios' // 后端项目的时候 http://localhost:8080 // axios设置一个默认的路径 // 创建实例时配置默认值 const instance = axios.create({// 访问路径的时候假的一个基础的路径baseURL: 'http://localhost:8080/',// withCredentials: true });请求拦截器与响应拦截器
// 请求拦截器 // instance.interceptors.request.use( config=> {// config 前端 访问后端的时候 参数// 如果sessionStorage里面于token 携带着token 过去if(sessionStorage.getItem("token")){// token的值 放到请求头里面let token = sessionStorage.getItem("token");config.headers['token']=token;}// config.headers['Authorization']="yyl"return config; }, error=> {// 超出 2xx 范围的状态码都会触发该函数。// 对响应错误做点什么return Promise.reject(error); });// 添加响应拦截器 instance.interceptors.response.use( response=> {console.log(response)// 状态码 500if(response.data.code!=200){alert("chucuole")console.log(response.data);router.push({path:"/login"});return;}return response; }, error=> {// 超出 2xx 范围的状态码都会触发该函数。// 对响应错误做点什么return Promise.reject(error); });Vue.prototype.$axios = instance; // 引入组件挂载点
new Vue({router,render: h => h(App) }).$mount('#app')
4.搭建一个.vue页面,并在 router 目录下的 index.js 文件配置好路由
<template><div class="login-container"><el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="login-form"><el-form-item label="用户名" prop="username"><el-input type="text" v-model="ruleForm.username" autocomplete="off"></el-input></el-form-item><el-form-item label="确认密码" prop="password"><el-input type="password" v-model="ruleForm.password" autocomplete="off"></el-input></el-form-item><el-form-item><el-button type="primary" @click="submitForm('ruleForm')">提交</el-button><el-button @click="resetForm('ruleForm')">重置</el-button></el-form-item></el-form></div>
</template>
搭建的页面包含基本的登录表单,在新建一个页面用于成功的页面展示,如 图中跳转的main.vue
methods: {submitForm(formName) {this.$refs[formName].validate((valid) => {if (valid) {alert('submit!');// 请求 userlogin userlogin//post i请求 json 数据 后端接受的时候 @RequestBodythis.$axios.post("userlogin",qs.stringify(this.ruleForm)).then(r=>{// 获取token的值console.log(r.data.t);// 存起来sessionStorage.setItem("token",r.data.t)// 成功之后 跳转 /mainthis.$router.push("/main");//console.log(r.data);})} else {console.log('error submit!!');return false;}});},}
import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'Vue.use(VueRouter)const routes = [{path: '/',name: 'home',component: HomeView},{path: '/login',name: 'login',component: () => import(/* webpackChunkName: "about" */ '../views/login.vue')},{path: '/about',name: 'about',// route level code-splitting// this generates a separate chunk (about.[hash].js) for this route// which is lazy-loaded when the route is visited.component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')},{path: '/main',name: 'main',component: () => import(/* webpackChunkName: "about" */ '../views/main.vue')},
]
// 针对ElementUI导航栏中重复导航报错问题
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {return originalPush.call(this, location).catch(err => err)
}
这里配置了导航重复导航的问题,我们在响应拦截器配置了code非200的跳转登录的情况,为了避免登录失败导致跳转登录页面,重复导航的问题
5.后端加入跨域的配置文件
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;@Configuration
public class CrossConfig {@Beanpublic CorsFilter corsFilter() {final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();final CorsConfiguration corsConfiguration = new CorsConfiguration();//corsConfiguration.setAllowCredentials(true); // 允许 携带cookie 的信息corsConfiguration.addAllowedHeader("*"); // 允许所有的头corsConfiguration.addAllowedOrigin("*");// 允许所有的请求源corsConfiguration.addAllowedMethod("*"); // 所欲的方法 get post delete putsource.registerCorsConfiguration("/**", corsConfiguration); // 所有的路径都允许跨域return new CorsFilter(source);}}
6.统一返回数据实体
@Data
@AllArgsConstructor //
@NoArgsConstructor //
public class Result<T> {/*** code编码*/private Integer code = 200;/*** 消息*/private String msg = "操作成功";/*** 具体的数据*/private T t;/*** 成功的静态方法*/public static <T> Result success(T t){return new Result<>(200,"操作成功",t);}public static <T> Result <T> fail(){return new Result<>(500,"操作失败",null);}public static <T> Result <T> forbidden(){return new Result<>(403,"权限不允许",null);}
}
7.对实现了UserDetailsService接口的service层进行了修改
@Service
public class MyUserDetailService implements UserDetailsService {@Resourceprivate TabUserMapper userMapper;@Resourceprivate TabUserRoleMapper userRoleMapper;@Resourceprivate TabRoleMapper roleMapper;@Resourceprivate TabMenuMapper menuMapper;// 根据用户的名字 加载用户的信息@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// username 代表前端传递过来的名字
// 根据名字去数据库查询一下有没有这个用户的信息QueryWrapper queryWrapper = new QueryWrapper();queryWrapper.eq("username",username);TabUser tabUser = userMapper.selectOne(queryWrapper);if(tabUser != null) {
// 有值 查询用户对应的角色的idQueryWrapper queryWrapper1 = new QueryWrapper();queryWrapper1.eq("uid",tabUser.getId());List<TabUserRole> tabUserRoles = userRoleMapper.selectList(queryWrapper1);List<Integer> rids = tabUserRoles.stream().map(tabUserRole -> tabUserRole.getRid()).collect(Collectors.toList());
// 根据角色的id 查询rcodeList<TabRole> tabRoles = roleMapper.selectBatchIds(rids);
// 角色的修信息 角色管理 修改角色的名字List<SimpleGrantedAuthority> collect = tabRoles.stream().map(tabRole -> new SimpleGrantedAuthority("ROLE_" + tabRole.getRcode())).collect(Collectors.toList());
// 根据角色的id 查询菜单的mcodeList<TabMenu> menus = menuMapper.selectCodeByRids(rids);List<SimpleGrantedAuthority> resources = menus.stream().map(tabMenu -> new SimpleGrantedAuthority(tabMenu.getMcode())).collect(Collectors.toList());
// 将角色的所有信息,和资源信息合并在一起List<SimpleGrantedAuthority> allresource = Stream.concat(collect.stream(), resources.stream()).collect(Collectors.toList());return new User(username, tabUser.getPassword(), allresource);}return null;}
}
8.数据链路层,对前后端的认证进行判断与返回的JSON数据
@Component
public class JwtFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {/*解析token1.获取token -> 存在 -> 解析不存在返回 null 没有认证2.效验token真的还是假的 真-> 过 -> 用户的信息存放到安全框架的上下文路径里面假-> 返回一个Json 数据 没有认证* */String[] whitename = {"/userlogin"};String token = request.getHeader("token");
// token存在if(StringUtils.isNotBlank(token)) {
// 存在 解析boolean verify = JWTUtil.verify(token, "hp".getBytes());if(verify) {
// 效验合格
// 获取用户的名字 和密码的信息JWT jwt = JWTUtil.parseToken(token);String username = (String) jwt.getPayload("username");List<String> resources = (List<String>) jwt.getPayload("resources");
// 资源的信息List<SimpleGrantedAuthority> collect = resources.stream().map(s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList());
// 保存用户的信息UsernamePasswordAuthenticationToken usertoken = new UsernamePasswordAuthenticationToken(username, null, collect);
// 存起来用户的信息SecurityContextHolder.getContext().setAuthentication(usertoken);
// 放行filterChain.doFilter(request,response);}else {Result result = new Result(401, "没有登录", null);printJsonData(response,result);}}else {
// 查看是否在白名单 如果在 就放行String requestURL = request.getRequestURI();if(ArrayUtils.contains(whitename,requestURL)) {filterChain.doFilter(request,response);}else {Result result = new Result(401, "没有登录", null);printJsonData(response,result);}}}public void printJsonData(HttpServletResponse response, Result result) {try {response.setContentType("application/json;charset=utf8"); //json格式 编码是中文ObjectMapper objectMapper = new ObjectMapper();String s = objectMapper.writeValueAsString(result);// 使用objectMapper将result转化为json字符串PrintWriter writer = response.getWriter();writer.print(s);writer.flush();writer.close();}catch (Exception e) {e.printStackTrace();}}
}
9.对config文件进行修改(前后端分离情况)
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Resourceprivate JwtFilter jwtFilter;@Overrideprotected void configure(HttpSecurity http) throws Exception {// 配置 登录form 表单
// 路劲前面必须加 /http.formLogin().loginProcessingUrl("/userlogin").successHandler((request, response, authentication) -> {System.out.println("authentication"+authentication);
// 资源的信息Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();List<String> allresources = authorities.stream().map(s -> s.getAuthority()).collect(Collectors.toList());System.out.println("allresources"+allresources);
// 认证成功
// 生成tokenMap map =new HashMap<>();map.put("username",authentication.getName()); // 认证成功之后 用户的名字map.put("resources",allresources);
// 资源的信息设置签发时间
// Calendar instance = Calendar.getInstance(); //获取当前的时间
// Date time = instance.getTime();过期的时间设置为2小时之后
// instance.add(Calendar.HOUR,2); //两个小时之后
// Date time1 = instance.getTime();
// map.put(JWTPayload.EXPIRES_AT,time1);
// map.put(JWTPayload.ISSUED_AT,time);
// map.put(JWTPayload.NOT_BEFORE,time);String token = JWTUtil.createToken(map, "hp".getBytes());System.out.println(token);Result result = new Result(200,"登录成功",token);printJsonData(response,result);}) //前后端分离的时候 认证成功 走的方法.failureHandler((request, response, exception) -> {Result result = new Result(500, "失败", null);printJsonData(response,result);}); //认证失败 走的方法http.authorizeRequests().antMatchers("/userlogin").permitAll(); //代表放行 "/userlogin"http.authorizeRequests().anyRequest().authenticated();
// 权限不允许的时候http.exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -> {Result result = new Result(403, "权限不允许", null);printJsonData(response,result);});http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
// csrf 方便html文件 能够通过http.csrf().disable();http.cors(); // 可以跨域}@Resourceprivate UserDetailsService userDetailsService;@Beanpublic PasswordEncoder getPassword() {return new BCryptPasswordEncoder();}// 自定义用户的信息@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(getPassword());}public void printJsonData(HttpServletResponse response, Result result) {try {response.setContentType("application/json;charset=utf8");ObjectMapper objectMapper = new ObjectMapper();String s = objectMapper.writeValueAsString(result);PrintWriter writer = response.getWriter();writer.print(s);writer.flush();writer.close();}catch (Exception e) {e.printStackTrace();}}
}
10.配置完成
相关文章:
Spring-Security前后端分离权限认证
前后端分离 一般来说,我们用SpringSecurity默认的话是前后端整在一起的,比如thymeleaf或者Freemarker,SpringSecurity还自带login登录页,还让你配置登出页,错误页。 但是现在前后端分离才是正道,前后端分离的话,那就…...
Django中Cookie和Session的使用
目录 一、Cookie的使用 1、什么是Cookie? 2、Cookie的优点 3、Cookie的缺点 4、Django中Cookie的使用 二、Session的使用 1、什么是Session? 2、Session的优点 3、Session的缺点 4、Django中Session的使用 三、Cookie和Session的对比 总结 D…...
云原生周刊:KubeSphere 3.4.1 发布 | 2023.11.13
开源项目推荐 Inspektor Gadget Inspektor Gadget 是一组用于调试和检查 Kubernetes 资源与应用程序的工具(或小工具)。它在 Kubernetes 集群中管理 eBPF 程序的打包、部署和执行,包括许多基于 BCC 工具的程序,以及一些专为在 I…...
逐帧动画demo
用这一张图实现一个在跑的猎豹的动画 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta http-equiv"X…...
Mongodb 中,与索引相关的监控指标
Mongodb为集合添加索引,能够提高查询的效率,减少查询过程中检索文档的数量,改变文档检索的方式。 索引,采用集合中的一部分数据,构建了B tree,支持mongodb的高效检索。除$indexStats命令外,mong…...
图论14-最短路径-Dijkstra算法+Bellman-Ford算法+Floyed算法
文章目录 0 代码仓库1 Dijkstra算法2 Dijkstra算法的实现2.1 设置距离数组2.2 找到当前路径的最小值 curdis,及对应的该顶点cur2.3 更新权重2.4 其他接口2.4.1 判断某个顶点的连通性2.4.2 求源点s到某个顶点的最短路径 3使用优先队列优化-Dijkstra算法3.1 设计内部类…...
OpenCV 实现透视变换
一:OpenCV透视变换的概念 仿射变换(affine transform)与透视变换(perspective transform)在图像还原、图像局部变化处理方面有重要意义。通常,在2D平面中,仿射变换的应用较多,而在3D平面中,透视变换又有了自己的一席之…...
ChinaSoft 论坛巡礼|开源软件供应链论坛
2023年CCF中国软件大会(CCF ChinaSoft 2023)由CCF主办,CCF系统软件专委会、形式化方法专委会、软件工程专委会以及复旦大学联合承办,将于2023年12月1-3日在上海国际会议中心举行。 本次大会主题是“智能化软件创新推动数字经济与社…...
VUE 组合式API
响应式 data 选项式API_响应式 <template><h3>选项式API</h3><p>{{ message }}</p> </template> <script> export default {data(){return{message:"选项式API 绑定数据"}} } </script>组合式API_响应式 <…...
尝试使用php给pdf添加水印
在开发中增加pdf水印的功能是很常见的,经过实验发现这中间还是会有很多问题的。第一种模式,采用生成图片的方式把需要添加的内容保存成图片,再将图片加到pdf中间,这种方法略麻烦一些,不过可以解决中文乱码的问题&#…...
ubuntu上安装edge浏览器
1下载edge浏览器 官网下载 edge浏览器的linux版本可在上面的官网中寻找。 我选择的是Linux(.deb)。 2 安装 可在终端的edge安装包所在的路径下输入下面命令安装。 sudo dpkg -i edge安装包的名称.deb3 安装可能存在的问题 1dpkg:依赖关系问题使得edge-stable的配置工作不…...
动态切换 Spring Boot 打包配置:使用 Maven Profiles 管理 JAR 和 WAR
引言 在多环境开发中,我们经常需要根据部署环境来改变 Spring Boot 应用的打包方式。本文将探讨如何使用 Maven Profiles 结合依赖排除来动态地切换 JAR 和 WAR 打包配置。 1. 修改 pom.xml 以支持 WAR 包 转换 Spring Boot 应用从 JAR 到 WAR 时,首先…...
微信小程序使用阿里巴巴矢量图标
一,介绍 微信小程序使用图标有两种方式,一种是在线获取,一种是下载到本地使用, 第一种在线获取的有个缺点就是图标是灰色的,不能显示彩色图标,而且第一种是每次请求资源的,虽然很快࿰…...
使用JAVA pdf转word
使用spire.pdf 非常简单。 查看 https://mvnrepository.com/artifact/e-iceblue/spire.pdf 注意,这个包在 e-iceblue 下。 下面开始撸代码 先来pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://mav…...
成都瀚网科技有限公司抖音带货的正规
成都瀚网科技有限公司,一家在科技领域有着深厚积累的公司,近年来也开始涉足电子商务领域,特别是在抖音等短视频平台上进行带货活动。在这个充满机遇与挑战的时代,该公司以其独特的商业模式和运营策略,正在赢得消费者的…...
windows服务器热备、负载均衡配置
安装网络负载平衡 需要加入的服务器上全部需要安装网络负载平衡管理器 图形化安装:使用服务器管理器安装 在服务器管理器中,使用“添加角色和功能”向导添加网络负载均衡功能。 完成向导后,将安装 NLB,并且不需要重启计算机。 …...
samba服务器搭建 挂载远程目录 常用配置参数介绍
samba 直接复用linux的用户,但是Linux 用户的密码和 smbpasswd 设置的密码是分开的。 Linux 用户的密码是存储在 Linux 系统的用户数据库中,通常是 /etc/shadow 文件中以加密形式存储的。Samba 用户的密码是存储在专门的 Samba 密码数据库中 smbpasswd…...
Ansible命令使用
ansible ansible的命令 ansible命令模块Pingcommand 模块shell 模块copy 模块file 模块fetch 模块cron 模块yum 模块service 模块user 模块group 模块script 模块setup 模块get_url模块stat模块unarchive模块unarchive模块 ansible的命令 /usr/bin/ansible Ansibe AD-Hoc 临…...
element 周选择器el-date-picker
2023.11.13今天我学习了在使用element 周选择器的时候,我们会发现默认的时间选择为星期日到下一个星期一,如图: 我们需要改成显示星期一到星期天,只需要加一行代码:picker-options <el-date-pickertype"week&…...
No200.精选前端面试题,享受每天的挑战和学习
🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…...
Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...
React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...
关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...
抖音增长新引擎:品融电商,一站式全案代运营领跑者
抖音增长新引擎:品融电商,一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中,品牌如何破浪前行?自建团队成本高、效果难控;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...
Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...
k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
Yolov8 目标检测蒸馏学习记录
yolov8系列模型蒸馏基本流程,代码下载:这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中,**知识蒸馏(Knowledge Distillation)**被广泛应用,作为提升模型…...
Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...
站群服务器的应用场景都有哪些?
站群服务器主要是为了多个网站的托管和管理所设计的,可以通过集中管理和高效资源的分配,来支持多个独立的网站同时运行,让每一个网站都可以分配到独立的IP地址,避免出现IP关联的风险,用户还可以通过控制面板进行管理功…...
