这一篇博客跟着我上一篇博客的思路和流程继续,如果想看上一篇博客的童鞋,可以点击微信支付----统一下单接口对接进行阅读,本篇博客将介绍申请退款和退款通知两个接口的开发。

一,申请退款接口

 注意点:

  • ​​​此处需要使用证书,Java版本的话只需要导入apiclient_cert.p12格式的证书即可,其他版本请参考微信官方文档
  • 必须要当天先做交易才能退款,我的理解是每天微信都会对资金进行清算,然后将余额进行清零(此处官方文档未提及)
  • 退款回调地址不可与下单回调地址一样,因为通知的内容和解密方式是不一样的。

退款请求参数封装:

//商户订单号map.put("out_trade_no",weixinRefundDto.getOutTradeNo());//退款订单号map.put("out_refund_no",weixinRefundDto.getOutRefundNo());//订单金额 ----传入元为单位,请求第三方微信以分为单位map.put("total_fee", multiplyAmount(weixinRefundDto.getTotalFee())+"");//退款金额 ----传入元为单位,请求第三方微信以分为单位map.put("refund_fee",multiplyAmount(weixinRefundDto.getRefundFee())+"");//退款通知地址map.put("notify_url",refundNotifyUrl);WXPayConfig wxPayConfig = WxPayConfigImpl.getInstance();WXPay wxPay = new WXPay(wxPayConfig);Map<String, String> map1 = wxPay.refund(map);//获取返回状态String returnCode = map1.get("return_code");//获取业务状态String resultCode = map1.get("result_code");

实际对相关参数做了配置处理,以及对金额做了转换,这里需要注意的是,不论是统一下单还是申请退款,都需要传递int类型,也就是说不能传递带小数点的值,此处附上我解决bigdecimal格式的金额代码

/*** 统一除法运算(100)* @param amount* @return*/public BigDecimal divideAmount(String amount){BigDecimal amt = new BigDecimal(amount).divide(new BigDecimal(100));return amt;}/*** 统一乘法运算(100)* @param amount* @return*/public BigDecimal multiplyAmount(String amount){BigDecimal amt = new BigDecimal(amount).multiply(new BigDecimal(100)).setScale(0);return amt;}

调用微信退款接口如果出现

代表你当前需要新做一笔交易,昨天测试没有退出来的钱你就不要想啦!!

如果成功,我们最好记录一下微信订单号和退款订单号,根据测试发现,一笔订单可以进行多次退款,微信订单号不变的情况下退款订单号是会变化的,所以最好记录下,毕竟是和钱有关系的东西,谨慎点好。

//微信订单号String transaction_id = map1.get("transaction_id");//微信退款订单号String refund_id = map1.get("refund_id");//商户订单号String out_trade_no = map1.get("out_trade_no");//商户退款订单号String out_refund_no = map1.get("out_refund_no");//退款金额String refund_fee = map1.get("refund_fee");

此处是获取的相关订单号和退款订单金额,一切成功就可以在微信上面看到退款金额了噢。

二,退款回调

   其实退款是否成功在调用申请退款接口的时候微信就已经可以明确答复你了,但是有一些重要信息只能在退款回调中给到你,我们可以看一下微信官方文档

其中,这几个参数只能在退款通知中给到你,如果你自己要做更为细致的操作,那就需要拿到这些信息。

但是,怎么拿到这些信息呢???

我们需要对这个risk_info字段进行解密,才能拿到里面的值,解密方式如下

先解码,再md5,最后aes-256解密,其中最为重要的是aes-256解密,因为aes进行加解密时,当密钥大于128时,代码会抛出java.security.InvalidKeyException: Illegal key size or default parametersIllegal key size or default parameters是指密钥长度是受限制的,java运行时环境读到的是受限的policy文件。文件位于${java_home}/jre/lib/security(自身jdk/jre文件路径)下 这种限制是因为美国对软件出口的控制。

然而微信推荐的就是aes-256,所以我们需要修改下我们自己的jdk文件,大家可以去下载替换jar包,然后将jar包粘贴替换至自己的相应路径下,以我本地路径为例:

