今天来分享一下之前做微信小程序微信支付遇到的一些坑,博主这里是微信小程序支付功能,因此选择的微信支付方式是JSAPI支付方式(温馨提示左下角有音乐哦)。

首先我们肯定是要在小程序后台绑定一个商户号的,接下来我们看一下整个开发流程如下图(微信官方图):

由此我们就可以得出下面这个支付的大致流程:

首先,选择商品和数量等,点击下单,然后后台将这些参数生成数字签名并以xml的方式传递,并调用微信统一下订单接口生成一张微信预支付订单表(此时也可以添加上自己业务逻辑),订单有效期都在半小时内,半小时后该条下单数据就失效了,因此应该在半小时内完成支付,签名成功后将微信返回的prepay_id等数据返回给前端,再由前端调起收银台完成支付。

由上面我们大概清楚了两点:

1.生成数字签名;

2.调用微信统一下订单;

3.小程序支付;

那么我们再来看看微信支付AIP接口文档:传送门

在返回数据中return_code 和result_code都为SUCCESS的为签名成功,返回参数含义可以通过传送门了解,这里博主就不再说明了。

通过AIP接口文档我们发现要想调用微信统一下订单接口那么我们就需要传递以下必填参数:

字段名变量名必填类型示例值描述

小程序ID

appid

String(32)

wxd678efh567hg6787

微信分配的小程序ID

商户号

mch_id

String(32)

1230000109

微信支付分配的商户号

随机字符串

nonce_str

String(32)

5K8264ILTKCH16CQ2502SI8ZNMTM67VS

随机字符串,长度要求在32位以内。推荐随机数生成算法

签名

sign

String(64)

C380BEC2BFD727A4B6845133519F3AD6

通过签名算法计算得出的签名值,详见签名生成算法

商品描述

body

String(128)

腾讯充值中心-QQ会员充值

商品简单描述,该字段请按照规范传递,具体请见参数规定

商户订单号

out_trade_no

String(32)

20150806125346

商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一。详见商户订单号

标价金额

total_fee

Int

88

订单总金额,单位为分,详见支付金额

终端IP

spbill_create_ip

String(64)

123.12.12.123

支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IP

通知地址

notify_url

String(256)

http://www.weixin.qq.com/wxpay/pay.php

异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。

交易类型

trade_type

String(16)

JSAPI

小程序取值如下:JSAPI,详细说明见参数规定

以上参数请仔细查看API接口文档说明,需注意随机字符转生成方法及位数,签名生成算法(博主采用MD5),商户订单号也就是自己平台生

成的订单号,金额是以分为单位;

下面我们重点来说一说数字签名的生成如图:

以上是java后台签名完调用微信统一下订单的步骤,下面我们来看小程序端完成支付前需要获取那些数据,官方文档:传送门

通过以上我们看到它是需要这些必填参数:

参数类型必填说明

timeStamp

String

时间戳从1970年1月1日00:00:00至今的秒数,即当前的时间

nonceStr

String

随机字符串,长度为32个字符以下。

package

String

统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=*

signType

String

签名类型,默认为MD5,支持HMAC-SHA256和MD5。注意此处需与统一下单的签名类型一致

paySign

String

签名,具体签名方案参见微信公众号支付帮助文档;

其中我们需要注意的是paySign这个参数,根据文档我看可以看到,生成这个参数是需要以下这些必填参数:

字段名变量名必填类型示例值描述

小程序ID

appId

String

wxd678efh567hg6787

微信分配的小程序ID

时间戳

timeStamp

String

1490840662

时间戳从1970年1月1日00:00:00至今的秒数,即当前的时间

随机串

nonceStr

String

5K8264ILTKCH16CQ2502SI8ZNMTM67VS

随机字符串,不长于32位。推荐随机数生成算法

数据包

package

String

prepay_id=wx2017033010242291fcfe0db70013231072

统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=wx2017033010242291fcfe0db70013231072

签名方式

signType

String

MD5

签名类型,默认为MD5,支持HMAC-SHA256和MD5。注意此处需与统一下单的签名类型一致

