SpringSecurity集成第三方登錄過程詳解(最新推薦)
SpringSecurity 集成第三方登錄
認(rèn)證及自定義流程

首先我們提供一個(gè)實(shí)現(xiàn)了AbstractAuthenticationProcessingFilter抽象類的過濾器,用來代替UsernamePasswordAuthenticationFilter邏輯,然后提供一個(gè)AuthenticationProvider實(shí)現(xiàn)類代替AbstractUserDetailsAuthenticationProvider或DaoAuthenticationProvider,最后再提供一個(gè)UserDetailsService實(shí)現(xiàn)類。
1.驗(yàn)證碼登錄
1.通用過濾器實(shí)現(xiàn)–ThirdAuthenticationFilter
這個(gè)ThirdAuthenticationFilter過濾器我們可以仿照UsernamePasswordAuthenticationFilter來實(shí)現(xiàn)(也實(shí)現(xiàn)了AbstractAuthenticationProcessingFilter抽象類),主要是重新定義了attemptAuthentication()方法,這里需要根據(jù)“authType”參數(shù)值的類別構(gòu)建不同的AbstractAuthenticationToken,具體實(shí)現(xiàn)如下:
//驗(yàn)證類型,比如Sms,uernamepassword等
private String authTypeParameter = "authType";
//對(duì)應(yīng)用戶名或手機(jī)號(hào)等
private String principalParameter = "principal";
//對(duì)應(yīng)密碼或驗(yàn)證碼等
private String credentialsParameter = "credentials";
private boolean postOnly = true;
public ThirdAuthenticationFilter() {
super(new AntPathRequestMatcher("/login/doLogin", "POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
String authType = request.getParameter(authTypeParameter);
if(StringUtils.isEmpty(authType)){
authType = AuthTypeEnum.AUTH_TYPE_DEFAULT.getAuthType();
}
String principal = request.getParameter(principalParameter);
String credentials = request.getParameter(credentialsParameter);
AbstractAuthenticationToken authRequest = null;
switch (authType){
case "sms":
authRequest = new SmsAuthenticationToken(principal, credentials);
((SmsAuthenticationToken)authRequest).setCode((String)request.getSession().getAttribute("code"));
break;
case "github":
authRequest = new GithubAuthenticationToken(principal, credentials);
break;
case "default":
authRequest = new UsernamePasswordAuthenticationToken(principal, credentials);
}
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
return this.getAuthenticationManager().authenticate(authRequest);
}
}定義了ThirdAuthenticationSecurityConfig 配置類,我們還需要在SpringSecurity配置類中應(yīng)用才能生效,具體實(shí)現(xiàn)如下:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/error","/login/**","/login/goLogin","/login/doLogin","/login/code","/login/authorization_code").anonymous()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login/goLogin")
.loginProcessingUrl("/login/doLogin")
.failureUrl("/login/error")
.permitAll()
.successHandler(new QriverAuthenticationSuccessHandler("/index/toIndex"));
//這里我們省略了一些配置 ……
//應(yīng)用前面定義的配置
http.apply(thirdAuthenticationSecurityConfig);
}至此,我們定義的通用第三方過濾器就完成了,并且也完成了在SpringSecurity中生效的配置。下面我們就開始分別實(shí)現(xiàn)不同類型登錄的具體過程。
在ThirdAuthenticationFilter 類的attemptAuthentication()方法中,我們通過authType類型,然后創(chuàng)建對(duì)應(yīng)的Authentication實(shí)現(xiàn)來實(shí)現(xiàn)不同方式的登錄,這里我們主要實(shí)現(xiàn)了如下三種方式,我們分別梳理一下。
3、默認(rèn)的登錄過程
默認(rèn)的登錄過程,即根據(jù)用戶名密碼進(jìn)行登錄,需要使用到UsernamePasswordAuthenticationToken,當(dāng)“authType”參數(shù)為"default"時(shí),這里就會(huì)創(chuàng)建UsernamePasswordAuthenticationToken對(duì)象,然后后續(xù)通過ProviderManager的authenticate()方法,最后就會(huì)調(diào)用AbstractUserDetailsAuthenticationProvider(DaoAuthenticationProvider)的 authenticate()方法,最終又會(huì)調(diào)用定義的UserDetailsService實(shí)現(xiàn)類。這是默認(rèn)的過程,這里就不再重復(fù)其中的邏輯,除了UserDetailsService實(shí)現(xiàn)類需要自己定義,其他都是SpringSecurity提供的實(shí)現(xiàn)類。
4、短信驗(yàn)證碼登錄實(shí)現(xiàn)
短信驗(yàn)證碼登錄,是最貼近用戶名密碼登錄的一種方式,所以我們完全可以仿照用戶名密碼這種方式實(shí)現(xiàn)。我們這里先梳理一下短信驗(yàn)證碼登錄的業(yè)務(wù)邏輯:首先,登錄界面輸入手機(jī)號(hào)碼,然后再點(diǎn)擊“獲取驗(yàn)證碼”按鈕獲取短信驗(yàn)證碼,然后輸入收到的短信驗(yàn)證碼,最后點(diǎn)擊“登錄”按鈕進(jìn)行登錄認(rèn)證。和用戶名密碼登錄相比,短信驗(yàn)證碼登錄多了一個(gè)獲取驗(yàn)證碼的過程,其他其實(shí)都是一樣的,我們下面逐步實(shí)現(xiàn)短信驗(yàn)證碼登錄:
@RestController
@RequestMapping("/login")
public class SmsValidateCodeController {
//生成驗(yàn)證碼的實(shí)例對(duì)象
@Autowired
private ValidateCodeGenerator smsCodeGenerator;
//調(diào)用服務(wù)商接口,發(fā)送短信驗(yàn)證碼的實(shí)例對(duì)象
@Autowired
private DefaultSmsCodeSender defaultSmsCodeSender;
@RequestMapping("/code")
public String createSmsCode(HttpServletRequest request, HttpServletResponse response) throws ServletRequestBindingException {
ValidateCode smsCode = smsCodeGenerator.generate(new ServletWebRequest(request));
String mobile = (String)request.getParameter("principal");
request.getSession().setAttribute("code",smsCode.getCode());
defaultSmsCodeSender.send(mobile, smsCode.getCode());
System.out.println("驗(yàn)證碼:" + smsCode.getCode());
return "驗(yàn)證碼發(fā)送成功!";
}
}在上述方法中,我們注入了smsCodeGenerator和defaultSmsCodeSender兩個(gè)實(shí)例對(duì)象,分別用來生成驗(yàn)證碼和發(fā)送短信驗(yàn)證碼,這個(gè)可以根據(jù)項(xiàng)目的實(shí)際情況進(jìn)行定義和實(shí)現(xiàn),這里不再貼出其中的實(shí)現(xiàn)。同時(shí)在createSmsCode()方法中,還有一點(diǎn)需要注意的就是,我們發(fā)出去的短信驗(yàn)證碼需要進(jìn)行保存,方便后續(xù)登錄時(shí)進(jìn)行驗(yàn)證,這個(gè)也可以選擇很多方法,比如說會(huì)話、數(shù)據(jù)庫(kù)、緩存等,我這里為了簡(jiǎn)單,直接存到了session會(huì)話中了。
然后,我們前面定義ThirdAuthenticationFilter過濾器時(shí),根據(jù)登錄方式不同,需要對(duì)應(yīng)的Authentication對(duì)象,這里我們還需要?jiǎng)?chuàng)建短信驗(yàn)證登錄需要的Authentication類,這里我們可以仿照UsernamePasswordAuthenticationToken類進(jìn)行編寫,實(shí)現(xiàn)如下
public class SmsAuthenticationToken extends AbstractAuthenticationToken {
//對(duì)應(yīng)手機(jī)號(hào)碼
private final Object principal;
//對(duì)應(yīng)手機(jī)驗(yàn)證碼
private Object credentials;
//后臺(tái)存儲(chǔ)的短信驗(yàn)證碼,用于驗(yàn)證前端傳過來的是否正確
private String code;
public SmsAuthenticationToken(String mobile, Object credentials){
super(null);
this.principal = mobile;
this.credentials = credentials;
this.code = code;
setAuthenticated(false);
}
public SmsAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities, Object credentials){
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return this.credentials;
}
@Override
public Object getPrincipal() {
return this.principal;
}
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException(
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
credentials = null;
}
}在SmsAuthenticationToken 類中,我們?cè)黾恿艘粋€(gè)code屬性,其實(shí)該屬性不是必須的,我這里是為了方便傳遞存儲(chǔ)在session會(huì)話中的驗(yàn)證碼而添加的,如果使用緩存或數(shù)據(jù)庫(kù)進(jìn)行存儲(chǔ)驗(yàn)證碼,該屬性就可以省略。
在AuthenticationManager的authenticate()方法中,會(huì)根據(jù)Authentication類型選擇AuthenticationProvider對(duì)象,所以我們這里自定義短信驗(yàn)證碼需要的AuthenticationProvider對(duì)象,實(shí)現(xiàn)如下:
@Component
public class SmsAuthenticationProvider implements AuthenticationProvider{
@Autowired
@Qualifier("smsUserDetailsService")
private UserDetailsService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
SmsAuthenticationToken token = (SmsAuthenticationToken) authentication;
String mobile = (String)token.getPrincipal();
//首先,驗(yàn)證驗(yàn)證碼是否正確
String code = (String)token.getCredentials();
String sCode = token.getCode();
if(StringUtils.isEmpty(code) || !code.equalsIgnoreCase(sCode)){
throw new BadCredentialsException("手機(jī)驗(yàn)證碼錯(cuò)誤(Bad credentials),請(qǐng)重試!");
}
//然后,查詢對(duì)應(yīng)用戶
UserDetails user = userDetailsService.loadUserByUsername(mobile);
if (Objects.isNull(user)) {
throw new InternalAuthenticationServiceException("根據(jù)手機(jī)號(hào):" + mobile + ",無法獲取對(duì)應(yīng)的用戶信息!");
}
SmsAuthenticationToken authenticationResult = new SmsAuthenticationToken(user.getUsername(), user.getAuthorities(), token.getCredentials());
authenticationResult.setDetails(token.getDetails());
return authenticationResult;
}
@Override
public boolean supports(Class<?> authentication) {
return SmsAuthenticationToken.class.isAssignableFrom(authentication);
}
}在SmsAuthenticationProvider 中,supports()方法決定了該實(shí)例對(duì)象僅支持SmsAuthenticationToken對(duì)象的驗(yàn)證。同時(shí),根據(jù)authenticate()方法傳遞參數(shù)authentication對(duì)象(包括了登錄信息:手機(jī)號(hào)和驗(yàn)證碼,session存儲(chǔ)的驗(yàn)證碼),我們這里session存儲(chǔ)的驗(yàn)證碼,是因?yàn)槲覀儾捎昧藭?huì)話存儲(chǔ)的方式,如果使用數(shù)據(jù)庫(kù),我們這里就可以通過手機(jī)號(hào),去數(shù)據(jù)庫(kù)或緩存查詢對(duì)應(yīng)的驗(yàn)證碼,然后和authentication對(duì)象傳遞過來的驗(yàn)證碼進(jìn)行比對(duì),驗(yàn)證成功,說明登錄認(rèn)證成功,否則登錄認(rèn)證失敗。登錄成功后,我們就可以調(diào)用userDetailsService對(duì)象的loadUserByUsername()方法獲取登錄用戶的其他相關(guān)信息(權(quán)限等),具體實(shí)現(xiàn)在自定義的SmsUserDetailsService類中實(shí)現(xiàn),具體如下:
@Component("smsUserDetailsService")
public class SmsUserDetailsService implements UserDetailsService {
private Logger logger = LoggerFactory.getLogger(SmsUserDetailsService.class);
@Autowired
private SysUserService sysUserService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//1、查詢用戶信息
SysUser user = new SysUser();
user.setMobile(username);
SysUser qUser = sysUserService.getOne(new QueryWrapper<>(user),true);
if(qUser == null) {
logger.info("手機(jī)號(hào)為”" + username + "“的用戶不存在?。?!");
throw new UsernameNotFoundException("手機(jī)號(hào)為”" + username + "“的用戶不存在!!!");
}
//2、封裝用戶角色
UserRole userRole = sysUserService.getRoleByUserId(qUser.getId());
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(String.valueOf(userRole.getRoleId())));
return new LoginUser(qUser.getUsername(), qUser.getPassword(),authorities);
}
}2.GitHub登錄
和短信驗(yàn)證碼登錄認(rèn)證相比,Github登錄又會(huì)有自己的特殊性,我們這里先梳理一下基于Github進(jìn)行登錄驗(yàn)證的大致邏輯:首先,點(diǎn)擊Github登錄認(rèn)證按鈕,然后會(huì)跳轉(zhuǎn)到github登錄界面,輸入github系統(tǒng)的用戶名密碼,登錄成功,就會(huì)跳轉(zhuǎn)到我們自己的系統(tǒng)中的首頁(yè)。和基于用戶名密碼的登錄方式相比,Github登錄不需要類似用戶名和密碼這樣的輸入(在自己的系統(tǒng)中),同時(shí)又需要根據(jù)獲取到的github用戶信息,換取在自己系統(tǒng)對(duì)應(yīng)的用戶信息。具體實(shí)現(xiàn)步驟如下:
在github的配置省略
@Controller
@RequestMapping("/login")
public class GithubValidateController {
@Autowired
private GithubClientService githubClientService;
@RequestMapping("/authorization_code")
public void authorization_code(HttpServletRequest request, HttpServletResponse response, String code) throws ServletRequestBindingException, IOException {
//github登錄驗(yàn)證,并獲取access_token
Map<String,String> resp = githubClientService.queryAccessToken(code);
//跳轉(zhuǎn)本系統(tǒng)的登錄流程,獲取用戶信息,實(shí)現(xiàn)兩個(gè)系統(tǒng)用戶的對(duì)接
String url = "http://localhost:8888/qriver-admin/login/doLogin";
this.sendByPost(response, url,resp.get("access_token"),"github");
//this.sendByPost(response, url,"access_token","github");
}
public void sendByPost(HttpServletResponse response,String url, String principal, String authType) throws IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
out.println("<HTML>");
out.println(" <HEAD><TITLE>Post 方法</TITLE></HEAD>");
out.println(" <BODY>");
out.println("<form name=\"submitForm\" action=\"" + url + "\" method=\"post\">");
out.println("<input type=\"hidden\" name=\"principal\" value=\"" + principal + "\"/>");
out.println("<input type=\"hidden\" name=\"authType\" value=\"" + authType + "\"/>");
out.println("</from>");
out.println("<script>window.document.submitForm.submit();</script> ");
out.println(" </BODY>");
out.println("</HTML>");
out.flush();
out.close();
}
}“/login/authorization_code”接口對(duì)應(yīng)了我們?cè)贕ithub中配置的回調(diào)函數(shù),即在Github登錄驗(yàn)證成功后,就會(huì)回調(diào)該接口,我們就是就在回調(diào)方法中,模擬了用戶名密碼登錄的方式,調(diào)用了SpringSecurity登錄認(rèn)證需要的“/login/doLogin”接口。這里,我們通過queryAccessToken()方法根據(jù)回調(diào)傳遞的code獲取對(duì)應(yīng)的accessToken,然后把a(bǔ)ccessToken作為登錄使用的principal 參數(shù)值,之而立不需要傳遞密碼,因?yàn)槲覀兘?jīng)過Github授權(quán),就可以認(rèn)為完成了登錄認(rèn)證的判斷過程了。
其中GithubClientService類,提供了獲取accessToken和用戶信息的兩個(gè)方法,具體實(shí)現(xiàn)方式如下:
@Service
public class GithubClientService {
//前面在github中配置時(shí)產(chǎn)生的
private String clientId = "######";
private String clientSecret = "######";
private String state = "123";
private String redirectUri = "http://localhost:8888/qriver-admin/login/authorization_code";
@Autowired
private RestTemplate restTemplate;
@Nullable
private WebApplicationContext webApplicationContext;
//獲取accessToken
public Map<String, String> queryAccessToken(String code ){
Map<String, String> map = new HashMap<>();
map.put("client_id", clientId);
map.put("client_secret", clientSecret);
map.put("state", state);
map.put("code", code);
map.put("redirect_uri", redirectUri);
Map<String,String> resp = restTemplate.postForObject("https://github.com/login/oauth/access_token", map, Map.class);
return resp;
}
//獲取用戶信息
public Map<String, Object> queryUser(String accessToken){
HttpHeaders httpheaders = new HttpHeaders();
httpheaders.add("Authorization", "token " + accessToken);
HttpEntity<?> httpEntity = new HttpEntity<>(httpheaders);
ResponseEntity<Map> exchange = restTemplate.exchange("https://api.github.com/user", HttpMethod.GET, httpEntity, Map.class);
System.out.println("exchange.getBody() = " + exchange.getBody());
return exchange == null ? null : exchange.getBody();
}
}其實(shí),完成了上述的配置和方式后,后續(xù)的方式就和短信驗(yàn)證碼的邏輯一樣了,這里我們簡(jiǎn)要的再梳理一下。
首先,我們也需要定義一個(gè)基于Github登錄需要的Authentication實(shí)現(xiàn)類,具體實(shí)現(xiàn)和前面的SmsAuthenticationToken類似,這里不再重復(fù)貼代碼了。
然后,我們?cè)俣x一個(gè)AuthenticationProvider實(shí)現(xiàn)類GithubAuthenticationProvider,具體實(shí)現(xiàn)如下:
@Component
public class GithubAuthenticationProvider implements AuthenticationProvider{
@Autowired
@Qualifier("githubUserDetailsService")
private UserDetailsService userDetailsService;
@Autowired
private GithubClientService githubClientService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
GithubAuthenticationToken token = (GithubAuthenticationToken) authentication;
String accessToken = (String)token.getPrincipal();
//根據(jù)accessToken 獲取github用戶信息
Map<String, Object> userInfo = githubClientService.queryUser(accessToken);
//然后,根據(jù)github用戶,查詢對(duì)應(yīng)系統(tǒng)用戶信息
UserDetails user = userDetailsService.loadUserByUsername((String)userInfo.get("login"));
if (Objects.isNull(user)) {
throw new InternalAuthenticationServiceException("根據(jù)accessToken:" + accessToken + ",無法獲取對(duì)應(yīng)的用戶信息!");
}
GithubAuthenticationToken authenticationResult = new GithubAuthenticationToken(user.getUsername(), user.getAuthorities(), token.getCredentials());
authenticationResult.setDetails(token.getDetails());
return authenticationResult;
}
@Override
public boolean supports(Class<?> authentication) {
return GithubAuthenticationToken.class.isAssignableFrom(authentication);
}
}在GithubAuthenticationProvider 類的authenticate()方法中,參數(shù)authentication中對(duì)應(yīng)的是Github授權(quán)后傳遞的accessToken值,我們這里需要根據(jù)accessToken值換取Github用戶信息,這里通過queryUser()方法實(shí)現(xiàn),然后根據(jù)github用戶名去獲取對(duì)應(yīng)的系統(tǒng)用戶信息。如果根據(jù)github用戶名用戶獲取的系統(tǒng)用戶為空,我們可以根據(jù)自己的需求,自動(dòng)生成一個(gè)用戶或者跳轉(zhuǎn)到注冊(cè)頁(yè)面,讓用戶注冊(cè)一個(gè)頁(yè)面,這里為了簡(jiǎn)單,我們直接拋出了一個(gè)異常。
關(guān)于自定義UserDetailsService實(shí)現(xiàn)類,主要需要實(shí)現(xiàn)根據(jù)github用戶名查詢對(duì)應(yīng)系統(tǒng)用戶的功能
當(dāng)認(rèn)證完成后要返回token可以實(shí)現(xiàn)AuthenticationSuccessHandler
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private final JwtTokenProvider jwtTokenProvider; // 假設(shè)你有一個(gè)JwtTokenProvider類來生成JWT
public CustomAuthenticationSuccessHandler(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
// 生成JWT
String token = jwtTokenProvider.generateToken(authentication);
// 將JWT添加到響應(yīng)頭中
response.setHeader("Authorization", "Bearer " + token);
// 或者將JWT添加到響應(yīng)體中(取決于你的API設(shè)計(jì))
// response.getWriter().write(token);
response.setStatus(HttpServletResponse.SC_OK);
}
}并在securityconfig中設(shè)置
到此這篇關(guān)于SpringSecurity集成第三方登錄的文章就介紹到這了,更多相關(guān)SpringSecurity登錄內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot+SpringSecurity處理Ajax登錄請(qǐng)求問題(推薦)
- 解析SpringSecurity自定義登錄驗(yàn)證成功與失敗的結(jié)果處理問題
- SpringSecurity動(dòng)態(tài)加載用戶角色權(quán)限實(shí)現(xiàn)登錄及鑒權(quán)功能
- 解決SpringSecurity 一直登錄失敗的問題
- SpringSecurity多表多端賬戶登錄的實(shí)現(xiàn)
- SpringBoot如何整合Springsecurity實(shí)現(xiàn)數(shù)據(jù)庫(kù)登錄及權(quán)限控制
- SpringSecurity6.x多種登錄方式配置小結(jié)
- SpringSecurity表單配置之登錄成功及頁(yè)面跳轉(zhuǎn)原理解析
- Spring?Security重寫AuthenticationManager實(shí)現(xiàn)賬號(hào)密碼登錄或者手機(jī)號(hào)碼登錄
相關(guān)文章
Spring中的SpringApplicationRunListener詳細(xì)解析
這篇文章主要介紹了Spring中的SpringApplicationRunListener詳細(xì)解析,SpringApplicationRunListener是一個(gè)監(jiān)聽SpringApplication中run方法的接口,在項(xiàng)目啟動(dòng)過程的各個(gè)階段進(jìn)行事件的發(fā)布,需要的朋友可以參考下2023-11-11
Java object wait notify notifyAll代碼解析
這篇文章主要介紹了Java object wait notify notifyAll代碼解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11
http協(xié)議進(jìn)階之Transfer-Encoding和HttpCore實(shí)現(xiàn)詳解
這篇文章主要給大家介紹了http協(xié)議之Transfer-Encoding和HttpCore實(shí)現(xiàn)的相關(guān)資料,文中介紹的非常詳細(xì),相信對(duì)大家具有一定的參考價(jià)值,需要的朋友們下面來一起看看吧。2017-04-04
mybatis批量添加,批量更新之前如何判斷是否已經(jīng)存在
這篇文章主要介紹了mybatis批量添加,批量更新之前如何判斷是否已經(jīng)存在,具有很好的參考價(jià)值,希望對(duì)的有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
Java Spring @Autowired的這些騷操作,你都知道嗎
這篇文章主要介紹了徹底搞明白Spring中的自動(dòng)裝配和Autowired注解的使用,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2021-09-09
MyBatis實(shí)現(xiàn)三級(jí)樹查詢的示例代碼
在實(shí)際項(xiàng)目開發(fā)中,樹形結(jié)構(gòu)的數(shù)據(jù)查詢是一個(gè)非常常見的需求,比如組織架構(gòu)、菜單管理、地區(qū)選擇等場(chǎng)景都需要處理樹形數(shù)據(jù),本文將詳細(xì)講解如何使用MyBatis實(shí)現(xiàn)三級(jí)樹形數(shù)據(jù)的查詢,需要的朋友可以參考下2024-12-12
Java getParameter()獲取數(shù)據(jù)為空的問題
這篇文章主要介紹了Java getParameter()獲取數(shù)據(jù)為空的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03

