Spring?Boot+微信小程序開發(fā)平臺保存微信登錄者的個人信息
1. 前言
微信小程序
開發(fā)平臺,提供有一類 API
,可以讓開發(fā)者獲取到微信登錄用戶的個人數(shù)據(jù)。這類 API
統(tǒng)稱為開放接口
。
Tip:微信小程序開發(fā)平臺,會把微信登錄用戶的個人信息分為明文數(shù)據(jù)和敏感數(shù)據(jù)。
明文數(shù)據(jù)也稱為公開數(shù)據(jù),開發(fā)者可以直接獲取到,如登錄者的昵稱、頭像……
敏感數(shù)據(jù)如電話號碼、唯一標識符……等數(shù)據(jù),只有高級認證開發(fā)者和經(jīng)過登錄者授權(quán)后才能解密獲取到。
這一類 API
較多,且 API
之間功能有重疊之處,相互之間的區(qū)別較微小。有的適用于低版本,有的適用于高版本。
為了避免在使用時出現(xiàn)選擇混亂,本文將通過具體應(yīng)用案例介紹幾個常用 API
的使用。
2. 開放接口
開放接口
是對一類 API
的統(tǒng)稱,開發(fā)者
可以通過調(diào)用這類接口得到微信登錄用戶的授權(quán)
或獲取登錄者的個人數(shù)據(jù)
。開放接口
又分成幾個子類 API
:
- 登錄接口: 包括
wx.pluginLogin(Object args)
、wx.login(Object object)
、wx.checkSession(Object object)
幾 個API
。 - 賬號信息: 包括
Object wx.getAccountInfoSync()
此接口用來獲取開發(fā)者的賬號信息。 - 用戶信息: 包括
wx.getUserProfile(Object object)
、wx.getUserInfo(Object object)
、UserInfo
。使用頻率非常高的接口,常用于小程序中獲取登錄者個人公開數(shù)據(jù)。 - 授權(quán)接口:
wx.authorizeForMiniProgram(Object object)
、wx.authorize(Object object)
除上述列出的子類接口,還有收貨地址、生物認證……等諸多子類 API
,有興趣者可以自行了解。
2.1 登錄接口
登錄
接口中有 3
個 API
,對于開發(fā)者來說,使用頻率較高的是 login
接口,此環(huán)節(jié)將重點介紹此接口。
非本文特別關(guān)注的接口,會簡略帶過。
wx.pluginLogin(Object args)
:此接口只能在插件中可以調(diào)用,調(diào)用此接口獲得插件用戶的標志憑證code
,插件可使用此憑證換取用于識別用戶的唯一標識 OpenpId
。
用戶不同、宿主小程序不同或插件不同的情況下,該標識均不相同,即當且僅當同一個用戶在同一個宿主小程序中使用同一個插件時,OpenpId
才會相同。
對于一般開發(fā)者,此 接口用的不是很多,具體使用細節(jié)在此處也不做過多復述。
什么是 OpenId
?
當微信用戶登錄公眾號或小程序時,微信平臺為每一個微信登錄者分配的一個唯一標識符號。
2.1.1 wx.login(Object object)
功能描述:
開發(fā)者使用此接口可以獲取到微信登錄者
的登錄憑證(code)
。
登錄憑證
具有臨時性,也就是每次調(diào)用時都會不一樣,所以code
只能使用一次。
開發(fā)者可以通過臨時code
,再向微信接口服務(wù)器索取登錄者的唯一標識符 OpenId
、微信開發(fā)平臺賬號的唯一標識 UnionID
(需要當前小程序已綁定到微信開放平臺帳號)、以及會話密鑰 session_key
。
那么,獲取到的openId
和session_key
對于開發(fā)者而言,有什么實質(zhì)性的意義?
- 根據(jù)
OpenId
的唯一性特點,可以在微信用戶第一次登錄時,把OpenID
保存在數(shù)據(jù)庫或緩存中,在后續(xù)登錄時,只需要檢查用戶的OpenId
是否存在于數(shù)據(jù)庫或緩存中,便能實現(xiàn)自動登錄功能。 session_key
也稱會話密鑰,用來解密微信登錄者的敏感數(shù)據(jù)。
后文將詳細介紹。
如何獲取OpenId
?
現(xiàn)通過一個簡單案例,實現(xiàn)微信小程序端與開發(fā)者服務(wù)器之間的數(shù)據(jù)交互。以此了解開發(fā)者服務(wù)器如何通過微信小程序傳遞過來的用戶臨時 code
換取到登錄者的更多信息。
實現(xiàn)之前,先通過一個簡易演示圖了解其過程。
簡單描述整個請求過程:
- 微信用戶打開微信小程序后,開發(fā)者在微信小程序中通過調(diào)用
wx.login
接口獲取到臨時登錄憑證code
。 - 在微信小程序中調(diào)用
wx.request
接口向開發(fā)者服務(wù)器發(fā)送http
請求,需要把登錄憑證code
一并發(fā)送過去。 - 開發(fā)者服務(wù)器使用發(fā)送過來的
code
以及開發(fā)者憑證信息向微信接口服務(wù)器
索取微信登錄者的openId
和session_key
。
簡而言之,就是 3
者(微信小程序、開發(fā)者服務(wù)器、微信接口服務(wù)器)之間的一個擊鼓傳花游戲。
開發(fā)流程:
第一步:項目結(jié)構(gòu)分析
完整的系統(tǒng)由 2
個部分組成:
微信小程序端 APP
。
如對微信小程序開發(fā)不是很了解,請先閱讀官方提供的相關(guān)文檔。
服務(wù)器端應(yīng)用程序。
本文的服務(wù)器端應(yīng)用程序基于 Spring Boot
開發(fā)平臺。
本項目結(jié)構(gòu)是標準的前后端分離模式,微信小程序是前端應(yīng)用,服務(wù)器端應(yīng)用程序為后臺應(yīng)用。
第二步:新建微信小程序(前端應(yīng)用)
打開微信開發(fā)工具,新建一個名為 guokeai
的小程序項目 ,項目會初始化一個index
頁面。在 index.js
中編寫如下代碼。
//index.js const app = getApp() const httpRequest = require("../../utils/request.js") Page({ data: { isHasUserInfo: null, userInfo: null }, //啟動時 onLoad: function () { let this_ = this /*** * 檢查微信用戶是否已經(jīng)登錄到后臺服務(wù)器 * 已經(jīng)登錄的標志,數(shù)據(jù)庫中存在 OPENID */ let code = null //調(diào)用 login 接口 wx.login({ success: (res) => { //得到登錄用戶的臨時 code code = res.code //向開發(fā)者服務(wù)器發(fā)送請求 let api = "wx/getLoginCertificate" let config = { url: api, method: "GET", data: { code: code } } let promise = httpRequest.wxRequest(config) promise.then(res => { let isHas = null // 有沒有完整的微信登錄者信息 isHas = res.data == 0 ? false : true app.globalData.isHasUserInfo = isHas this_.setData({ isHasUserInfo: isHas }) }).catch(res => { console.log("fail", res) }); } }) } })
代碼解釋:
- 一般會在微信小程序啟動時,也就是在頁面
onload
函數(shù)中調(diào)用wx.login
接口,檢查用戶是否登錄過。 http://127.0.0.1:8080/wx/getLoginCertificate
是開發(fā)者服務(wù)器
提供的對外處理微信用戶信息的接口。- 最后只是簡單地輸出開發(fā)者服務(wù)器端返回的數(shù)據(jù)。
httpRequest.wxRequest(config)
是自定義的封裝wx.request
接口的請求組件。
function wxRequest(config) { //返回的數(shù)據(jù)類型 let dataType = config.dataType == null ? "json" : config.dataType; let responseType = config.responseType == null ? "text" : config.responseType; //服務(wù)器基地址 let serverUrl = "http://127.0.0.1:8080/" //超時 let timeout = config.timeout == null ? 50000 : config.timeout; //目標地址,基地址+接口 let url = serverUrl + config.url; //數(shù)據(jù)提交方式 let method = config.method == null ? "GET" : config.method; //提交數(shù)據(jù) let data = config.data == null ? null : config.data //頭信息 let header = { // 默認值 'content-type': 'application/json', 'x-requested-with': 'XMLHttpRequest' } let sessionId = wx.getStorageSync('sessionId') if (sessionId) { header["cookie"] = sessionId } return new Promise(function (resolve, reject) { wx.request({ url: url, data: data, //返回的數(shù)據(jù)類型(json) dataType: dataType, enableCache: false, enableHttp2: false, enableQuic: false, method: method, header: header, responseType: responseType, timeout: timeout, success: (res) => { console.log("requestData", res) if (res.cookies != null && res.cookies.length != 0) wx.setStorageSync('sessionId', res.cookies[0]) resolve(res) }, fail: (res) => { console.log("requestException", res) reject(res) } }) }) }
第三步:創(chuàng)建開發(fā)者服務(wù)器程序(后臺應(yīng)用)
本文使用 spring boot
快速搭建后臺應(yīng)用程序。在項目的 pom.xml
文件中除了必要的依賴包外,還需要添加以下 的依賴包。
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.73</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.1</version> </dependency>
fastjson
是阿里云
提供的開源 JSON
解析框架。
微信小程序
和開發(fā)者服務(wù)器
構(gòu)建的項目結(jié)構(gòu),是標準的前后端分離模式。
請求與響應(yīng)時,數(shù)據(jù)交互常使用JSON
格式。這時使用 fastjson
作為json
解析器,當然,也可以選擇其它的類似解析器。
httpclient
是一個http
請求組件。mysql-connector-java
本文案例使用MySQL
數(shù)據(jù)庫,需要加載相應(yīng)的驅(qū)動包。mybatis-plus-boot-starter
,mybatis-plus
依賴包。
在后臺應(yīng)用中編寫處理器(響應(yīng))組件:
@RestController @RequestMapping("/wx") public class WxAction { @Autowired private IWxService wxService; /*** * 獲取到微信用戶的 OPENID */ @GetMapping("/getLoginCertificate") public String getLoginCertificate(@RequestParam("code") String code) throws Exception { WxUserInfo wxInfo = this.wxService.getLoginCertificate(code); //用戶不存在,或者用戶的信息不全 return wxInfo==null || wxInfo.getNickName()==null?"0":"1"; }
代碼解釋:
IWxService
是處理器依賴的業(yè)務(wù)組件,提供有 getLoginCertificate()
方法用來實現(xiàn)通過code
向微信接口服務(wù)器
換取微信登錄者的 openId
和session_key
。
編寫業(yè)務(wù)組件:
@Service public class WxService implements IWxService { @Override public WxUserInfo getLoginCertificate(String code) throws Exception { //請求地址 String requestUrl = WxUtil.getWxServerUrl(code); // 發(fā)送請求 String response = HttpClientUtils.getRequest(requestUrl); //格式化JSON數(shù)據(jù) WxUserInfo wxUserInfo = JSONObject.parseObject(response, WxUserInfo.class); //檢查數(shù)據(jù)庫中是否存在 OPENID WxUserInfo wxUserInfo_ = this.wxUserMapper.selectById(wxUserInfo.getOpenId()); if (wxUserInfo_ == null) { //數(shù)據(jù)庫中沒有用戶的 OPENID,添加到數(shù)據(jù)庫中 this.wxUserMapper.insert(wxUserInfo); } else { if (!wxUserInfo.getSessionKey().equals(wxUserInfo_.getSessionKey())) { //如果數(shù)據(jù)庫保存的session_key和最新的session_key 不相同,則更新 wxUserInfo_.setSessionKey(wxUserInfo.getSessionKey()); this.wxUserMapper.updateById(wxUserInfo_); } } return wxUserInfo_; } }
代碼解釋:
WxUtil
是自定義的一個工具組件,用來構(gòu)建請求微信接口服務(wù)器
的url
。
https://api.weixin.qq.com/sns/jscode2session
是微信接口服務(wù)器
對外提供的接口,請求此接口時,需要提供 4
個請求數(shù)據(jù)。
appid
:小程序 appId。
secret
:小程序 appSecret。
js_code
:獲取到的微信登錄者的臨時 code
。
grant_type
:授權(quán)類型,此處只需填寫 authorization_code
。
public class WxUtil { private final static String APP_ID = "微信小程序開發(fā)者申請的 appid"; private final static String APP_SECRET = "微信小程序開發(fā)者申請的 APP_SECRET"; // private final static String WX_LOGIN_SERVER_URL = "https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code"; public static String getWxServerUrl(String code) throws IOException { String url = MessageFormat.format(WX_LOGIN_SERVER_URL, new String[]{APP_ID, APP_SECRET, code}); return url; } }
HttpClientUtils
也是一個自定義組件,用來向指定的服務(wù)器發(fā)送 http
請求。
public class HttpClientUtils { /** * GET請求 */ public static String getRequest(String url) throws Exception { //HttpClient對象 CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = null; try { HttpGet httpGet = new HttpGet(url); response = httpClient.execute(httpGet); //響應(yīng)體 HttpEntity entity = response.getEntity(); if (entity != null) { //格式化響應(yīng)體 return EntityUtils.toString(entity); } } catch (ClientProtocolException e) { throw e; } catch (IOException e) { throw e; } finally { response.close(); httpClient.close(); } return null; } }
WxUserInfo
是自定義的數(shù)據(jù)封裝類。微信接口服務(wù)器
返回的數(shù)據(jù)是以JSON
格式組裝的,這里需要格式成對象數(shù)據(jù),便于在 java
中處理。本文使用 MyBatisPlus
操作數(shù)據(jù)庫,此類也對應(yīng)數(shù)據(jù)庫中的gk_wx_user
表。
@Data @AllArgsConstructor @NoArgsConstructor @TableName("gk_wx_user") public class WxUserInfo { //OPEN_id @TableId(type = IdType.ASSIGN_ID, value = "open_id") private String openId; //會話密鑰 @TableField(value = "session_key") private String sessionKey; //頭像路徑 @TableField("avatar_url") private String avatarUrl; //城市 private String city; //國家 private String country; //性別 private String gender; //語言 private String language; //昵稱 @TableField("nick_name") private String nickName; //備注名或真實名 @TableField("real_name") private String realName; //省份 private String province; //學生ID @TableField("stu_id") private Integer stuId; }
MyBatis 數(shù)據(jù)庫映射組件:
@Repository public interface WxUserMapper extends BaseMapper<WxUserInfo> { }
第四步:測試。
先啟動后臺應(yīng)用程序,再啟動微信小程序,可以在數(shù)據(jù)庫表中查看到如下信息。
微信用戶的openid
和session_key
已經(jīng)保存到后臺的數(shù)據(jù)庫表中。
2.1.2 wx.checkSession(Object object)
官方文檔中,有一段對 session_key
的生命周期的描述。
session_key
的生命周期有不確定性,可以使用wx.login
接口刷新session_key
。為了避免頻繁調(diào)用wx.login
接口,可以通過調(diào)用wx.checkSession(Object object)
接口判斷session_key
是否已經(jīng)過期。- 當開發(fā)者在實現(xiàn)自定義登錄態(tài)時,可以考慮以
session_key
有效期作為自身登錄態(tài)有效期,也可以實現(xiàn)自定義的時效性策略。
wx.checkSession
的功能,可以使用此接口判斷session_key
是否過期。
- 調(diào)用成功說明當前
session_key
未過期。 - 調(diào)用失敗說明
session_key
已過期。
2.2 用戶信息接口
wx.login
接口僅能獲取到微信登錄者的有限數(shù)據(jù),如果想要獲取到登錄者的更多個人信息,可以使用用戶信息接口中的相關(guān)API
。
wx.getUserProfile(Object object)
。獲取用戶信息,頁面產(chǎn)生點擊事件(例如button
上bindtap
的回調(diào)中)后才可調(diào)用,每次請求都會彈出授權(quán)窗口,用戶同意后返回userInfo
。wx.getUserInfo(Object object)
。和wx.getUserProfile
的功能一樣,在基礎(chǔ)庫 2.10 的后續(xù)版本中,其功能已經(jīng)被削弱。UserInfo
是用戶信息封裝類。
getUserProfile
是從 基礎(chǔ)庫2.10.4
版本開始支持的接口,該接口用來替換 wx.getUserInfo
,意味著官方不建議再使用getUserInfo
接口獲取用戶的個人信息。
下圖是官方提供的 2
個接口的功能對比圖。
為了避免頻繁彈窗,可以在第一次獲取到用戶信息后保存在數(shù)據(jù)庫中以備以后所用。為了獲取到用戶的敏感數(shù)據(jù),在后臺要通過getUserProfile
接口所獲取的數(shù)據(jù)進行解密操作。
2.2.2 wx.getUserProfile
下面通過具體代碼講解如何保存微信登錄者的個人數(shù)據(jù)。先了解一下整個數(shù)據(jù)獲取的流程,這里直接截取官方提供的一張流程圖。
獲取微信登錄者的個人信息,需要經(jīng)過 2
個步驟。
簽名效驗:
- 通過調(diào)用
wx.getUserProfile
接口獲取數(shù)據(jù)時,接口會同時返回rawData
、signature
,其中signature = sha1( rawData + session_key )
。 - 開發(fā)者將
signature
、rawData
發(fā)送到開發(fā)者服務(wù)器進行校驗。服務(wù)器利用用戶對應(yīng)的session_key
使用相同的算法計算出簽名signature2
,比對signature
與signature2
即可校驗數(shù)據(jù)的完整性。
解密加密數(shù)據(jù):
- 對稱解密使用的算法為
AES-128-CBC
,數(shù)據(jù)采用PKCS#7
填充。 - 對稱解密的目標密文為
Base64_Decode(encryptedData)
。 - 對稱解密秘鑰
aeskey = Base64_Decode(session_key)
,aeskey
是16
字節(jié)。 - 對稱解密算法初始向量 為
Base64_Decode(iv)
,其中iv
由數(shù)據(jù)接口返回。
具體編寫實現(xiàn)。
第一步:在微信小程序端編碼。
在index.wxml
頁面中添加一個按鈕,并注冊bindtap
事件。
<view> <button bindtap="getUserProfile">獲取用戶數(shù)據(jù)</button> </view>
在index.js
中添加一個名為getUserProfile
的事件回調(diào)函數(shù)。為了避免不必要的彈窗,只有當后臺沒有獲取到個人數(shù)據(jù)時,才調(diào)用wx.getUserProfile
接口。
getUserProfile: function (e) { let this_ = this if (!this.data.isHasUserInfo) { //如果服務(wù)器端沒有保存完整的微信登錄者信息 wx.getUserProfile({ desc: '需要完善您的資料!', success: (res) => { this_.setData({ //小程序中用來顯示個人信息 userInfo: res.userInfo, isHasUserInfo: true }) //再次登錄,因為 session_key 有生命中周期 wx.login({ success(res_) { //保存到服務(wù)器端 let config = { url: "wx/wxLogin", method: "GET", data: { code: res_.code, //明文數(shù)據(jù) rawData: res.rawData, //加密數(shù)據(jù) encryptedData: res.encryptedData, iv: res.iv, //數(shù)字簽名 signature: res.signature } } let promise = httpRequest.wxRequest(config) promise.then(res => { //返回 console.log("wxLogin", res) }).catch(res => { console.log("fail", res) }); } }) } }) } }
服務(wù)器端代碼:
在pom.xml
文件中添加如下依賴包,用來解密數(shù)據(jù)。
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk16</artifactId> <version>1.46</version> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.15</version> </dependency>
在處理器類WxAction
中添加wxLogin
響應(yīng)方法。
@RestController @RequestMapping("/wx") public class WxAction { @Autowired private IWxService wxService; /*** * * @param code * @param rawData * @param encryptedData * @param iv * @param signature * @return * @throws Exception */ @GetMapping("/wxLogin") public WxUserInfo wxLogin(@RequestParam("code") String code, @RequestParam("rawData") String rawData, @RequestParam("encryptedData") String encryptedData, @RequestParam("iv") String iv, @RequestParam("signature") String signature) throws Exception { WxUserInfo wxInfo = this.wxService.getWxUserInfo(code, rawData, encryptedData, iv, signature); return wxInfo; } }
業(yè)務(wù)代碼:
小程序中傳遞過來的數(shù)據(jù)是經(jīng)過base64
編碼以及加密的數(shù)據(jù),需要使用 Base64
解碼字符串,再使用解密算法解密數(shù)據(jù)。先提供一個解密方法。
public String decrypt(String session_key, String iv, String encryptData) { String decryptString = ""; //解碼經(jīng)過 base64 編碼的字符串 byte[] sessionKeyByte = Base64.getDecoder().decode(session_key); byte[] ivByte = Base64.getDecoder().decode(iv); byte[] encryptDataByte = Base64.getDecoder().decode(encryptData); try { Security.addProvider(new BouncyCastleProvider()); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); //得到密鑰 Key key = new SecretKeySpec(sessionKeyByte, "AES"); //AES 加密算法 AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance("AES"); algorithmParameters.init(new IvParameterSpec(ivByte)); cipher.init(Cipher.DECRYPT_MODE, key, algorithmParameters); byte[] bytes = cipher.doFinal(encryptDataByte); decryptString = new String(bytes); } catch (Exception e) { e.printStackTrace(); } return decryptString; }
具體獲取數(shù)據(jù)的業(yè)務(wù)實現(xiàn):
@Override public WxUserInfo getWxUserInfo(@NotNull String code, @NotNull String rawData, @NotNull String encryptedData, @NotNull String iv, @NotNull String signature) throws Exception { //會話密鑰 WxUserInfo wxUserInfo = this.getLoginCertificate(code); String signature2 = DigestUtils.sha1Hex(rawData + wxUserInfo.getSessionKey()); if (!signature.equals(signature2)) { throw new Exception("數(shù)字簽名驗證失敗"); } //數(shù)字簽名驗證成功,解密 String infos = this.decrypt(wxUserInfo.getSessionKey(), iv, encryptedData); //反序列化 JSON 數(shù)據(jù) WxUserInfo wxUserInfo_ = JSONObject.parseObject(infos, WxUserInfo.class); wxUserInfo_.setSessionKey(wxUserInfo.getSessionKey()); wxUserInfo_.setOpenId(wxUserInfo.getOpenId()); //更新數(shù)據(jù)庫 this.wxUserMapper.updateById(wxUserInfo_); return wxUserInfo_; }
測試,啟動微信小程序和后臺應(yīng)用,在小程序中觸發(fā)按鈕事件。
在彈出的對話框中,選擇允許。
查看后臺數(shù)據(jù)庫表中的數(shù)據(jù)。
能夠獲取到的微信登錄者個人信息都保存到了數(shù)據(jù)庫表中。至于怎么使用這些數(shù)據(jù),可以根據(jù)自己的業(yè)務(wù)需要定制。
3.總結(jié)
微信開發(fā)平臺,提供有諸多接口,可以幫助開發(fā)者獲取到有用的數(shù)據(jù)。本文主要介紹 wx.login
和wx.getProfile
接口,因篇幅所限,不能對其它接口做詳細介紹 ,有興趣者可以查閱官方文檔。
官方文檔只會對接口功能做些介紹 ,如要靈活運用這些接口,還需要結(jié)合實際需要演練一下,如此方能有切身體會。
到此這篇關(guān)于Spring Boot+微信小程序開發(fā)平臺保存微信登錄者的個人信息的文章就介紹到這了,更多相關(guān)springboot微信小程序內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中的synchronized和ReentrantLock的區(qū)別詳細解讀
這篇文章主要介紹了Java中的synchronized和ReentrantLock的區(qū)別詳細解讀,synchronized是Java內(nèi)建的同步機制,所以也有人稱其為 IntrinsicLocking,它提供了互斥的語義和可見性,當一個線程已經(jīng)獲取當前鎖時,其他試圖獲取的線程只能等待或者阻塞在那里,需要的朋友可以參考下2024-01-01Java+MySQL實現(xiàn)圖書管理系統(tǒng)(完整代碼)
這篇文章主要介紹了Java+MySQL實現(xiàn)圖書管理系統(tǒng)(完整代碼),本文給大家介紹的非常想詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01Java實現(xiàn)無損Word轉(zhuǎn)PDF的示例代碼
本文將利用Java中的兩個jar包:pdfbox和aspose-words實現(xiàn)無損Word轉(zhuǎn)PDF功能,文中的示例代碼講解詳細,感興趣的小伙伴可以動手嘗試一下2022-06-06idea創(chuàng)建properties文件,解決亂碼問題
這篇文章主要介紹了idea創(chuàng)建properties文件,解決亂碼問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07基于springboot攔截器HandlerInterceptor的注入問題
這篇文章主要介紹了springboot攔截器HandlerInterceptor的注入問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09File.createTempFile創(chuàng)建臨時文件的示例詳解
這篇文章主要介紹了File.createTempFile創(chuàng)建臨時文件的示例詳解,在默認臨時文件目錄中創(chuàng)建一個空文件,使用給定前綴和后綴生成其名稱。 如果感興趣來了解一下2020-07-07