而刚好这些参数都是调用统一下订单接口成功后返回的参数,并将这些数据以数字签名的方式合成paySign字段,如有不清楚签名方式可以通过表格传送门了解,完成完这些将数据返回给前端,并由前端调用wx.requesetPayment()方法完成支付。

根据以上的了解咱们话不多说上代码:

Controller层

@RestControllerpublic classWeixinController {public final Logger log = Logger.getLogger(this.getClass());

@Autowired

OrdersService ordersService;//微信支付的商户密钥(换成自己的)

public static final String key = "1111111111111111";//微信统一下单接口地址

public static final String pay_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";

@RequestMapping(value="/pay/placeanorder" ,method =RequestMethod.POST)private Map goPay(@RequestParam Map maps, HttpServletRequest request) throwsException {

String openid=(String) maps.get("openid");

PaymentPo paymentPo=newPaymentPo();

paymentPo.setOpenid(openid);//生成的随机字符串

String nonce_str = StringUtils.getRandomStringByLength(32);

paymentPo.setNonce_str(nonce_str);//商品名称

String body = (String) maps.get("body");

paymentPo.setBody(body);//获取本机的ip地址

String spbill_create_ip =IpUtils.getIpAddr(request);

paymentPo.setSpbill_create_ip(spbill_create_ip);//小程序金额不是元,单位是分(如:100=1元)

paymentPo.setTotal_fee((String) maps.get("totalfee"));

String total_fee= String.valueOf(new BigDecimal(paymentPo.getTotal_fee()).multiply(new BigDecimal(100)).intValue());//商户订单号(自己写逻辑生成)

paymentPo.setOut_trade_no(ordersService.getOrderNoByAtomics());//组装参数,用户生成统一下单接口的签名

Map packageParams = new HashMap();

packageParams.put("appid", paymentPo.getAppid());//小程序ID

packageParams.put("body", paymentPo.getBody());//商品描述

packageParams.put("mch_id", paymentPo.getMch_id());//商户号

packageParams.put("nonce_str", paymentPo.getNonce_str());//随机字符串

packageParams.put("notify_url", paymentPo.getNotify_url());//支付成功后的回调地址

packageParams.put("openid", paymentPo.getOpenid());

packageParams.put("out_trade_no", paymentPo.getOut_trade_no());//商户订单号

packageParams.put("spbill_create_ip", paymentPo.getSpbill_create_ip());//终端IP

packageParams.put("total_fee", total_fee);//支付金额,这边需要转成字符串类型,否则后面的签名会失败

packageParams.put("trade_type", paymentPo.getTrade_type());//支付方式

String prestr= PayUtil.createLinkString(packageParams); //把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串(第一步)

log.info("第一次签名:" +prestr);//MD5运算生成签名,这里是第一次签名,用于调用统一下单接口(第二步)

String mysign = PayUtil.sign(prestr, key, "utf-8").toUpperCase();//拼接统一下单接口使用的xml数据,要将上一步生成的签名一起拼接进去(第三步)

String xml = "" + paymentPo.getAppid() + ""

+ "

" + paymentPo.getBody() + ""

+ "" + paymentPo.getMch_id() + ""

+ "" + paymentPo.getNonce_str() + ""

+ "" + paymentPo.getNotify_url() + ""

+ "" + paymentPo.getOpenid() + ""

+ "" + paymentPo.getOut_trade_no() + ""

+ "" + paymentPo.getSpbill_create_ip() + ""

+ "" + Integer.valueOf(total_fee) + ""

+ "" + paymentPo.getTrade_type() + ""

+ "" + mysign + ""

+ "";

log.info("调试模式_统一下单接口 请求XML数据:" +xml);//调用统一下单接口,并接受返回的结果

String res = PayUtil.httpRequest(pay_url, "POST", xml);

log.info("调试模式_统一下单接口 返回XML数据:" +res);//将解析结果存储在HashMap中

Map map =PayUtil.doXMLParse(res);

String return_code= (String)map.get("return_code");//返回状态码

String result_code = (String)map.get("result_code");//结果状态码

Map result = new HashMap();//返回给小程序端需要的参数

String prepay_id = null;if ("SUCCESS".equals(return_code) &&return_code.equals(result_code)) {

prepay_id= (String) map.get("prepay_id");//返回的预付单信息

result.put("nonceStr", paymentPo.getNonce_str());

result.put("package", "prepay_id=" +prepay_id);

Long timeStamp= System.currentTimeMillis() / 1000;

result.put("timeStamp", timeStamp + "");//这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误//拼接签名需要的参数

String stringSignTemp = "appId=" + paymentPo.getAppid() + "&nonceStr=" + paymentPo.getNonce_str() + "&package=prepay_id=" + prepay_id + "&signType=MD5&timeStamp=" +timeStamp;//再次签名,这个签名用于小程序端调用wx.requesetPayment方法完成支付(生成paySign字段)

String paySign = PayUtil.sign(stringSignTemp, key, "utf-8").toUpperCase();

result.put("paySign", paySign);

result.put("signType", "MD5");

result.put("moneyorderid", paymentPo.getOut_trade_no());

}

result.put("appid", paymentPo.getAppid());

log.info("结束1" +res);returnresult;

}

}

