Ruoyi-cloud集成Sa-Token SSO单点登录

2023-11-17


https://github.com/dromara/Sa-Token

Sa-Token SSO 模式三
修改本地hosts

127.0.0.1 sa-sso-server.com
127.0.0.1 sa-sso-client1.com
127.0.0.1 sa-sso-client2.com
127.0.0.1 sa-sso-client3.com

服务端

使用的是源码里面的 sa-token-demo-sso-server

package com.pj.sso;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.sso.SaSsoUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

import com.ejlchina.okhttps.OkHttps;

import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.sso.SaSsoHandle;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;

/**
 * Sa-Token-SSO Server端 Controller 
 * @author kong
 *
 */
@RestController
public class SsoServerController {

	/*
	 * SSO-Server端:处理所有SSO相关请求 
	 * 		http://{host}:{port}/sso/auth			-- 单点登录授权地址,接受参数:redirect=授权重定向地址 
	 * 		http://{host}:{port}/sso/doLogin		-- 账号密码登录接口,接受参数:name、pwd 
	 * 		http://{host}:{port}/sso/checkTicket	-- Ticket校验接口(isHttp=true时打开),接受参数:ticket=ticket码、ssoLogoutCall=单点注销回调地址 [可选] 
	 * 		http://{host}:{port}/sso/logout			-- 单点注销地址(isSlo=true时打开),接受参数:loginId=账号id、secretkey=接口调用秘钥 
	 */
	@RequestMapping("/sso/*")
	public Object ssoRequest() {
		return SaSsoHandle.serverRequest();
	}
	
	// 配置SSO相关参数 
	@Autowired
	private void configSso(SaSsoConfig sso) {
		
		// 配置:未登录时返回的View 
		sso.setNotLoginView(() -> {
			return new ModelAndView("sa-login.html");
		});
		
		// 配置:登录处理函数 
		sso.setDoLoginHandle((name, pwd) -> {
			// 此处仅做模拟登录,真实环境应该查询数据进行登录 
			if("sa".equals(name) && "123456".equals(pwd)) {
				StpUtil.login(10001);
				return SaResult.ok("登录成功!").setData(StpUtil.getTokenValue());
			}
			return SaResult.error("登录失败!");
		});
		
		// 配置 Http 请求处理器 (在模式三的单点注销功能下用到,如不需要可以注释掉) 
		sso.setSendHttp(url -> {
			try {
				// 发起 http 请求 
				System.out.println("发起请求:" + url);
				return OkHttps.sync(url).get().getBody().toString();
			} catch (Exception e) {
				e.printStackTrace();
				return null;
			}
		});
	}

    // 自定义接口:获取userinfo
    @RequestMapping("/sso/userinfo")
    public Object userinfo(String loginId) {
        System.out.println("---------------- 获取userinfo --------");

        // 校验签名,防止敏感信息外泄
        SaSsoUtil.checkSign(SaHolder.getRequest());

        // 自定义返回结果(模拟) name和pwd返回给client前端登录用,因为只有登录成功才返回密码,应该没安全问题
        return SaResult.ok()
                .set("id", loginId)
                .set("name", "admin")
				.set("pwd", "admin123")
                .set("sex", "女")
                .set("age", 18);
    }
	
}

客户端前端

src/api/login.js

export function ssoLogout(satoken) {
  return request({
    url: '/auth/sso/logout',
    headers: {
      isToken: false
    },
    method: 'post',
    data: { satoken}
  })
}

src/permission.js
增加 /sso

const whiteList = ['/sso']

src/router/index.js

 {
   path: '/sso',
   component: () => import('@/views/sso'),
   hidden: true
 }

src/store/modules/user.js

	import { ssoLogout } from '@/api/login'
	
    // 退出系统
    LogOut({ commit, state }) {
      return new Promise((resolve, reject) => {   
        logout(state.token).then(() => {
          commit('SET_TOKEN', '')
          commit('SET_ROLES', [])
          commit('SET_PERMISSIONS', [])
          removeToken()
          resolve()
          // sso登录退出
          let satoken = localStorage.satoken;
          ssoLogout(satoken).then(res => {
          });
        }).catch(error => {
          reject(error)
        })
      })
    },

src/views/login.vue

