微信支付 V3 开发教程(一):初识 Senparc.Weixin.TenPayV3
前言
我在 9 年前发布了 Senparc.Weixin SDK 第一个开源版本,一直维护至今,如今 Stras 已经破 7K,这一路上得到了 .NET 社区的积极响应和支持,也受到了非常多的宝贵建议,甚至代码的 PR,目前累计的代码贡献者数量已经超过350人,在此表示衷心的感谢!
我们也总在第一时间及时更新微信官方的各类接口,其中也包括微信支付。
如今,针对已经发布了一段时间的“微信支付V3”,我们发布了一个完全重构后的全新版本:Senparc.Weixin.TenPayV3。
即使您没有开发过之前版本的微信支付也没有关系,因为这是一个完全崭新的开始,下面让我们开始最新一代的微信支付开发之旅。
关于微信支付 V2 和 V3
从微信支付 V2 开始,我们第一时间上线了微信支付的功能,并在 2018 年正式分离出独立的 Senparc.Weixin.TenPay 作为微信支付的专用类库。
微信支付自诞生以来进行了多次升级,其中比较容易混淆的是 V2 和 V3 两个版本号,在继续介绍之前,必须要做一个说明:
目前社区中流传的“微信支付V3”实际上有 2 个版本的说法,一个 V3 是早期微信支付文档和接口进行了一轮升级,当时文档称其为 V3,后来又出来一个是微信支付官方对 API 的版本号进行了升级,也称其为 V3。
后者的 V3 是真正意义上的“微信支付V3”,本次发布的模块也是针对这个 V3 而言的。
由于历史原因,在先前发布的 Senparc.Weixin.TenPay 中也已经包含了 V2 和 V3 两个版本的命名,这里的 V3 就是早期文档的 V3,和“微信支付V3"的用法实际上有很大差别,但在功能上,基本上属于“微信支付V3”的子集。
快速开发-准备
这里,我先从宏观演示一下 Senparc.Weixin.TenPayV3 的能力,通过网页演示和单元测试,完成最简单的鉴权、支付、退款和订单拉取功能(这些功能代表了几乎所有微信支付内部接口的形式),后续的章节将继续展开细节进行介绍。
关于具体的接口和流程介绍,大家还是要耐心看官方的文档:https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml,准备好微信支付V3所需的所有配置(V3 比之前的文档已经有了很大的飞跃,照着做基本上可以顺利完成)。下面的示例将以【普通商户+微信公众号JSAPI】这个组合进行展示,其他组合功能将在后续展开介绍。
所有微信支付形式的 Sample 已经在开源项目中,默认使用 .NET 6 项目打开:https://github.com/JeffreySu/WeiXinMPSDK/tree/master/Samples/net6-mvc,为了方便测试,您可以直接下载或者克隆项目,机型测试,对应代码可以移植到自己的项目中。
下载代码并打开上述目录中的 Senparc.Weixin.Sample.Net6.sln:
其中,Controller 和 Views 的命名,为了和之前已经诞生的旧版本 V3 区分,我们暂时命名为 RealV3 :
不需要修改任何代码,直接运行 Senparc.Weixin.Sample.NET6 项目,即可打开 Sample 首页:
由于 Sample 集成了微信公众号、小程序、企业微信、微信支付,以及相关的缓存、模拟消息、文档下载等演示,所以看上去内容比较多,不用着急,Sample 配有详细的注释,并且对文件进行了分类,我们只需聚焦相关的部分。
开发第一步:引用 Nuget 包
Sample 项目已经引用好了源码项目,如果您是全新的项目,可以直接引用 Senparc.Weixin.TenPayV3 包。
方法一:使用 VS 管理器引用:
方法二:直接在 .csproj 文件中引用(注意从 Senparc.Weixin.TenPayV3 网页查看最新版本):
<ItemGroup>
<PackageReference Include="Senparc.Weixin.TenPayV3" Version="0.3.500.2-preview2" />
</ItemGroup>
开发第二步:设置微信支付信息
在 Web 项目下面,找到 appsettings.json 文件,设置微信公众号和微信支付信息(其他信息根据说明,不需要的可以删除,或者保留原状),默认情况下只需要修改 SenparcWeixinSetting 节点下的“公众号”和“微信支付V3(新版)”的对应信息:
"SenparcWeixinSetting": {
//注意:所有的字符串值都可能被用于字典索引,因此请勿留空字符串(但可以根据需要,删除对应的整条设置)! //微信全局
"IsDebug": true, //以下不使用的参数可以删除,key 修改后将会失效 //公众号
"Token": "微信支付不需要",
"EncodingAESKey": "微信支付不需要",
"WeixinAppId": "MyWeixinAppId",
"WeixinAppSecret": "MyWeixinAppSecret", //微信支付V3(新版)
"TenPayV3_AppId": "MyWeixinAppId(同上)",
"TenPayV3_AppSecret": "MyWeixinAppSecret(同上)",
"TenPayV3_SubAppId": "",
"TenPayV3_SubAppSecret": "",
"TenPayV3_MchId": "xxxxxxxx",
"TenPayV3_SubMchId": "", //子商户,没有可留空
"TenPayV3_Key": "79xxxxxxxxxxxxxxxxxxxxxxxxxxx",
"TenPayV3_CertPath": "可留空", //支付证书物理路径,如:D:\\cert\\apiclient_cert.p12
"TenPayV3_CertSecret": "可留空", //支付证书密码(原始密码和 MchId 相同)
"TenPayV3_TenpayNotify": "http://sdk.weixin.senparc.com/TenpayV3/PayNotifyUrl", //http://YourDomainName/TenpayV3/PayNotifyUrl
"TenPayV3_PrivateKey": "MIIExxxxxxxxxxxxxxxxx", //(新)证书私钥
"TenPayV3_SerialNumber": "5Bxxxxxxxxxxxxxxxxxxxxxx", //证书序列号
"TenPayV3_ApiV3Key": "xxxxxxxxxxxxxxxxxxxxxxxx", //(新)APIv3 密钥
//如果不设置TenPayV3_WxOpenTenpayNotify,默认在 TenPayV3_TenpayNotify 的值最后加上 "WxOpen"
"TenPayV3_WxOpenTenpayNotify": "http://sdk.weixin.senparc.com/TenpayV3/PayNotifyUrlWxOpen" //http://YourDomainName/TenpayV3/PayNotifyUrlWxOpen
}
说明:TenPayV3_CertPath 和 TenPayV3_CertSecret 是“文档版本V3"时期的遗留产物,在新V3中已经可以忽略
开发第三步:开发商品列表和 JSAPI 支付页面
Sample 中提供了一个非常简约的商品列表和支付(详情)页:
功能 | Controller文件 | View文件 |
商品列表 | TenPayRealV3Controller.cs / ProductList() | /Views/TenPayRealV3/ProductList.cshtml |
JSAPI支付页面 (商品详情) |
TenPayRealV3Controller.cs / JsApi() | /Views/TenPayRealV3/JsApi.cshtml |
具体业务的实现这里不再展开,相关 OAuth 授权的内容属于公众号开发的范畴,详细介绍可以参考《Senparc.Weixin.MP SDK 微信公众平台开发教程(十二):OAuth2.0说明》。
这里着重讲一下 JSAPI 支付页面,为了方便演示,Sample 中把 JSAPI 和详情页放到了一起,实际项目中,详情页可以单独安排,此处 JSAPI 页面相当于是订单支付页面。
Controller:
先看 TenPayRealV3Controller.cs 下的 JsApi() 方法中的关键代码:
sp_billno = string.Format("{0}{1}{2}", TenPayV3Info.MchId/*10位*/, SystemTime.Now.ToString("yyyyMMddHHmmss"),
TenPayV3Util.BuildRandomStr(6));
上述代码用于生成订单号(在文档中也叫 out_trade_no),订单号建议加上日期,方便排序,然后加上流水号或者随机数,根据具体项目情况而定。这里一定要确保唯一性。
var notifyUrl = TenPayV3Info.TenPayV3Notify.Replace("/TenpayV3/", "/TenpayRealV3/").Replace("http://", "https://");
上述代码用于定义支付回调的地址,这里使用 Replace 是因为 Sample 中兼容了 2 套支付示范,实际开发过程中直接设置好 appsettings.json 中的参数即可。
TransactionsRequestData jsApiRequestData = new(TenPayV3Info.AppId, TenPayV3Info.MchId, name + " - 微信支付 V3", sp_billno, new TenpayDateTime(DateTime.Now.AddHours(1), false), null, notifyUrl, null, new() { currency = "CNY", total = price }, new(openId), null, null, null);
上述代码用于组装访问预支付接口的参数。
var result = await _basePayApis.JsApiAsync(jsApiRequestData);
上述代码用于调用预支付接口,获取 prepay_id,其中已经在构造函数中定义好的私有变量 _basePayApis(BasePayApis 类型),是执行相关一系列支付接口的实例化类:
public TenPayRealV3Controller()
{
_tenpayV3Setting = Senparc.Weixin.Config.SenparcWeixinSetting.TenpayV3Setting;
_basePayApis = new BasePayApis(_tenpayV3Setting);
}
if (result.VerifySignSuccess != true)
{
throw new WeixinException("获取 prepay_id 结果校验出错!");
}
获取到 result 后,一定要进行签名验证(包括其他接口)!实际的签名和验证过程比较复杂,SDK 已经完全封装好,您只需要确保 VerifySignSuccess 参数为 true 即可。
var jsApiUiPackage = TenPaySignHelper.GetJsApiUiPackage(TenPayV3Info.AppId, result.prepay_id);
ViewData["jsApiUiPackage"] = jsApiUiPackage;
上述代码用于生成前端 UI JsSdk 所需的所有信息,包括时间戳、随机字符串、签名字符串等,开发者不需要自行编写加密算法,开箱即用。
jsApiUiPackage 信息存放在 ViewData["jsApiUiPackage"] 中,在 View 中可以直接被调用。实际开发环境下,可以用各类方式传递此信息,包括 Ajax + Json。
View:
对应 View 页面(JsApi.cshtml)关键代码介绍如下:
document.addEventListener('WeixinJSBridgeReady', function onBridgeReady() {
//...
}
上述代码是监听 JSAPI 就绪的方法。
1 WeixinJSBridge.invoke('getBrandWCPayRequest', {
2 "appId": "@jsApiUiPackage.AppId", //公众号名称,由商户传入
3 "timeStamp": "@jsApiUiPackage.Timestamp", //时间戳
4 "nonceStr": "@jsApiUiPackage.NonceStr", //随机串
5 "package": "@Html.Raw(jsApiUiPackage.PrepayIdPackage)",//扩展包
6 "signType": "RSA", //微信V3签名方式:RSA
7 "paySign": "@Html.Raw(jsApiUiPackage.Signature)" //微信签名
8 }, function (res) {
9
10 //alert(JSON.stringify(res));
11
12 if (res.err_msg == "get_brand_wcpay_request:ok") {
13 if (confirm('支付成功!点击“确定”进入退款流程测试。')) {
14 location.href = '@Url.Action("Refund", "TenPayRealV3")';
15 }
16 //console.log(JSON.stringify(res));
17 }else{
18 alert(JSON.stringify(res));
19 }
20 // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
21 //因此微信团队建议,当收到ok返回时,向商户后台询问是否收到交易成功的通知,若收到通知,前端展示交易成功的界面;若此时未收到通知,商户后台主动调用查询订单接口,查询订单的当前状态,并反馈给前端展示相应的界面。
22 });
上述代码在用户点击支付按钮的时候触发,将自动进行一系列验证,并唤起客户端的微信支付界面(如输入密码或指纹)。
其中:
- 第 2-7 行:注入之前在 Controller 中配置的各类参数。注意:paySign 参数一定要加 Html.Raw(),否则可能因为加密字符串被转义而失败!
- 第 12 行:判断是否支付成功,并进行下一步操作。注意:此处的成功不一定是微信支付真的成功了,因为此信息有被篡改的可能性,因此正式环境一定要以 PayNotifyUrl 中的验证结果为准!
回调验证 PayNotifyUrl:
微信客户端收到的支付成功信息始终具有被篡改的可能性,因此,千万不要:
- 因为客户端的 JS 收到了看似正确的信息,就触发服务器端完成支付的指令(如一条Ajax请求);
- 即使触发服务器端的下一步指令,也不要在该条指令中进行订单“已支付”状态的修改,订单状态修改,必须是在 PayNotifyUrl 中!
根据之前 appsettings.json 以及 JsApi() 方法中的设置,最终的回调地址为:https://sdk.weixin.senparc.com/TenpayRealV3/PayNotifyUrl,代码在 TenPayRealV3Controller 中的 PayNotifyUrl() 方法,此方法中演示了正确的验证支付状态的最佳实践:
1 /// <summary>
2 /// JS-SDK支付回调地址(在下单接口中设置的 notify_url)
3 /// </summary>
4 /// <returns></returns>
5 public async Task<IActionResult> PayNotifyUrl()
6 {
7 try
8 {
9 //获取微信服务器异步发送的支付通知信息
10 var resHandler = new TenPayNotifyHandler(HttpContext);
11 var orderReturnJson = await resHandler.AesGcmDecryptGetObjectAsync<OrderReturnJson>();
12
13 //记录日志
14 Senparc.Weixin.WeixinTrace.SendCustomLog("PayNotifyUrl 接收到消息", orderReturnJson.ToJson(true));
15
16 //演示记录 transaction_id,实际开发中需要记录到数据库,以便退款和后续跟踪
17 TradeNumberToTransactionId[orderReturnJson.out_trade_no] = orderReturnJson.transaction_id;
18
19 //获取支付状态
20 string trade_state = orderReturnJson.trade_state;
21
22 //验证请求是否从微信发过来(安全)
23 NotifyReturnData returnData = new();
24
25 //验证可靠的支付状态
26 if (orderReturnJson.VerifySignSuccess == true && trade_state == "SUCCESS")
27 {
28 returnData.code = "SUCCESS";//正确的订单处理
29 /* 提示:
30 * 1、直到这里,才能认为交易真正成功了,可以进行数据库操作,但是别忘了返回规定格式的消息!
31 * 2、上述判断已经具有比较高的安全性以外,还可以对访问 IP 进行判断进一步加强安全性。
32 * 3、下面演示的是发送支付成功的模板消息提示,非必须。
33 */
34
35 #region 发送支付成功模板消息提醒
36 //略...
37 #endregion
38 }
39 else
40 {
41 returnData.code = "FAILD";//错误的订单处理
42 returnData.message = "验证失败";
43
44 //此处可以给用户发送支付失败提示等
45 }
46
47 #region 记录日志(也可以记录到数据库审计日志中)
48 //略...
49 #endregion
50
51 return Json(returnData);
52 }
53 catch (Exception ex)
54 {
55 WeixinTrace.WeixinExceptionLog(new WeixinException(ex.Message, ex));
56 throw;
57 }
58 }
注释已经比较详细,这里不再赘述,所有签名校验等安全验证信息已经全部封装在接口中,开箱即用。官方要求的完整流程可参考文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_5.shtml。
开发第四步:Startup.cs 中配置启动代码
Senparc.Weixin.TenPayV3 基于 Senparc.Weixin SDK 整体基座,同时由 CO2NET、NeuChar 等基础库提供强大的底层能力支撑,同时我们需要使用一些代码,完成 appsettings.json 等信息的自动注入,因此,需要在 Web 项目的 startup.cs 中添加一些代码,以下是关键代码的介绍(Sample 中为了演示所有的模块所以代码比较多,可以根据需要选用下方的代码):
ConfigureServices() 方法:
1 public void ConfigureServices(IServiceCollection services)
2 {
3 services.AddSession();//使用Session(实践证明需要在配置 Mvc 之前)
4
5 var builder = services.AddControllersWithViews()
6 .AddNewtonsoftJson();// 支持 NewtonsoftJson
7
8 services.AddSingleton<ITempDataProvider, CookieTempDataProvider>();
9
10 services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
11 services.AddMemoryCache();//使用本地缓存必须添加
12
13 services.AddSenparcWeixinServices(Configuration);//Senparc.Weixin 注册(必须)
14 }
上述代码完成了 Web 项目的一系列注册,其中:
- 第 3 行:为了让 Demo 不依赖数据库,我们使用了 Session 进行个人临时数据的存储,实际开发项目中不一定需要,可根据需要添加。
- 第 5-6 行:注册 MVC 和 JSON 相关能力,根据需要添加。
- 第 8 行:提供 Cookie 支持,根据需要添加。
- 第 10 行:为自动注入 HttpContext 添加注册,根据需要添加。
- 第 11 行:注册本地缓存,这一行为必须,因为 SDK 运行过程总需要使用到本地缓存。
- 第 13 行:对 Senparc.Weixin SDK 进行注册,必须。
可以看到,最小化支持 Senpar.Weixin.TenPayV3,此处实际上只需要最少添加 2 行代码。
Configure() 方法:
1 public void Configure(IApplicationBuilder app, IWebHostEnvironment env,
2 IOptions<SenparcSetting> senparcSetting, IOptions<SenparcWeixinSetting> senparcWeixinSetting)
3 {
4 app.UseHttpsRedirection();
5 app.UseStaticFiles();
6 app.UseRouting();
7
8 var registerService = app
9 //使用 Senparc.CO2NET 引擎
10 .UseSenparcGlobal(env, senparcSetting.Value, g => { })
11 //使用 Senparc.Weixin SDK
12 .UseSenparcWeixin(senparcWeixinSetting.Value, weixinRegister =>
13 {
14 //注册最新的 TenPay V3
15 weixinRegister.RegisterTenpayRealV3(senparcWeixinSetting.Value, "【盛派网络小助手】公众号-RealV3");
16 });
17 }
上述代码中:
- 第 4-6 行:常规方法。
- 第 10 行:启动 Senparc.CO2NET 引擎,提供一系列基础能力(如缓存、日志、队列等)。
- 第 12 行:启动 Senparc.Weixin SDK,其中可以进行微信公众号、小程序、企业微信、微信支付等不同模块的注册。
- 第 15 行:注册微信支付V3的信息,数据源头为 appsettings.json。注意:这一行注册过程可以在使用微信支付功能前的任意地方执行,但建议在启动时就完成注册。除使用 appsetting.json 方式自动注入,也可以手动构造实体类,赋值并传入。
上线演示
上述 Sample 可以直接发布,最新的代码我们已经发布到了到官方在线示例站点:https://sdk.weixin.senparc.com/,有两种途径可以进入上述 JsApi 页面进行支付测试。
方式一:关注公众号:盛派网络小助手,点击菜单:
进入菜单【更多测试】>【微信支付V3】:
选择任意一个商品,如【产品1】,点击进入:
点击【点击提交可体验微信支付】按钮,进入客户端支付状态:
在客户端完成支付(输入密码或指纹),即可出现支付完成的官方界面:
点击【完成】按钮,可以继续体验退款流程(开发相关功能介绍请看下一篇系列文章:《微信支付 V3 开发教程(二):退款》。
返回公众号内,可以看到已经通过 PayNotifyUrl 发送过来的模板消息(同时已经经过安全验证):
并可以在微信支付消息中,看到官方的消息推送:
方式二:通过 https://sdk.weixin.senparc.com/ 顶部菜单【工具箱】>【微信支付 V3 测试(PC端)】进入:
进入后同样是 ProductList 页面:
选择一个商品进入,可以看到 PC 端提供了多种支付方式的演示,包括:H5 支付、Native 支付,以及扫一扫支付:
提示:由于产品Id随每次系统启动变化,所以上述二维码在您看到的时候已经失效,您可以重新从入口进入,获得最新的二维码。
- 关于 H5 支付请关注后续文章:《微信支付 V3 开发教程(三):H5 支付》
- 关于 Native 支付请关注后续文章:《微信支付 V3 开发教程(四):Native 支付》
当前演示的 JsApi 支付,可在“扫一扫”支付方式中,使用微信扫码进入,即可在微信端打开上述“方法一”中介绍的产品列表,并体验支付流程。
更多内容
本文是《微信支付 V3 开发教程》的开篇,后续还将对包括退款、对账订单、H5 支付、Native 支付、微信分等更多的接口展开介绍,欢迎关注,感谢大家的支持!
微信支付 V3 开发教程(一):初识 Senparc.Weixin.TenPayV3的更多相关文章
- 微信支付v3开发(5) 扫码并输入金额支付
关键字:微信支付 微信支付v3 动态native支付 统一支付 Native支付 prepay_id 作者:方倍工作室 本文介绍微信支付下的扫描二维码并输入自定义金额的支付的开发过程. 注意 微信支付 ...
- php 微信开发之 微信支付 V3 开发 -CURLOP_TIMEOUT问题
如果不懂怎么配置的话请看文章 php 微信开发之 微信支付配置 基本配置后在继续本文章的开发 . 本文章就先继续基本的实现!也并不困难.我大概的思路的返回购买者的唯一id 和 订单号的唯一 id 就2 ...
- 微信支付v3开发(6) 收货地址共享接口
请看新版教程 微信支付开发(7) 收货地址共享接口V2 本文介绍微信支付下的收货地址共享接口的开发过程. 一. 简单介绍 微信收货地址共享,是指用户在微信浏览器内打开网页,填写过地址后,兴许能够免填 ...
- Senparc.Weixin.MP SDK 微信公众平台开发教程(五):使用Senparc.Weixin.MP SDK
Senparc.Weixin.MP SDK已经涵盖了微信6.x的所有公共API. 整个项目的源代码以及已经编译好的程序集可以在这个项目中获取到:https://github.com/JeffreySu ...
- Senparc.Weixin.MP SDK 微信公众平台开发教程(十八):Web代理功能
在Senparc.Weixin.dll v4.5.7版本开始,我们提供了Web代理功能,以方便在受限制的局域网内的应用可以顺利调用接口. 有关的修改都在Senparc.Weixin/Utilities ...
- Senparc.Weixin.MP SDK 微信公众平台开发教程(十七):个性化菜单接口说明
前不久微信上线了个性化菜单接口,Senparc.Weixin SDK也已经同步更新. 本次更新升级Senparc.Weixin.MP版本到v13.5.2,依赖Senparc.Weixin版本4.5.4 ...
- Senparc.Weixin.MP SDK 微信公众平台开发教程(三):微信公众平台开发验证
要对接微信公众平台的"开发模式",即对接到自己的网站程序,必须在注册成功之后(见Senparc.Weixin.MP SDK 微信公众平台开发教程(一):微信公众平台注册),等待官方 ...
- Senparc.Weixin.MP SDK 微信公众平台开发教程(四):Hello World
============= 以下写于2013-07-20 ============= 这一篇文章其实可以写在很前面,不过我还是希望开发者们尽多地了解清楚原理之后再下手. 通过上一篇Senparc.W ...
- Senparc.Weixin.MP SDK 微信公众平台开发教程(六):了解MessageHandler
上一篇<Senparc.Weixin.MP SDK 微信公众平台开发教程(五):使用Senparc.Weixin.MP SDK>我们讲述了如何使用Senparc.Weixin.MP SDK ...
随机推荐
- Python - 基础数据类型 dict 字典
字典简介 字典在 Python 里面是非常重要的数据类型,而且很常用 字典是以关键字(键)为索引,关键字(键)可以是任意不可变类型 字典由键和对应值成对组成,字典中所有的键值对放在 { } 中间,每一 ...
- upload-lab 靶场实战
文件上传/下载 漏洞 冲冲冲,好好学习 2020.02.13 淦靶场之前,先来点知识铺垫铺垫. 文件上传漏洞 前端Js绕过. MIME类型绕过 后缀名大写写绕过 / php4 .php5 00截断 覆 ...
- 大都市meg DFS序
题目描述 在经济全球化浪潮的影响下,习惯于漫步在清晨的乡间小路的邮递员Blue Mary也开始骑着摩托车传递邮件了.不过,她经常回忆起以前在乡间漫步的情景.昔日,乡下有依次编号为1..n的n个小村庄, ...
- Go测试技术分享(一):场景化接口Case编写
一.前言 本人负责的支付清结算方向的测试工作,在测试项目中,会出现流程化的接口调用,请求完一个接口后,继续请求另一个接口(这里的接口可以指Http,也指rpc接口),这里以一个真实场景为例:用户在平台 ...
- HCNA Routing&Switching之GVRP
前文我们了解了不同vlan间路由相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/15110336.html,今天我们来聊一聊vlan动态注册相关话题: ...
- 运行第一个程序!hello world!
第一个程序:1 //打印 hello world 2 3 #include <stdio.h> 4 int main() 5 { 6 printf("hello world!\n ...
- UI:PointerEventData
原因:判断是否将一个UI物体拖放到另一个UI物体上面. 1.拖拽实现可以直接用EventTrigger组件或者自己实现拖拽事件的接口完成: 2.在OnDrag方法中借助PointEventData事件 ...
- 痞子衡嵌入式:在IAR开发环境下将关键函数重定向到RAM中执行的三种方法
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是在IAR开发环境下将关键函数重定向到RAM中执行的三种方法. 嵌入式项目里应用程序代码正常是放在 Flash 中执行的,但有时候也需要将 ...
- Haskell Command-line Application Building
Haskeline Package Haskeline provides a user interface for line input in command-line programs. This ...
- 计算机网络 中国大学MOOC 哈尔滨工业大学 习题答案
转自 https://blog.csdn.net/qq_37514135/article/details/82733651 计算机网络作业题 第一章 第一题 如图所示网络.A在t=0时刻开始向C发送一 ...