单点登录系统:登录,登出,拦截器

news2025/2/25 1:57:14

什么是单点登录?

单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO
的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
假设一个企业有多个应用程序,比如OA系统、CRM系统、ERP系统等等,每个系统都有自己的用户账号和密码。如果用户要使用这些系统,需要在每个系统中单独输入账号密码进行身份验证。如果应用了单点登录技术,用户只需要在其中一个系统中验证身份,例如在OA系统中输入账号和密码登录,之后用户就可以无需再次输入账号和密码自动登录其他系统(如CRM系统、ERP系统等),因为这些系统都已经通过单点登录技术与OA系统建立了连接,可以自动获取OA系统的用户登录信息。
原理:
当用户第一次登录应用系统1的时候,会被引导到认证系统中进行登录,根据用户提供的登录信息进行校验,校验成功后认证系统会返回一个认证凭证ticket,并把这个ticket存到redis缓存中。当用户登录其他的系统的时候,只需要带着这个ticket,经过认证系统的校验,校验成功则无需再次登录即可访问其他。
在这里插入图片描述

其他登录方式

1、免密登录:登录成功之后,硬盘cookie中会存在相应的验证信息,即使浏览器关闭,再次打开浏览器的时候可以在一段时间内都不用输入密码和用户名即可登录。
实现方式:cookie
优点:简单
缺点:不安全
2、 同域登录:同域名下不同模块互相登录
实现方式:cookie,session
优点:相对简单,可以同域名下不同模块互相登录
缺点:不安全
代表技术:Spring session
3、单点登录:
实现方式:cookie,redis
优点:可以在不同域名下互相登录
缺点:管理麻烦,部署复杂,代码编写量提升。可能不安全
代表技术:shiro,Spring Security,cas,OAuth2.。。。
在第一次登录成功的之后利用cookie来存储登录信息,以便在去其他系统的时候直接从request域中获取cookie中的用户信息,从而login(admin),validate(ticket),去认证系统认证。

要实现SSO,需要以下主要的功能:

redis的数据是(key-value)->(ticket,admin)
1、所有应用系统共享一个身份认证系统。(抽取出来一个系统)
2、登录成功,返回ticket。
根据前台传过来的用户信息获取用户名和密码。先判断用户名和密码是否为空,是否存在,是否匹配,如果成功,则创建唯一数据ticket数据,并缓存到redis中设置过期时间,返回一个ticket,便于用户拿到ticket,在后面进入其他系统的时候来用ticket进入认证系统。
3、用户在其他系统通过票据来进行认证,返回用户。
用户拿着票据,要先进行校验,判断ticket是否为空,是否过期,过期就是获取key值的时候返回的value值为空,所以我们只需要判断value值是否为空即可,如果以上情况都不存在,那么就说明ticket,admin是存在的,返回用户。

为什么不把sso系统放在rpc系统下呢?

有个疑问就是sso系统也是使用了dubbo协议,有rpc协议框架的影子,那为什么不把他们放在一块呢?

因为Shop-RPC 和 Shop-SSO 是两个独立的系统,因为它们分别提供不同的服务。

Shop-RPC 是一个远程过程调用框架,主要用于分布式系统中不同节点之间的通信。它将不同节点之间的通信封装成了一种类似于本地方法调用的方式,使得远程调用变得像本地调用一样简单。Shop-RPC 的主要作用是提供高效的远程服务调用,为服务之间的通信提供便利。

Shop-SSO 是一个单点登录系统,主要用于用户认证和授权。它能够提供用户中心、权限管理、单点登录等功能,为多个系统之间的用户认证提供了便利。Shop-SSO 的主要作用是提供安全的用户认证和授权,为不同系统之间的用户认证提供统一的解决方案。

通过将 Shop-RPC 和 Shop-SSO 进行分离可以使它们各自专注于自己的领域,提高系统的可维护性和可扩展性。同时,它们之间可以通过 Shop-RPC 提供的远程调用功能进行通信,保证了系统之间的协作和数据的传递。

代码实现