<template>
  <div class="login">
    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
      <h3 class="title">若依后台管理系统</h3>
      <el-form-item prop="username">
        <el-input
          v-model="loginForm.username"
          type="text"
          auto-complete="off"
          placeholder="账号"
        >
          <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
        </el-input>
      </el-form-item>
      <el-form-item prop="password">
        <el-input
          v-model="loginForm.password"
          type="password"
          auto-complete="off"
          placeholder="密码"
          @keyup.enter.native="handleLogin"
        >
          <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
        </el-input>
      </el-form-item>
      <el-form-item prop="code" v-if="captchaEnabled">
        <el-input
          v-model="loginForm.code"
          auto-complete="off"
          placeholder="验证码"
          style="width: 63%"
          @keyup.enter.native="handleLogin"
        >
          <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
        </el-input>
        <div class="login-code">
          <img :src="codeUrl" @click="getCode" class="login-code-img"/>
        </div>
      </el-form-item>
      <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
      <el-form-item style="width:100%;">
        <el-button
          :loading="loading"
          size="medium"
          type="primary"
          style="width:100%;"
          @click.native.prevent="handleLogin"
        >
          <span v-if="!loading">登 录</span>
          <span v-else>登 录 中...</span>
        </el-button>
        <div style="float: right;" v-if="register">
          <router-link class="link-type" :to="'/register'">立即注册</router-link>
        </div>
      </el-form-item>
      <!-- <el-form-item style="width:100%;">
        <el-button
          :loading="loading"
          size="medium"
          type="primary"
          style="width:100%;"
          @click.native.prevent="handleSsoLogin"
        >
          <span>登 录2</span>      
        </el-button>
      </el-form-item>
      <el-form-item style="width:100%;">
        <el-button
          :loading="loading"
          size="medium"
          type="primary"
          style="width:100%;"
          @click.native.prevent="handleSsoLogout"
        >
          <span>注销</span>      
        </el-button>
      </el-form-item>
      <p>当前是否登录:<b>{{ isLogin }}</b></p> -->
    </el-form>
    
    <!--  底部  -->
    <div class="el-login-footer">
      <span>Copyright © 2018-2022 ruoyi.vip All Rights Reserved.</span>
    </div>
  </div>
</template>

<script>
import { getCodeImg, ssoLogout } from "@/api/login";
import Cookies from "js-cookie";
import { encrypt, decrypt } from '@/utils/jsencrypt'
import request from '@/utils/request'

export default {
  name: "Login",
  data() {
    return {
      codeUrl: "",
      loginForm: {
        username: "admin",
        password: "admin123",
        rememberMe: false,
        code: "",
        uuid: "",
        login: false,
      },
      loginRules: {
        username: [
          { required: true, trigger: "blur", message: "请输入您的账号" }
        ],
        password: [
          { required: true, trigger: "blur", message: "请输入您的密码" }
        ],
        code: [{ required: true, trigger: "change", message: "请输入验证码" }]
      },
      loading: false,
      // 验证码开关
      captchaEnabled: true,
      // 注册开关
      register: false,
      redirect: undefined,
      saname:"",
      sapwd:"",
      isLogin:false
    };
  },
  watch: {
    $route: {
      handler: function(route) {
        this.redirect = route.query && route.query.redirect;
        this.saname = route.query && route.query.saname;
        this.sapwd = route.query && route.query.sapwd; 
      },
      immediate: true
    }
  },
  created() {
    this.getCode();
    this.getCookie();
    // 是否自动登录
    // this.ssoIsLogin();
  },
  methods: {
    getCode() {
      getCodeImg().then(res => {
        this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled;
        if (this.captchaEnabled) {
          this.codeUrl = "data:image/gif;base64," + res.img;
          this.loginForm.uuid = res.uuid;
        }
      });
    },
    getCookie() {
      const username = Cookies.get("username");
      const password = Cookies.get("password");
      const rememberMe = Cookies.get('rememberMe')
      this.loginForm = {
        username: username === undefined ? this.loginForm.username : username,
        password: password === undefined ? this.loginForm.password : decrypt(password),
        rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
      };
    },
    handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.loading = true;
          if (this.loginForm.rememberMe) {
            Cookies.set("username", this.loginForm.username, { expires: 30 });
            Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
            Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
          } else {
            Cookies.remove("username");
            Cookies.remove("password");
            Cookies.remove('rememberMe');
          }
          this.$store.dispatch("Login", this.loginForm).then(() => {
            this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
          }).catch(() => {
            this.loading = false;
            if (this.captchaEnabled) {
              this.getCode();
            }
          });
        }
      });
    },
    handleSsoLogin(){
      //let _url = location.href + '&saname='+ this.loginForm.username + '&sapwd='+ this.loginForm.password;
      let _url = location.href;
      this.$router.push({ path: '/sso', query: { back: encodeURIComponent(_url) } });
    },
    handleSsoLogout(){
      let satoken = localStorage.satoken; 
      ssoLogout(satoken).then(res => {
        this.ssoIsLogin();
      });
    },
    ssoIsLogin(){
      request({
        url: '/auth/sso/isLogin',
        headers: {
          isToken: false
        },
        method: 'post'
      }).then(res => {
        this.isLogin = res.data;
        if(res.data){
          console.log("sso登录成功");
          // 跳转若依
          this.handleRuoyiAutoLogin();
        }
        else{
          console.log("sso未登录");
          // 跳转sso登录
          this.handleSsoLogin();
        }
      });
    },
    handleRuoyiAutoLogin(){
      request({
        url: '/auth/sso/myinfo',
        headers: {
          isToken: false
        },
        method: 'post'
      }).then(res => {
        if(res.code==200){
          this.loginForm.username = res.name;
          this.loginForm.password = res.pwd;
          debugger;
          this.$store.dispatch("Login", this.loginForm).then(() => {
            this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
          }).catch(() => {
            this.loading = false;
            if (this.captchaEnabled) {
              this.getCode();
            }
          });
        }
      });
    }
  }
};
</script>