1.修改jdk文件路径下面的jar包
JDK:C:\Program Files\Java\jdk1.8.0_31\jre\lib\security
JRE:C:\Program Files\Java\jre1.8.0_31\lib\security

2.在C:\Program Files\Java\jre1.8.0_31\lib\security\java.security新增一条代码
security.provider.11=org.bouncycastle.jce.provider.BouncyCastleProvider

3.导入jar包---bcprov-jdk15-145.jar(可忽略)

然后再次通过微信的解密步骤进行请求即可,最终会得到一个xml文件,最后转换成map格式即可。

AESUtil

package com.youfuli.vendor.utils.weixin;import org.bouncycastle.jce.provider.BouncyCastleProvider;import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.security.Security;/*** Created by 李啸天 on 2018/11/29.*/
public class AESUtil {/*** 密钥算法*/private static final String ALGORITHM = "AES";/*** 加解密算法/工作模式/填充方式*/private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS7Padding";/*** 生成key*///微信支付API密钥设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置// private static String paySign = "微信支付API密钥";//keyprivate static  String paySign = WeixinConfig.getInstance().getProperty("weixin.key");//对商户key做md5,得到32位小写key*private static SecretKeySpec key = new SecretKeySpec(MD5Util.MD5Encode(paySign, "UTF-8").toLowerCase().getBytes(), ALGORITHM);static {}/*** AES加密** @param data* @return* @throws Exception*/public static String encryptData(String data) throws Exception {Security.addProvider(new BouncyCastleProvider());// 创建密码器Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, "BC");// 初始化cipher.init(Cipher.ENCRYPT_MODE, key);return Base64Util.encode(cipher.doFinal(data.getBytes()));}/*** AES解密**(1)对加密串A做base64解码,得到加密串B*(2)用key*对加密串B做AES-256-ECB解密(PKCS7Padding)* @param base64Data* @return* @throws Exception*/public static String decryptData(String base64Data) throws Exception {Security.addProvider(new BouncyCastleProvider());Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, "BC");cipher.init(Cipher.DECRYPT_MODE, key);return new String(cipher.doFinal(Base64Util.decode(base64Data)));}/* public static void main(String[] args) throws Exception {String req_info = "nQ0oEMVFOVXsbxVsw3xb1mYZDo1YSNUQYA9hJKrKQkR1TOpADrPCQCWrTCgD+UJwFKWI9edv7UVRqLBh/1jXncv4Fa2iOxgoDKcdIANCzisIPFC5Gxo5v9sURmxti+s5aZK6cj/VC2/CPpDJrb0c1GhXx4Qek+9emTRi/rtgrjc/ui4INPMHING3Tap1Xsc2n/2O9skCYACBYP34YO0qEb0x27mz0iDJG4nPFC1Qv3lyzX5Or+5KylpP8yT5FfsMY7tZnCMbRo6mAkXNVEfIuIXQPrv+oH3LE2OzVGbPxF9/6YXKs7QxUGMyDheSCica+OibMPkvneTNjCI+3Q97oJBB8Dwv8xP346XrFXMmy+942xBISELNQr1pS+eGsvuj+4YpVEcMQjOI9M2bBjJK6YfU+TcJ0L5Yh75pk3E7R+K3TfEHmqEV50zAC2L5L5j6BGjj5GdXgpuIvxHvAtnpb7uqF3+/PmtaGvCIT9p8zebsCwT0QhtpXR/ITO7FPLUvk9EGjB7uVmy/b2lygiIJ97d0UyFnun0kCY42KVp6EpT+1F2p3j81z8FagVXkP5XFn1KAWV77fahAK8DJnzG47ozlRwMQ676GG8dnqIoP21ivu304IoBEVnNl8FPASeR5qJjTeaT/tajT+Exjg4Lo2BGlQlbSXvy9kMrNZ+Qnr+pUZ1m55qJPeUOpqbfKCst6bHBzPywpCZCTBSzJonz6fd75hPAdmmKchBKGQ/1UmC1nEwJ1JwgiMfyvTBbSiFNgLfkTgAeURm87/9lwvccSwPzxiFbHGeA/uo3SxKPUjBt0ExOpADVNr2gZ/uCO/4eWBVxtHKJJqNOm8JdvF7fhJd/SufNArysltwQkeh713MQb+phJQbSlcmI/02e9SWT6yL/e82qhrZyZ1ccoWJ+yprqvZJ4RapJq44LI1UjoHI0YARF+oY9usW9WRrh0x4RBBhv4gbGxSiaihgFeg6QAxuIXEqeB0dxyrdCrAdf7j88hinqyIjjYndCM8a3NL40suEqfHTHnqkJ1VvRxyMa/NK50rAY9hJpD9K+PdaplL";String aa = AESUtil.decryptData(req_info);System.out.println(aa);}*/
}

