Java實(shí)現(xiàn)用戶短信驗(yàn)證碼登錄功能實(shí)例代碼
此處使用阿里提供的API解決方案
同時(shí)需要注意的是,此文章在Java項(xiàng)目操作上需要有一定的編程基礎(chǔ),因?yàn)椴幌肓_里吧嗦的一大堆,對(duì)于分層理解較差和基礎(chǔ)編程能力較低的小白不建議
1.前置阿里云操作
1.登錄阿里云后,搜索“短信服務(wù)”
2.點(diǎn)擊后進(jìn)入如下界面此處點(diǎn)擊“免費(fèi)開(kāi)通”,
此處若項(xiàng)目必須要求有真實(shí)短信發(fā)送,則建議購(gòu)買(mǎi)最便宜的先進(jìn)行測(cè)試即可
3.點(diǎn)擊“快速學(xué)習(xí)和測(cè)試”,依次根據(jù)提示,申請(qǐng)“資質(zhì)”,“簽名”,“模板”
此處三個(gè)都對(duì)應(yīng)個(gè)人和企業(yè),申請(qǐng)需要時(shí)間
4.模擬測(cè)試,在“快速學(xué)習(xí)和測(cè)試”界面的下方,有測(cè)試的模板可以使用,測(cè)試需要綁定測(cè)試手機(jī)號(hào)、申請(qǐng)自定義測(cè)試模板和自定義測(cè)試簽名
5.調(diào)用API發(fā)送短信
在此需要注意的是,VS Code和IEDA需要下載對(duì)應(yīng)的插件才能保證在后續(xù)自己的項(xiàng)目中能正常調(diào)用到短信發(fā)送的API接口
點(diǎn)擊SDK實(shí)例后能看到完整的調(diào)用代碼,此時(shí)建議使用V2.0,代碼包含java(異步)和java
,采用哪種方式都無(wú)所謂,只需將代碼全部復(fù)制即可
2.java項(xiàng)目操作
0.注意事項(xiàng)
在此之前,你需要準(zhǔn)備的東西如下
1.短信簽名名稱 SignName
2.短信模板Code TemplateCode
3.SDK實(shí)例代碼
4.對(duì)應(yīng)編譯器的插件必須安裝完畢
5.對(duì)應(yīng)的ALIBABA_CLOUD_ACCESS_KEY_ID和ALIBABA_CLOUD_ACCESS_KEY_SECRET
ALIBABA_CLOUD_ACCESS_KEY_ID和ALIBABA_CLOUD_ACCESS_KEY_SECRET可以在個(gè)人中心看到
創(chuàng)建對(duì)應(yīng)的AccessKey時(shí),需要保存好對(duì)應(yīng)的ALIBABA_CLOUD_ACCESS_KEY_ID和ALIBABA_CLOUD_ACCESS_KEY_SECRET,這是調(diào)用API時(shí)傳遞到阿里的憑證,十分重要!
以上準(zhǔn)備完畢后,就可以在java項(xiàng)目中嵌入對(duì)應(yīng)的API實(shí)現(xiàn)驗(yàn)證碼的發(fā)送
1.創(chuàng)建兩個(gè)接口,一個(gè)是獲取驗(yàn)證碼,一個(gè)是攜帶驗(yàn)證碼登錄
在此方案下,采用了將驗(yàn)證碼存儲(chǔ)到Redis中,此處存入Redis后,可設(shè)置驗(yàn)證碼的過(guò)期時(shí)間,減少對(duì)底層的訪問(wèn)壓力,也能實(shí)現(xiàn)驗(yàn)證碼限時(shí)的操作,另外此出也可加入對(duì)應(yīng)的手機(jī)號(hào)在固定時(shí)間內(nèi)對(duì)于獲取驗(yàn)證碼接口的訪問(wèn)次數(shù)限制,避免惡意訪問(wèn)造成服務(wù)器壓力過(guò)大。
Controller
package com.ruoyi.controller; import java.net.SocketException; import java.net.UnknownHostException; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.ruoyi.common.constant.ReturnConstants; import com.ruoyi.common.constant.UserConstants; import com.ruoyi.domain.dto.*; import com.ruoyi.domain.entity.User; import com.ruoyi.pojo.vo.CurrentPrincipal; import com.ruoyi.security.center.RequestLimit; import com.ruoyi.service.WeChatLoginService; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.mp.api.WxMpService; import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.service.IUserService; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.core.page.TableDataInfo; @Slf4j @RestController @RequestMapping("/v1/user") public class UserController extends BaseController { @Autowired private IUserService userService; @Autowired private WxMpService wxService; @Autowired private WeChatLoginService weChatLoginService; /** * - 手機(jī)號(hào)格式錯(cuò)誤 * - 為空 * - 不符合手機(jī)號(hào) * - 手機(jī)號(hào)未注冊(cè) * - 用戶被禁用 * - 在此處直接驗(yàn)證,以減少發(fā)送短信的成本 * 驗(yàn)證碼存儲(chǔ)到redis中,在五分鐘內(nèi)可以通過(guò)驗(yàn)證碼登錄 * @param userVerifyDTO * @return * @throws Exception */ @RequestLimit @PostMapping("verify") public AjaxResult verify(@RequestBody UserVerifyDTO userVerifyDTO) throws Exception { log.debug("處理驗(yàn)證碼獲取"); log.debug("驗(yàn)證信息:{}", userVerifyDTO); return AjaxResult.success(userService.verify(userVerifyDTO)); } /** * 登錄請(qǐng)求,匹配redis中的驗(yàn)證碼和數(shù)據(jù)庫(kù)中的信息 * @param userLoginDTO * @param request * @return * @throws SocketException * @throws UnknownHostException */ @RequestLimit @PostMapping("login") public Object login(@RequestBody UserLoginDTO userLoginDTO, HttpServletRequest request) throws SocketException, UnknownHostException { log.debug("處理登錄請(qǐng)求-攜帶驗(yàn)證碼"); log.debug("登錄信息:{}", userLoginDTO); return userService.login(userLoginDTO, request); } }
2.編寫(xiě)service的實(shí)現(xiàn),
ServiceImpl
package com.ruoyi.service.impl; import java.net.SocketException; import java.net.UnknownHostException; import java.time.Duration; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import cn.hutool.core.bean.BeanUtil; import com.alibaba.fastjson2.JSON; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.ruoyi.common.constant.ReturnConstants; import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.ip.AddressUtils; import com.ruoyi.common.utils.ip.IpUtils; import com.ruoyi.common.utils.uuid.UUID; import com.ruoyi.constans.JwtConstans; import com.ruoyi.domain.bo.UserInsertBo; import com.ruoyi.domain.dto.*; import com.ruoyi.domain.entity.Addr; import com.ruoyi.domain.entity.Logininfor; import com.ruoyi.domain.entity.Loginlogs; import com.ruoyi.domain.entity.User; import com.ruoyi.domain.param.UserLoginInfoVO; import com.ruoyi.domain.vo.UserLoginResultVO; import com.ruoyi.mapper.LoginlogsMapper; import com.ruoyi.pojo.vo.CurrentPrincipal; import com.ruoyi.pojo.vo.PageData; import com.ruoyi.pojo.vo.UserCachePO; import com.ruoyi.service.IUserService; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.utils.*; import eu.bitwalker.useragentutils.UserAgent; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.annotation.Cacheable; import com.ruoyi.common.core.redis.RedisCache; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.stereotype.Service; import com.ruoyi.mapper.UserMapper; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import static com.ruoyi.common.utils.PageUtils.startPage; @Slf4j @Service public class UserServiceImpl implements IUserService, JwtConstans { @Value("${token.secret}") private String secretKey; @Value("${token.expireTime}") private Integer expireTime; @Value("${wkzr.redis.test}") private String secret; /** * 驗(yàn)證碼過(guò)期時(shí)間 */ @Value("${wkzr.redis.verificationExpirationTime}") private Integer verificationExpirationTime; @Autowired private StringRedisTemplate redisTemplate; @Autowired private LoginlogsMapper loginlogsMapper; @Autowired private UserMapper userMapper; @Autowired private RedisCache redisCache; //設(shè)置初始密碼屬性 @Value("${userPassword.initPassword}") private String initPassword; /** * - 手機(jī)號(hào)格式錯(cuò)誤 * - 為空 * - 不符合手機(jī)號(hào) * - 手機(jī)號(hào)未注冊(cè) * - 用戶被禁用 * - 在此處直接驗(yàn)證,以減少發(fā)送短信的成本 * 驗(yàn)證碼存儲(chǔ)到redis中,在五分鐘內(nèi)可以通過(guò)驗(yàn)證碼登錄 * @param userVerifyDTO * @return * @throws Exception */ @Override public String verify(UserVerifyDTO userVerifyDTO) throws Exception { String phoneNumber = userVerifyDTO.getPhoneNumber(); // 格式錯(cuò)誤 if (phoneNumber.length() != 11) { throw new AccessDeniedException(ReturnConstants.PHONENUMBER_FORMAT_ERROR); } // 不能為空 if (StringUtils.isEmpty(phoneNumber)) { throw new AccessDeniedException(ReturnConstants.PHONENUMBER_NOT_EMPTY); } User user = userMapper.selectUserByPhone(phoneNumber); // 用戶不存在 if (user == null) { throw new AccessDeniedException(ReturnConstants.ACCOUNT_NOT_EXIST); } // 未啟用 if (user.getEnabled() != null && user.getEnabled() == 0) { throw new AccessDeniedException(ReturnConstants.USER_IS_UNENABLED); } // 生成隨機(jī)驗(yàn)證碼 // String verificationCode = SendCodeUtils.generateVerificationCode(); String verificationCode = "000000"; System.out.println("驗(yàn)證碼:" + verificationCode); // 發(fā)送驗(yàn)證碼 log.info("發(fā)送驗(yàn)證碼!"); SendCodeUtils.verify(phoneNumber, verificationCode); // 存儲(chǔ)驗(yàn)證碼到Redis,設(shè)置有效期為5分鐘 String rediskey = secret + phoneNumber; redisTemplate.opsForValue().set(rediskey, verificationCode, verificationExpirationTime, TimeUnit.MINUTES); // 單位為分鐘 return ReturnConstants.CAPTCHA_SEND_SUCCESS; } /** * 從redis中獲取驗(yàn)證碼 * 匹配 * - 未通過(guò) * - 不存在或過(guò)期 * - 存在且通過(guò) * @param userLoginDTO * @param request * @return * @throws SocketException * @throws UnknownHostException */ @Override public Object login(UserLoginDTO userLoginDTO, HttpServletRequest request) throws SocketException, UnknownHostException { log.info("request:{}", request); String phoneNumber = userLoginDTO.getPhoneNumber(); String remoteAddr = IpUtils.getIpAddr();// ip地址 String macaddr = GetMacAddr.getLocalMac(remoteAddr);//mac地址 //獲取瀏覽器 UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); String browser = userAgent.getBrowser().getName(); //獲取操作系統(tǒng) String os = userAgent.getOperatingSystem().getName(); //獲取操作地點(diǎn) String location = AddressUtils.getRealAddressByIP(remoteAddr); log.info("remoteAddr:{}", remoteAddr); log.info("userAgent:{}", userAgent); // 從Redis中獲取存儲(chǔ)的驗(yàn)證碼 String redisKey = secret + phoneNumber; String storedCode = redisTemplate.opsForValue().get(redisKey); if (storedCode == null) { return AjaxResult.forbidden(ReturnConstants.CAPTCHA_NOT_EXIT); } if (!userLoginDTO.getVerificationCode().equals(storedCode)) { return AjaxResult.forbidden(ReturnConstants.CAPTCHA_IS_ERROR); } User user = userMapper.selectUserByPhone(phoneNumber); log.info("user信息:{}", user); if (user == null) { return AjaxResult.forbidden(ReturnConstants.USER_NOT_EXIST); } if (user.getEnabled() == 0){ return AjaxResult.forbidden(ReturnConstants.USER_IS_UNENABLED); } // 獲取用戶信息 Integer userId = user.getUserId(); String userAccount = user.getUserAccount(); String userName = user.getUsername(); Logininfor logininfor = Logininfor.builder() .userAccount(userAccount) .userName(userName) .ipaddr(remoteAddr) .macAddr(macaddr) .browser(browser) .os(os) .loginLocation(location) .loginTime(new Date()) .phoneNumber(user.getPhoneNumber()) .enable(1) .wxBind(user.getWxBind()) .status(1) .build(); loginlogsMapper.insertTrinvLoginlogs(logininfor); // 生成token // JWT Map<String, Object> claims = new HashMap<>(); String uuid = UUID.randomUUID().toString(); claims.put(CLAIM_USER_ID, userId); claims.put(CLAIM_UUID, uuid); claims.put(CLAIM_PHONE_NUMBER, phoneNumber); claims.put(CLAIM_USER_ACCOUNT, userAccount); claims.put(CLAIM_USER_NAME, userName); claims.put(CLAIM_USER_AGENT, userAgent); // mac claims.put(CLAIM_REMOTE_ADDR, remoteAddr); // ip claims.put(CLAIM_OS, os); // os操作系統(tǒng) claims.put(CLAIM_MAC, macaddr); // mac地址 claims.put(CLAIM_BROWSER, browser); // 瀏覽器名稱 String jwt = JwtUtils.createJWT(claims, secretKey); log.info("生成用戶的JWT數(shù)據(jù):{}", jwt); UserLoginInfoVO userLoginInfoVO = userMapper.getLoginInfoByUsername(userName); log.info("userLoginInfoVO:{}", userLoginInfoVO); List<GrantedAuthority> authorities = new ArrayList<>(); // 獲取角色關(guān)鍵字 用于后續(xù)權(quán)限判斷 List<String> rolekeys = userLoginInfoVO.getRolekeys(); for (String rolekey : rolekeys) { authorities.add(new SimpleGrantedAuthority(rolekey)); } String authoritiesJsonString = JSON.toJSONString(authorities); UserCachePO userCachePO = new UserCachePO(); userCachePO.setEnable(userLoginInfoVO.getEnable()); userCachePO.setAuthoritiesJsonString(authoritiesJsonString); userCachePO.setToken(jwt); // 轉(zhuǎn)換hash數(shù)據(jù)類(lèi)型,存入redis String jwtRedisKey = "JWT_Token:" + uuid;// 鍵 HashOperations<String, Object, Object> opsForHash = redisTemplate.opsForHash(); Map<String, Object> userLoginInfoMap = BeanUtil.beanToMap(userCachePO); opsForHash.putAll(jwtRedisKey, userLoginInfoMap); redisTemplate.expire(jwtRedisKey, 86400, TimeUnit.MINUTES);// 過(guò)期時(shí)間 log.info("向緩存中存入用戶狀態(tài)數(shù)據(jù):{}", userCachePO); // 返回登錄結(jié)果VO UserLoginResultVO userLoginResultVO = new UserLoginResultVO() .setUserId(userId) .setUsername(userName) .setToken(jwt) .setAuthorities(rolekeys); return AjaxResult.success(userLoginResultVO); } }
3.發(fā)送短信的API
此處代碼有兩個(gè)工具類(lèi)
// 生成隨機(jī)驗(yàn)證碼 String verificationCode = SendCodeUtils.generateVerificationCode(); System.out.println("驗(yàn)證碼:" + verificationCode); // 發(fā)送驗(yàn)證碼 log.info("發(fā)送驗(yàn)證碼!"); SendCodeUtils.verify(phoneNumber, verificationCode);
package com.ruoyi.utils; import com.aliyun.tea.TeaException; import java.util.Random; public class SendCodeUtils { public static String generateVerificationCode() { // 設(shè)置驗(yàn)證碼長(zhǎng)度為6 int length = 6; // 驗(yàn)證碼字符集 String digits = "0123456789"; Random random = new Random(); StringBuilder sb = new StringBuilder(); // 生成六位數(shù)驗(yàn)證碼 for (int i = 0; i < length; i++) { int index = random.nextInt(digits.length()); sb.append(digits.charAt(index)); } return sb.toString(); } /** * <b>description</b> : * <p>使用AK&SK初始化賬號(hào)Client</p> * @return Client * * @throws Exception */ public static com.aliyun.dysmsapi20170525.Client createClient() throws Exception { // 工程代碼泄露可能會(huì)導(dǎo)致 AccessKey 泄露,并威脅賬號(hào)下所有資源的安全性。以下代碼示例僅供參考。 // 建議使用更安全的 STS 方式,更多鑒權(quán)訪問(wèn)方式請(qǐng)參見(jiàn):https://help.aliyun.com/document_detail/378657.html。 com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config() // 必填,請(qǐng)確保代碼運(yùn)行環(huán)境設(shè)置了環(huán)境變量 ALIBABA_CLOUD_ACCESS_KEY_ID。 .setAccessKeyId("ALIBABA_CLOUD_ACCESS_KEY_ID") // 必填,請(qǐng)確保代碼運(yùn)行環(huán)境設(shè)置了環(huán)境變量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。 .setAccessKeySecret("ALIBABA_CLOUD_ACCESS_KEY_SECRET"); // Endpoint 請(qǐng)參考 https://api.aliyun.com/product/Dysmsapi config.endpoint = "dysmsapi.aliyuncs.com"; return new com.aliyun.dysmsapi20170525.Client(config); } public static String verify(String phoneNumber, String verificationCode) throws Exception { // java.util.List<String> args = java.util.Arrays.asList(args_); com.aliyun.dysmsapi20170525.Client client = SendCodeUtils.createClient(); com.aliyun.dysmsapi20170525.models.SendSmsRequest sendSmsRequest = new com.aliyun.dysmsapi20170525.models.SendSmsRequest() .setPhoneNumbers(phoneNumber) .setSignName("簽名名稱") .setTemplateCode("模板Code") .setTemplateParam("{\"code\":\"" + verificationCode + "\"}"); try { // 復(fù)制代碼運(yùn)行請(qǐng)自行打印 API 的返回值 client.sendSmsWithOptions(sendSmsRequest, new com.aliyun.teautil.models.RuntimeOptions()); return verificationCode; } catch (TeaException error) { // 此處僅做打印展示,請(qǐng)謹(jǐn)慎對(duì)待異常處理,在工程項(xiàng)目中切勿直接忽略異常。 // 錯(cuò)誤 message System.out.println(error.getMessage()); // 診斷地址 System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); return null; } catch (Exception _error) { TeaException error = new TeaException(_error.getMessage(), _error); // 此處僅做打印展示,請(qǐng)謹(jǐn)慎對(duì)待異常處理,在工程項(xiàng)目中切勿直接忽略異常。 // 錯(cuò)誤 message System.out.println(error.getMessage()); // 診斷地址 System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); return null; } } }
之前需要的幾個(gè)關(guān)鍵信息可以在此發(fā)揮用處
此處若是公司內(nèi)部代碼可將ALIBABA_CLOUD_ACCESS_KEY_ID和ALIBABA_CLOUD_ACCESS_KEY_SECRET的值直接填入對(duì)應(yīng)位置
但若是代碼可能會(huì)泄露,則還是建議在對(duì)應(yīng)的環(huán)境中部署環(huán)境變量,代碼運(yùn)行時(shí)獲取環(huán)境變量自動(dòng)填入,涉及的Windows和Linux環(huán)境下的環(huán)境變量設(shè)置在后續(xù)文章中可找到,此處不多贅述。
// 必填,請(qǐng)確保代碼運(yùn)行環(huán)境設(shè)置了環(huán)境變量 ALIBABA_CLOUD_ACCESS_KEY_ID。 .setAccessKeyId(System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")) // 必填,請(qǐng)確保代碼運(yùn)行環(huán)境設(shè)置了環(huán)境變量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。 .setAccessKeySecret(System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET"));
4.以上操作和數(shù)據(jù)庫(kù)都完成后,即可實(shí)現(xiàn)短信驗(yàn)證碼的發(fā)送,登錄時(shí)要求用戶攜帶驗(yàn)證碼,并與Redis中存儲(chǔ)的驗(yàn)證碼匹配即可通過(guò)校驗(yàn)。
到此這篇關(guān)于Java實(shí)現(xiàn)用戶短信驗(yàn)證碼登錄功能的文章就介紹到這了,更多相關(guān)Java用戶短信驗(yàn)證碼登錄內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- JAVA實(shí)現(xiàn)利用第三方平臺(tái)發(fā)送短信驗(yàn)證碼
- Java實(shí)現(xiàn)短信驗(yàn)證碼和國(guó)際短信群發(fā)功能的示例
- Java實(shí)現(xiàn)發(fā)送短信驗(yàn)證碼功能
- java短信驗(yàn)證碼獲取次數(shù)限制實(shí)例
- Java實(shí)現(xiàn)短信發(fā)送驗(yàn)證碼功能
- java實(shí)現(xiàn)發(fā)送短信驗(yàn)證碼
- Java開(kāi)發(fā)完整短信驗(yàn)證碼功能的全過(guò)程
- Java實(shí)現(xiàn)短信驗(yàn)證碼服務(wù)的完整代碼示例
相關(guān)文章
SpringCloud 服務(wù)負(fù)載均衡和調(diào)用 Ribbon、OpenFeign的方法
這篇文章主要介紹了SpringCloud 服務(wù)負(fù)載均衡和調(diào)用 Ribbon、OpenFeign的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09MyBatis的SQL執(zhí)行結(jié)果和客戶端執(zhí)行結(jié)果不一致問(wèn)題排查
本文主要介紹了MyBatis的SQL執(zhí)行結(jié)果和客戶端執(zhí)行結(jié)果不一致問(wèn)題排查,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04java中TCP實(shí)現(xiàn)回顯服務(wù)器及客戶端
本文主要介紹了java中TCP實(shí)現(xiàn)回顯服務(wù)器及客戶端,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02idea如何關(guān)閉頁(yè)面顯示的瀏覽器圖標(biāo)
這篇文章主要介紹了idea如何關(guān)閉頁(yè)面顯示的瀏覽器圖標(biāo)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07Java多線程之JUC(java.util.concurrent)的常見(jiàn)類(lèi)(多線程編程常用類(lèi))
這篇文章主要給大家介紹了關(guān)于Java多線程之JUC(java.util.concurrent)的常見(jiàn)類(lèi)(多線程編程常用類(lèi))的相關(guān)資料,Java中的JUC(java.util.concurrent)包提供了一些并發(fā)編程中常用的類(lèi),這些類(lèi)可以幫助我們更方便地實(shí)現(xiàn)多線程編程,需要的朋友可以參考下2024-02-02使用maven方式創(chuàng)建springboot項(xiàng)目的方式
使用Spring Initializr創(chuàng)建spring boot項(xiàng)目,因?yàn)橥饩W(wǎng)問(wèn)題導(dǎo)致很難成功,所以只能使用maven方式,這里介紹下使用maven方式創(chuàng)建springboot項(xiàng)目的方法,感興趣的朋友一起看看吧2022-09-09