新建sso-model,导入依赖,写yml文件,加@enableDubbo注解

同rpc一样的依赖,yml文件也类似,但是这是一个新的系统了,因此dubbo端口号需要更改。

利用Mybatis-Plus 生成pojo类,编写service,serviceImpl

疑问?如果前端判断了用户名是否为空,如果为空就不发送请求,后端还有必要判断用户名是否为空嘛

后端在处理用户请求时仍然需要对用户名是否为空做必要的校验。这是因为前端校验只是一种辅助手段,用户可以通过浏览器控制台等方式绕过前端校验,直接发送带有空用户名的请求到后端

因此,为了保证应用的安全性和正确性,后端需要对所有的输入参数进行严格的校验和过滤。对于前端已经校验过的参数,后端可以简单地进行再次校验,以避免非法参数对系统造成的潜在危害。在实际开发中,我们通常会采用多层校验的方式,既在前端进行校验,又在后端进行校验,以确保输入参数的正确性和安全性。

ServiceImpl

@Service(interfaceClass = ShopSSOService.class)
public class ShopSSOServiceImpl implements ShopSSOService {
    @Resource
    AdminMapper adminMapper;
    @Resource
    RedisTemplate<String,String> redisTemplate;
    @Value("${user.ticket}")
    String userticket;

    /**
     * 登录成功,返回ticket
     * @param admin
     * @return
     */
    @Override
    public String login(Admin admin) {
        String ticket = "";
        //即使前端有所校验,但是后端仍然有必要再次进行校验,你永远不知道黑客的操作。
        if(StringUtils.isEmpty(admin.getPassword().trim())||StringUtils.isEmpty(admin.getUserName().trim())){
            System.out.println("用户名或密码为空");
            return null;
        }
        AdminExample adminExample = new AdminExample();
        adminExample.createCriteria().andUserNameEqualTo(admin.getUserName());
        List<Admin> admins = adminMapper.selectByExample(adminExample);
        //得到的list集合是否为空,用户名是否唯一
        if(CollectionUtils.isEmpty(admins) ||1!=admins.size()){
            System.out.println("获取admins失败");
            return null;
        }
       Admin adminJdbc = admins.get(0);

        //用户名验证成功,接下来验证密码,因为存入数据库的密码是经过MD5加密的,所以我们要将用户传过来的明文数据进行MD5加密,再与数据库中进行比对看是否一致。
        String adminPassword = Md5Util.getMd5WithSalt(admin.getPassword(),adminJdbc.getEcSalt());
        System.out.println(admin.getPassword()+":"+adminJdbc.getEcSalt());
        System.out.println(adminPassword);
        if(!adminPassword.equals(adminJdbc.getPassword())){
            System.out.println("密码错误");
            return null;
        }
        //验证通过
        ticket = UUIDUtil.getUUID();
        //加入缓存中
        ValueOperations<String,String> valueOperations = redisTemplate.opsForValue();

        valueOperations.set((userticket+":"+ticket), JsonUtil.object2JsonStr(adminJdbc),30, TimeUnit.MINUTES);
        return ticket;
    }

    /**
     * 其他系统验证ticket,返回用户信息
     * @param ticket
     * @return
     */
    @Override
    public Admin validate(String ticket) {
        //判断ticket是否为空
        if(StringUtils.isEmpty(ticket)){
            System.out.println("ticket为空");
            return null;
        }
        //获取ticket对应的admin
        ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
        String s = valueOperations.get((userticket +":" +ticket));
        if(StringUtils.isEmpty(s)){
            System.out.println("ticket过期");
            return null;
        }
        //返回admin
        return JsonUtil.jsonStr2Object(s,Admin.class);
    }
}

这里有一个redis缓存乱码的问题。

后台管理系统

在后台管理系统实现单点登录

已知后台管理系统需要传过来三个参数,用户名,用户密码,验证码。
然后我们经过Controller拦截器,判断有没有登录,如果登录过了进入系统。如果没有登录进入登录页面。如果要进入其他系统,则实现单点登录。

导入sso依赖