src/views/sso.vue

<template>
  <div class="">    
  </div>
</template>

<script>
import { getCodeImg, getRedirectUrl } from "@/api/login";
import Cookies from "js-cookie";
import { encrypt, decrypt } from '@/utils/jsencrypt'
import request from '@/utils/request'

export default {
  name: "Sso",
  data() {
    return {
      baseUrl: "http://localhost/auth",
      back: undefined,
      ticket: undefined
    };
  },
  watch: {
    $route: {
      handler: function(route) {
        this.back = route.query && route.query.back;
        this.ticket = route.query && route.query.ticket;        
      },
      immediate: true
    }
  },
  created() {    
    if(this.ticket) {
      this.doLoginByTicket(this.ticket);
    } else {
      this.goSsoAuthUrl();
    }
  },
  methods: {    
    goSsoAuthUrl() {      
      request({
        url: '/auth/sso/getSsoAuthUrl',
        headers: {
          isToken: false
        },
        method: 'post',
        params: { clientLoginUrl:location.href }
      }).then(res => {        
				location.href = res.data;
      });

    },
    doLoginByTicket() {
      request({
        url: '/auth/sso/doLoginByTicket',
        headers: {
          isToken: false
        },
        method: 'post',
        params: { ticket: this.ticket }
      }).then(res => {              
        if(res.code == 200) {
						localStorage.setItem('satoken', res.data);            
            let _back =  decodeURIComponent(this.back); 
						location.href = _back; 
					} else {
						alert(res.msg);
					}
      });      
    },

  }
};
</script>

<style rel="stylesheet/scss" lang="scss">
</style>

客户端后端