Base64Util

package com.youfuli.vendor.utils.weixin;import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;/*** Created by 李啸天 on 2018/11/29.*/
public class Base64Util {private static final char S_BASE64CHAR[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S','T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't','u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};private static final byte S_DECODETABLE[];static {S_DECODETABLE = new byte[128];for (int i = 0; i < S_DECODETABLE.length; i++)S_DECODETABLE[i] = 127;for (int i = 0; i < S_BASE64CHAR.length; i++)S_DECODETABLE[S_BASE64CHAR[i]] = (byte) i;}/*** @param ibuf* @param obuf* @param wp* @return*/private static int decode0(char ibuf[], byte obuf[], int wp) {int outlen = 3;if (ibuf[3] == '=')outlen = 2;if (ibuf[2] == '=')outlen = 1;int b0 = S_DECODETABLE[ibuf[0]];int b1 = S_DECODETABLE[ibuf[1]];int b2 = S_DECODETABLE[ibuf[2]];int b3 = S_DECODETABLE[ibuf[3]];switch (outlen) {case 1: // '\001'obuf[wp] = (byte) (b0 << 2 & 252 | b1 >> 4 & 3);return 1;case 2: // '\002'obuf[wp++] = (byte) (b0 << 2 & 252 | b1 >> 4 & 3);obuf[wp] = (byte) (b1 << 4 & 240 | b2 >> 2 & 15);return 2;case 3: // '\003'obuf[wp++] = (byte) (b0 << 2 & 252 | b1 >> 4 & 3);obuf[wp++] = (byte) (b1 << 4 & 240 | b2 >> 2 & 15);obuf[wp] = (byte) (b2 << 6 & 192 | b3 & 63);return 3;}throw new RuntimeException("Internal error");}/*** @param data* @param off* @param len* @return*/public static byte[] decode(char data[], int off, int len) {char ibuf[] = new char[4];int ibufcount = 0;byte obuf[] = new byte[(len / 4) * 3 + 3];int obufcount = 0;for (int i = off; i < off + len; i++) {char ch = data[i];if (ch != '=' && (ch >= S_DECODETABLE.length || S_DECODETABLE[ch] == 127))continue;ibuf[ibufcount++] = ch;if (ibufcount == ibuf.length) {ibufcount = 0;obufcount += decode0(ibuf, obuf, obufcount);}}if (obufcount == obuf.length) {return obuf;} else {byte ret[] = new byte[obufcount];System.arraycopy(obuf, 0, ret, 0, obufcount);return ret;}}/*** @param data* @return*/public static byte[] decode(String data) {char ibuf[] = new char[4];int ibufcount = 0;byte obuf[] = new byte[(data.length() / 4) * 3 + 3];int obufcount = 0;for (int i = 0; i < data.length(); i++) {char ch = data.charAt(i);if (ch != '=' && (ch >= S_DECODETABLE.length || S_DECODETABLE[ch] == 127))continue;ibuf[ibufcount++] = ch;if (ibufcount == ibuf.length) {ibufcount = 0;obufcount += decode0(ibuf, obuf, obufcount);}}if (obufcount == obuf.length) {return obuf;} else {byte ret[] = new byte[obufcount];System.arraycopy(obuf, 0, ret, 0, obufcount);return ret;}}/*** @param data* @param off* @param len* @param ostream* @throws IOException*/public static void decode(char data[], int off, int len, OutputStream ostream) throws IOException {char ibuf[] = new char[4];int ibufcount = 0;byte obuf[] = new byte[3];for (int i = off; i < off + len; i++) {char ch = data[i];if (ch != '=' && (ch >= S_DECODETABLE.length || S_DECODETABLE[ch] == 127))continue;ibuf[ibufcount++] = ch;if (ibufcount == ibuf.length) {ibufcount = 0;int obufcount = decode0(ibuf, obuf, 0);ostream.write(obuf, 0, obufcount);}}}/*** @param data* @param ostream* @throws IOException*/public static void decode(String data, OutputStream ostream) throws IOException {char ibuf[] = new char[4];int ibufcount = 0;byte obuf[] = new byte[3];for (int i = 0; i < data.length(); i++) {char ch = data.charAt(i);if (ch != '=' && (ch >= S_DECODETABLE.length || S_DECODETABLE[ch] == 127))continue;ibuf[ibufcount++] = ch;if (ibufcount == ibuf.length) {ibufcount = 0;int obufcount = decode0(ibuf, obuf, 0);ostream.write(obuf, 0, obufcount);}}}/*** @param data* @return*/public static String encode(byte data[]) {return encode(data, 0, data.length);}/*** @param data* @param off* @param len* @return*/public static String encode(byte data[], int off, int len) {if (len <= 0)return "";char out[] = new char[(len / 3) * 4 + 4];int rindex = off;int windex = 0;int rest;for (rest = len - off; rest >= 3; rest -= 3) {int i = ((data[rindex] & 255) << 16) + ((data[rindex + 1] & 255) << 8) + (data[rindex + 2] & 255);out[windex++] = S_BASE64CHAR[i >> 18];out[windex++] = S_BASE64CHAR[i >> 12 & 63];out[windex++] = S_BASE64CHAR[i >> 6 & 63];out[windex++] = S_BASE64CHAR[i & 63];rindex += 3;}if (rest == 1) {int i = data[rindex] & 255;out[windex++] = S_BASE64CHAR[i >> 2];out[windex++] = S_BASE64CHAR[i << 4 & 63];out[windex++] = '=';out[windex++] = '=';} else if (rest == 2) {int i = ((data[rindex] & 255) << 8) + (data[rindex + 1] & 255);out[windex++] = S_BASE64CHAR[i >> 10];out[windex++] = S_BASE64CHAR[i >> 4 & 63];out[windex++] = S_BASE64CHAR[i << 2 & 63];out[windex++] = '=';}return new String(out, 0, windex);}/*** @param data* @param off* @param len* @param ostream* @throws IOException*/public static void encode(byte data[], int off, int len, OutputStream ostream) throws IOException {if (len <= 0)return;byte out[] = new byte[4];int rindex = off;int rest;for (rest = len - off; rest >= 3; rest -= 3) {int i = ((data[rindex] & 255) << 16) + ((data[rindex + 1] & 255) << 8) + (data[rindex + 2] & 255);out[0] = (byte) S_BASE64CHAR[i >> 18];out[1] = (byte) S_BASE64CHAR[i >> 12 & 63];out[2] = (byte) S_BASE64CHAR[i >> 6 & 63];out[3] = (byte) S_BASE64CHAR[i & 63];ostream.write(out, 0, 4);rindex += 3;}if (rest == 1) {int i = data[rindex] & 255;out[0] = (byte) S_BASE64CHAR[i >> 2];out[1] = (byte) S_BASE64CHAR[i << 4 & 63];out[2] = 61;out[3] = 61;ostream.write(out, 0, 4);} else if (rest == 2) {int i = ((data[rindex] & 255) << 8) + (data[rindex + 1] & 255);out[0] = (byte) S_BASE64CHAR[i >> 10];out[1] = (byte) S_BASE64CHAR[i >> 4 & 63];out[2] = (byte) S_BASE64CHAR[i << 2 & 63];out[3] = 61;ostream.write(out, 0, 4);}}/*** @param data* @param off* @param len* @param writer* @throws IOException*/public static void encode(byte data[], int off, int len, Writer writer) throws IOException {if (len <= 0)return;char out[] = new char[4];int rindex = off;int rest = len - off;int output = 0;do {if (rest < 3)break;int i = ((data[rindex] & 255) << 16) + ((data[rindex + 1] & 255) << 8) + (data[rindex + 2] & 255);out[0] = S_BASE64CHAR[i >> 18];out[1] = S_BASE64CHAR[i >> 12 & 63];out[2] = S_BASE64CHAR[i >> 6 & 63];out[3] = S_BASE64CHAR[i & 63];writer.write(out, 0, 4);rindex += 3;rest -= 3;if ((output += 4) % 76 == 0)writer.write("\n");}while (true);if (rest == 1) {int i = data[rindex] & 255;out[0] = S_BASE64CHAR[i >> 2];out[1] = S_BASE64CHAR[i << 4 & 63];out[2] = '=';out[3] = '=';writer.write(out, 0, 4);} else if (rest == 2) {int i = ((data[rindex] & 255) << 8) + (data[rindex + 1] & 255);out[0] = S_BASE64CHAR[i >> 10];out[1] = S_BASE64CHAR[i >> 4 & 63];out[2] = S_BASE64CHAR[i << 2 & 63];out[3] = '=';writer.write(out, 0, 4);}}
}

MD5Utils

/** PROJECT NAME: api-webapp* PACKAGE NAME: com.twsz.api.controller.utils* FILE    NAME: MD5Util.java* COPYRIGHT: Copyright(c) 2016 上海共进医疗科技有限公司 All Rights Reserved.*/
package com.youfuli.vendor.utils.weixin;import java.security.MessageDigest;/*** @author lixiaotian*/
public class MD5Utils {private static String byteArrayToHexString(byte b[]) {StringBuffer resultSb = new StringBuffer();for (int i = 0; i < b.length; i++)resultSb.append(byteToHexString(b[i]));return resultSb.toString();}private static String byteToHexString(byte b) {int n = b;if (n < 0)n += 256;int d1 = n / 16;int d2 = n % 16;return hexDigits[d1] + hexDigits[d2];}public static String MD5Encode(String origin, String charsetname) {String resultString = null;try {resultString = new String(origin);MessageDigest md = MessageDigest.getInstance("MD5");if (charsetname == null || "".equals(charsetname))resultString = byteArrayToHexString(md.digest(resultString.getBytes()));elseresultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));} catch (Exception exception) {}return resultString;}private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5","6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };}