PaymentPo类

@Setter

@Getterpublic classPaymentPo {private static final long serialVersionUID = 1712467669291115101L;private String appid="1233444";//小程序ID(改为自己的)

private String mch_id="123333";//商户号(改为自己的)

private String device_info;//设备号

private String nonce_str;//随机字符串

private String sign;//签名

private String body;//商品描述

private String detail;//商品详情

private String attach;//附加数据

private String out_trade_no;//商户订单号

private String fee_type;//货币类型

private String spbill_create_ip;//终端IP

private String time_start;//交易起始时间

private String time_expire;//交易结束时间

private String goods_tag;//商品标记

private String total_fee;//总金额

private String notify_url="http://11111111";//通知地址(改为自己的)

private String trade_type="JSAPI";//交易类型

private String limit_pay;//指定支付方式

private String openid;//用户标识

private String code;//用户标识

private String placeId;//用户标识

private Integer carSum;//用户购买了多少次

private Integer type;//用户购卡类型

private String key = "11111111";//微信支付的商户密钥(改为自己的)

private String pay_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";//微信统一下单接口地址

}

StringUtils工具类

public class StringUtils extendsorg.apache.commons.lang3.StringUtils{/*** StringUtils工具类方法

* 获取一定长度的随机字符串,范围0-9,a-z

*@paramlength:指定字符串长度

*@return一定长度的随机字符串*/

public static String getRandomStringByLength(intlength) {

String base= "abcdefghijklmnopqrstuvwxyz0123456789";

Random random= newRandom();

StringBuffer sb= newStringBuffer();for (int i = 0; i < length; i++) {int number =random.nextInt(base.length());

sb.append(base.charAt(number));

}returnsb.toString();

}

}

PayUtil工具类

public classPayUtil {/*** 签名字符串

*

*@paramtext需要签名的字符串

*@paramkey 密钥

*@paraminput_charset编码格式

*@return签名结果*/

public staticString sign(String text, String key, String input_charset) {

text= text + "&key=" +key;

System.out.println(text);returnDigestUtils.md5Hex(getContentBytes(text, input_charset));

}/*** 签名字符串

*

*@paramtext需要签名的字符串

*@paramsign 签名结果

*@paramkey密钥

*@paraminput_charset 编码格式

*@return签名结果*/

public static booleanverify(String text, String sign, String key, String input_charset) {

text= text +key;

String mysign=DigestUtils.md5Hex(getContentBytes(text, input_charset));if(mysign.equals(sign)) {return true;

}else{return false;

}

}/***@paramcontent

*@paramcharset

*@return*@throwsSignatureException

*@throwsUnsupportedEncodingException*/

public static byte[] getContentBytes(String content, String charset) {if (charset == null || "".equals(charset)) {returncontent.getBytes();

}try{returncontent.getBytes(charset);

}catch(UnsupportedEncodingException e) {throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" +charset);

}

}private static boolean isValidChar(charch) {if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))return true;if ((ch >= 0x4e00 && ch <= 0x7fff) || (ch >= 0x8000 && ch <= 0x952f))return true;//简体中文汉字编码