aihub-auth/pom.xml

    <properties>
        <sa-token-version>1.30.0</sa-token-version>
    </properties>

        <!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-spring-boot-starter</artifactId>
            <version>${sa-token-version}</version>
        </dependency>

        <!-- Sa-Token 插件:整合SSO -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-sso</artifactId>
            <version>${sa-token-version}</version>
        </dependency>

        <!-- Sa-Token 插件:整合redis (使用jackson序列化方式) -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-dao-redis-jackson</artifactId>
            <version>${sa-token-version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

        <!-- Http请求工具(在模式三的单点注销功能下用到,如不需要可以注释掉) -->
        <dependency>
            <groupId>com.ejlchina</groupId>
            <artifactId>okhttps</artifactId>
            <version>3.5.3</version>
            <exclusions>
                <exclusion>
                    <groupId>com.squareup.okio</groupId>
                    <artifactId>okio</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.squareup.okio</groupId>
            <artifactId>okio</artifactId>
            <version>2.8.0</version>
        </dependency>

aihub-auth/src/main/resources/bootstrap.yml

# sa-token配置
sa-token:
  # SSO-相关配置
  sso:
    # SSO-Server端 统一认证地址
    auth-url: http://sa-sso-server.com:9000/sso/auth
    # 使用Http请求校验ticket
    is-http: true
    # SSO-Server端 ticket校验地址
    check-ticket-url: http://sa-sso-server.com:9000/sso/checkTicket
    # 是否打开单点注销接口
    is-slo: true
    # 单点注销地址
    slo-url: http://sa-sso-server.com:9000/sso/logout
    # 接口调用秘钥
    secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
    # SSO-Server端 查询userinfo地址
    userinfo-url: http://sa-sso-server.com:9000/sso/userinfo

  # 配置Sa-Token单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis)
  alone-redis:
    # Redis数据库索引
    database: 1
    # Redis服务器地址
    host: 127.0.0.1
    # Redis服务器连接端口
    port: 6379
    # Redis服务器连接密码(默认为空)
    password:
    # 连接超时时间
    timeout: 10s
    lettuce:
      pool:
        # 连接池最大连接数
        max-active: 200
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms
        # 连接池中的最大空闲连接
        max-idle: 10
        # 连接池中的最小空闲连接
        min-idle: 0

aihub-auth/src/main/java/com/aihub/auth/controller/CorsFilter.java

package com.aihub.auth.controller;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 跨域过滤器
 * @author kong 
 */
@Component
@Order(-200)
public class CorsFilter implements Filter {

	static final String OPTIONS = "OPTIONS";

	@Override
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
		
		// 允许指定域访问跨域资源
		response.setHeader("Access-Control-Allow-Origin", "*");
		// 允许所有请求方式
		response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
		// 有效时间
		response.setHeader("Access-Control-Max-Age", "3600");
		// 允许的header参数
		response.setHeader("Access-Control-Allow-Headers", "x-requested-with,satoken");

		// 如果是预检请求,直接返回
		if (OPTIONS.equals(request.getMethod())) {
			System.out.println("=======================浏览器发来了OPTIONS预检请求==========");
			response.getWriter().print("");
			return;
		}

		// System.out.println("*********************************过滤器被使用**************************");
		chain.doFilter(req, res);
	}

	@Override
	public void init(FilterConfig filterConfig) {
	}

	@Override
	public void destroy() {
	}

}

aihub-auth/src/main/java/com/aihub/auth/controller/H5Controller.java

package com.aihub.auth.controller;

import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.sso.SaSsoHandle;
import cn.dev33.satoken.sso.SaSsoUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.ejlchina.okhttps.OkHttps;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 前后台分离架构下集成SSO所需的代码 (SSO-Client端)
 * <p>(注:如果不需要前后端分离架构下集成SSO,可删除此包下所有代码)</p>
 * @author kong
 *
 */
@RestController
public class H5Controller {

	/*
	 * SSO-Client端:处理所有SSO相关请求
	 * 		http://{host}:{port}/sso/login			-- Client端登录地址,接受参数:back=登录后的跳转地址
	 * 		http://{host}:{port}/sso/logout			-- Client端单点注销地址(isSlo=true时打开),接受参数:back=注销后的跳转地址
	 * 		http://{host}:{port}/sso/logoutCall		-- Client端单点注销回调地址(isSlo=true时打开),此接口为框架回调,开发者无需关心
	 */
	@RequestMapping("/sso/*")
	public Object ssoRequest() {
		return SaSsoHandle.clientRequest();
	}

	// 配置SSO相关参数
	@Autowired
	private void configSso(SaSsoConfig sso) {
		// 配置Http请求处理器
		sso.setSendHttp(url -> {
			System.out.println("发起请求:" + url);
			return OkHttps.sync(url).get().getBody().toString();
		});
	}

	// 当前是否登录 
	@RequestMapping("/sso/isLogin")
	public Object isLogin() {
		return SaResult.data(StpUtil.isLogin());
	}

	// 查询我的账号信息
	@RequestMapping("/sso/myinfo")
	public Object myinfo() {
		Object userinfo = SaSsoUtil.getUserinfo(StpUtil.getLoginId());
		System.out.println("--------info:" + userinfo);
		return userinfo;
	}
	