<dependency>
            <groupId>com.wll</groupId>
            <artifactId>shop-sso</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

登录,编写Controller

在serviceImpl中传过来的三个参数,首先需要拿到ticket来判断是否登录成功。如果登录成功就把ticket存到硬盘cookie中,便于用户访问其他的系统的时候拿到cookie进行vilidate(ticket),因为页面需要反显出我们的用户信息,因此我们把用户信息存到session中。
因为session和cookie也会有异常。所以可以对他们进行包装。

@Controller
@RequestMapping("/user")
public class UserController {

	@Reference(interfaceClass = ShopSSOService.class)
	private ShopSSOService ssoService;
	@Autowired
	private CookieService cookieService;

	@RequestMapping("/login")
	@ResponseBody
	public BaseResult login(Admin admin, HttpServletRequest request, HttpServletResponse response, String verify){
		//获取验证码
		String capText = (String) request.getSession().getAttribute("pictureVerifyKey");
		BaseResult baseResult = new BaseResult();
		//判断验证码是否为空或者验证码是否一致
		if (StringUtils.isEmpty(verify.trim())||!verify.trim().equals(capText)){
			baseResult.setCode(BaseResultEnum.PASS_ERROR_03.getCode());
			baseResult.setMessage(BaseResultEnum.PASS_ERROR_03.getMessage());
			return baseResult;
		}
		String ticket = ssoService.login(admin);
		//登录失败
		if(!StringUtils.isEmpty(ticket)){
			//登录成功
			boolean result = cookieService.setCookie(request, response, ticket);
			request.getSession().setAttribute("user",admin);
			return result?BaseResult.success():BaseResult.error();
		}
		baseResult.setCode(BaseResultEnum.PASS_ERROR_04.getCode());
		baseResult.setMessage(BaseResultEnum.PASS_ERROR_04.getMessage());
		return baseResult;
	}

拦截器

拦截器只需要两步
一、编写拦截器类
二、注册拦截器到springmvc中

一、编写拦截器类

实现HandlerInterceptor接口,重写他的preHandler,postHandler,afterCompotation方法,
拦截器怎么拦截登录方法看登录的时候我们做了什么,登录的时候我们把ticket存到了cookie里面,在login(admin)函数里面把ticket存到了redis里面并设置了过期时间,把用户信息存到了session里面,因此我们可以通过判断cookie里面是否有ticket,如果有则进行验证,验证成功的话重新设置redis中的过期时间。如果没有则进入登录页面。

@Component
public class ManagerLoginIntercepter implements HandlerInterceptor {
    @Resource
    RedisTemplate<String,String> redisTemplate ;
    @Value("${user.ticket}")
    String userTicket;
    @Resource
    ShopSSOService ssoService;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String ticket = CookieUtil.getCookieValue(request, "userTicket");
        if(!StringUtils.isEmpty(ticket)){
            //票据不为空,进行验证
            Admin admin = ssoService.validate(ticket);
            if(null != admin){
                //验证通过,重新设置过期时间,存入session域中
                ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
                request.getSession().setAttribute("user",admin);
                valueOperations.set(userTicket+":"+ticket, JsonUtil.object2JsonStr(admin));
                return true;
            }
        }
        response.sendRedirect(request.getContextPath()+"/login");
            return false;
    }

配置拦截器类。
这是一个Spring MVC的配置类,@Configuration表示这是一个Java配置类,@EnableWebMvc表示启用Spring MVC框架的特性。该类实现了WebMvcConfigurer接口,用来配置Spring MVC的相关属性。

register.addInterceptor(配置器)
.addPathPattern(拦截路径)
.excludePathPatterns(不拦截的路径)
@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {

	@Resource
	private ManagerLoginIntercepter loginInterceptor;

	/**
	 * addInterceptor:添加自定义拦截器
	 * addPathPatterns:添加拦截请求  /**表示拦截所有
	 * excludePathPatterns:不拦截的请求
	 * @param registry
	 */
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(loginInterceptor)
				.addPathPatterns("/**")
				.excludePathPatterns("/static/**")
				.excludePathPatterns("/login/**")
				.excludePathPatterns("/image/**")
				.excludePathPatterns("/user/login/**")
				.excludePathPatterns("/user/logout/**");
	}