return false;

}/*** 除去数组中的空值和签名参数

*

*@paramsArray 签名参数组

*@return去掉空值与签名参数后的新签名参数组*/

public static Map paraFilter(MapsArray) {

Map result = new HashMap();if (sArray == null || sArray.size() <= 0) {returnresult;

}for(String key : sArray.keySet()) {

String value=sArray.get(key);if (value == null || value.equals("") || key.equalsIgnoreCase("sign")|| key.equalsIgnoreCase("sign_type")) {continue;

}

result.put(key, value);

}returnresult;

}/*** 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串

*

*@paramparams 需要排序并参与字符拼接的参数组

*@return拼接后字符串*/

public static String createLinkString(Mapparams) {

List keys = new ArrayList(params.keySet());//对key键值按字典升序排序

Collections.sort(keys);

String prestr= "";for (int i = 0; i < keys.size(); i++) {

String key=keys.get(i);

String value=params.get(key);if (i == keys.size() - 1) {//拼接时,不包括最后一个&字符

prestr = prestr + key + "=" +value;

}else{

prestr= prestr + key + "=" + value + "&";

}

}returnprestr;

}public String getSignToken(HashMapmap) {

String result= "";

Collection keyset=map.keySet();

List list = new ArrayList(keyset);//对key键值按字典升序排序

Collections.sort(list);//构造签名键值对的格式

StringBuilder sb = newStringBuilder();for (int i = 0; i < list.size(); i++) {if (list.get(i) != null || list.get(i) != "") {

String key=list.get(i);

String val=map.get(list.get(i));if (!(val == "" || val == null)) {

sb.append(key+ "=" + val + "&");

}

}

}returnresult;

}/***@paramrequestUrl请求地址

*@paramrequestMethod请求方法

*@paramoutputStr参数*/

public staticString httpRequest(String requestUrl, String requestMethod, String outputStr) {//创建SSLContext

StringBuffer buffer = null;try{

URL url= newURL(requestUrl);

HttpURLConnection conn=(HttpURLConnection) url.openConnection();

conn.setRequestMethod(requestMethod);

conn.setDoOutput(true);

conn.setDoInput(true);

conn.connect();//往服务器端写内容

if (null !=outputStr) {

OutputStream os=conn.getOutputStream();

os.write(outputStr.getBytes("utf-8"));

os.close();

}//读取服务器端返回的内容

InputStream is =conn.getInputStream();

InputStreamReader isr= new InputStreamReader(is, "utf-8");

BufferedReader br= newBufferedReader(isr);

buffer= newStringBuffer();

String line= null;while ((line = br.readLine()) != null) {

buffer.append(line);

}

br.close();

}catch(Exception e) {

e.printStackTrace();

}returnbuffer.toString();

}public staticString urlEncodeUTF8(String source) {

String result=source;try{

result= java.net.URLEncoder.encode(source, "UTF-8");

}catch(UnsupportedEncodingException e) {//TODO Auto-generated catch block

e.printStackTrace();

}returnresult;

}/*** 向指定 URL 发送POST方法的请求

*

*@paramurl 发送请求的 URL

*@paramparam 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。

*@return所代表远程资源的响应结果*/

public staticString sendPost(String param,String pay_url) {

PrintWriter out= null;

BufferedReader in= null;

String result= "";try{

URL realUrl= newURL(pay_url);//打开和URL之间的连接

URLConnection conn =realUrl.openConnection();//设置通用的请求属性

conn.setRequestProperty("accept", "*/*");

conn.setRequestProperty("connection", "Keep-Alive");

conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");//conn.setRequestProperty("Pragma:", "no-cache");//conn.setRequestProperty("Cache-Control", "no-cache");//conn.setRequestProperty("Content-Type", "text/xml;charset=utf-8");//发送POST请求必须设置如下两行

conn.setDoOutput(true);

conn.setDoInput(true);//获取URLConnection对象对应的输出流

out = newPrintWriter(conn.getOutputStream());//发送请求参数

out.print(param);//flush输出流的缓冲

out.flush();//定义BufferedReader输入流来读取URL的响应

in = newBufferedReader(newInputStreamReader(conn.getInputStream()));

String line;while ((line = in.readLine()) != null) {

result+=line;

}

}catch(Exception e) {

System.out.println("发送 POST 请求出现异常!" +e);

e.printStackTrace();

}//使用finally块来关闭输出流、输入流

finally{try{if (out != null) {

out.close();

}if (in != null) {

in.close();

}

}catch(IOException ex) {

ex.printStackTrace();

}

}returnresult;

}/*** 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。

*

*@paramstrxml

*@return*@throwsJDOMException

*@throwsIOException*/