	// 返回SSO认证中心登录地址 
	@RequestMapping("/sso/getSsoAuthUrl")
	public SaResult getSsoAuthUrl(String clientLoginUrl) {
		String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(clientLoginUrl, "");
		return SaResult.data(serverAuthUrl);
	}
	
	// 根据ticket进行登录 
	@RequestMapping("/sso/doLoginByTicket")
	public SaResult doLoginByTicket(String ticket) {
		Object loginId = SaSsoHandle.checkTicket(ticket, "/sso/doLoginByTicket");
		if(loginId != null) {
			StpUtil.login(loginId);
			return SaResult.data(StpUtil.getTokenValue());
		}
		return SaResult.error("无效ticket:" + ticket); 
	}

	// 全局异常拦截 
	@ExceptionHandler
	public SaResult handlerException(Exception e) {
		e.printStackTrace(); 
		return SaResult.error(e.getMessage());
	}
	
}

aihub-gateway/src/main/java/com/aihub/gateway/filter/ValidateCodeFilter.java

 //sso自动登录去掉验证码
 private final static String[] VALIDATE_URL = new String[] { "/auth/loginxxx", "/auth/register" };
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Ruoyi-cloud集成Sa-Token SSO单点登录 的相关文章

  • 如何使用Spring WebClient进行同步调用?

    Spring Framework in 休息模板 https docs spring io spring framework docs current javadoc api org springframework web client R
  • Java 7 默认语言环境

    我刚刚安装了 jre7 我很惊讶地发现我的默认区域设置现在是 en US 对于jre6 它是de CH 与jre7有什么不同 默认区域设置不再是操作系统之一吗 顺便说一句 我使用的是Windows7 谢谢你的回答 编辑 我已经看到了语言环境
  • 如何使用 JAVA 代码以编程方式捕获线程转储?

    我想通过 java 代码生成线程转储 我尝试使用 ThreadMXBean 为此 但我没有以正确的格式获得线程转储 因为我们正在使用jstack命令 请任何人提供一些帮助 他们是否有其他方式获取线程转储 使用任何其他 API 我想要的线程转
  • HAProxy SSL终止+客户端证书验证+curl/java客户端

    我希望使用我自己的自签名证书在 HAProxy 上进行 SSL 终止 并使用我创建的客户端证书验证客户端访问 我通过以下方式创建服务器 也是 CA 证书 openssl genrsa out ca key 1024 openssl req
  • 在 Struts 2 中传递 URL 参数而不使用查询字符串

    我想使用类似的 URL host ActionName 123 abc 而不是像这样传递查询字符串 host ActionName parm1 123 parm2 abc 我怎样才能在 Struts 2 中做到这一点 我按照下面的方法做了
  • 为自定义驱动程序创建 GraphicsDevice

    我正在开发一个在嵌入式系统中使用 Java 的项目 我有用于屏幕和触摸输入的驱动程序 以及用于文本输入的虚拟键盘 我的屏幕驱动程序有一个Graphics2D您可以绘制的对象和repaint Rectangle 更新方法 类似地 触摸驱动器能
  • 为什么 MOVE CURSOR 在 OS X Mountain Lion 上不显示?

    我正在做一个项目 想看看 Swing 提供的每个光标是什么样子的 public class Test public static void main String args JFrame frame new JFrame frame set
  • Java:从集合中获取第一项

    如果我有一个集合 例如Collection
  • 如何使用正则表达式验证 1-99 范围?

    我需要验证一些用户输入 以确保输入的数字在 1 99 范围内 含 这些必须是整数 Integer 值 允许前面加 0 但可选 有效值 1 01 10 99 09 无效值 0 007 100 10 5 010 到目前为止 我已经制定了以下正则
  • org/codehaus/plexus/archiver/jar/JarArchiver(不支持的major.minor版本49.0)-Maven构建错误

    下午大家 我在尝试构建项目时收到上述错误 我很确定这与使用 Java 1 6 编译的 Maven 最新更新有关 而我们尝试构建的项目是 1 4 项目 在此之前的插件工作没有问题 因此我将以下内容添加到 POM xml 文件中以尝试强制使用现
  • Spring Data JPA:查询如何返回非实体对象或对象列表?

    我在我的项目中使用 Spring Data JPA 我正在演奏数百万张唱片 我有一个要求 我必须获取各种表的数据并构建一个对象 然后将其绘制在 UI 上 现在如何实现我的 Spring 数据存储库 我读到它可以通过命名本机查询来实现 如果指
  • 如何通过 Android 按钮单击运行单独的应用程序

    我尝试在 Android 应用程序中添加两个按钮 以从单独的两个应用程序订单系统和库存系统中选择一个应用程序 如图所示 我已将这两个应用程序实现为两个单独的 Android 项目 当我尝试运行此应用程序时 它会出现直到正确选择窗口 但是当按
  • Karaf / Maven - 无法解决:缺少需求 osgi.wiring.package

    我无法在 Karaf 版本 3 0 1 中启动捆绑包 该包是使用 Maven 构建的并导入gson http mvnrepository com artifact com google code gson gson 2 3 1 我按照要求将
  • Lombok @Builder 不创建不可变对象?

    在很多网站上 我看到 lombok Builder 可以用来创建不可变的对象 https www baeldung com lombok builder singular https www baeldung com lombok buil
  • 如何从 Ant 启动聚合 jetty-server JAR?

    背景 免责声明 I have veryJava 经验很少 我们之前在 Ant 构建期间使用了 Jetty 6 的包装版本来处理按需静态内容 JS CSS 图像 HTML 因此我们可以使用 PhantomJS 针对 HTTP 托管环境运行单元
  • Hadoop NoSuchMethodError apache.commons.cli

    我在用着hadoop 2 7 2我用 IntelliJ 做了一个 MapReduce 工作 在我的工作中 我正在使用apache commons cli 1 3 1我把库放在罐子里 当我在 Hadoop 集群上使用 MapReduceJob
  • 替换文件中的字符串

    我正在寻找一种方法来替换文件中的字符串而不将整个文件读入内存 通常我会使用 Reader 和 Writer 即如下所示 public static void replace String oldstring String newstring
  • 记录类名、方法名和行号的性能影响

    我正在我的 java 应用程序中实现日志记录 以便我可以调试应用程序投入生产后可能出现的潜在问题 考虑到在这种情况下 人们不会奢侈地使用 IDE 开发工具 以调试模式运行事物或单步执行完整代码 因此在每条消息中记录类名 方法名和行号将非常有
  • 检查应用程序是否在 Android Market 上可用

    给定 Android 应用程序 ID 包名称 如何以编程方式检查该应用程序是否在 Android Market 上可用 例如 com rovio angrybirds 可用 而 com random app ibuilt 不可用 我计划从
  • 如何使用通配符模拟泛型方法的行为

    我正在使用 EasyMock 3 2 我想基于 Spring Security 为我的部分安全系统编写一个测试 我想嘲笑Authentication http docs spring io autorepo docs spring secu