	/**
	 * 放行静态资源
	 * @param registry
	 */
	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
	}
}

登出

在sso中写出登出,登出的时候需要将redis清空。
在调用的时候需要把cookie清空,session清空。何时创建,何时消亡,都是一对一的。

验证码

父项目中声明依赖,定义版本号
manager系统导入依赖

<dependency>
        <groupId>com.github.axet</groupId>
        <artifactId>kaptcha</artifactId>
      </dependency>

写配置类,定义验证码的格式,字体颜色等等

@Configuration
public class CaptchaConfig {

   @Bean
   public DefaultKaptcha getDefaultKaptcha(){
      //验证码生成器
      DefaultKaptcha defaultKaptcha=new DefaultKaptcha();
      //配置
      Properties properties = new Properties();
      //是否有边框
      properties.setProperty("kaptcha.border", "yes");
      //设置边框颜色
      properties.setProperty("kaptcha.border.color", "105,179,90");
      //边框粗细度,默认为1
      // properties.setProperty("kaptcha.border.thickness","1");
      //验证码
      properties.setProperty("kaptcha.session.key","code");
      //验证码文本字符颜色 默认为黑色
      properties.setProperty("kaptcha.textproducer.font.color", "blue");
      //设置字体样式
      properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
      //字体大小,默认40
      properties.setProperty("kaptcha.textproducer.font.size", "40");
      //验证码文本字符内容范围 默认为abced2345678gfynmnpwx
      // properties.setProperty("kaptcha.textproducer.char.string", "");
      //字符长度,默认为5
      properties.setProperty("kaptcha.textproducer.char.length", "4");
      //字符间距 默认为2
      properties.setProperty("kaptcha.textproducer.char.space", "6");
      //验证码图片宽度 默认为200
      properties.setProperty("kaptcha.image.width", "200");
      //验证码图片高度 默认为40
      properties.setProperty("kaptcha.image.height", "50");
      Config config = new Config(properties);
      defaultKaptcha.setConfig(config);
      return defaultKaptcha;
   }
}

写生成验证码的代码
生成验证码
这段代码是一个Java Spring框架的ImageController控制器类,主要是实现生成图片验证码的功能。在getKaptchaImage方法中,通过使用第三方库DefaultKaptcha生成验证码的文本和图片,并将验证码文本放入session中保存。然后将生成的图片输出到HttpServletResponse的输出流中,返回给前端页面展示。在输出图片之前,还设置了一些响应头,这些响应头主要是为了防止浏览器缓存该图片,确保每次生成的验证码都是随机的。

前端的页面通过向后端发起请求获取验证码图片。比如在页面中有一个验证码图片的img标签,src属性指向的就是后端生成验证码图片的接口,即ImageController中的getKaptchaImage方法。

<img src="/image/getKaptchaImage" alt="验证码">

这样,当页面加载时,会向后端发送一个getKaptchaImage接口的请求,后端会返回生成的验证码图片,前端就可以显示该图片了。在后续用户输入验证码时,前端会将用户输入的验证码和后端保存在session中的验证码进行比对,以此来验证验证码是否正确。

@Controller
@RequestMapping("image")
public class ImageController {

	@Autowired
	private DefaultKaptcha defaultKaptcha;