public static Map doXMLParse(String strxml) throwsException {if (null == strxml || "".equals(strxml)) {return null;

}

Map m= newHashMap();

InputStream in=String2Inputstream(strxml);

SAXBuilder builder= newSAXBuilder();

Document doc=builder.build(in);

Element root=doc.getRootElement();

List list=root.getChildren();

Iterator it=list.iterator();while(it.hasNext()) {

Element e=(Element) it.next();

String k=e.getName();

String v= "";

List children=e.getChildren();if(children.isEmpty()) {

v=e.getTextNormalize();

}else{

v=getChildrenText(children);

}

m.put(k, v);

}//关闭流

in.close();returnm;

}/*** 获取子结点的xml

*

*@paramchildren

*@returnString*/

public staticString getChildrenText(List children) {

StringBuffer sb= newStringBuffer();if (!children.isEmpty()) {

Iterator it=children.iterator();while(it.hasNext()) {

Element e=(Element) it.next();

String name=e.getName();

String value=e.getTextNormalize();

List list=e.getChildren();

sb.append("");if (!list.isEmpty()) {

sb.append(getChildrenText(list));

}

sb.append(value);

sb.append("" + name + ">");

}

}returnsb.toString();

}public staticInputStream String2Inputstream(String str) {return newByteArrayInputStream(str.getBytes());

}

}

那么以上代码部分就是后端完成前支付的全部过程,支付成功后的业务逻辑我就不再分享,可以通过前端异步通知后端支付成功,并执行平台自己的业务;

当然这其中可能会出现签名返回错误,以及该订单已支付问题;

· 订单已支付可能是因为商户订单号没有更新,导致第二次支付是传递的是同一个订单号所导致,可以沿着这个方向去排查;

·签名错误请仔细查看签名生成方法,可通过验证工具去验证,传送门

如出现下面问题签名验证正确,但是调用微信统一下订单返回签名错误;

那就请返回查看一下商户key,需注意商户key设置官方说明如图:

仔细看其实它的意思是要32个字符,其中包含数字,大写字母和小写字母的组合,一定要有这三个,切记,切记,切记。

下面就是我的测试成功图片

这里商户单号就是自己平台所产生的订单号。

好了今天的分享就到这里了

这里是小宋

如果问题请联系qq邮箱:

感谢大家观看,祝大家生活学习和工作愉快!

