[{"content":" Hi, there! 👋 别在迷失自我的道路上越走越远 你好，我是珩宇！是一个会一点前端的后端开发者，热爱编程，喜欢将工作生活中遇到的问题记录下来。\n这里是我分享个人见解、经验和技术教程的地方，希望和你一起进步\u0026hellip;💯\n","date":null,"permalink":"/","section":"","summary":"","title":""},{"content":"","date":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories"},{"content":"","date":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts"},{"content":"","date":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags"},{"content":"Web漏洞扫描工具ZAP的简单使用 简介 Zed Attack Proxy(ZAP)\n一个免费开源的web应用扫描程序，由专门的国际志愿者团队维护，GitHub Top 1000项目。\n官网地址 [ZAP (zaproxy.org)](https://www.zaproxy.org/) (可能需要科学网络) github [zaproxy/zaproxy: The ZAP core project (github.com)](https://github.com/zaproxy/zaproxy) 安装说明 注：需要JAVA11以上版本java运行环境，否则无法正常安装运行\n官网或github下载exe安装包即可。\n软件截图 简单使用 进入主页面点击右侧自动扫描转到如下界面 扫描站点页面及请求 这一步是利用爬虫爬取站点相关页面及请求，要攻击的URL中输入目标站点地址。\n点击攻击按钮，开始扫描站点相关页面及请求，如图所示：\n扫描系统漏洞 右键站点下的路径，选择攻击-\u0026gt;主动扫描\n在弹框中可配置扫描策略\n一般使用默认策略即可，点击开始扫描\n可在主动扫描窗口中查看扫描进度\n扫描结束后点击警报窗口即可查看扫描结果\n点击对应的警报可查看详情信息，即可根据警报详情做出相应防护措施\n生成报告 点击工具栏报告-\u0026gt;生成报告弹出生成报告选项弹框\n在生成报告弹框中可配置报告信息\n选择模板可生成不同格式的报告，根据实际情况选择即可\n其他 本介绍只是初步简单使用该攻击，具体用法以及其他功能可以查看官网教程，或github介绍\n","date":"2024-09-05","permalink":"/posts/essays/003-zap%E5%AE%89%E5%85%A8%E6%94%BB%E5%87%BB%E4%BD%BF%E7%94%A8/","section":"Posts","summary":"","title":"Web漏洞扫描工具ZAP的简单使用"},{"content":"","date":null,"permalink":"/tags/%E9%9A%8F%E7%AC%94/","section":"Tags","summary":"","title":"随笔"},{"content":"","date":null,"permalink":"/categories/%E9%9A%8F%E7%AC%94/","section":"Categories","summary":"","title":"随笔"},{"content":"","date":null,"permalink":"/tags/.net/","section":"Tags","summary":"","title":".Net"},{"content":"","date":null,"permalink":"/categories/.net/","section":"Categories","summary":"","title":".Net"},{"content":"概述 上一篇文章C#实现微信扫码登录（绕过微信开发平台） - ihuadz 提到封装微信接口，本篇文章将从以下方面介绍如何使用C#实现简单的微信SDK。\n仓库地址：https://github.com/ihuadz/QI.WxSdk.git\n核心类库 配置 具体实现 如何使用 核心类库介绍 WebApiClient WebApiClient 是一个集高性能高可扩展性于一体的声明式http客户端库，具体使用方法可以查看一下官方文档\n其他的都是使用dotnet提供的类库\n配置 配置文件长这样，可配置多个小程序\n# 微信应用(公众号/小程序) WeixinSetting: # 应用配置数组,可配置多个 App: - AppId: xxxxxxxxxxxxxx AppSecret: xxxxxxxxxxxxxxxxxxxxxxx Name: xxxxxxxxxxxxxxx - AppId: xxxxxxxxxxxxxx AppSecret: xxxxxxxxxxxxxxxxxxxxxxx Name: xxxxxxxxxxxxxxx 具体实现 封装代码太多，文章内就放出核心代码，全部sdk查看概述。\nIWxApiBase /// \u0026lt;summary\u0026gt; /// 微信接口映射API /// \u0026lt;/summary\u0026gt; [JsonNetReturn(EnsureMatchAcceptContentType = false)] public interface IWxApiBase { } 获取Access Token // \u0026lt;summary\u0026gt; /// 获取全局接口调用凭据, 用于小程序和公众号 /// \u0026lt;/summary\u0026gt; [HttpHost(\u0026#34;https://api.weixin.qq.com/cgi-bin/\u0026#34;)] public interface IWxTokenApi : IWxApiBase { /// \u0026lt;summary\u0026gt; /// 获取全局接口调用 Access token /// \u0026lt;/summary\u0026gt; /// \u0026lt;remarks\u0026gt; /// 用于小程序和公众号 /// https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html /// https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getAccessToken.html /// \u0026lt;/remarks\u0026gt; /// \u0026lt;param name=\u0026#34;appId\u0026#34;\u0026gt;公众号或小程序的应用id\u0026lt;/param\u0026gt; /// \u0026lt;param name=\u0026#34;secret\u0026#34;\u0026gt;应用密钥，即appsecret\u0026lt;/param\u0026gt; /// \u0026lt;param name=\u0026#34;grant_type\u0026#34;\u0026gt;授权类型，获取access_token填写client_credential\u0026lt;/param\u0026gt; /// \u0026lt;returns\u0026gt;\u0026lt;/returns\u0026gt; [HttpGet(\u0026#34;token\u0026#34;)] Task\u0026lt;TokenApiResult\u0026gt; GetAsync([Required] string appId, [Required] string secret, string grant_type = \u0026#34;client_credential\u0026#34;); } 将AccessToken缓存 /// \u0026lt;summary\u0026gt; /// 检查应用配置数据 /// \u0026lt;/summary\u0026gt; protected virtual void CheckAppSetting() { if (CurApp == null) throw new WxException(\u0026#34;应用配置不能空\u0026#34;); if (string.IsNullOrEmpty(CurApp.AppId)) throw new WxException(\u0026#34;应用AppId能空\u0026#34;); if (string.IsNullOrEmpty(CurApp.AppSecret)) throw new WxException(\u0026#34;应用AppSecret能空\u0026#34;); } /// \u0026lt;summary\u0026gt; /// 获取Access Token /// \u0026lt;/summary\u0026gt; /// \u0026lt;remarks\u0026gt;如果可能,会进行缓存\u0026lt;/remarks\u0026gt; /// \u0026lt;returns\u0026gt;\u0026lt;/returns\u0026gt; public virtual async Task\u0026lt;string\u0026gt; GetAccessTokenAsync() { CheckAppSetting(); //获取token,先查询缓存 string token = await GetAccessTokenFromCacheAsync(); if (!string.IsNullOrEmpty(token)) return token; //如果缓存中没有，调用Wx Api获取Token IWxTokenApi tokenApi = GetService\u0026lt;IWxTokenApi\u0026gt;(); TokenApiResult result = await tokenApi.GetAsync(CurApp.AppId, CurApp.AppSecret); //缓存token await SetAccessTokenToCacheAsync(result.AccessToken, result.Expires); return result.AccessToken; } 最主要的是这一步，使用WebApiClient过滤器将access_token设置到每次请求微信的url中 /// \u0026lt;summary\u0026gt; /// WebApiClient过滤器,自动设置url的access_token请求参数 /// \u0026lt;/summary\u0026gt; public class AccessTokenApiFilter : ApiFilterAttribute { /// \u0026lt;summary\u0026gt; /// 请求 /// \u0026lt;/summary\u0026gt; /// \u0026lt;param name=\u0026#34;context\u0026#34;\u0026gt;\u0026lt;/param\u0026gt; /// \u0026lt;returns\u0026gt;\u0026lt;/returns\u0026gt; public override async Task OnRequestAsync(ApiRequestContext context) { var tokenManager = context.HttpContext.ServiceProvider.GetRequiredService\u0026lt;ITokenManager\u0026gt;(); string accessToken = await tokenManager.GetAccessTokenAsync(); context.HttpContext.RequestMessage.AddUrlQuery(\u0026#34;access_token\u0026#34;, accessToken); } /// \u0026lt;summary\u0026gt; /// 响应 /// \u0026lt;/summary\u0026gt; /// \u0026lt;param name=\u0026#34;context\u0026#34;\u0026gt;\u0026lt;/param\u0026gt; /// \u0026lt;returns\u0026gt;\u0026lt;/returns\u0026gt; public override Task OnResponseAsync(ApiResponseContext context) { if(context.Result is ApiResultBase apiResult \u0026amp;\u0026amp; apiResult!=null) { //如果请求的accesstoken过期，清除当前缓存的过期token if (apiResult.IsAccessTokenInvalid) { var tokenManager = context.HttpContext.ServiceProvider.GetRequiredService\u0026lt;ITokenManager\u0026gt;(); tokenManager.ClearAccessTokenAsync(); } } return Task.CompletedTask; } } 以上次文章中使用到的请求短链为例，实现请求微信端。\n/// \u0026lt;summary\u0026gt; /// 小程序 URL Link, URL Scheme 接口 /// \u0026lt;/summary\u0026gt; public interface IWxMpUrllinkApi : IWxApiWithAccessTokenFilter { /// \u0026lt;summary\u0026gt; /// 获取小程序 URL Link /// \u0026lt;/summary\u0026gt; /// \u0026lt;remarks\u0026gt; /// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-link/urllink.generate.html /// https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/url-link.html /// \u0026lt;/remarks\u0026gt; /// \u0026lt;param name=\u0026#34;input\u0026#34;\u0026gt;请求数据\u0026lt;/param\u0026gt; /// \u0026lt;returns\u0026gt;\u0026lt;/returns\u0026gt; [HttpPost(\u0026#34;https://api.weixin.qq.com/wxa/generate_urllink\u0026#34;)] ITask\u0026lt;GenerateUrlLinkResult\u0026gt; GenerateUrllinkAsync([JsonNetContent] GenerateUrlLinkInput input); } IWxApiWithAccessTokenFilter\n/// \u0026lt;summary\u0026gt; /// WebApiClient过滤器,自动设置url的access_token请求参数 /// \u0026lt;/summary\u0026gt; [JsonNetReturn(EnsureMatchAcceptContentType = false)] [AccessTokenApiFilter] public interface IWxApiWithAccessTokenFilter { } 然后就是WxMpApiService，这里就只放短链相关\n/// \u0026lt;summary\u0026gt; /// 微信小程序接口聚合服务, 生命周期为Scoped /// \u0026lt;/summary\u0026gt; public class WxMpApiService : WxApiServiceBase { /// \u0026lt;summary\u0026gt; /// 小程序 URL Link, URL Scheme 接口 /// \u0026lt;/summary\u0026gt; public IWxMpUrllinkApi IUrllinkApi =\u0026gt; GetService\u0026lt;IWxMpUrllinkApi\u0026gt;(); } WxApiServiceBase 接口聚合服务 基类\n/// \u0026lt;summary\u0026gt; /// Wx接口聚合服务 基类 /// \u0026lt;/summary\u0026gt; public class WxApiServiceBase { /// \u0026lt;summary\u0026gt; /// 微信接口集合服务 /// \u0026lt;/summary\u0026gt; /// \u0026lt;param name=\u0026#34;tokenManager\u0026#34;\u0026gt;\u0026lt;/param\u0026gt; public WxApiServiceBase(ITokenManager tokenManager) { TokenManager = tokenManager; } /// \u0026lt;summary\u0026gt; /// 微信AccessToken管理器 /// \u0026lt;/summary\u0026gt; public virtual ITokenManager TokenManager { get; } /// \u0026lt;summary\u0026gt; /// 得到服务对象 /// \u0026lt;/summary\u0026gt; /// \u0026lt;typeparam name=\u0026#34;T\u0026#34;\u0026gt;\u0026lt;/typeparam\u0026gt; /// \u0026lt;returns\u0026gt;\u0026lt;/returns\u0026gt; protected virtual T GetService\u0026lt;T\u0026gt;() { return TokenManager.GetService\u0026lt;T\u0026gt;(); } /// \u0026lt;summary\u0026gt; /// 设置当前的应用 /// \u0026lt;/summary\u0026gt; /// \u0026lt;param name=\u0026#34;wxApp\u0026#34;\u0026gt;\u0026lt;/param\u0026gt; public virtual void SetCurApp(WxAppSetting wxApp) { TokenManager.SetCurApp(wxApp); } /// \u0026lt;summary\u0026gt; /// 设置当前的应用,根据Appid从WxContext中查找 /// \u0026lt;/summary\u0026gt; /// \u0026lt;param name=\u0026#34;appId\u0026#34;\u0026gt;\u0026lt;/param\u0026gt; public virtual void SetCurApp(string appId) { TokenManager.SetCurApp(appId); } /// \u0026lt;summary\u0026gt; /// 得到会话当前AppId /// \u0026lt;/summary\u0026gt; /// \u0026lt;returns\u0026gt;\u0026lt;/returns\u0026gt; public virtual string GetCurAppId() { return TokenManager?.GetCurAppId() ?? string.Empty; } /// \u0026lt;summary\u0026gt; /// 当前应用 /// \u0026lt;/summary\u0026gt; /// \u0026lt;returns\u0026gt;\u0026lt;/returns\u0026gt; public virtual WxAppSetting GetCurApp() { return TokenManager.GetCurApp(); } } 至于ITokenManager，自己设计一下就好，都是读取配置和缓存AccessToken的一些操作，也可以查看源码概述。\n最后就是如何使用了。添加一个扩展类，将封装好的Api注册\n/// \u0026lt;summary\u0026gt; /// 添加WxSdk 接口、服务,IWxSession会话 /// \u0026lt;/summary\u0026gt; /// \u0026lt;param name=\u0026#34;services\u0026#34;\u0026gt;\u0026lt;/param\u0026gt; /// \u0026lt;returns\u0026gt;\u0026lt;/returns\u0026gt; public static IServiceCollection AddWxSdkAll(this IServiceCollection services) { //注册接口，服务 services.AddWxSdkApiAndServices(); //注册token管理器 services.AddWxSdkTokenManager(); return services; } /// \u0026lt;summary\u0026gt; /// 注册token管理器,会话服务 /// \u0026lt;/summary\u0026gt; /// \u0026lt;param name=\u0026#34;services\u0026#34;\u0026gt;\u0026lt;/param\u0026gt; /// \u0026lt;returns\u0026gt;\u0026lt;/returns\u0026gt; public static IServiceCollection AddWxSdkTokenManager(this IServiceCollection services) { //添加WxSession services.AddScoped\u0026lt;ITokenManager, TokenManager\u0026gt;(); return services; } /// \u0026lt;summary\u0026gt; /// 添加WxSdk 接口、服务 /// \u0026lt;/summary\u0026gt; /// \u0026lt;param name=\u0026#34;services\u0026#34;\u0026gt;\u0026lt;/param\u0026gt; /// \u0026lt;returns\u0026gt;\u0026lt;/returns\u0026gt; public static IServiceCollection AddWxSdkApiAndServices(this IServiceCollection services) { services .AddWebApiClient() .UseJsonFirstApiActionDescriptor(); //添加Wx上下文 services.AddSingleton\u0026lt;WxConfig\u0026gt;(); //添加微信API映射 //公众接口 services.AddHttpApi\u0026lt;IWxTokenApi\u0026gt;(); //注册聚合/扩展服务 -公众号 services.AddScoped\u0026lt;WxPaApiService\u0026gt;(); //小程序接口 services.AddHttpApi\u0026lt;IWxMpUrllinkApi\u0026gt;(); //注册聚合/扩展服务 -小程序 services.AddScoped\u0026lt;WxMpApiService\u0026gt;(); return services; } 如何使用 在你的应用中添加注册\n//注册微信Sdk集成 services.AddWxSdkAll(); 在service里注入后调用\n//请求微信短链接口 var urlResult = await _wxMpService.IUrllinkApi.GenerateUrllinkAsync(new GenerateUrlLinkInput() { Path = path, Query = query, EnvironmentVersion = environmentVersion, ExpireTimestamp = DateTime.Now.AddMinutes(10).ConvertToTimeStamp() }); ","date":"2024-03-18","permalink":"/posts/dotnet/007-dotnet%E5%AE%9E%E7%8E%B0%E7%AE%80%E5%8D%95%E7%9A%84%E5%BE%AE%E4%BF%A1sdk/","section":"Posts","summary":"","title":"DotNet实现简单微信SDK"},{"content":"","date":null,"permalink":"/tags/%E5%BE%AE%E4%BF%A1/","section":"Tags","summary":"","title":"微信"},{"content":"不用科学上网无阻访问GitHub 相信点开这篇文章的人都很清楚GitHub是啥了，并且很可能在GitHub里尝到了不少甜头\n但是有不少朋友访问GitHub时经常被拦下，下面介绍一下我自己使用了很久能正常访问GitHub的方式\nFastGitHub 这个项目的来源呢就不多介绍了，感兴趣可以自己搜搜看，现在这个项目的地址已经404了，也不知道为啥。\n运行后像这样，就可以正常访问了\n不想使用这个的话，可以添加到windows服务\nfastgithub.exe start //卸载删除 fastgithub.ext stop 使用nssm 这是一个安装window服务的小工具，挺好用\n将nssm.exe复制到fastgithub目录下，运行命令\nnssm install FastGithub 会有一个设置service的信息的弹框，输入对应信息即可\n这两个工具都能搜索到，懒得找的话留言邮箱，我会发送工具以及使用方法\n","date":"2024-03-06","permalink":"/posts/essays/002-github%E5%8A%A0%E9%80%9F/","section":"Posts","summary":"","title":"不用科学上网无阻访问github"},{"content":"概述 最近公司打算引进触屏终端，需要将已有小程序打包为APP，并且实现微信扫码登录终端。\n查阅微信官方文档后发现微信提供了APP端SDK，直接调用接口即可获得登录授权二维码，wow，那岂不是没有我后端啥事了，果断把链接丢给前端，官方文档：微信移动应用扫码登录。\n然而，因为APP是直接用uniapp打包的，没有找到使用微信SDK的方法；时间紧，也懒得去研究uniapp那一套（有知道咋处理的小伙伴可以告知一下），然后就想了另外一套方案。\n使用微信的OAuth2.0授权登录，打算使用做个中间页获取到授权信息；\n流程大概是：\n将微信小程序绑定至微信开放平台 在微信开放平台中创建一个网站应用 使用新创建的应用的appid和code去登录 拿到access_token、openid、unionid 最终获取用户信息 这样处理有几个问题：\n获取到openid和小程序的不是同一个应用的openid，无法绑定用户 可以使用unionid去获取用户信息，但是小程序已经上线一段时间了，上线的时候没有绑定开放平台（我也不知道为什么不绑定，可能之前就没有想过现在这种情况），所以用户都没有unionid，无法绑定用户 使用code换取手机号？OAuth2.0好像没有提供，文档上是没说，也懒得试了（需要微信开放平台创建应用、上传审核资料balabalabala \u0026hellip;） 好吧，那只能再换一个方案了。\n流程 大概流程就是：\nAPP终端向后端获取登录二维码 后端返回终端qrid参数以及base64二维码图片，并将二维码信息缓存 用户扫码（可用微信或其他扫码浏览器） APP终端轮询二维码状态接口 后端获取微信调起小程序短链，并将二维码状态更改，重定向短链 用户同意打开小程序，授权登录 后端将登录成功token存入刚才的二维码缓存信息内 此时APP端轮询获取的登录信息，成功登录 代码 获取二维码\n生成二维码的代码QRCodeHelper.GenerateQrCodeBase64(qrUrl)就不放了，一搜一大堆\n/// \u0026lt;summary\u0026gt; /// 获取登录二维码 /// \u0026lt;/summary\u0026gt; /// \u0026lt;returns\u0026gt;\u0026lt;/returns\u0026gt; public async Task\u0026lt;KeyValuePair\u0026lt;string, string\u0026gt;\u0026gt; GetLoginQrAsync() { //构建扫码回调链接 var request = App.HttpContext.Request; string protocol = request.IsHttps ? \u0026#34;https\u0026#34; : \u0026#34;http\u0026#34;; string host = request.Host.Value; string fullHost = $\u0026#34;{protocol}://{host}\u0026#34;; var url = $\u0026#34;{fullHost}/api/auth/scan-qr-cb?qrId=\u0026#34;; var qrId = $\u0026#34;qr_{GuidHelper.GenerateSequentialGuid16String()}\u0026#34;; var qrUrl = $\u0026#34;{url}{qrId}\u0026#34;; //构建扫描二维码状态 var scanQrStatus = new ScanQrStatus(qrId, QrStatus.NotScan, string.Empty); //保存到缓存 var cacheKey = $\u0026#34;{CachesConst.CacheKey_Auth_LoginQr}:{qrId}\u0026#34;; CacheSetOptions cacheOption = new() { Expiration = (int)TimeSpan.FromMinutes(10).TotalSeconds, ExpirationType = ExpirationType.Absolute }; await _cache.SetAsync(cacheKey, scanQrStatus, cacheOption); //返回二维码图片 var qrImg = QRCodeHelper.GenerateQrCodeBase64(qrUrl); return new KeyValuePair\u0026lt;string, string\u0026gt;(qrId, qrImg); } 扫码回调，用户扫码后进入该接口，修改状态以及重定向至微信短链\n此处_wxMpService中封装了调用微信接口的方法，包括token管理，可参考微信官方文档自行封装：获取小程序URL Link\n/// \u0026lt;summary\u0026gt; /// 扫码回调 /// \u0026lt;/summary\u0026gt; /// \u0026lt;returns\u0026gt;\u0026lt;/returns\u0026gt; public async Task\u0026lt;IActionResult\u0026gt; GetScanQrCbAsync([FromQuery] string qrId) { //更新扫描二维码状态 ScanQrStatus scanQrStatus = await GetQrStatusAsync(qrId); if (scanQrStatus.Status != QrStatus.NotScan) Throws.BizException(\u0026#34;二维码已使用，请重新获取\u0026#34;); scanQrStatus.Status = QrStatus.Scanned; var cacheKey = $\u0026#34;{CachesConst.CacheKey_Auth_LoginQr}:{qrId}\u0026#34;; await _cache.SetAsync(cacheKey, scanQrStatus); //获取微信提供的小程序短链接 var path = $\u0026#34;/pages/tabbar/mine/index\u0026#34;; var query = $\u0026#34;addCode={qrId}\u0026#34;; var environmentVersion = \u0026#34;release\u0026#34;; if (appSetting.IsDevMode) { environmentVersion = \u0026#34;develop\u0026#34;; } //请求微信短链接口 var urlResult = await _wxMpService.IUrllinkApi.GenerateUrllinkAsync(new GenerateUrlLinkInput() { Path = path, Query = query, EnvironmentVersion = environmentVersion, ExpireTimestamp = DateTime.Now.AddMinutes(10).ConvertToTimeStamp() }); if (!urlResult.IsSuccess) Throws.BizLogException($\u0026#34;获取小程序短链失败：{urlResult.Message}\u0026#34;); //重定向短链接 var redirectUrl = urlResult.UrlLink; return new RedirectResult(redirectUrl); } 轮询获取扫码状态及登录数据接口\n/// \u0026lt;summary\u0026gt; /// 获取二维码状态 /// \u0026lt;/summary\u0026gt; /// \u0026lt;param name=\u0026#34;qrId\u0026#34;\u0026gt;\u0026lt;/param\u0026gt; /// \u0026lt;returns\u0026gt;\u0026lt;/returns\u0026gt; public async Task\u0026lt;ScanQrStatus\u0026gt; GetQrStatusAsync(string qrId) { var cacheKey = $\u0026#34;{CachesConst.CacheKey_Auth_LoginQr}:{qrId}\u0026#34;; var status = await _cache.GetAsync\u0026lt;ScanQrStatus\u0026gt;(cacheKey); if (status.IsNull()) Throws.BizException(\u0026#34;二维码已失效，请重新获取！\u0026#34;); return status; } 小结 总的来说呢，还是解决了当前问题，建议设计时就考虑到后期扩展的问题，直接将应用添加到微信开放平台，这样就不用那么麻烦了。\n代码中的已封装的部分没有展示，主要是一个解决遇到问题的方案，大部分都能自行实现，若需要微信接口封装代码后期可考虑写篇文章\n","date":"2024-03-04","permalink":"/posts/dotnet/006-%E5%AE%9E%E7%8E%B0%E5%BE%AE%E4%BF%A1%E6%89%AB%E7%A0%81%E7%99%BB%E5%BD%95_%E7%BB%95%E8%BF%87%E5%BE%AE%E4%BF%A1%E5%BC%80%E5%8F%91%E5%B9%B3%E5%8F%B0/","section":"Posts","summary":"","title":"C#实现微信扫码登录（绕过微信开发平台）"},{"content":"概述 最近在工作中要开发一个微信支付的功能，项目比较急，第一反应就是去官方文档找SDK，发现官方只提供了JAVA、PHP、Go的SDK。（对C#玩家真不友好！=_=!）。好在微信支付的开发者社区有位大哥fudiwei (RHQYZ) (github.com)提供了C#版的SDK，项目地址：DotNetCore.SKIT.FlurlHttpClient.Wechat，果断Install一下。\nSDK简介 SKIT.FlurlHttpClient.Wechat是基于 Flurl.Http 的微信 HTTP API SDK，目前已包含公众平台、开放平台、商户平台、企业微信、广告平台、对话开放平台等模块。\n看了一下文档，已经把微信已知的API都封装了，而且更新的还比较快，github上作者Issues回复也很快，还是比较稳定，完全够用啦。\n接入使用 我这里只用到了JSAPI支付，其他的需求可查看项目文档，都有详细介绍。\n准备工作 申请商户号 开启V3 api权限 下载密钥和序列号 配置文件 我是新建了一个叫tenpay_setting.json的配置文件，也可以直接加到appsetting.json\n{ \u0026#34;Tenpay\u0026#34;: { \u0026#34;Merchants\u0026#34;: [ { \u0026#34;MerchantId\u0026#34;: \u0026#34;********\u0026#34;, //商户号 \u0026#34;SecretV3\u0026#34;: \u0026#34;*********\u0026#34;, //V3版本Api的Secret \u0026#34;CertificateSerialNumber\u0026#34;: \u0026#34;*********\u0026#34;,//证书序列号 \u0026#34;CertificatePrivateKey\u0026#34;: \u0026#34;apiclient_key.pem\u0026#34;//api key 可以直接将内容复制上来，我嫌太长，用的路径 } ], \u0026#34;NotifyUrl\u0026#34;: \u0026#34;http://www.host.com/api/notify/wx/{merchant_id}/pay\u0026#34; } } 请求客户端创建 项目文档上是在多租户的情况下使用factory创建，我这里没有分租户。\n/// \u0026lt;summary\u0026gt; /// 创建微信支付客户端 /// \u0026lt;/summary\u0026gt; /// \u0026lt;returns\u0026gt;\u0026lt;/returns\u0026gt; /// \u0026lt;exception cref=\u0026#34;Exception\u0026#34;\u0026gt;\u0026lt;/exception\u0026gt; public WechatTenpayClient CreateClient(string merchantId) { var tenpayMerchantConfig = GetDefaultMerchant(merchantId); var options = new WechatTenpayClientOptions() { //商户号 MerchantId = tenpayMerchantConfig.MerchantId, //v3Secret MerchantV3Secret = tenpayMerchantConfig.SecretV3, //证书序列号 MerchantCertificateSerialNumber = tenpayMerchantConfig.CertificateSerialNumber, //私钥 MerchantCertificatePrivateKey = GetCertPrivateKey(tenpayMerchantConfig.CertificatePrivateKey), //证书管理器 PlatformCertificateManager = _redisCertificateManager, //开启自动加密 AutoEncryptRequestSensitiveProperty = true, //开启自动解密 AutoDecryptResponseSensitiveProperty = true }; var wechatTenpayClient = WechatTenpayClientBuilder.Create(options).Build(); return wechatTenpayClient; } 读取私钥内容 /// \u0026lt;summary\u0026gt; /// 读取私钥内容 /// \u0026lt;/summary\u0026gt; /// \u0026lt;param name=\u0026#34;path\u0026#34;\u0026gt;\u0026lt;/param\u0026gt; /// \u0026lt;returns\u0026gt;\u0026lt;/returns\u0026gt; /// \u0026lt;exception cref=\u0026#34;BizException\u0026#34;\u0026gt;\u0026lt;/exception\u0026gt; private static string GetCertPrivateKey(string path) { try { // 私钥不包括私钥文件起始的-----BEGIN PRIVATE KEY----- // 亦不包括结尾的 -----END PRIVATE KEY----- var certPath = path; var certPrivateKey = File.ReadAllText(certPath); //以前的版本需要移出这两行，现在好像不需要了 //certPrivateKey = certPrivateKey.Replace(\u0026#34;-----BEGIN PRIVATE KEY-----\u0026#34;, string.Empty); //certPrivateKey = certPrivateKey.Replace(\u0026#34;-----END PRIVATE KEY-----\u0026#34;, string.Empty); //certPrivateKey = certPrivateKey.Trim(); return certPrivateKey; } catch (Exception ex) { throw new BizException($\u0026#34;获取证书内容失败！：{ex.Message}\u0026#34;); } } 注意，新版本中不需要处理-----BEGIN PRIVATE KEY-----和-----END PRIVATE KEY-----，否则会出现Private key format is not supported.\n自动刷新证书 项目simple中是使用内存管理证书，我这项目需要其他微服务使用，就用redis缓存管理\n/// \u0026lt;summary\u0026gt; /// redis缓存管理证书 /// \u0026lt;/summary\u0026gt; public class RedisCertificateManager : ICertificateManager, ITransient { private const string REDIS_KEY_PREFIX = \u0026#34;wxpaypc-\u0026#34;; private ICacheManager _cache; /// \u0026lt;summary\u0026gt; /// 构造 /// \u0026lt;/summary\u0026gt; /// \u0026lt;param name=\u0026#34;cacheManager\u0026#34;\u0026gt;\u0026lt;/param\u0026gt; public RedisCertificateManager(ICacheManager cacheManager) { _cache = cacheManager; } private string GenerateRedisKey(string serialNumber) { return $\u0026#34;{REDIS_KEY_PREFIX}{serialNumber}\u0026#34;; } /// \u0026lt;summary\u0026gt; /// 获得所有证书 /// \u0026lt;/summary\u0026gt; /// \u0026lt;returns\u0026gt;\u0026lt;/returns\u0026gt; public IEnumerable\u0026lt;CertificateEntry\u0026gt; AllEntries() { var certs = _cache.GetByPrefix\u0026lt;CertificateEntry\u0026gt;($\u0026#34;{REDIS_KEY_PREFIX}*\u0026#34;) ?? new Dictionary\u0026lt;string, CertificateEntry\u0026gt;(); if (certs.Any()) { return certs .Select(t =\u0026gt; t.Value) .ToArray(); } return Array.Empty\u0026lt;CertificateEntry\u0026gt;(); } /// \u0026lt;summary\u0026gt; /// 添加证书 /// \u0026lt;/summary\u0026gt; /// \u0026lt;param name=\u0026#34;entry\u0026#34;\u0026gt;\u0026lt;/param\u0026gt; public void AddEntry(CertificateEntry entry) { string key = GenerateRedisKey(entry.SerialNumber); _cache.Set(key, entry); } /// \u0026lt;summary\u0026gt; /// 获得最新的证书 /// \u0026lt;/summary\u0026gt; /// \u0026lt;param name=\u0026#34;serialNumber\u0026#34;\u0026gt;\u0026lt;/param\u0026gt; /// \u0026lt;returns\u0026gt;\u0026lt;/returns\u0026gt; public CertificateEntry? GetEntry(string serialNumber) { string key = GenerateRedisKey(serialNumber); var values = AllEntries(); if (values.Any()) { return values.OrderByDescending(a =\u0026gt; a.ExpireTime).FirstOrDefault(); } return null; } /// \u0026lt;summary\u0026gt; /// 移出一个证书 /// \u0026lt;/summary\u0026gt; /// \u0026lt;param name=\u0026#34;serialNumber\u0026#34;\u0026gt;\u0026lt;/param\u0026gt; /// \u0026lt;returns\u0026gt;\u0026lt;/returns\u0026gt; public bool RemoveEntry(string serialNumber) { string key = GenerateRedisKey(serialNumber); _cache.Remove(key); return true; } } Worker干的事，为了以后可能出现多商户，也搞了循环商户数组\nwhile (!stoppingToken.IsCancellationRequested) { foreach (var tenpayMerchantOptions in _tenpayOptions.Merchants) { try { const string ALGORITHM_TYPE = \u0026#34;RSA\u0026#34;; var client = _tenpayService.CreateClient(tenpayMerchantOptions.MerchantId); var request = new QueryCertificatesRequest() { AlgorithmType = ALGORITHM_TYPE }; var response = await client.ExecuteQueryCertificatesAsync(request, cancellationToken: stoppingToken); if (response.IsSuccessful()) { // NOTICE: // 如果构造 Client 时启用了 `AutoDecryptResponseSensitiveProperty` 配置项，则无需再执行下面一行的手动解密方法： //response = client.DecryptResponseSensitiveProperty(response); foreach (var certificate in response.CertificateList) { client.PlatformCertificateManager.AddEntry(CertificateEntry.Parse(ALGORITHM_TYPE, certificate)); } _logger.LogInformation(\u0026#34;刷新微信商户平台证书成功。\u0026#34;); } else { _logger.LogWarning( \u0026#34;刷新微信商户平台证书失败（状态码：{0}，错误代码：{1}，错误描述：{2}）。\u0026#34;, response.GetRawStatus(), response.ErrorCode, response.ErrorMessage ); } } catch (Exception ex) { _logger.LogError(ex, \u0026#34;刷新微信商户平台证书遇到异常。\u0026#34;); } } await Task.Delay(TimeSpan.FromDays(1), stoppingToken); // 每隔 1 天轮询刷新 } 创建订单 /// \u0026lt;summary\u0026gt; /// 通过Jsapi创建订单 /// \u0026lt;/summary\u0026gt; /// \u0026lt;param name=\u0026#34;requestModel\u0026#34;\u0026gt;\u0026lt;/param\u0026gt; /// \u0026lt;returns\u0026gt;\u0026lt;/returns\u0026gt; public async Task\u0026lt;CreatePayTransactionJsapiResponse\u0026gt; CreateOrderByJsapi(CreateOrderByJsapiRequest requestModel) { var client = CreateClient(requestModel.MerchantId); var notifyUrl = _tenpayOptions.NotifyUrl; if (notifyUrl.Contains(\u0026#34;{merchant_id}\u0026#34;)) notifyUrl = notifyUrl.Replace(\u0026#34;{merchant_id}\u0026#34;, requestModel.MerchantId); var amount = Convert.ToInt32(requestModel.Amount * 100); var request = new CreatePayTransactionJsapiRequest() { OutTradeNumber = requestModel.OutTradeNumber, AppId = requestModel.AppId, Description = requestModel.Description, NotifyUrl = notifyUrl, Amount = new CreatePayTransactionJsapiRequest.Types.Amount() { Total = amount }, Payer = new CreatePayTransactionJsapiRequest.Types.Payer() { OpenId = requestModel.OpenId } }; CreatePayTransactionJsapiResponse response = await client.ExecuteCreatePayTransactionJsapiAsync(request); if (!response.IsSuccessful()) { throw new( $\u0026#34;JSAPI 下单失败（状态码：{response.GetRawStatus()}，错误代码：{response.ErrorCode}，错误描述：{response.ErrorMessage}）。\u0026#34; ); } return response; } 生成客户端参数 拿着下单后的PrepayId就可以去获取客户端所需参数了\n/// \u0026lt;summary\u0026gt; /// 生成客户端 JSAPI / 小程序调起支付所需的参数字典 /// \u0026lt;/summary\u0026gt; /// \u0026lt;returns\u0026gt;\u0026lt;/returns\u0026gt; public IDictionary\u0026lt;string, string\u0026gt; GetParametersForJsapiPayRequest(ParametersForJsapiPayRequest requestModel) { try { // 创建客户端 var client = CreateClient(requestModel.MerchantId); var res = client.GenerateParametersForJsapiPayRequest(requestModel.AppId, requestModel.PrepayId); var ret = new Dictionary\u0026lt;string, string\u0026gt;(res) { // 加上生成的业务单号，方便查询订单 { \u0026#34;billno\u0026#34;, requestModel.BillNo } }; return ret; } catch (Exception ex) { throw new(\u0026#34;生成客户端 JSAPI / 小程序调起支付所需的参数字典异常\u0026#34;, ex); } } 回调方法 /// \u0026lt;summary\u0026gt; /// 微信支付回调 /// \u0026lt;/summary\u0026gt; /// \u0026lt;param name=\u0026#34;merchantId\u0026#34;\u0026gt;\u0026lt;/param\u0026gt; /// \u0026lt;param name=\u0026#34;timestamp\u0026#34;\u0026gt;\u0026lt;/param\u0026gt; /// \u0026lt;param name=\u0026#34;nonce\u0026#34;\u0026gt;\u0026lt;/param\u0026gt; /// \u0026lt;param name=\u0026#34;signature\u0026#34;\u0026gt;\u0026lt;/param\u0026gt; /// \u0026lt;param name=\u0026#34;serialNumber\u0026#34;\u0026gt;\u0026lt;/param\u0026gt; /// \u0026lt;returns\u0026gt;\u0026lt;/returns\u0026gt; [HttpPost] [Route(\u0026#34;wx/{merchant_id}/pay\u0026#34;)] public async Task\u0026lt;IActionResult\u0026gt; ReceiveMessage( [FromRoute(Name = \u0026#34;merchant_id\u0026#34;)] string merchantId, [FromHeader(Name = \u0026#34;Wechatpay-Timestamp\u0026#34;)] string timestamp, [FromHeader(Name = \u0026#34;Wechatpay-Nonce\u0026#34;)] string nonce, [FromHeader(Name = \u0026#34;Wechatpay-Signature\u0026#34;)] string signature, [FromHeader(Name = \u0026#34;Wechatpay-Serial\u0026#34;)] string serialNumber) { using var reader = new StreamReader(App.HttpContext.Request.Body, Encoding.UTF8); string content = await reader.ReadToEndAsync(); //_logger.LogInformation(\u0026#34;接收到微信支付推送的数据：{0}\u0026#34;, content); var client = _tenPayService.CreateClient(merchantId); bool valid = client.VerifyEventSignature( webhookTimestamp: timestamp, webhookNonce: nonce, webhookBody: content, webhookSignature: signature, webhookSerialNumber: serialNumber ); if (!valid) { // NOTICE: // 需提前注入 CertificateManager、并下载平台证书，才可以使用扩展方法执行验签操作。 // 请参考本示例项目 TenpayCertificateRefreshingBackgroundService 后台任务中的相关实现。 // 有关 CertificateManager 的完整介绍请参阅《开发文档 / 基础用法 / 如何验证回调通知事件签名？》。 // 后续如何解密并反序列化，请参阅《开发文档 / 基础用法 / 如何解密回调通知事件中的敏感数据？》。 //记录验签失败日志 await ExceptionLogHelper.WriteRequestToLogAsync(_logger, App.HttpContext, new Exception(\u0026#34;微信支付验签失败\u0026#34;), App.Configuration); return new JsonResult(new { code = \u0026#34;FAIL\u0026#34;, message = \u0026#34;验签失败\u0026#34; }); } WechatTenpayEvent callbackModel = client.DeserializeEvent(content); var eventType = callbackModel.EventType?.ToUpper(); //解密数据 TransactionResource callbackResource = client.DecryptEventResource\u0026lt;TransactionResource\u0026gt;(callbackModel); switch (eventType) { case \u0026#34;TRANSACTION.SUCCESS\u0026#34;: { //_logger.LogInformation(\u0026#34;接收到微信支付推送的订单支付成功通知，商户订单号：{0}\u0026#34;, callbackResource.OutTradeNumber); // 成功情况 _vipService.DoPaySuccessOp(callbackResource, callbackModel); } break; default: { if (callbackResource.IsNull()) { //记录其他情况日志 await ExceptionLogHelper.WriteRequestToLogAsync(_logger, App.HttpContext, new Exception(\u0026#34;微信支付通知失败\u0026#34;), App.Configuration); } // 其他情况默认失败 await _vipService.DoPayFailureOpAsync($\u0026#34;回调发生错误：{callbackModel.Summary}\u0026#34;, callbackResource.OutTradeNumber, callbackModel.CreateTime.ToDate(), JToken.FromObject(callbackModel)); } break; } return new JsonResult(new { code = \u0026#34;SUCCESS\u0026#34;, message = \u0026#34;成功\u0026#34; }); } 至此就ok啦，业务处理的代码就不放了，根据自己的情况编写。\n其他 前端 本地测试时，支付用的时wx官方提供的小程序示例，其中有个模块叫接口能力——发起支付，改改提交方法即可\nwx.requestPayment ( { \u0026#34;appId\u0026#34;: \u0026#34;**********\u0026#34;, \u0026#34;timeStamp\u0026#34;: \u0026#34;1708416150\u0026#34;, \u0026#34;nonceStr\u0026#34;: \u0026#34;\u0026#34;**********\u0026#34;,\u0026#34;, \u0026#34;package\u0026#34;: \u0026#34;prepay_id=\u0026#34;**********\u0026#34;,\u0026#34;, \u0026#34;signType\u0026#34;: \u0026#34;RSA\u0026#34;, \u0026#34;paySign\u0026#34;: \u0026#34;\u0026#34;**********\u0026#34;,\u0026#34;, \u0026#34;billno\u0026#34;: \u0026#34;cz_517123880726597\u0026#34;, \u0026#34;success\u0026#34;:function(res){}, \u0026#34;fail\u0026#34;:function(res){}, \u0026#34;complete\u0026#34;:function(res){} } ) 回调测试 如果没有测试服务器，像我一样，可以使用natapp内网穿透，如果在公司测试的话开个内网穿透就ok了\n如果有其他啥好方法也可以分享一下！\n","date":"2024-02-23","permalink":"/posts/dotnet/005-%E4%BD%BF%E7%94%A8csharp%E7%89%88%E7%AC%AC%E4%B8%89%E6%96%B9%E5%BE%AE%E4%BF%A1sdk%E9%9B%86%E6%88%90%E5%BE%AE%E4%BF%A1%E6%94%AF%E4%BB%98/","section":"Posts","summary":"","title":"使用C#版第三方微信SDK集成微信支付"},{"content":"","date":null,"permalink":"/tags/aginx/","section":"Tags","summary":"","title":"Aginx"},{"content":"","date":null,"permalink":"/tags/linux/","section":"Tags","summary":"","title":"Linux"},{"content":"","date":null,"permalink":"/categories/linux/","section":"Categories","summary":"","title":"Linux"},{"content":"","date":null,"permalink":"/tags/ssl/","section":"Tags","summary":"","title":"SSL"},{"content":"使用acme.sh配置SSL证书并自动续签 参考官网\nHome · acmesh-official/acme.sh Wiki (github.com)\nacme.sh用于生成免费的ssl证书，其完整实现了acme协议，并且由纯Shell脚本语言编写，没有过多的依赖项，安装和使用都非常方便。\n支持多个ssl签发平台，如Let\u0026rsquo;s Encrypt (letsencrypt.org)、Free SSL Certificates and SSL Tools - ZeroSSL\n本文使用Let\u0026rsquo;s Encrypt\n准备环境： 域名 Linux版本Ubuntu22.04 代理服务器Nginx1.18.0 acem.sh 下载安装acme.sh curl https://get.acme.sh | sh -s email=my@example.com 使用这个安装干了些啥\n部署acme.sh程序到用户文件夹\n为脚本创建新名称 acme.sh=~/.acme.sh/acme.sh\n需重启终端，不想重启的话 .acme.sh# alias acme.sh=~/.acme.sh/acme.sh\n创建一个每日定时任务，用于自动更新即将过期的证书\n通过crontab -l 查看：\n18 18 * * * \u0026#34;/root/.acme.sh\u0026#34;/acme.sh --cron --home \u0026#34;/root/.acme.sh\u0026#34; \u0026gt; /dev/null 安装后目录\naccount.conf acme.sh acme.sh.env deploy dnsapi http.header notify 具体命令可查看官网\n使用acme.sh配置ssl证书 直接指向项目 acme.sh --issue -d example.com -d test.com -w /var/www/project -d参数指定域名，多个域名使用多个-d参数，第一个-d参数指定的域名即证书的主体名称，其它-d参数指定的域名为证书的可选主体名称。\n-w参数指定的是域名的webroot目录，以example.com为例，-w参数的值即是http://example.com对应的webroot目录。如果使用多个-d参数同时指定了多个域名，则所有这些域名必须对应同一个webroot目录；另外，当前系统用户必须具有webroot目录的写入权限。\nDNS自动模式（建议） 此方法会向域名解析平台添加一个TXT记录值，需要提供对应平台的Key和Secret，具体参考acme.sh/dnsapi/dns_ali.sh at master · acmesh-official/acme.sh (github.com) 我使用的是dns_ali。\n#切换默认平台为letsencrypt 其他更换最后平台即可 acme.sh --set-default-ca --server letsencrypt 默认的签发平台是ZeroSSL，我使用的时候想着就用默认的平台，可是一直报错 et authz objec with invalid status, please try again later. github上有人说是ZeroSSL的dns身份认证出问题了。This domain won\u0026rsquo;t issue · Issue #4991 · acmesh-official/acme.sh (github.com) 所以我换成了用letsencrypt\n获取阿里云的Key和Secret，添加到系统环境变量\nexport Ali_Key=\u0026#34;*******************\u0026#34; export Ali_Secret=\u0026#34;*********************\u0026#34; 生成证书\nacme.sh --issue --dns dns_ali -d example.com -d www.example.com 该命令从系统变量中读取aliyun的api授权ID和密码，并通过dns_ali参数指定DNS提供商为阿里云。该命令将通过api自动为指定域名添加txt记录，并在验证完毕后自动移除txt记录。\naliyun的api授权ID和密码将被保存在.acme.sh的账户配置文件中，以供将来自动更新证书时使用，存储位置为：\n~/.acme.sh/account.conf\n通过 --install-cert部署证书\nacme.sh --install-cert -d *.example.com \\ --key-file /etc/nginx/ssl/in/example.com.key \\ --fullchain-file /etc/nginx/ssl/fullchain.cer \\ --reloadcmd \u0026#34;nginx -s reload\u0026#34; 使用 acme.sh --list查看证书列表\nMain_Domain KeyLength SAN_Domains CA Created Renew *.ihuadz.top \u0026#34;ec-256\u0026#34; ihuadz.top LetsEncrypt.org 2024-02-18T04:06:07Z 2024-04-17T04:06:07Z Nginx 部署 在项目对应的Nginx配置文件中添加\nserver { listen 443 ssl; server_name blog.ihuadz.top; root /var/www/hexo; ssl_certificate \u0026#34;/etc/nginx/ssl/ihuadz.top/fullchain.cer\u0026#34;; ssl_certificate_key \u0026#34;/etc/nginx/ssl/ihuadz.top/*.ihuadz.top.key\u0026#34;; ssl_session_cache shared:SSL:1m; ssl_session_timeout 10m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; } 访问https://blog.ihuadz.top ok。没问题\n不同的项目配置不同的域名和路径即可，使用泛域名的话就不用重新申请证书\n","date":"2024-02-18","permalink":"/posts/linux/001-%E4%BD%BF%E7%94%A8acme.sh%E8%87%AA%E5%8A%A8%E7%BB%AD%E7%AD%BEssl%E8%AF%81%E4%B9%A6/","section":"Posts","summary":"","title":"使用acme.sh配置ssl证书并自动续签"},{"content":"","date":null,"permalink":"/tags/efcore/","section":"Tags","summary":"","title":"EFCore"},{"content":"","date":null,"permalink":"/categories/efcore/","section":"Categories","summary":"","title":"EFCore"},{"content":"本系列文章参考微软官方文档：Entity Framework Core | Microsoft Learn；\nEntity Framework(Ef) Core是轻量化、可扩展、开源和跨平台的常用Entity Framework数据访问技术。\nEF Core可用作对象关系映射程序（O/RM），这可以实现以下两点：\n使.NET开发人员能够使用.NET对象处理数据库。 无需再像通常那样编写大部分数据访问代码。 EF Core 支持多个数据库引擎。\n","date":"2024-02-01","permalink":"/posts/ef_core/01-efcore%E6%A6%82%E8%BF%B0/","section":"Posts","summary":"","title":"EFCore概述"},{"content":"本篇文章参考微软官方文章使用 Nginx 在 Linux 上托管 ASP.NET Core\n准备环境 Linux版本Ubuntu22.04 .Net运行时.NetRutime8.0 代理服务器Nginx1.18.0 安装.Net # 运行时 sudo apt-get update \u0026amp;\u0026amp; \\ sudo apt-get install -y dotnet-runtime-8.0 # Sdk sudo apt install dotnet-sdk-8.0 安装sdk时可能会出现找不到包的情况，这时使用官方的安装脚本即可\n# 下载脚本 wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh # 修改脚本权限 chmod +x ./dotnet-install.sh # 安装最新版本，若需要LTS删除--version latest ./dotnet-install.sh --version latest #runtime也可以用脚本安装 ./dotnet-install.sh --version latest --runtime aspnetcore # 查看dotnet版本 dotnet --info 部署Asp.Net项目 本地运行OK后发布上传到服务器，这里我使用的时XFtp，和XShell一套挺好用的；\n测试应用：\n注意：当NETCORE的版本低于3.0时，对应的命令为\ndotnet web.dll --server.urls=\u0026quot;http://localhost:7102\u0026quot;\n当NETCORE高于等于3.0版本时\n--urls \u0026quot;http://localhost:7102\u0026quot;\n# Linux进入文件目录运行应用 dotnet RazorPageDemo.dll --urls \u0026#34;http://localhost:7102\u0026#34; 配置nginx nginx默认的配置路径/etc/nginx/conf.d\n新建文件rdemo.conf并添加内容\nserver{ listen 7102; server_name *.exmple.com; location /{ proxy_pass http://127.0.0.1:7202/; } } 重新加载nginx配置文件nginx -s reload\n访问页面\n启动后台守护程序 ctrl+c停止程序运行\n创建服务定义文件：\nsudo nano /etc/systemd/system/kestrel-rdemo.service 以下示例是应用的一个 .ini 服务文件：\n[Unit] Description=Example .NET Web API App running on Linux [Service] WorkingDirectory=/var/www/razorpagedemo ExecStart=/usr/bin/dotnet /var/www/razorpagedemo/RazorPageDemo.dll --urls \u0026#34;http://localhost:7202\u0026#34; Restart=always # Restart service after 10 seconds if the dotnet service crashes: RestartSec=10 KillSignal=SIGINT SyslogIdentifier=razorpagedemo User=www-data Environment=ASPNETCORE_ENVIRONMENT=Production Environment=DOTNET_NOLOGO=false [Install] WantedBy=multi-user.target #启动服务 sudo systemctl enable kestrel-rdemo.service #运行 sudo systemctl start kestrel-rdemo.service #查看状态 sudo systemctl status kestrel-rdemo.service OK，访问没问题就可以了，云服务器的话记得打开端口\n","date":"2024-01-26","permalink":"/posts/dotnet/004-%E5%9C%A8linux%E4%B8%8A%E9%83%A8%E7%BD%B2asp.net-core%E5%BA%94%E7%94%A8/","section":"Posts","summary":"","title":"在Linux上使用Nginx部署ASP.NET Core应用"},{"content":"","date":null,"permalink":"/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8/","section":"Tags","summary":"","title":"服务器"},{"content":"起因 事情是这样的，收到一条阿里云的短信，说是云服务器99元一年，这配置得心动一下啊；\n之前不是在国外域名商namesilo买了域名嘛，刚好买一台Linux拿来练练手。\n购买服务器 开开心心付了款，等待分配实例。OK，成功登录上熟练的sudo apt update \u0026amp;\u0026amp; sudo apt upgrade；没问题，一切都正常。好了，开始配置nginx，把我的博客给上传，配置好域名。\n不错，正常访问\n关于博客搭建可以参看我的第一篇博客https://gblog.ihuadz.top\n出问题了 下班回家，想继续看一下还能不能正常访问，毕竟用的是国外域名商，果然：\n好了，那就备案呗；疯狂填信息，哦豁！需要实名域名。。。\n咋整，namesilo又不提供实名服务。没办法，只能在阿里云重新买一个域名了\n对了，这里用的都是gblog.ihuadz.top域名，部署到了github page上，blog.ihuadz.top暂时用不了是因为服务器需要等域名实名后2-3天才能备案\n总结 服务器和域名，要么全国内，要么全国外\n自己玩又有购买能力的话，全国外吧，毕竟实名认证挺麻烦\n怕封ip又想向我一样贪小便宜就老老实实玩全国内吧\n","date":"2024-01-25","permalink":"/posts/essays/001-%E4%B8%80%E6%AC%A1%E8%B4%AD%E4%B9%B0%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%9A%84%E7%BB%8F%E5%8E%86/","section":"Posts","summary":"","title":"一次购买服务器的经历"},{"content":"","date":null,"permalink":"/tags/%E8%8B%B1%E8%AF%AD/","section":"Tags","summary":"","title":"英语"},{"content":"","date":null,"permalink":"/categories/%E8%8B%B1%E8%AF%AD/","section":"Categories","summary":"","title":"英语"},{"content":" ","date":"2023-10-30","permalink":"/posts/english/001-%E8%8B%B1%E8%AF%AD%E8%AF%AD%E6%B3%95%E6%9E%B6%E6%9E%84/","section":"Posts","summary":"","title":"英语语法架构"},{"content":" 时间\\时态 简单时态 进行时态 完成时态 完成进行时态 现在 She goes shopping every Saturday. (她每个星期六去购物。) She is going shopping right now. (她正在去购物。) She has already gone shopping. (她已经去购物了。) She has been going shopping for two hours. (她已经去购物两个小时了。) 过去 She went shopping last Sunday. (她上个星期天去购物了。) She was going shopping when I called. (我给她打电话时，她正在去购物。) She had already gone shopping when her friends arrived. (她的朋友到达时，她已经去购物了。) She had been going shopping for two hours before the mall closed. (购物中心关门前，她已经去购物两个小时了。) 将来 She will go shopping tomorrow. (她明天会去购物。) She will be going shopping at this time tomorrow. (明天这个时候，她将在去购物的路上。) She will have gone shopping by the time you arrive. (到你到达的时候，她会已经去购物了。) She will have been going shopping for three hours by 5 PM. (到下午5点，她将已经去购物三个小时了。) 过去将来 She said she would go shopping last weekend, but she couldn\u0026rsquo;t. (她说上个周末会去购物，但她没去成。) She said she would be going shopping at this time last Saturday. (她说上个星期六这个时候会去购物。) She said she would have gone shopping before the store closed. (她说店关门前会去购物。) She said she would have been going shopping for two hours by the time her friends arrived. (她说她的朋友到达时，她已经去购物两个小时了。) ","date":"2023-08-02","permalink":"/posts/english/002-%E5%8A%A8%E8%AF%8D%E6%97%B6%E6%80%81%E4%BE%8B%E5%8F%A5/","section":"Posts","summary":"","title":"英语动词时态例句"},{"content":"因为要上班，学习的时候总是东一榔头西一斧头的，导致啥啥都学不好，还经常忘记，本篇文章暂拟一个计划，深入学习DotNET。由于是空余时间学习，就按照这个计划推进吧，在学习过程中逐渐优化，加油吧！\n计划 确定学习目标：\n了解.Net平台的概念和组成部分\n学习 C# 编程语言。\n掌握 ASP.NET Core 开发。\n学习使用 Entity Framework Core 进行数据库开发。\n掌握常用的 .NET 开发工具和技术。\n学习基础知识：\n学习 C# 编程语言基础，包括语法、数据类型、控制流等。 了解面向对象编程（OOP）的基本概念和原则。 学习使用 Visual Studio 或者 Visual Studio Code 进行 .NET 开发。 深入学习 .NET 平台和核心概念：\n了解 .NET 平台的历史和演变。 学习 .NET 标准库和常用的命名空间。（太多了，持续学习中\u0026hellip;） 了解 .NET Core 和 .NET Framework 的区别和共同点。(最大区别，跨平台..) 学习 ASP.NET Core 开发：\n了解 ASP.NET Core 的基本概念和架构。 学习使用 MVC（模型-视图-控制器）或者 Razor Pages 进行 Web 开发。 掌握路由、中间件、模型绑定、身份验证和授权等核心功能。 学习前端开发技术，如 HTML、CSS 和 JavaScript。 数据库开发：\n学习使用 Entity Framework Core 进行数据库访问和操作。 掌握数据库迁移和代码优先开发的概念和实践。 了解常见的数据库管理系统，如 SQL Server、MySQL 或者 PostgreSQL。 学习资源和实践：\n阅读官方文档和教程，如 Microsoft 的官方文档和 ASP.NET Core 文档。 参加在线课程或者培训班，如 Microsoft Learn 或者 Pluralsight 提供的相关课程。 参与开发社区和论坛，与其他开发者交流和分享经验。 尝试编写小型项目或者参与开源项目，实践所学知识。 学习开源框架研究源码\n学习研究公司框架源码 学习研究Furion框架 学习研究FreeSql框架-与EFCore比较 持续学习和更新：\n订阅 .NET 相关的博客、新闻和邮件列表，了解最新的技术动态。 参与技术会议和线下活动，与其他开发者面对面交流和学习。 持续关注 .NET 社区的发展和新兴技术，不断扩展自己的知识面。 ","date":"2023-05-31","permalink":"/posts/dotnet/001-dotnet%E5%AD%A6%E4%B9%A0%E8%AE%A1%E5%88%92/","section":"Posts","summary":"","title":".NET学习计划"},{"content":".NET 平台是一个跨平台的软件开发平台，由微软公司开发和维护。它提供了一个统一的环境和工具集，用于开发和执行各种类型的应用程序，包括桌面应用程序、Web 应用程序、移动应用程序、云服务等。下面是.NET 平台的概念和组成部分的介绍：\nCommon Language Runtime (CLR) CLR 是.NET 平台的核心组件之一，它提供了应用程序的执行环境。CLR 负责将.NET 程序编译为中间语言（IL）并执行，同时处理内存管理、安全性、异常处理等运行时任务。\n.NET Framework .NET Framework 是最早引入的.NET 平台版本，它是一个用于构建 Windows 应用程序的完整开发框架。它包括了大量的类库、工具和运行时环境，支持多种编程语言（如C#、VB.NET 和 F#）。\n.NET Core .NET Core 是.NET 平台的开源版本，它是跨平台的，可以在 Windows、Linux 和 macOS 等操作系统上运行。.NET Core 面向云和容器化应用开发，具有更轻量级的体积和更高的性能。\nASP.NET ASP.NET 是.NET 平台的 Web 开发框架，用于构建动态的、可扩展的 Web 应用程序。它提供了一组强大的工具和功能，包括模型-视图-控制器（MVC）模式、Web API、身份验证、授权等。\nEntity Framework Entity Framework 是.NET 平台的对象关系映射（ORM）框架，用于简化与数据库的交互和数据持久化。它提供了一种面向对象的方式来操作数据库，支持多种数据库管理系统。\n编程语言 .NET 平台支持多种编程语言，包括C#、VB.NET、F#等。C# 是.NET 平台的主要语言之一，它是一种现代、面向对象的编程语言，用于开发各种类型的应用程序。\n开发工具 在.NET 平台上开发应用程序时，常用的开发工具包括 Visual Studio 和 Visual Studio Code。Visual Studio 是一个强大的集成开发环境（IDE），提供了丰富的功能和工具，简化开发过程。\nNuGet NuGet 是.NET 平台的软件包管理系统，用于方便地添加、更新和管理项目所依赖的第三方库和工具。\n","date":"2023-05-30","permalink":"/posts/dotnet/002-dotnet%E5%B9%B3%E5%8F%B0%E7%9A%84%E6%A6%82%E5%BF%B5%E5%92%8C%E7%BB%84%E6%88%90%E9%83%A8%E5%88%86/","section":"Posts","summary":"","title":".Net平台的概念和组成部分"},{"content":"面向对象编程（Object-Oriented Programming，简称OOP）是一种常用的编程范式，它以对象为基本单位，通过封装、继承和多态等概念来组织和构建程序。\n在面向对象编程中，一个对象是由数据（属性）和操作（方法）组成的实体。对象可以互相通信和交互，通过调用彼此的方法来实现各种功能。以下是面向对象编程中的一些关键概念：\n类（Class） 类是对象的模板或蓝图，描述了对象的属性和方法。它定义了对象共享的特征和行为。类是创建对象的基础，可以看作是对象的类型。\n对象（Object） 对象是类的实例，具体的实体。它具有类定义的属性和方法，并且可以进行特定的操作。每个对象都是独立的，拥有自己的状态。\n封装（Encapsulation） 封装是将数据和方法组合在一个单元内，对外部隐藏对象的内部实现细节。通过封装，对象的内部状态和实现细节对外部是不可见的，只有通过公开的接口（方法）来访问和操作对象。\n继承（Inheritance） 继承是一种通过扩展现有类来创建新类的机制。子类可以继承父类的属性和方法，并可以新增或修改其行为。继承使得代码重用变得更加容易，同时可以建立类之间的层次关系。\n多态（Polymorphism） 多态是指在父类的引用中使用子类的对象。多态允许使用统一的接口来处理不同的对象，通过动态绑定，可以在运行时确定要调用的方法。\n面向对象编程的优点 模块化：通过封装和抽象，代码可以被组织成独立的模块，便于理解和维护。 代码重用：通过继承和多态，可以有效地重用已有的代码，减少重复编写相似功能的工作。 可扩展性：面向对象编程提供了良好的扩展性，可以通过新增类和修改现有类来扩展程序的功能。 更易于理解：面向对象编程提供了一种自然的、类似现实世界的抽象方式，使得代码更易于理解和交流。 ","date":"2023-05-30","permalink":"/posts/dotnet/003-dotnet%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%BC%96%E7%A8%8B/","section":"Posts","summary":"","title":" .NET面向对象编程"},{"content":"","date":null,"permalink":"/tags/%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA/","section":"Tags","summary":"","title":"博客搭建"},{"content":" 已迁移至Hugo 第一篇博客也不知道写点啥，就先写一下如何建站的吧！\n首先，为啥要弄这个博客呢？\n一个程序员的好奇心\n真的想学点东西\n装x\n就是这几点原因造就了这个站点，下面说一下技术吧。\nHexo 这就不用多说了，点击快速建站Hexo。这是我接触到的第一个博客框架，没用过其他的，感觉上手还是挺简单的，官网介绍的比较详细，轻松运行。\n主题 我用的是Fluid，点击Fluid官方介绍 ，各主题应该大同小异可去Hexo官网选择。\n站点计数与评论 计数和评论放在一起是因为都用到了LeanCloud，一个基于Docker的云计算平台，分国内版和海外版，我用的是海外版，因为不需要实名。计数和评论都调用了此平台的API，网上有很多其他博主写的详细教程。\n评论功能还用到了Vercel，一个网站托管服务，类似GitHub Pages，主要用它部署了Waline，一款简洁安全的评论系统，挺牛的系统，登录、回复、表情、图片、管理评论啥啥都有。\n域名 之前申请过阿里、腾讯的域名，那一个麻烦申请过的朋友应该都清楚，也是准备建站的时候才了解到NameSilo，国外的一个域名服务网站，价格低，主要是续费也是差不多价格，学习使用域名的话，这个挺好的，还可以配置50个二级域名，自己玩够够的了。\n总结 第一次写博客，不知道写啥，慢慢学习吧。\n上面说到的技术都是粗略介绍了一下，之后有时间或者有需求的话做个系列介绍。\n学习的时间不多，逐渐优化完善吧。\n","date":"2023-05-19","permalink":"/posts/essays/000-%E7%AC%AC%E4%B8%80%E7%AF%87blog/","section":"Posts","summary":"","title":"我的第一篇博客"}]