	@RequestMapping("getKaptchaImage")
	public void getKaptchaImage(HttpServletRequest request, HttpServletResponse response) {
		// 定义response输出类型为image/jpeg类型
		response.setDateHeader("Expires", 0);
		// Set standard HTTP/1.1 no-cache headers.
		response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
		// Set IE extended HTTP/1.1 no-cache headers (use addHeader).
		response.addHeader("Cache-Control", "post-check=0, pre-check=0");
		// Set standard HTTP/1.0 no-cache header.
		response.setHeader("Pragma", "no-cache");
		// return a jpeg
		response.setContentType("image/jpeg");
		//---------------生成验证码----------------
		String text = defaultKaptcha.createText();
		System.out.println("验证码内容为:" + text);
		//将验证码放入session中
		request.getSession().setAttribute("pictureVerifyKey",text);
		BufferedImage image = defaultKaptcha.createImage(text);
		ServletOutputStream outputStream = null;
		try {
			outputStream = response.getOutputStream();
			ImageIO.write(image, "jpg", outputStream);
			outputStream.flush();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (null != outputStream) {
					outputStream.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	//---------------生成验证码----------------
}

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

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

相关文章

贪心算法(无规则)

目录 1.easy1.455. 分发饼干2.1005. K 次取反后最大化的数组和3.860. 柠檬水找零 2.medium1.序列问题1.376. 摆动序列2.738. 单调递增的数字 2.贪心解决股票问题1.122. 买卖股票的最佳时机 II 3.两个维度权衡问题1.135. 分发糖果*2.406. 根据身高重建队列(linklist&#xff0c;…

【WOA-LSTM】基于WOA优化 LSTM神经网络预测研究(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Neuron 2.4.0 发布:体验下一代工业物联网连接和管理

近日&#xff0c;EMQ 旗下的工业协议网关软件 Neuron 发布了最新的 2.4.0 版本。 该版本新增了包括 ABB COMLI 在内的四个南向驱动和一个北向应用&#xff0c;同时对现有的插件功能和 UI 进行了优化。 快速体验 Neuron 新版本 新增驱动插件满足不同场景需求 IEC61850 MMS 和 …

springboot项目如何优雅停机

文章目录 前言kill -9 pid的危害如何优雅的停机理论步骤优雅方式1、kill -15 pid 命令停机2、ApplicationContext close停机3、actuator shutdown 停机 前言 相信很多同学都会用Kill -9 PID来杀死进程&#xff0c;如果用在我们微服务项目里面&#xff0c;突然杀死进程会有什么…

计算机组成原理基础练习题第二章

1.下面四种语言中&#xff0c;()更适应网络环境 A、FORTRAN    B、JavaC、C        D、PASCAL 2.下列叙述中正确的是&#xff08;&#xff09; A.终端是计算机硬件的一部分,好比电视中的小屏幕 B. ALU是代数逻辑单元的缩写 C.导航用计算机属于一般用途计算…

网络数据链路层介绍

文章目录 一、以太网二、以太网的帧格式三、局域网通信的原理四、ARP协议1.ARP协议的介绍2.ARP协议的工作流程3.ARP数据报格式 一、以太网 以太网并不是一种具体的网络&#xff0c;而是一种技术标准&#xff0c;它既包含了数据链路层的内容&#xff0c;也包含了一些物理层的内…

【数据分享】2023年全国A级景区数据(14847个景区)

我国的旅游景区&#xff0c;依据景区质量划分为五级&#xff0c;从高到低依次为5A、4A、3A、2A、A级旅游景区。我国旅游管理部门对于A级景区实行“有进有出”的动态管理&#xff0c;也就是A级景区的名单每年都在变&#xff01;我们立方数据学社也一直在跟踪整理每年的A级景区数…

Android面试指南:说说你对组件化/模块化的理解

到现在组件化真的不是什么新鲜东西了&#xff0c;大公司都用的滚瓜烂熟&#xff0c;龙飞凤舞了&#xff0c;也就是现在部分中型项目和小项目在组件化的路上努力。所以同志们&#xff0c;组件化没玩过的&#xff0c;不熟悉的赶紧搞起来&#xff0c;说一点&#xff0c;你不会组件…

【牛客小白月赛72】BCD题

B、数数 比赛AC代码&#xff1a; #include <iostream> using namespace std;int t, n; int ans1;int check(int x) {int ans 0;for(int i 1; i < x/i; i)if(x%i 0){ans ;if(i ! x/i) ans;}ans1 ans;return ans1; } int main() {cin>> t;while(t--){cin&…

每日一练 | 华为认证真题练习Day44

1、如Display信息所示&#xff0c;当此交换机需要转发目的IAC地址为5489-98ec-f011的帧时&#xff0c;下面描述正确的是&#xff08;&#xff09;。 A. 交换机将会在除了收到该帧的端口之外的所有端口泛洪该帧 B. 交换机将会发送目标不可达的消息给源设备 C. 交换机在MAC地址…

easyExcel 与 POI 基础知识

文章目录 POI 与 easyExcel一、 了解1.1 Apache POI1.2 easyExcel 二、 准备工作2.1 Maven坐标2.2 Excel讲解 三、 Excel基本写操作&#xff08;导出Excel&#xff09;3.1 03 版本Excel导出操作3.2 07版本Excel导出操作3.3 大数据量的导出&#xff08;数据批量导入到磁盘&#…

含电动汽车的区域综合能源系统优化调度研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

django路由(多应用配置动态路由正则路由)

一、配置全局路由 在应用下&#xff0c;定义视图函数views.py from django.http import HttpResponse from django.shortcuts import render# Create your views here.def get_order(request):return HttpResponse("orders应用下的路由") 在项目的urls路由配置中&…

输入URL到显示界面的整个过程

以如下这个比较简单的网络拓扑模型作为例子&#xff0c;探究中间发生的整个过程&#xff1a; 1 HTTP 浏览器做的第一步工作就是要对 URL 进行解析&#xff0c;从而生成发送给 Web 服务器的请求信息。下图展示了一条长长的URL里各个元素代表什么&#xff1a; 所以整个长长的URL…

〖C++11〗智能指针详解

「前言」文章是关于C11的智能指针方面 「归属专栏」C嘎嘎 「笔者」枫叶先生(fy) 「座右铭」前行路上修真我 「枫叶先生有点文青病」 「每篇一句」 人生就是一列开往坟墓的列车&#xff0c; 路途上会有很多站&#xff0c; 很难有人自始至终陪着你走完。 当陪你的人要下车时&am…

42个网工高效率工具,我只告诉你(二)

晚上好&#xff0c;我是老杨。好用工具上期更新之后&#xff0c;不少小友催我更新下期&#xff0c;这不就来了&#xff1f; 今日文章阅读福利&#xff1a;《42个好用工具下载包》 私信老杨&#xff0c;回复关键词“工具42”&#xff0c;领取2022网工好用工具大全&#xff0c;同…

软件工程师,要么不写代码,要么就写优雅的代码

何为优雅的代码 优雅的代码&#xff0c;至少需要遵循以下几个原则&#xff1a; 遵守规范 优雅的代码&#xff0c;首先让人看起来就是很整洁的。而这种整洁&#xff0c;则来源于代码规范。严格地遵守代码规范&#xff0c;是提高且保证代码质量的最有效方法。从个人开发的角度来看…

【Python入门】Python的判断语句(判断语句的嵌套)

前言 &#x1f4d5;作者简介&#xff1a;热爱跑步的恒川&#xff0c;致力于C/C、Java、Python等多编程语言&#xff0c;热爱跑步&#xff0c;喜爱音乐的一位博主。 &#x1f4d7;本文收录于Python零基础入门系列&#xff0c;本专栏主要内容为Python基础语法、判断、循环语句、函…

Linux高级(shell)

文章目录 一、shell概述Linux 提供的 Shell 解析器有bash 和 sh 的关系Centos 默认的解析器是 bash 二、shell脚本入门脚本格式第一个shell脚本&#xff1a;helloworld.sh 三、变量系统预定义变量自定义变量特殊变量 四、删除变量五、运算符六、条件判断七、流程控制if判断case…

猜谜游戏、彩云词典爬虫、SOCKS5代理的 Go(Golang) 小实践,附带全代码解释

猜谜游戏在编程语言实践都已经和 HelloWord 程序成为必不可少的新手实践环节&#xff0c;毕竟&#xff0c;它能够让我们基本熟悉 for 循环、变量定义、打印、if else 语句等等的使用&#xff0c;当我们基本熟悉该语言基础之后&#xff0c;就要学会其优势方面的程序实践&#xf…