随机推荐

  • 使用idea将一个web项目部署到tomcat上

    使用idea将一个web项目部署到tomcat上 点击Run Edit Configurations 单击 找到tomcat server local 选定tomcat版本 点击Fix 单击Apply OK 最终成果
  • centos linux 安装RDMA Soft-RoCE

    RoCE既可以通过硬件实现 也可以通过软件实现 Soft RoCE 是 RDMA 传输的软件实现 什么是Soft RoCE softRoCE的目标是在所有支持以太网的设备上都可以部署RDMA传输 可以使不具备RoCE能力的硬件和支持RoCE
  • vim 编辑器 bash文件测试

    1 编辑 x 删除光标所在处字符 x 删除光标所在处开始往后的 个字符 d 删除命令 dd 删除光标所在处一整行 d 删除光标所在处往后的 行 2 末行模式 start end eg 3 4 10 9 表示光标所在行 最后一行 2 当前到倒
  • Win10下安装Tensorflow

    建议安装AnacondaWindows下 只有python3能安装Tensorflow 1 打开命令行窗口 创建conda环境 conda create n tensorflow python 3 x 对应着自己的python版本 必须要3
  • 【前端】求职必备知识点2-CSS:优先级、盒子模型、标准流、浮动流、定位流

    文章目录 CSS优先级 盒子模型 标准流 浮动流 定位流 标准流 浮动流 定位流 思维导图 CSS优先级 class类选择器 属性选择器 伪类 的权值为10 元素选择器 伪元素选择器权值为1 属性选择器 如 将有title的元素变为红色 t
  • Obsidian Tasks插件介绍

    背景 按照之前对 DataView 插件的介绍 对于任务列表的使用其实就可以使用其中的 list 插件完成的 但是 DataView 插件只能完成列表的查询功能 而查询功能只能是任务列表中其中一个功能 因此就使用 DataVIew 插件是不
  • java集合List

    Java集合概况 Java集合一直理解的都是片面的 整理一下 将知识组织成面 更便于理解 上图来自Java 集合系列01之 总体框架 如果天空不死 博客园 虽然博主是基于java1 6整理的 但也不碍于我们学习 理解了上图 对于学习java
  • 排序遍历带前缀的文件名

    排序遍历带前缀的文件名 def getTimeId file fileAttrs file split fileTime fileAttrs 0 return fileTime def CleanUpExpireTar backupDir
  • 树莓派4B安装Batocera V35版本 前言

    前言 说说为什么要写这个 原因比较多 我在大学的时候买了树莓派4B 那时正价购买 刷了个OpenWrt系统在宿舍当软路由再跑 毕业之后买了个X86主机当软路由 树莓派就放着吃灰了 大学时期同样买了个北通的游戏手柄 当初为了玩崩坏3购入的 后
  • DDR2 DDR3的区别

    DDR2 DDR3的区别 功耗进一步减少 DDR2内存的默认电压为1 8V 而DDR3内存的默认电压只有1 5V 因此内存的功耗更小 发热量也相应地会减少 值得一提的是 DDR3内存还新增了温度监控 采用了ASR Automatic sel
  • Unity5.x运行场景直接卡死的问题

    今天遇到一个很奇葩的问题 就是电脑中Unity5 x版本的都不能运行场景了 包括新建空的工程也不行 重装unity软件重装VS环境也不行 神奇的是2017 2018 2019版本的都没有问题 并且界面也没有任何报错 爬各种论坛谷歌都没有找到
  • 山海演武传·黄道·第一卷 雏龙惊蛰 第二十二 ~ 二十四章 真龙之剑·星墟列将...

    我是第一次 请你 请你温柔一点 少女一边娇喘着 一边将稚嫩的红唇紧贴在男子耳边 樱桃小嘴盈溢出如兰香气 这样子 人家骑在上面 她紧紧地依偎在某个男子身上 窈窕的身躯与丰盈的酥胸 伴随着男子身体晃动而滑上滑下 起伏不定 啊 不要晃的那样厉害
  • ios appStore上架审核通过后,appStore搜索不到该应用

    问题描述 前两天上架一款ios App 周一到公司看审核已经通过了 去appStore上搜索一直搜索不到 ios appStore connect点击提示该商品在中国大陆没有上架 解决方法 通过app store connect 最下面的联
  • vue3.0 + element Plus实现页面中引入弹窗组件及defineExpose用法

    1 在需要弹窗显示的页面引入你所写的弹窗组件 index html
  • AI革命:AI+算力,霸主即将诞生!

    随着人工智能技术的飞速发展 AI 算力 的结合应用已成为科技行业的热点话题 甚至诞生出 AI 算力 最强龙头 的网络热门等式 该组合不仅可以提高计算效率 还可以为各行各业带来更强大的数据处理和分析能力 从而推动创新和增长 那么对于这个时下的
  • 二维多孔介质图像的粒度分布研究(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码实现 1 概述 使用流域分割算法对岩石二维二值图像进行粒度
  • Scala深入浅出——从Java到Scala

    本文适合有一定Java基础的 并想系统学习Scala的小伙伴借鉴学习 文章有大量实例 建议自己跑一遍 Scala深入浅出 从Java到Scala Scala 一 介绍 1 什么是Scala 2 特点 3 安装 二 Scala特点 三 sca
  • SecureCRT9.1高亮配色设置

    参考 http zh cjh com qita 1623 html https download csdn net download qq 45698138 88310255 spm 1001 2014 3001 5503 1 创建文件co
  • fork的例子

    以下是下列代码的头文件 forks c Examples of Unix process control include
  • Ruoyi-cloud集成Sa-Token SSO单点登录

    文章目录 服务端 客户端前端 客户端后端 https github com dromara Sa Token Sa Token SSO 模式三 修改本地hosts 127 0 0 1 sa sso server com 127 0 0 1