最后配合即可,其实退款通知最大的难点在于,微信一句轻飘飘的aes-256加密,却没有给我们提示这样那样的坑,看来还得我们开发者自己琢磨。

String reqInfo = m.get("req_info");String infos = AESUtil.decryptData(reqInfo);Map map = XMLUtil.doXMLParse(infos);System.out.println(map.toString());

最终可以解密成功并成功打印即代表成功了。至此,微信退款回调也就已经完毕。

微信支付-----申请退款和退款通知接收相关推荐

  1. 微信退款返回58 linux,小程序微信支付申请退款返回cUrl错误,错误码:58

    2019-04-03 15:15:29 如何看待微信公开课小程序热门讨论「小程序微信支付申请退款返回cUrl错误,错误码:58」 摘要:小程序微信支付申请退款返回cUrl错误,错误码:58 展开:调用 ...

  2. 微信支付v2开发(7) 告警通知

    本文介绍微信支付中如何获得告警通知. 一.告警通知 为了及时通知商户异常,提高商户在微信平台的服务质量.微信后台会向商户推送告警通知,包括发货延迟.调用失败.通知失败等情况,通知的地址是商户在申请支付 ...

  3. 微信支付申请资料及费率

    微信支付申请资料及费率 一.申请资料 1.(个体工商户)接入申请资料 2.(企业)接入申请资料 3.(民办非企业.社会团体.基金会等其他组织)接入申请资料 4.(党团组织)接入申请资料 5.(政府及事 ...

  4. 前端--微信小程序(1) 小程序注册与微信支付申请

    1:注册微信小程序 (1)没有注册过的邮箱 (2)营业执照(没有的话,小程序会有很多限制) (3)对公账户 或者 法人个人银行账户 (4)运营者身份证.手机号 (5)运营者个人运营的微信号 (6)企业 ...

  5. 小程序微信支付申请与配置完整版操作流程

    小程序微信支付申请与配置完整版操作流程 一. 申请小程序微信支付 微信支付申请分为两种情况: 情况一,申请新的微信支付商户号: 情况二,绑定已有微信支付商户号 注意:申请微信支付的小程序账户需要进行微 ...

  6. 微信支付-“申请退款”接口遇到curl出错,错误码:58解决方案

    微信支付后 退款,接口遇到curl出错,错误码:58 这个问题基本上是证书没对应上(微信支付不需要申请证书,退款要用到证书),证书要去微信商户号后台(账户中心-API安全-申请证书)下载,已经下载过忘 ...

  7. 微信支付退款 java_Java微信支付申请退款

    一.申请退款内容 1.证书使用 申请退款是需要证书的. 证书名字:证书使用中,说明了Java使用的是第一种:pkcs12格式的apiclient_cert.p12. 下载证书:微信商户平台--> ...

  8. 微信支付申请测试号 -- 填写接口配置信息提示配置失败

    背景 对接微信支付开发时需要申请商户号和公众号,对于个人开发来说门槛相对偏高,我们可以在微信官方申请测试号.微信官方申请链接 申请方式 主要就是第二步在填写接口配置信息的时候,经常会提示配置失败,具体 ...

  9. 基本功能之-微信支付申请

    微信支付有的经营类目是要收取手续费的 申请流程 1.基本信息 1.1联系信息 1.2经营信息 1.3商户信息 1.4结算信息 2.账户验证 当第一步完成时,微信会给你的邮箱发送一个账号和密码,你可以在 ...

最新文章

  1. 华为擎云w510_鸿蒙系统之后,华为突然官宣新生态,正式对windows说不
  2. 上海计算机应用基础自考上机,上海2010年自考计算机应用基础上机大纲
  3. 讲述CCF-腾讯犀牛鸟基金项目成长故事
  4. Android笔记之调用其他软件
  5. python如何创建excel文件_Python xlrd/xlwt 创建excel文件及常用操作
  6. linux系统基础与应用,Linux操作系统:基础、原理与应用
  7. python 插入排序,选择排序
  8. 有计算机考试励志的文案,关于考试的句子励志
  9. 计算机exsl表f4代表锁定,Excel中F4技巧,相对引用、绝对引用和混合引用
  10. 我的创作纪念日--明天的我一定会感谢今天努力的自己
  11. 多个vmdk合并成一个vmdk方法
  12. 90%的android开发者都会遇到的问题,当sjk_daemon遇见ADB server didn't ACK
  13. “卖给我一支笔”:乔布斯会如何应对这个经典问题?
  14. 第五人格显示服务器错误,第五人格服务器异常玩家秒变GM,时装随便买,玩家:这感觉真好!...
  15. js 跳转到指定位置 高德地图_JS引入高德地图定位
  16. 微信淘宝抖音互通,旅游商家如何玩转私域流量?
  17. 安卓-QQ-课程设计
  18. kotlin 原生字符串输出三个引号
  19. Django-rest-framework框架之APIView与序列化详解
  20. linux系统下下载jenkins

热门文章

  1. 怎么将图片格式转换成JPG?学会这两种方法轻松转换
  2. Learning by Fixing: Solving Math Word Problems with Weak Supervision论文阅读
  3. 【C++】入门(上)
  4. specular图使用方法_【太干货】八猴中角色和材质的设置瞬间提升作品逼格。(图文教程)...
  5. 如何将多个腾讯|爱奇艺|优酷视频转换成mp4
  6. Oberon相关资源
  7. matlab fdatool陷波器,基于MATLAB的简单50Hz数字陷波器.doc
  8. win11使用wsl2安装Linux子系统
  9. VMware平台部署KVM网络无法通信
  10. C++plog库,轻量级日志框架(日志库)