java微信公眾號支付
最近公司突然叫我開發微信公眾號相關的東西鳥,作為一個新人(其實也不怎麼新了但是很菜就對了),開發過程還是有點坎坷的~~ 現在開發完啦,決定寫下來供大家參考參考~ 因為比較粗心,估計還是有不少可以優化的地方,也可能有一些寫得不好的地方,歡迎指正~
一.準備工作
所用環境 jdk1.7 spring+springmvc+mybatis
1.開通商戶
設定祕鑰key(設定路徑:微信商戶平臺(pay.weixin.qq.com)–>賬戶設定–>API安全–>金鑰設定)
2.擁有微信公眾號,並開通微信支付
3.可外網訪問的連結
4.實體類和工具類
注:實體類省略get set方法
統一下單必填請求資訊實體UnifiedOrderRequest
public class UnifiedOrderRequest {
private String appid; //公眾賬號ID
private String mch_id; //商戶號
private String nonce_str; //隨機字串
private String sign; //簽名
private String body; //商品描述
private String out_trade_no; //商戶訂單號
private Integer total_fee; //總金額
private String spbill_create_ip; //終端IP
private String notify_url; //通知地址
private String trade_type; //交易型別
private String openid; //使用者標識
}
統一下單請求擴充套件資訊實體UnifiedOrderRequestExt
public class UnifiedOrderRequestExt extends UnifiedOrderRequest{
private String device_info; //裝置號
private String detail; //商品詳情
private String attach; //附加資料
private String fee_type; //貨幣型別
private String time_start; //交易起始時間
private String time_expire; //交易結束時間
private String goods_tag; //商品標記
private String product_id; //商品ID
private String limit_pay; //指定支付方式
}
統一下單返回資訊實體UnifiedOrderRespose
timeStamp、paySign、packages為jsapi支付介面中必要引數。接收統一下單返回訊息後,對訊息進行處理,生成這三個欄位。
public class UnifiedOrderRespose {
private String return_code; //返回狀態碼
private String return_msg; //返回資訊
private String appid; //公眾賬號ID
private String mch_id; //商戶號
private String device_info; //裝置號
private String nonce_str; //隨機字串
private String sign; //簽名
private String result_code; //業務結果
private String err_code; //錯誤程式碼
private String err_code_des; //錯誤程式碼描述
private String trade_type; //交易型別
private String prepay_id; //預支付交易會話標識
private String code_url; //二維碼連結
private String timeStamp; //時間戳 [jsapi用]
private String paySign; //簽名 [jsapi]
private String packages; //資料包(預訂單id)[jsapi]
}
Util-Redis
用於存放統一下單的信以及支付結果等,以防重複多次請求微信端。存放時間為2小時,保持和微信端一致。在此我使用jedis。由於專案差異性,具體實現不再贅述。
依賴jar
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>${spring-redis.version}</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${redis.version}</version>
</dependency>
Util-簽名驗證
/**
* 生成簽名
* @param packageParams 介面引數map
* @param key 設定路徑:微信商戶平臺(pay.weixin.qq.com)-->賬戶設定-->API安全-->金鑰設定
* @return
*/
public static String createSign(SortedMap<String, Object> packageParams, String key) {
StringBuffer sb = new StringBuffer();
// 第一步按字典序排列引數
Set es = packageParams.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = String.valueOf(entry.getKey());
if(!StringUtils.isEmpty(entry.getValue())){
String v = String.valueOf(entry.getValue());
// 為空不參與簽名、引數名區分大小寫
if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
}
// 第二步拼接key
sb.append("key=" + key);
// 第三步MD5加密
String sign = MD5Util.turnToMd5(sb.toString()).toUpperCase();
return sign;
}
Util-跨域請求
1.用於請求微信端統一下單介面
/**
* 跨域請求
* @param xmlStr 請求xml
* @param url 請求地址
* @return
*/
public static Object getDataFromURL(String xmlStr, String url) {
Object resObj=null;
try {
URLConnection conn = new URL(url).openConnection();
// 加入資料
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
BufferedOutputStream buffOutStr = new BufferedOutputStream(conn.getOutputStream());
buffOutStr.write(xmlStr.getBytes());
buffOutStr.flush();
buffOutStr.close();
// 獲取輸入流
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line = null;
StringBuffer sb = new StringBuffer();
while ((line = reader.readLine()) != null) {
sb.append(line);
}
XStream xStream = new XStream(new XppDriver(new XmlFriendlyNameCoder("_-", "_")));// 解決轉換時'_' 變成 '__'的問題
// 將請求返回的內容通過xStream轉換為UnifiedOrderRespose物件
xStream.alias("xml", UnifiedOrderRespose.class);
resObj = xStream.fromXML(sb.toString());
} catch (Exception e) {
e.printStackTrace();
}
return resObj;
}
- 用於一般跨域請求
public JSONObject getDataFromURL(String strURL, Map<String, Object> param, String requestType) throws Exception {
URL url = new URL(strURL);
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
OutputStreamWriter writer = null;
if (param != null) {
if ("POST".equals(requestType)) {
conn.setRequestProperty("Content-Type", "application/json");
writer = new OutputStreamWriter(conn.getOutputStream());
if (!StringUtils.isEmpty(param.get("JSON"))) {
writer.write(param.get("JSON").toString());
} else {
writer.write(new StringBuilder(100 << 4).toString());
}
} else {
writer = new OutputStreamWriter(conn.getOutputStream());
JSONObject jsonRes = JSONObject.fromObject(param);
StringBuilder sb = new StringBuilder(jsonRes.size() << 4);// 4次方
final Set<String> keys = jsonRes.keySet();
for (final String key : keys) {
final Object value = jsonRes.get(key);
sb.append(key); // 不能包含特殊字元
sb.append('=');
sb.append(value);
sb.append('&');
}
// 將最後的 '&' 去掉
sb.deleteCharAt(sb.length() - 1);
writer.write(sb.toString());
}
} else {
writer = new OutputStreamWriter(conn.getOutputStream());
writer.write(new StringBuilder(100 << 4).toString());
}
writer.flush();
writer.close();
InputStreamReader reder = new InputStreamReader(conn.getInputStream(), "utf-8");
BufferedReader breader = new BufferedReader(reder);
String content = null;
String result = "";
while ((content = breader.readLine()) != null) {
result += content;
}
JSONObject jsonRes = JSONObject.fromObject(result);
return jsonRes;
}
Util-讀取配置檔案內容
/**
* 讀取配置檔案
*
* @param fileName
* @param key
* @return
* @throws Exception
*/
public static String getPropertyVal(String fileName, String key) throws Exception {
Common c = new Common();
String classPath = c.getPath();
Properties pps = new Properties();
pps.load(new FileInputStream(classPath + fileName));
String strValue = pps.getProperty(key);
return strValue;
}
/**
* 獲取路徑
*/
public String getPath() {
return this.getClass().getResource("/").getPath();
}
配置檔案 weChatConfig.properties
AppID=公眾號唯一標識
AppSecret=xxxxxx
#商戶號
mch_id=xxxxxxx
#支付key
signKey=xxxxxxx
#支付回撥url
payNoticeUrl=http://xxx.xxxx.xxx/wechat/payResponseNotice
grant_type_client=client_credential
grant_type_auth=authorization_code
getTokenUrl=https://api.weixin.qq.com/cgi-bin/token
getTokenUrlForGetUser=https://api.weixin.qq.com/sns/oauth2/access_token
getUserListUrl=https://api.weixin.qq.com/cgi-bin/user/get
getUserInfo=https://api.weixin.qq.com/cgi-bin/user/info
unifiedorder=https://api.mch.weixin.qq.com/pay/unifiedorder
signToken=xxxxxx
二.流程
- 第一步,獲取使用者openid。
- 第二步,點選支付按鈕後,呼叫統一下單介面,獲取訂單id,並生成 timeStamp、paySign、packages三個引數加上appid、nonceStr一併返回給頁面。
- 第三步,頁面呼叫jsapi支付介面,完成支付。
- 第四步,支付回撥處理。(微信端非同步處理)。
1.獲取使用者openid
流程簡述:嚴格按照微信授權步驟,準備好授權連結,將目標頁面作為引數放入授權連結中。微信端返回code至目標頁面,頁面呼叫controller方法,在方法中用code等引數請求微信獲取使用者openid的介面,將openid存入session備用。
1.1 連結
https://open.weixin.qq.com/connect/oauth2/authorize?appid=公眾號標識&redirect_uri=encode之後的目標頁面連結&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect
說明:snsapi_base為靜默授權,用於僅需要取得openid時。其他需要請參考介面文件。
1.2 目標頁面js接收code
var code=GetQueryString("code");
if(code != "" && code != null){
$.ajax({
url:"/wechat/dealWechatCode",
type:"post",
dataType:"json",
async:true,
data:{
code:code
},
success:function(data){},
error:function(data){}
})
}
小工具
//獲取位址列引數方法
function GetQueryString(name)
{
var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
var r = window.location.search.substr(1).match(reg);
if(r!=null){ return decodeURI(r[2])}
return null;
}
1.3 處理code生成openid並存入session
controller層:
/**
* 處理code
* @param code
*
*/
@RequestMapping(value = "/dealWechatCode", method = RequestMethod.POST)
public void dealWechatCode(String code, HttpServletRequest request, HttpServletResponse response) {
LOGGER.info("POST請求處理code");
HttpSession session=request.getSession();
try {
Object openIdobj = GetAPIResult.getToken(code).get("openid");
if(openIdobj!=null){
//存入session
session.setAttribute("openId", openIdobj.toString());
System.out.println(openIdobj.toString());
}
} catch (Exception e) {
e.printStackTrace();
}
}
其中getToken方法用於用code換取openid:
/**
* 獲取token(獲取使用者資訊)
* @param code
* @return
* @throws Exception
*/
public static Map<String, String> getToken(String code) throws Exception{
Map<String,String> resMap=new HashMap<String, String>();
Map<String,Object> paraMap=new HashMap<String, Object>();
paraMap.put("grant_type", Common.getPropertyVal("weChatConfig.properties", "grant_type_auth"));
paraMap.put("appid", Common.getPropertyVal("weChatConfig.properties", "AppID"));
paraMap.put("secret", Common.getPropertyVal("weChatConfig.properties", "AppSecret"));
paraMap.put("code", code);
JSONObject obj=util.getDataFromURL(Common.getPropertyVal("weChatConfig.properties", "getTokenUrlForGetUser"), paraMap,"GET");
if(obj.get("access_token")!=null&&obj.get("openid")!=null){
resMap.put("access_token", obj.get("access_token").toString());
resMap.put("openid", obj.get("openid").toString());
}
return resMap;
}
2.頁面點選“支付”呼叫方法submitOrder()
function submitOrder(){
var orderInfo={
"body":"xxxx-xxxx", //商品資訊
"out_trade_no":"10f0f6f24c304b5eb955a3d3b816fa7b", //系統生成訂單號
"total_fee":1 //支付金額
};
$.ajax({
type : "post",
async: false,
data:{orderInfo:JSON.stringify(orderInfo)},
url : "/wechat/test/pay",
dataType : "json",
success : function(data) {
if (data.code == 200) {
var appid = data.obj.appid;
var nonceStr = data.obj.nonce_str;
var packages = data.obj.packages;
var timeStamp=data.obj.timeStamp;
var paySign=data.obj.paySign;
//調取JSSDK支付介面
pay(appid,nonceStr,packages,timeStamp,paySign);
}
}
});
}
3.統一下單
controller層
@RequestMapping(value = "/test/pay", method = RequestMethod.POST)
@ResponseBody
public PageInfo pay(HttpServletRequest request, HttpServletResponse response,String orderInfo) {
PageInfo pageInfo=new PageInfo();
//接受支付資訊
JSONObject jsonObj=JSONObject.fromObject(HtmlUtils.htmlUnescape(orderInfo));//消除json中資料encode後可能產生的影響
UnifiedOrderRequest wechatPay=(UnifiedOrderRequest)JSONObject.toBean(jsonObj, UnifiedOrderRequest.class);
//驗證必要引數
if(StringUtils.isBlank(wechatPay.getBody())){
pageInfo.setCode(1001);
pageInfo.setMsg("引數:商品描述[body]不可為空");
return pageInfo;
}
if(StringUtils.isBlank(wechatPay.getOut_trade_no())){
pageInfo.setCode(1001);
pageInfo.setMsg("引數:訂單號[out_trade_no]不可為空");
return pageInfo;
}
if(wechatPay.getTotal_fee()==null&&wechatPay.getTotal_fee()==0){
pageInfo.setCode(1001);
pageInfo.setMsg("引數:訂單金額[total_fee]不可為0或null");
return pageInfo;
}
LOGGER.info("訂單"+wechatPay.getOut_trade_no()+"請求支付");
//獲取使用者orderId
HttpSession session = request.getSession();
Object openId=session.getAttribute("openId");
if(openId!=null){
wechatPay.setOpenid(openId.toString());
}else{
pageInfo.setCode(1001);
pageInfo.setMsg("openId未取到,請檢查訪問渠道");
return pageInfo;
}
wechatPay.setSpbill_create_ip(Common.getIpAddr(request));
pageInfo=weChatService.pay(wechatPay);
return pageInfo;
}
service層:
@Override
public PageInfo pay(UnifiedOrderRequest unifiedOrder) {
PageInfo pageInfo = new PageInfo();
try {
if (unifiedOrder.getOut_trade_no() != null) {
Object redisPrePayStr = redisGet.getRedisStringResult("weChatPay_" + unifiedOrder.getOut_trade_no());
if (!StringUtils.isEmpty(redisPrePayStr)) {
UnifiedOrderRespose redisPrePay = (UnifiedOrderRespose) JSONObject
.toBean(JSONObject.fromObject(redisPrePayStr), UnifiedOrderRespose.class);
pageInfo.setObj(redisPrePay);
pageInfo.setCode(200);
} else {
// 已失效或不存在
// 準備預訂單資料
unifiedOrder.setAppid(Common.getStaticProperty("weChatConfig.properties", "AppID"));
unifiedOrder.setMch_id(Common.getStaticProperty("weChatConfig.properties", "mch_id"));
unifiedOrder.setNonce_str(Common.generateRandomCharArray(32));
unifiedOrder.setNotify_url(Common.getStaticProperty("weChatConfig.properties", "payNoticeUrl"));// 通知結果回撥地址
unifiedOrder.setTrade_type("JSAPI");// jsapi方式用於微信內建瀏覽器內
// 支付key
String key = Common.getStaticProperty("weChatConfig.properties", "signKey");
// 獲取簽名
SortedMap<String, Object> packageParams = new TreeMap<String, Object>();
packageParams.put("appid", unifiedOrder.getAppid());
packageParams.put("body", unifiedOrder.getBody());
packageParams.put("mch_id", unifiedOrder.getMch_id());
packageParams.put("nonce_str", unifiedOrder.getNonce_str());
packageParams.put("notify_url", unifiedOrder.getNotify_url());
packageParams.put("openid", unifiedOrder.getOpenid());
packageParams.put("out_trade_no", unifiedOrder.getOut_trade_no());
packageParams.put("spbill_create_ip", unifiedOrder.getSpbill_create_ip());
packageParams.put("total_fee", unifiedOrder.getTotal_fee());
packageParams.put("trade_type", unifiedOrder.getTrade_type());
unifiedOrder.setSign(SignUtil.createSign(packageParams, key));
// 轉換為xml
XStream xStream = new XStream(new XppDriver(new XmlFriendlyNameCoder("_-", "_")));
xStream.alias("xml", UnifiedOrderRequest.class);
String xmlStr = xStream.toXML(unifiedOrder);
// 請求微信統一下單介面
UnifiedOrderRespose respose = (UnifiedOrderRespose) UrlUtil.getDataFromURL(xmlStr,
Common.getStaticProperty("weChatConfig.properties", "unifiedorder"));
// 根據微信文件return_code
// result_code都為SUCCESS的時候才會返回prepay_id和code_url
if (null != respose && "SUCCESS".equals(respose.getReturn_code())
&& "SUCCESS".equals(respose.getResult_code())) {
//jsapi所需引數
String timeStamp = String.valueOf(System.currentTimeMillis());
String packages = "prepay_id=" + respose.getPrepay_id();
respose.setTimeStamp(timeStamp);
respose.setPackages(packages);
// 獲取jsapi所需簽名
SortedMap<String, Object> paramsMap = new TreeMap<String, Object>();
paramsMap.put("appId", respose.getAppid());
paramsMap.put("timeStamp", timeStamp);
paramsMap.put("nonceStr", respose.getNonce_str());
paramsMap.put("package", packages);
paramsMap.put("signType", "MD5");
respose.setPaySign(SignUtil.createSign(paramsMap, key));
// 將處理好的資料放到obj中 返回給頁面
pageInfo.setObj(respose);
pageInfo.setCode(200);
// 存入redis 有效期為2小時(與微信官方一致) 當同一訂單重複請求時 不再請求生成新的預支付訂單碼
String resposeStr = JSONObject.fromObject(respose).toString();
redisSet.setRedisStringResult("weChatPay_" + unifiedOrder.getOut_trade_no(), resposeStr, 2,
TimeUnit.HOURS);
} else {
pageInfo.setCode(1020);
pageInfo.setMsg("微信支付平臺錯誤程式碼:" + respose.getReturn_code() + ":" + respose.getReturn_msg());
}
}
} else {
pageInfo.setCode(1020);
pageInfo.setMsg("訂單號為空");
}
} catch (Exception e) {
pageInfo.setCode(500);
pageInfo.setMsg(e.getMessage());
e.printStackTrace();
}
return pageInfo;
}
4.頁面呼叫jsapi支付介面
//微信支付
function pay(appid,nonceStr,packages,timeStamp,paySign){
//判斷是否為微信瀏覽器
//如果是
if(is_weixin()){
if (typeof WeixinJSBridge == "undefined") {
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady', onBridgeReady,
false);
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
} else {
onBridgeReady(appid,nonceStr,packages,timeStamp,paySign);
}
}else{
alert("請在微信中開啟此連結");
}
}
function onBridgeReady(appid,nonceStr,packages,timeStamp,paySign) {
WeixinJSBridge.invoke('getBrandWCPayRequest', {
"appId" : appid, //公眾號名稱,由商戶傳入
"timeStamp" : timeStamp, //時間戳,自1970年以來的秒數
"nonceStr" : nonceStr, //隨機串
"package" : packages, //預訂單id
"signType" : "MD5", //微信簽名方式
"paySign" : paySign //簽名
}, function(res) {
alert(res.err_msg);
if (res.err_msg == "get_brand_wcpay_request:ok") {
} // 使用以上方式判斷前端返回,微信團隊鄭重提示:res.err_msg將在使用者支付成功後返回ok,但並不保證它絕對可靠。
});
}
5.支付回撥
注意要驗證微信端返回的引數和簽名是否匹配,以防居心不良的人模仿回撥此介面假裝他支付過了!喵的
controller層:
/**
* 支付回撥[此介面配置在統一下單的引數值]
* @param request
* @param response
* @return
*/
@RequestMapping(value = "/payResponseNotice", method = RequestMethod.POST)
public String payResponseNotice(HttpServletRequest request, HttpServletResponse response) {
LOGGER.info("支付結果回撥");
String resXml=null;
try {
InputStream stream=request.getInputStream();
//接收支付結果
XStream xStream = new XStream(new XppDriver(new XmlFriendlyNameCoder("_-", "_")));
xStream.alias("xml", OrderRequest.class);
OrderRequest xmlStr = (OrderRequest)xStream.fromXML(stream);
//處理
resXml=weChatService.payResponseNotice(xmlStr);//service
} catch (Exception e) {
e.printStackTrace();
}
return resXml;
}
service層:
@Override
public String payResponseNotice(OrderRequest orderRequest) {
Object redisPayNoticeStr = redisGet.getRedisStringResult("weChatPayNotice_" + orderRequest.getOut_trade_no());
String res = "<xml>" + "<return_code><![CDATA[{0}]]></return_code>" + "<return_msg><![CDATA[{1}]]></return_msg>"
+ "</xml>";
if (!StringUtils.isEmpty(redisPayNoticeStr)) {
// 已經處理過
return MessageFormat.format(res, "SUCCESS", "OK");
} else {
// 判斷支付狀態
if ("SUCCESS".equals(orderRequest.getReturn_code())) {
// 通訊成功
// 為防止資料洩露造成的假通知,驗證簽名
// 支付key
String key = Common.getStaticProperty("weChatConfig.properties", "signKey");
Map<String, Object> map = Common.beanToMap(orderRequest);
SortedMap<String, Object> packageParams = new TreeMap<String, Object>(map);
packageParams.remove("sign");
String reqSign = orderRequest.getSign();
String mySign = SignUtil.createSign(packageParams, key);
if (reqSign.equals(mySign)) {
// 驗證成功
OrdersInfo orderInfo = new OrdersInfo();
orderInfo.setId(orderRequest.getOut_trade_no());
if ("SUCCESS".equals(orderRequest.getResult_code())) {
System.out.println(
"訂單[" + orderRequest.getOut_trade_no() + "]於" + orderRequest.getTime_end() + "已支付成功!");
} else {
// 支付失敗
System.out.println("訂單[" + orderRequest.getOut_trade_no() + "]於" + orderRequest.getTime_end()
+ "支付失敗!錯誤" + orderRequest.getErr_code() + ":" + orderRequest.getErr_code_des());
}
redisSet.setRedisStringResult("weChatPayNotice_" + orderRequest.getOut_trade_no(), 1, 2,
TimeUnit.HOURS);
// 返回
return MessageFormat.format(res, "SUCCESS", "OK");
} else {
return MessageFormat.format(res, "FAIL", "ops,我可能接到了假迴應!");
}
} else {
return MessageFormat.format(res, "FAIL", "接收通知失敗");
}
}
}
三、關於一些坑
1.關於jsapi報 “簽名錯誤” “total_fee”等錯誤
其實開始寫還是挺順利的,直到呼叫jsapi這一步。
這個介面引數有五個,一開始我是在js裡處理引數的。因為有個引數是 package:”prepay_id=XXXXXXXX” ,怎麼請求都不對,開始提示簽名錯誤,後來將引數encode後,簽名搞對了。可是又報類似 total_fee引數為空這種錯。方了一下午。。。 因為total_fee是統一下單介面就傳過去的引數,這一步已經執行完畢了,完全搞不懂為什麼支付的時候又報出來。於是又懷疑到了package這個詭異引數。。。最後,把這些引數在java裡處理完畢直接返回給頁面,終於ok了。我坐在工位上忍不住給自己鼓了一會兒掌。哈哈哈。
當然首先還是要檢查自己的引數到底是否正確,簽名的部分可以先用微信提供的簽名驗證工具驗證下自己生成的簽名是否正確。
2.關於非微信內建瀏覽器的支付辦法
嘗試過weixin://www.XXXXX.com 的方式喚醒微信瀏覽器,BUT,是我太天真。。確實可以在safari瀏覽器中喚醒微信,但是無法直接跳入微信瀏覽器。 所以不行啊~~
3.關於無法調起輸入密碼介面/過載動畫一閃而過的問題
前兩天支付碰見了一個問題,點選支付時,出現“微信支付”和省略號的過載動畫,動畫一閃而過,並沒有調起後續輸入密碼介面。這個問題表現在部分安卓手機上。
嘗試的解決方案:
1、更換支付祕鑰key:搜尋了下有些文章裡寫了如果更換支付授權目錄必須要更換key。正巧這個問題是我將連結更換到線上後出現的,於是真的跑去更換了key,然鵝並沒什麼卵用。。。
2、檢查統一下單返回的預訂單id:打了客服,客服表示如果出現一閃而過的問題,一般是用jsapi請求支付時,預訂單id引數有問題。於是我兢兢業業的檢查了這個,發現並沒問題。。
3、訊號問題:在辦公室測試基本每次都一閃而過,在樓外三次裡大概有一次能成功。。我也說不清,可能是有一些影響吧。。
4、快取:清了也沒用,嗯。
比較尷尬的是到現在我也不知道到底因為什麼,因為第二天這個問題就莫名好了呢。可能真的是訊號問題吧。寫出來下次遇到少走些彎路吧~~