微信小程序支付返回签名错误_java 微信小程序微信支付统一下订单及数字签名错误问题(后端)...相关推荐

  1. 微信小程序支付返回签名错误_PHP微信小程序支付——签名错误

    先分清几个概念:微信公众平台.微信开放平台.微信商户平台 1.微信公众平台.微信开放平台.微信商户平台是三个不同的平台 2.微信公众平台:用于公众号.小程序等等的设置平台,包括APPID.APPSEC ...

  2. 微擎支付返回商户单号_支付宝,微信,银联支付

    https://open.unionpay.com/tjweb/index 银联支付 统一支付接口 接口描述 用于线下刷卡交易.生物特征识别(例如人脸).被扫支付等后台交易. 对于被扫支付,收银员使用 ...

  3. 微信小程序左上角返回刷新

    微信小程序左上角返回刷新 文章目录 微信小程序左上角返回刷新 前言 一.小程序生命周期 二.解决方案(重点来了) 1.在data里创建标识 2.查看初始化onload中调用的初始化的方法 3.在onS ...

  4. php 苹果支付验证失败的原因,iOS 微信支付报错 支付验证签名失败

    可能的原因有两个: 一.传给微信sdk的参数中时间戳的格式错误. iOS中时间戳的格式要求是32位的int类型 image.png 一般服务器返回的是字符串需要强制转换成32位int类型的值 二.服务 ...

  5. ubuntu php 错误,Ubuntu下如何开启PHP错误提示教程

    ubuntu下默认是没有php语法错误提示的,如果要开启,需要修改几个地方: 1. 打开php.ini文件. 这个文件在: /etc/php5/apache2 目录下,需要修改这个文件的权限才能写入. ...

  6. 微信支付服务商,![CDATA[sub_mch_id与sub_appid不匹配],微信小程序支付,签名错误,CDATA[签名错误]解决方法,支付签名验证失败

    微信支付服务商,微信小程序支付,签名错误,CDATA[签名错误]解决方法 1:服务商和普通的商户支付在代码上没有太大差异(不同的是上传的参数,而且微信支付服务商参数上比较难找,官方文档又太简洁,所以很 ...

  7. 服务商模式下微信小程序支付时,一直报“支付验证签名失败”错误的解决办法

    在写小程序支付的时候,我是以服务商模式发起的支付,统一下单接口是调用成功的,但是在小程序调起支付的时候就报"支付验证签名失败"的错误. 主要有三个原因: 1.第二次签名用到的tim ...

  8. 微信小程序支付返回信息为空

    1.昨天公司说要实现微信小程序的支付,于是看了下微信小程序的开发api文档,和之前的app  端以及pc端基本相似:于是让他们把参数改了下,把之前的trade_type 由 app 改成 小程序要求的 ...

  9. 微擎支付返回商户单号_微信小程序支付流程

    微信支付之小程序支付 微信的支付方式有以下几种,不同的支付方式适用于不同的支付场景,而今天要给大家讲的就是 小程序支付 方式 说到支付功能就要涉及到金钱交易,必定是有比较严格的规范及流程,如要求小程序 ...

最新文章

  1. HBase安装配置以及Java操作hbase
  2. vn.py 2.0.2 发布,全功能交易程序开发框架
  3. 网上税务html模板,HTML黑色欧美形式税务动态邮件网页模板代码
  4. zookeeper的名词复盘-数据模型
  5. 前端学习(2152):Vue的template和el的关系
  6. 回文串判断(string类:反转reverse)
  7. (45)Verilog HDL 秒灯电路设计
  8. 蓝桥杯51单片机之独立按键控制数码管【单片机开发初学者必学会】
  9. requests 忽略证书
  10. 数据分析数据挖掘(五)
  11. 《OpenStack实战指南》—— 1.4 OpenStack与CloudStack的比较
  12. 电子病历系统服务器配置,河北省人民医院电子病历系统实施方案
  13. 用matlab算特征值,用Matlab用计算特征值和特征向量
  14. com.android.stfwd,[原创]360freewifi逆向分析
  15. 爬虫剑谱第七页(输入关键词爬取拼多多商品信息并进行保存)
  16. 深度学习用于股票预测_用于自动股票交易的深度强化学习
  17. 弹跳机器人 桌游_《碰撞机器人 Ricochet Robots 》介绍
  18. SV绿皮书笔记(四)
  19. 初识c语言加操作系统
  20. Matlab GUI界面表格中数据导出到excel文件带标题

热门文章

  1. 年轻白领生活正被“公司化”(转载)
  2. 游戏笔记本计算机购买,2021年4月|游戏笔记本电脑选购,个人主观推荐
  3. matlabqq(Quantile-Quantile)图绘制并讲解
  4. python关于类和对象说法正确的是_关于类和对象,下面说法正确的是?
  5. vray3.6不会弹出服务器准许协议,SU2018装Vray3.6出现这个怎么解决啊
  6. 我是如何将投资账本的用户数从0做到50万的
  7. 审计学课程01-10:审计基本方法和理论
  8. 全球与中国LFP阴极粉末市场深度研究分析报告
  9. 图像处理与机器视觉 综合课程设计
  10. .net mysql transactionscope_使用Transaction访问数据库(C#,TransactionScope,.NET 2.0)