首页
文章分类
逆向网安
中英演讲
杂类教程
学习笔记
前端开发
汇编
数据库
.NET
服务器
Python
Java
PHP
Git
算法
安卓开发
生活记录
读书笔记
作品发布
人体健康
网上邻居
留言板
欣赏小姐姐
关于我
Search
登录
1
利用AList搭建家庭个人影音库
4,658 阅读
2
浅尝Restful Fast Request插件,一句话完成 逆向过程
3,958 阅读
3
完美破解The Economist付费墙
2,725 阅读
4
i茅台app接口自动化csharp wpf实现,挂机windows服务器每日自动预约
2,610 阅读
5
青龙面板基本使用并添加修改微信/支付宝步数脚本
2,033 阅读
Search
标签搜索
PHP
Laravel
前端
csharp
安卓逆向
JavaScript
Python
Java
爬虫
抓包
Git
winform
android
Fiddler
Vue
selenium
LeetCode
每日一题
简单题
docker
Hygge
累计撰写
95
篇文章
累计收到
445
条评论
首页
栏目
逆向网安
中英演讲
杂类教程
学习笔记
前端开发
汇编
数据库
.NET
服务器
Python
Java
PHP
Git
算法
安卓开发
生活记录
读书笔记
作品发布
人体健康
页面
网上邻居
留言板
欣赏小姐姐
关于我
用户登录
搜索到
62
篇与
的结果
2022-08-12
初探Laravel Starters Kits的Breeze&Vue
简介Breeze 是官方推荐的起手套装,内建有登入、注册、忘记密码等常用的用户功能,另外可以选择使用 Vue 或者 React 来建立SPA单页页面。首先,Breeze 适用于专门初始化的工具,如果项目已经开发到一半的话可能会有冲突,所以不适用。最好建立一个初始化的工程。创建工程1.创建项目如果是8或更低的版本中前端资源打包使用了mix执行npm install 和 npm run dev后,页面无法正常解析,属于是坑了解决方案:https://learnku.com/laravel/t/69192所以避免踩坑直接采用了最新版的v 9.3.3的Laravelcomposer create-project laravel/laravel=9.*.* --prefer-dist projectName2.安装breezecomposer require laravel/breeze --dev官方文档:https://laravel.com/docs/9.x/starter-kits#laravel-breeze-installation3.安装breeze & React/Vuephp artisan breeze:install vue # Or... php artisan breeze:install react php artisan migrate npm install npm run dev4.查看效果需要跑两个控制台图中左侧运行服务端:php artisan serve右侧运行前端:npm run dev访问http://127.0.0.1:8000查看!自带了登录、注册功能,单页应用全程无刷新,nice!架构分析探索一下 Laravel 是如何实现这些的1.浅析路由和组件首先看到 routes/auth.php,这里定义了使用者登录之前的页面路由与 API,包含注册、登入登出、忘记密码等。以 Get/login 为例,由 AuthenticatedSessionController 的 create 方法处理请求后回传登录页面。 在这之前加上了 guest 这个 middleware,如果请求带有已登录的状态,则不回传页面而直接跳到首页。Route::middleware('guest')->group(function () { // ... Route::get('login', [AuthenticatedSessionController::class, 'create']) ->name('login'); // ... });看看 AuthenticatedSessionController 的 create方法做了什么/** * Display the login view. * * @return \Inertia\Response */ public function create() { return Inertia::render('Auth/Login', //resources\js\Pages\Auth\Login.vue 页面组件 // 页面组件的Props参数 [ 'canResetPassword' => Route::has('password.request'), 'status' => session('status'), ]); }再看一下对应的Vue组件<script> defineProps({ canResetPassword: Boolean, status: String, }); </script> <template> ... <div v-if="status" class="mb-4 font-medium text-sm text-green-600"> {{ status }} </div> ... ... <Link v-if="canResetPassword" :href="route('password.request')" class="underline text-sm text-gray-600 hover:text-gray-900"> Forgot your password? </Link> ... </template>瞬间明白,php中可以直接返回信息回传给Inertia.js进行渲染页面,组件中进行defineProps声明后即可使用。2.Inertia原理這邊簡單說下 Inertia.js 的運作方式,最初連上網站的時候,會回傳一個帶有 Inertia 功能的全頁應用,而這個應用裡所有的 link 都會經過 Inertia 的處理,當點擊 link 時並不會直接跳轉網址而是變成發送一個 XHR 到 Laravel ,並經由 API 回傳 Inertia 渲染的畫面元件,進行畫面的更新。而說是 API 渲染元件其實有點不太準確,收到請求後 Inertia Render 會將 Vue/React 元件轉換成 JSON 並回傳(不是 Html),再經由前端的 Inertia 解析後重新渲染部分的畫面,達到類前端 App 的效果,所以實際的渲染還是發生在前端,API 只是提供資料。另外這個方法前端是沒有路由器的,Laravel 伺服器提供畫面的路由器替代了這部分。本段来自:https://ithelp.ithome.com.tw/articles/10267034?sc=iThelpR3.Vue目录结构进一步学习留个官方文档入口: https://laravel.com/docs/9.x/vite引用1.Laravel8.5使用套件laravel breeze ,所有页面版式都未正确加载:https://learnku.com/laravel/t/691922.Laravel Starter kits :https://laravel.com/docs/9.x/starter-kits#introduction3.使用 Breeze 建立基礎專案框架:https://ithelp.ithome.com.tw/articles/10267034?sc=iThelpR4.Migrating from Laravel Mix to Vite:https://github.com/laravel/vite-plugin/blob/main/UPGRADE.md#migrating-from-laravel-mix-to-vite5.Bundling Assets (Vite):https://laravel.com/docs/9.x/vite
2022年08月12日
551 阅读
0 评论
0 点赞
2022-07-25
搞明白C#的Async和Await
本文参考: bilibili视频{bilibili bvid="BV1b54y1J72M" page=""/}讲的非常棒!{dotted startColor="#ff6c6c" endColor="#1989fa"/}需求引入 - 下载数据从不同的网站中下载很大的数据量,并且将数据结果呈现在UI客户端的一个经典的实际场景界面代码public partial class Form1 : Form { private readonly HttpClient httpClient = new HttpClient(); public Form1() { InitializeComponent(); } }界面中有一个同步下载按钮(SyncDownload)、异步下载按钮(AsyncDownload)、信息输出的文本框常量类public class Contents { public static readonly IEnumerable<string> WebSites = new string[] { "https://www.zhihu.com", "https://www.baidu.com", "https://www.weibo.com", "https://www.stackoverflow.com", "https://docs.microsoft.com", "https://docs.microsoft.com/aspnet/core", "https://docs.microsoft.com/azure", "https://docs.microsoft.com/azure/devops", "https://docs.microsoft.com/dotnet", "https://docs.microsoft.com/dynamics365", "https://docs.microsoft.com/education", "https://docs.microsoft.com/enterprise-mobility-security", "https://docs.microsoft.com/gaming", "https://docs.microsoft.com/graph", "https://docs.microsoft.com/microsoft-365", "https://docs.microsoft.com/office", "https://docs.microsoft.com/powershell", "https://docs.microsoft.com/sql", "https://docs.microsoft.com/surface", "https://docs.microsoft.com/system-center", "https://docs.microsoft.com/visualstudio", "https://docs.microsoft.com/windows", "https://docs.microsoft.com/xamarin" }; }一、同步下载/// 输出显示 private void ReportResult(string result) { Result.Text += result; } /// 同步下载按钮被单击 private void SyncDownload_Click(object sender, EventArgs e) { // 同步下载按钮点击后执行的代码 Result.Text = ""; // 为了测量程序的执行时间 var stopwatch = Stopwatch.StartNew(); // 启动下载的主函数 DownloadWebsitesSync(); // 输出函数执行的耗时情况 Result.Text += $"Elapsed time: {stopwatch.Elapsed}{Environment.NewLine}"; } /// 遍历所有站点 private void DownloadWebsitesSync() { foreach(var site in Contents.WebSites) { var result = DownloadWebSiteSync(site); ReportResult(result); } } /// 访问网站获取源代码 private string DownloadWebSiteSync(string url) { var response = httpClient.GetAsync(url).GetAwaiter().GetResult(); var responsePayloadBytes = response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult(); return $"Finish downloding data from {url}. Total bytes returned {responsePayloadBytes.Length}. {Environment.NewLine}"; }测试Finish downloding data from https://www.zhihu.com. Total bytes returned 35556. Finish downloding data from https://www.baidu.com. Total bytes returned 9508. Finish downloding data from https://www.weibo.com. Total bytes returned 0. Finish downloding data from https://www.stackoverflow.com. Total bytes returned 173712. Finish downloding data from https://docs.microsoft.com. Total bytes returned 132907. Finish downloding data from https://docs.microsoft.com/aspnet/core. Total bytes returned 90052. Finish downloding data from https://docs.microsoft.com/azure. Total bytes returned 428349. Finish downloding data from https://docs.microsoft.com/azure/devops. Total bytes returned 86931. Finish downloding data from https://docs.microsoft.com/dotnet. Total bytes returned 79622. Finish downloding data from https://docs.microsoft.com/dynamics365. Total bytes returned 56803. Finish downloding data from https://docs.microsoft.com/education. Total bytes returned 39267. Finish downloding data from https://docs.microsoft.com/enterprise-mobility-security. Total bytes returned 31372. Finish downloding data from https://docs.microsoft.com/gaming. Total bytes returned 61895. Finish downloding data from https://docs.microsoft.com/graph. Total bytes returned 45606. Finish downloding data from https://docs.microsoft.com/microsoft-365. Total bytes returned 68829. Finish downloding data from https://docs.microsoft.com/office. Total bytes returned 68829. Finish downloding data from https://docs.microsoft.com/powershell. Total bytes returned 57777. Finish downloding data from https://docs.microsoft.com/sql. Total bytes returned 55297. Finish downloding data from https://docs.microsoft.com/surface. Total bytes returned 40182. Finish downloding data from https://docs.microsoft.com/system-center. Total bytes returned 43407. Finish downloding data from https://docs.microsoft.com/visualstudio. Total bytes returned 31543. Finish downloding data from https://docs.microsoft.com/windows. Total bytes returned 27177. Finish downloding data from https://docs.microsoft.com/xamarin. Total bytes returned 56064. Elapsed time: 00:00:50.6908524缺点:使用了Foreach循环遍历网站请求数据占用了UI界面刷新的主线程,造成了UI刷新线程的阻塞在函数执行过程中,界面无法拖动,无法互动,用户体验效果差耗时50秒,由于是一个一个网址去请求,所以需要等待一个完成或失败后再去进行下一个请求,执行速度较慢!二、异步下载(一)-异步下载/// 异步下载按钮被单击 private void AsyncDownload_Click(object sender, EventArgs e) { Result.Text = ""; var stopwatch = Stopwatch.StartNew(); DownloadWebsitesAsync(); Result.Text += $"Elapsed time: {stopwatch.Elapsed}{Environment.NewLine}"; } /// 使用 async 用于提醒编译器 该方法中使用到了 Await 关键字 /// 对于希望使用Async和Await关键字的方法,返回值必须是Task或者是Task泛型 private async Task DownloadWebsitesAsync() { foreach (var site in Contents.WebSites) { var result = await Task.Run(() => DownloadWebSiteSync(site)); ReportResult(result); } }测试AsyncDownload_Click中并没有等待DownloadWebSitesAsync执行结束就打印了最后的执行时间相当于DownloadWebSitesAsync被忽略了。修改/// 异步下载按钮被单击 private async void AsyncDownload_Click(object sender, EventArgs e) { Result.Text = ""; var stopwatch = Stopwatch.StartNew(); await DownloadWebsitesAsync(); Result.Text += $"Elapsed time: {stopwatch.Elapsed}{Environment.NewLine}"; }总结经过如上编写,发现在测试异步下载时,UI界面线程不会阻塞,可以在下载时与界面进行交互。通过对比同步和异步两种下载方式,发现两者下载速度并没有太大差别。于是我们可以发现仅仅使用async和await并不能够提升我们处理数据的速度,给我们带来的仅仅是这个UI的响应更加及时。三、异步下载(二)-并发下载在上一节分发下载任务时,使用的是foreach进行遍历网址列表,逐个进行下载数据。虽然Ui Thread并没有被阻塞,但这并没有改变下载数据的策略,数据还是需要从这些网站中一个一个的下载。这样其实就很好理解,为什么在异步的模型中下载数据的时间总时长并没有被缩短。如果想缩短下载所有数据的总时间,那么就需要引入: 并行下载 即我们需要更多的线程并发的从这些网站中下载数据。/// 分发下载任务的主函数 private async Task DownloadWebsitesAsync() { /// 用于存储所有的Task List<Task<string>> downloadWebsiteTasks = new List<Task<string>>(); /// 将原来的下载完成一个再进行下一个,改成快速遍历所有站点,将每一个下载任务存入集合,集合中所有元素并发进行下载。 foreach (var site in Contents.WebSites) { downloadWebsiteTasks.Add(Task.Run(() => DownloadWebSiteSync(site))); } // 当集合所有任务都完成时,统一拿到Result Array<string>返回值 var results = await Task.WhenAll(downloadWebsiteTasks).Result; // 将所有results 全部打印出去。 foreach(var result in results) { ReportResult(result); } }注意:每次程序的第一次完成IO操作时,尤其是这种多线程从远端服务器需要用http下载数据,程序往往会经历一些冷启动的现象,原因是因为我们的 CLR 需要创建相应的Thread Pool来初始化Thread,而初始化每一个Thread也是需要时间的,并且完成这些Http Connection的TCP Connection的建立也是需要时间的。因此程序运行的第一次下载耗时不太稳定,会花费额外的时间,如果想要查看稳定的数据下载,需要多次Run一下这个实验的结果。测试观测到 异步下载+并行下载的时间稳定在2s左右修改减少由于初始化很多线程而造成的速度不稳定冷启动时间/// 访问网站获取源代码 更改为异步操作,命名遵循范式 private async Task<string> DownloadWebSiteAsync(string url) { var response = await httpClient.GetAsync(url); // 直接调用异步方法 var responsePayloadBytes = await response.Content.ReadAsByteArrayAsync(); // 直接调用异步方法 return $"Finish downloding data from {url}. Total bytes returned {responsePayloadBytes.Length}. {Environment.NewLine}"; } private async Task DownloadWebsitesAsync() { List<Task<string>> downloadWebsiteTasks = new List<Task<string>>(); foreach (var site in Contents.WebSites) { // 这时候不需要一个额外的Thread来进行等待 // 所以不再需要Tash.Run,可以直接调用方法。 downloadWebsiteTasks.Add(DownloadWebSiteAsync()); } var results = await Task.WhenAll(downloadWebsiteTasks).Result; foreach(var result in results) { ReportResult(result); } }程序运行后的第一次下载:耗时2s
2022年07月25日
316 阅读
0 评论
0 点赞
2022-07-10
记录一次调用OCR验证码识别库的过程
1.前言最近在写Python项目中用到了其他网站的接口,请求的时候对方接口需要验证码,之前使用的一直是联众打码平台( https://www.jsdati.com/ ),没想到今天访问的时候已经打不开了...{lamp/}谷歌了一下找到了这个库,名字挺有意思哒ddddocr(带带弟弟OCR): https://github.com/sml2h3/ddddocr2.Python版本配置我平常用的是普通的Python3.10+,安装不了这个库,找降低Python版本的方法也没找到。于是就卸载了Python换成带有版本管理的Anaconda,官网: https://www.anaconda.com/ 安装的时候不建议也不需要配置环境变量,控制台使用Anaconda自带的就好基础命令:# 1.创建新环境并指定环境的Python版本 conda create --name env_name python=version 例如: conda create --name python36 python=3.6 # 2.激活环境 activate env_name # 3.关闭环境 deactivate env_name # 4.删除环境 conda env remove -n env_name # 5.显示所有环境 conda env list # 6.查看anaconda中已经存在的镜像源 conda config --show channels # 7.添加镜像源(永久添加) conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/ # 8.设置搜索时显示通道地址 conda config --set show_channel_urls yespycharm创建Virtualenv,指定刚才创建的conda环境中python.exe解释器。3.安装ddddocrpip install ddddocr4.使用import ddddocr ocr = ddddocr.DdddOcr(old=True,show_ad=False) with open('stuExam.jpg', 'rb') as f: image = f.read() res = ocr.classification(image) print(res)比较清晰的是可以识别出来的,测试了三个验证码,完全识别正确的只有一张,看来是无法投入到当前项目中使用了。5.参考链接文安哲的博客-ddddocr作者: https://wenanzhe.com/阿迪(GIF)点选验证码识别测试页面:http://146.56.204.113:19199/preview
2022年07月10日
579 阅读
1 评论
1 点赞
2022-07-04
使用Nginx反向代理接入欣赏小姐姐视频站点
前言最近网上冲浪发现了这种欣赏小姐姐的视频站点 ::(你懂的) ,于是想整一个集成到blog里。源站点: 欣赏小姐姐:https://xjiejie.vip/ 成果展示: 欣赏小姐姐:https://www.lisok.cn/online-tiktok.html1.先抓包拿接口😋 对方的反扒意识蛮强的 1.屏蔽了页面上的右键 2.F12打开开发者工具,页面会自动关闭 3.若可以成功打开开发者工具,那么页面在请求数据的时候还会再次检测,若被发现页面会自动关闭。于是干脆放弃用开发者工具了,直接上Fiddler成功拿到接口:URL:https://xjiejie.vip/zb_users/theme/lanyexvideo/include/video.php Param: # 随机值,防止懒请求: t:xxx Header: # 模拟请求的过程中发现有防盗链,校验Referer请求头 Referer:https://xjiejie.vip/ 2.页面请求博客的网络请求用的JQuery,那请求就由Ajax负责:index.html$(".getUrl").click(function () { let url = "https://xjiejie.vip/zb_users/theme/lanyexvideo/include/video.php?t=" + Math.random(); $.ajax({ url: url, type: "GET", dataType: "json", success: function (data) { console.log(data); $("#video").attr("src", data.playurl); }, }); });遇到了跨域问题,意料之中,这肯定不能让对方服务器去加响应头,只得我们自己去做一个代理访问。3.PHP代理流程:由原来的前端直接访问对方服务器 变成了 前端访问自己的代理PHP文件,再由PHP去向对方服务器发起请求返回结果。proc.php<?php // 对方的响应有gzip压缩,需要解压缩 function gunzip($zipped) { $offset = 0; if (substr($zipped, 0, 2) == "\x1f\x8b") $offset = 2; if (substr($zipped, $offset, 1) == "\x08") { return gzinflate(substr($zipped, $offset + 8)); } return "Unknown Format"; } // 发起网络请求 $url = 'https://xjiejie.vip/zb_users/theme/lanyexvideo/include/video.php?t=' . time(); // 发起GET请求 $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // 设置请求头 curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'Content-Type: application/json', 'Accept: application/json', 'Referer: https://xjiejie.vip/', 'Accept-Encoding: gzip, deflate, br', )); // 关闭ssl curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLINFO_HEADER_OUT, 0); curl_setopt($ch, CURLOPT_HEADER, 0); $output = curl_exec($ch); // 若有错误,则抛出异常 if (curl_errno($ch)) { echo 'Error:' . curl_error($ch); exit; } curl_close($ch); // 输出结果 $arr = []; // 获取请求头的Referer $arr = json_decode(gunzip($output), true); $arr['referer'] = $_SERVER['HTTP_REFERER']; $arr['host'] = $_SERVER['HTTP_HOST']; // 输出json header('Content-Type: application/json'); echo json_encode($arr);对应的前端的请求路径也要修改index.htmllet url = "/proc.php" + Math.random();4.Nginx代理最近刚开始学习Nginx,边查资料边测试,实现了这个功能4.1 编写配置文件直接在网站的配置文件中做如下修改:lisok.cn.... location /xjiejie/video/ { proxy_pass https://xjiejie.vip/zb_users/theme/lanyexvideo/include/video.php; proxy_set_header Referer "https://xjiejie.vip/"; proxy_set_header Content-Type "application/json"; proxy_set_header Accept-Encoding "gzip, deflate, br"; proxy_set_header User-Agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"; proxy_set_header Accept "application/json"; proxy_set_header Host "xjiejie.vip"; } location / { # !!!原先下三行的内容是没有在 location / 中的!!! #REWRITE-START URL重写规则引用,修改后将导致面板设置的伪静态规则失效 include /www/server/panel/vhost/rewrite/lisok.cn.conf; #REWRITE-END } ....index.htmllet url = "/xjiejie/video?t=" + Math.random(); $.ajax({ url: url, type: "GET", dataType: "json", success: function (data) { console.log(data); $("#video").attr("src", data.playurl); }, }); });更新:2022.9.8 更新接口今天访问的时候发现不能播放视频了,查看接口发现对方更换了域名nginx配置文件也进行同步更新即可 location /xjiejie/video { proxy_pass https://xjiejie.co/zb_users/theme/lanyexvideo/include/video.php; proxy_set_header Referer "https://xjiejie.co/"; proxy_set_header Content-Type "application/json"; proxy_set_header Accept-Encoding "gzip, deflate, br"; proxy_set_header User-Agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"; proxy_set_header Accept "application/json"; proxy_set_header Host "xjiejie.co"; }5.注意事项PHPStudy集成的是Nginx v1.15.11,这版本使用中会有问题,即使修改了Referer后,反向代理访问对方的站点还是会返回404,后面升级到Nginx v1.16.1就好了。typecho会有伪静态的配置,若先加载的伪静态配置会导致访问接口时候返回页面未找到的404错误,所以nginx的配置项要先于伪静态的配置项。
2022年07月04日
643 阅读
0 评论
4 点赞
2022-06-29
如何优雅的写 Controller 层代码?
前言本篇主要要介绍的就是 controller 层的处理,一个完整的后端请求由 4 部分组成: ``接口地址(也就是 URL 地址)请求方式(一般就是 get、set,当然还有 put、delete)请求数据(request,有 head 跟 body)响应数据(response)本篇将解决以下 3 个问题:当接收到请求时,如何优雅的校验参数返回响应数据该如何统一的进行处理接收到请求,处理业务逻辑时抛出了异常又该如何处理Controller 层参数接收(太基础了,可以跳过)常见的请求就分为 get 跟 post 两种:@RestController @RequestMapping("/product/product-info") public class ProductInfoController { @Autowired ProductInfoService productInfoService; @GetMapping("/findById") public ProductInfoQueryVo findById(Integer id) { ... } @PostMapping("/page") public IPage findPage(Page page, ProductInfoQueryVo vo) { ... } }RestController:之前解释过,@RestController=@Controller+ResponseBody。加上这个注解,springboot 就会吧这个类当成 controller 进行处理,然后把所有返回的参数放到 ResponseBody 中。@RequestMapping:请求的前缀,也就是所有该 Controller 下的请求都需要加上 /product/product-info 的前缀。@GetMapping("/findById"):标志这是一个 get 请求,并且需要通过 /findById 地址才可以访问到。@PostMapping("/page"):同理,表示是个 post 请求。参数:至于参数部分,只需要写上 ProductInfoQueryVo,前端过来的 json 请求便会通过映射赋值到对应的对象中,例如请求这么写,productId 就会自动被映射到 vo 对应的属性当中。size : 1 current : 1 productId : 1 productName : 泡脚统一状态码返回格式为了跟前端妹妹打好关系,我们通常需要对后端返回的数据进行包装一下,增加一下状态码,状态信息,这样前端妹妹接收到数据就可以根据不同的状态码,判断响应数据状态,是否成功是否异常进行不同的显示。当然这让你拥有了更多跟前端妹妹的交流机会,假设我们约定了 1000 就是成功的意思。如果你不封装,那么返回的数据是这样子的:{ "productId": 1, "productName": "泡脚", "productPrice": 100.00, "productDescription": "中药泡脚加按摩", "productStatus": 0, }经过封装以后是这样子的{ "code": 1000, "msg": "请求成功", "data": { "productId": 1, "productName": "泡脚", "productPrice": 100.00, "productDescription": "中药泡脚加按摩", "productStatus": 0, } }封装 ResultVo这些状态码肯定都是要预先编好的,怎么编呢?写个常量 1000?还是直接写死 1000?要这么写就真的书白读的了,写状态码当然是用枚举拉:①首先先定义一个状态码的接口,所有状态码都需要实现它,有了标准才好做事:public interface StatusCode { public int getCode(); public String getMsg(); }②然后去找前端妹妹,跟他约定好状态码(这可能是你们唯一的约定了)枚举类嘛,当然不能有 setter 方法了,因此我们不能在用 @Data 注解了,我们要用 @Getter。@Getter public enum ResultCode implements StatusCode{ SUCCESS(1000, "请求成功"), FAILED(1001, "请求失败"), VALIDATE_ERROR(1002, "参数校验失败"), RESPONSE_PACK_ERROR(1003, "response返回包装失败"); private int code; private String msg; ResultCode(int code, String msg) { this.code = code; this.msg = msg; } }③写好枚举类,就开始写 ResultVo 包装类了,我们预设了几种默认的方法,比如成功的话就默认传入 object 就可以了,我们自动包装成 success。@Data public class ResultVo { // 状态码 private int code; // 状态信息 private String msg; // 返回对象 private Object data; // 手动设置返回vo public ResultVo(int code, String msg, Object data) { this.code = code; this.msg = msg; this.data = data; } // 默认返回成功状态码,数据对象 public ResultVo(Object data) { this.code = ResultCode.SUCCESS.getCode(); this.msg = ResultCode.SUCCESS.getMsg(); this.data = data; } // 返回指定状态码,数据对象 public ResultVo(StatusCode statusCode, Object data) { this.code = statusCode.getCode(); this.msg = statusCode.getMsg(); this.data = data; } // 只返回状态码 public ResultVo(StatusCode statusCode) { this.code = statusCode.getCode(); this.msg = statusCode.getMsg(); this.data = null; } }使用,现在的返回肯定就不是 return data;这么简单了,而是需要 new ResultVo(data); @PostMapping("/findByVo") public ResultVo findByVo(@Validated ProductInfoVo vo) { ProductInfo productInfo = new ProductInfo(); BeanUtils.copyProperties(vo, productInfo); return new ResultVo(productInfoService.getOne(new QueryWrapper(productInfo))); }最后返回就会是上面带了状态码的数据了。统一校验注:springboot 2.3之前的集成在spring-boot-starter-web里了,所以不需要额外引入包springboot 2.3之后需要引入 spring-boot-starter-validation原始做法假设有一个添加 ProductInfo 的接口,在没有统一校验时,我们需要这么做。@Data public class ProductInfoVo { // 商品名称 private String productName; // 商品价格 private BigDecimal productPrice; // 上架状态 private Integer productStatus; } @PostMapping("/findByVo") public ProductInfo findByVo(ProductInfoVo vo) { if (StringUtils.isNotBlank(vo.getProductName())) { throw new APIException("商品名称不能为空"); } if (null != vo.getProductPrice() && vo.getProductPrice().compareTo(new BigDecimal(0)) < 0) { throw new APIException("商品价格不能为负数"); } ... ProductInfo productInfo = new ProductInfo(); BeanUtils.copyProperties(vo, productInfo); return new ResultVo(productInfoService.getOne(new QueryWrapper(productInfo))); }这 if 写的人都傻了,能忍吗?肯定不能忍啊。@Validated 参数校验好在有 @Validated,又是一个校验参数必备良药了。有了 @Validated 我们只需要在 vo 上面加一点小小的注解,便可以完成校验功能。@Data public class ProductInfoVo { @NotNull(message = "商品名称不允许为空") private String productName; @Min(value = 0, message = "商品价格不允许为负数") private BigDecimal productPrice; private Integer productStatus; } @PostMapping("/findByVo") public ProductInfo findByVo(@Validated ProductInfoVo vo) { ProductInfo productInfo = new ProductInfo(); BeanUtils.copyProperties(vo, productInfo); return new ResultVo(productInfoService.getOne(new QueryWrapper(productInfo))); } 运行看看,如果参数不对会发生什么?我们故意传一个价格为 -1 的参数过去:productName : 泡脚 productPrice : -1 productStatus : 1{ "timestamp": "2020-04-19T03:06:37.268+0000", "status": 400, "error": "Bad Request", "errors": [ { "codes": [ "Min.productInfoVo.productPrice", "Min.productPrice", "Min.java.math.BigDecimal", "Min" ], "arguments": [ { "codes": [ "productInfoVo.productPrice", "productPrice" ], "defaultMessage": "productPrice", "code": "productPrice" }, 0 ], "defaultMessage": "商品价格不允许为负数", "objectName": "productInfoVo", "field": "productPrice", "rejectedValue": -1, "bindingFailure": false, "code": "Min" } ], "message": "Validation failed for object\u003d\u0027productInfoVo\u0027. Error count: 1", "trace": "org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors\nField error in object \u0027productInfoVo\u0027 on field \u0027productPrice\u0027: rejected value [-1]; codes [Min.productInfoVo.productPrice,Min.productPrice,Min.java.math.BigDecimal,Min]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [productInfoVo.productPrice,productPrice]; arguments []; default message [productPrice],0]; default message [商品价格不允许为负数]\n\tat org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:164)\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:167)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134)\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:660)\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:741)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:124)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1594)\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\n\tat java.base/java.lang.Thread.run(Thread.java:830)\n", "path": "/leilema/product/product-info/findByVo" }大功告成了吗?虽然成功校验了参数,也返回了异常,并且带上"商品价格不允许为负数"的信息。但是你要是这样返回给前端,前端妹妹就提刀过来了,当年约定好的状态码,你个负心人说忘就忘?用户体验小于等于 0 啊!所以我们要进行优化一下,每次出现异常的时候,自动把状态码写好,不负妹妹之约!优化异常处理首先我们先看看校验参数抛出了什么异常:Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors我们看到代码抛出了 org.springframework.validation.BindException 的绑定异常,因此我们的思路就是 AOP 拦截所有 controller,然后异常的时候统一拦截起来,进行封装!完美!玩你个头啊完美,这么呆瓜的操作 springboot 不知道吗?spring mvc 当然知道拉,所以给我们提供了一个 @RestControllerAdvice 来增强所有 @RestController,然后使用 @ExceptionHandler 注解,就可以拦截到对应的异常。这里我们就拦截 BindException.class 就好了。最后在返回之前,我们对异常信息进行包装一下,包装成 ResultVo,当然要跟上 ResultCode.VALIDATE_ERROR 的异常状态码。这样前端妹妹看到 VALIDATE_ERROR 的状态码,就会调用数据校验异常的弹窗提示用户哪里没填好。@RestControllerAdvice public class ControllerExceptionAdvice { @ExceptionHandler({BindException.class}) public ResultVo MethodArgumentNotValidExceptionHandler(BindException e) { // 从异常对象中拿到ObjectError对象 ObjectError objectError = e.getBindingResult().getAllErrors().get(0); return new ResultVo(ResultCode.VALIDATE_ERROR, objectError.getDefaultMessage()); } }来看看效果,完美。1002 与前端妹妹约定好的状态码:{ "code": 1002, "msg": "参数校验失败", "data": "商品价格不允许为负数" }统一响应统一包装响应再回头看一下 controller 层的返回:return new ResultVo(productInfoService.getOne(new QueryWrapper(productInfo)));开发小哥肯定不乐意了,谁有空天天写 new ResultVo(data) 啊,我就想返回一个实体!怎么实现我不管!好把,那就是 AOP 拦截所有 Controller,再 @After 的时候统一帮你封装一下咯。怕是上一次脸打的不够疼,springboot 能不知道这么个操作吗?@RestControllerAdvice(basePackages = {"com.bugpool.leilema"}) public class ControllerResponseAdvice implements ResponseBodyAdvice<Object> { @Override public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) { // response是ResultVo类型,或者注释了NotControllerResponseAdvice都不进行包装 return !methodParameter.getParameterType().isAssignableFrom(ResultVo.class); } @Override public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest request, ServerHttpResponse response) { // String类型不能直接包装 if (returnType.getGenericParameterType().equals(String.class)) { ObjectMapper objectMapper = new ObjectMapper(); try { // 将数据包装在ResultVo里后转换为json串进行返回 return objectMapper.writeValueAsString(new ResultVo(data)); } catch (JsonProcessingException e) { throw new APIException(ResultCode.RESPONSE_PACK_ERROR, e.getMessage()); } } // 否则直接包装成ResultVo返回 return new ResultVo(data); } } ①@RestControllerAdvice(basePackages = {"com.bugpool.leilema"}) 自动扫描了所有指定包下的 controller,在 Response 时进行统一处理。②重写 supports 方法,也就是说,当返回类型已经是 ResultVo 了,那就不需要封装了,当不等与 ResultVo 时才进行调用 beforeBodyWrite 方法,跟过滤器的效果是一样的。③最后重写我们的封装方法 beforeBodyWrite,注意除了 String 的返回值有点特殊,无法直接封装成 json,我们需要进行特殊处理,其他的直接 new ResultVo(data); 就 ok 了。打完收工,看看效果: @PostMapping("/findByVo") public ProductInfo findByVo(@Validated ProductInfoVo vo) { ProductInfo productInfo = new ProductInfo(); BeanUtils.copyProperties(vo, productInfo); return productInfoService.getOne(new QueryWrapper(productInfo)); }此时就算我们返回的是 po,接收到的返回就是标准格式了,开发小哥露出了欣慰的笑容。{ "code": 1000, "msg": "请求成功", "data": { "productId": 1, "productName": "泡脚", "productPrice": 100.00, "productDescription": "中药泡脚加按摩", "productStatus": 0, ... } }NOT 统一响应不开启统一响应原因:开发小哥是开心了,可是其他系统就不开心了。举个例子:我们项目中集成了一个健康检测的功能,也就是这货。@RestController public class HealthController { @GetMapping("/health") public String health() { return "success"; } }公司部署了一套校验所有系统存活状态的工具,这工具就定时发送 get 请求给我们系统:“兄弟,你死了吗?”“我没死,滚”“兄弟,你死了吗?”“我没死,滚”是的,web 项目的本质就是复读机。一旦发送的请求没响应,就会给负责人发信息(企业微信或者短信之类的),你的系统死啦!赶紧回来排查 bug 吧!好吧,没办法,人家是老大,人家要的返回不是:{ "code": 1000, "msg": "请求成功", "data": "success" }人家要的返回只要一个 success,人家定的标准不可能因为你一个系统改。俗话说的好,如果你改变不了环境,那你就只能我**新增不进行封装注解:因为百分之 99 的请求还是需要包装的,只有个别不需要,写在包装的过滤器吧?又不是很好维护,那就加个注解好了。所有不需要包装的就加上这个注解。@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface NotControllerResponseAdvice { }然后在我们的增强过滤方法上过滤包含这个注解的方法:@RestControllerAdvice(basePackages = {"com.bugpool.leilema"}) public class ControllerResponseAdvice implements ResponseBodyAdvice<Object> { @Override public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) { // response是ResultVo类型,或者注释了NotControllerResponseAdvice都不进行包装 return !(methodParameter.getParameterType().isAssignableFrom(ResultVo.class) || methodParameter.hasMethodAnnotation(NotControllerResponseAdvice.class)); } ...最后就在不需要包装的方法上加上注解:@RestController public class HealthController { @GetMapping("/health") @NotControllerResponseAdvice public String health() { return "success"; } }这时候就不会自动封装了,而其他没加注解的则依旧自动包装:统一异常每个系统都会有自己的业务异常,比如库存不能小于 0 子类的,这种异常并非程序异常,而是业务操作引发的异常,我们也需要进行规范的编排业务异常状态码,并且写一个专门处理的异常类,最后通过刚刚学习过的异常拦截统一进行处理,以及打日志。①异常状态码枚举,既然是状态码,那就肯定要实现我们的标准接口 StatusCode。@Getter public enum AppCode implements StatusCode { APP_ERROR(2000, "业务异常"), PRICE_ERROR(2001, "价格异常"); private int code; private String msg; AppCode(int code, String msg) { this.code = code; this.msg = msg; } }②异常类,这里需要强调一下,code 代表 AppCode 的异常状态码,也就是 2000;msg 代表业务异常,这只是一个大类,一般前端会放到弹窗 title 上;最后 super(message); 这才是抛出的详细信息,在前端显示在弹窗体中,在 ResultVo 则保存在 data 中。@Getter public class APIException extends RuntimeException { private int code; private String msg; // 手动设置异常 public APIException(StatusCode statusCode, String message) { // message用于用户设置抛出错误详情,例如:当前价格-5,小于0 super(message); // 状态码 this.code = statusCode.getCode(); // 状态码配套的msg this.msg = statusCode.getMsg(); } // 默认异常使用APP_ERROR状态码 public APIException(String message) { super(message); this.code = AppCode.APP_ERROR.getCode(); this.msg = AppCode.APP_ERROR.getMsg(); } }③最后进行统一异常的拦截,这样无论在 service 层还是 controller 层,开发人员只管抛出 API 异常,不需要关系怎么返回给前端,更不需要关心日志的打印。@RestControllerAdvice public class ControllerExceptionAdvice { @ExceptionHandler({BindException.class}) public ResultVo MethodArgumentNotValidExceptionHandler(BindException e) { // 从异常对象中拿到ObjectError对象 ObjectError objectError = e.getBindingResult().getAllErrors().get(0); return new ResultVo(ResultCode.VALIDATE_ERROR, objectError.getDefaultMessage()); } @ExceptionHandler(APIException.class) public ResultVo APIExceptionHandler(APIException e) { // log.error(e.getMessage(), e); 由于还没集成日志框架,暂且放着,写上TODO return new ResultVo(e.getCode(), e.getMsg(), e.getMessage()); } }④最后使用,我们的代码只需要这么写。 if (null == orderMaster) { throw new APIException(AppCode.ORDER_NOT_EXIST, "订单号不存在:" + orderId); }{ "code": 2003, "msg": "订单不存在", "data": "订单号不存在:1998" }就会自动抛出 AppCode.ORDER_NOT_EXIST 状态码的响应,并且带上异常详细信息订单号不存在:xxxx。后端小哥开发有效率,前端妹妹获取到 2003 状态码,调用对应警告弹窗,title 写上订单不存在,body 详细信息记载"订单号不存在:1998"。同时日志还自动打上去了。
2022年06月29日
251 阅读
0 评论
1 点赞
2022-06-18
Selenium破解学习通倍速限制
前言学习通的某些课程会有限制播放速度的功能,不仅播放器没有倍速的播放选项,甚至你通过代码修改播放器的速度也会被监听从而被重置播放器的速度。js修改原先尝试过setInterval设置定时器不断的去修改播放器速度,先不说优雅与否,反正是没有用的,每次修改速度,视频都会被暂停,速度也被重置。后面去油猴找了一个插件参考,扒下来了这段破解倍速的代码:{tabs}{tabs-pane label="代码"}(function () { 'use strict'; console.log(window.location.href) function hack() { if (typeof videojs !== "undefined" && typeof Ext !== "undefined") { Ext.define("ans.VideoJs", { override: "ans.VideoJs", constructor: function (b) { b = b || {}; const e = this; e.addEvents(["seekstart"]); e.mixins.observable.constructor.call(e, b); const c = videojs( b.videojs, e.params2VideoOpt(b.params), function () { } ); Ext.fly(b.videojs).on("contextmenu", function (f) { f.preventDefault(); }); Ext.fly(b.videojs).on("keydown", function (f) { if ( f.keyCode === 32 || f.keyCode === 37 || f.keyCode === 39 || f.keyCode === 107 ) { f.preventDefault(); } }); if (c.videoJsResolutionSwitcher) { c.on("resolutionchange", function () { const g = c.currentResolution(); const f = g.sources ? g.sources[0].res : false; Ext.setCookie("resolution", f); }); } }, }); } } if (window.location.href.indexOf('/ananas/modules/video') > -1) { try { hack(); window.document.addEventListener("readystatechange", hack); window.addEventListener("load", hack); } catch (e) { console.error(e.message); } } })();{/tabs-pane}{tabs-pane label="解释"}hack(); window.document.addEventListener("readystatechange", hack); window.addEventListener("load", hack);关键代码是上面这三行,更关键的是执行时机视频播放区域是位于页面的iframe中,/ananas/modules/video就是这个iframe的链接的一部分执行时间:iframe加载时执行,且先于该页面的其他js脚本,一旦页面加载完毕,再去执行代码就没有作用了。{/tabs-pane}{/tabs}破解完之后,再去执行$('video')[0].playbackRate = 16,就发现不会被重置倍速了。应用在Selenium效果:原理就是注入上一节提到的Js# chrome.execute_cdp_cmd会在所有页面加载前进行执行,先于页面自带的Js # 这段压缩过的Js里有判断Url是否为视频页面的逻辑 chrome.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', { 'source': 'function hack(){if(typeof videojs!=="undefined"&&typeof Ext!=="undefined"){Ext.define("ans.VideoJs",{override:"ans.VideoJs",constructor:function(b){b=b||{};const e=this;e.addEvents(["seekstart"]);e.mixins.observable.constructor.call(e,b);const c=videojs(b.videojs,e.params2VideoOpt(b.params),function(){});Ext.fly(b.videojs).on("contextmenu",function(f){f.preventDefault()});Ext.fly(b.videojs).on("keydown",function(f){if(f.keyCode===32||f.keyCode===37||f.keyCode===39||f.keyCode===107){f.preventDefault()}});if(c.videoJsResolutionSwitcher){c.on("resolutionchange",function(){const g=c.currentResolution();const f=g.sources?g.sources[0].res:false;Ext.setCookie("resolution",f)})}},})}}if(window.location.href.indexOf("/ananas/modules/video")>-1){try{hack();window.document.addEventListener("readystatechange",hack);window.addEventListener("load",hack)}catch(e){console.error(e.message)}};'})注意:chrome.execute_script("xxx")的执行是在页面的Js都加载完毕时才会执行,所以使用这个来执行脚本 此处并不适用。参考1.Selenium: How to Inject/execute a Javascript in to a Page before loading/executing any other scripts of the page? : https://stackoverflow.com/questions/31354352/selenium-how-to-inject-execute-a-javascript-in-to-a-page-before-loading-executi 2.OCS网课助手: https://github.com/ocsjs/ocsjs
2022年06月18日
1,517 阅读
2 评论
0 点赞
2022-06-16
【转载】linux screen的用法
1.前言大家在初次接触linux时,会发现linux操作和windows操作太不相同的,windows都是图形操作界面,而linux一般是命令行操作.当然,linux也有图形操作界面,但是我们在将vps作为网络生产环境时,安装图形界面不光费时费力,更费vps那少的可怜的系统资源,所以,还是命令行吧,可是命令行只有一个,linux下编译命令一般执行也比较慢,如编译军哥的lnmp,少则半小时二十分钟,多则三小时五小时,一旦断开,正在编译的软件也就完了,这很不符合偶们的折腾精神,难道就木有个解决方法?当然有,那就是screen。一、神马是screen?Screen是一个可以在多个进程之间多路复用一个物理终端的全屏窗口管理器。Screen中有会话的概念,用户可以在一个会话中创建多个screen窗口,在每一个screen窗口中就像操作一个真实的telnet/SSH连接窗口那样。通俗的讲,screen命令用于新建一个或多个“命令行窗口”,在新建的这“窗口”中,可以执行命令;每个“窗口”都是独立并行的。二、安装screen要想使用screen,当然得有screen了,不是所有的linux系统都安装了screen,如果木有安装,可以使用一下命令安装:# centos: yum install screen # debian / ubuntu: sudo apt-get install screen安装也就分分钟的事情,中途需要输入y并按回车执行;三、使用screen1.创建会话:最简单的就是直接输入screen回车,一个新会话就完成了(debian下会有提示,再按一次回车就可以了),但偶还是推荐用下面的命令来创建会话:screen -S php上面命令的意思就是新创建一个名为php的会话,这时你可以继续输入其它命令,如编译php,费时很长,不用管它,喝杯茶先;2.离开会话,按住键盘上的ctrl,然后依次按a和d,好了,又回到主会话了,这时你可以继续创建其它会话或者执行其它命令,对我们创建的会话内容没有任何影响,你甚至可以退出会话;3.恢复创建的会话:如果还记得会话名称,可以输入命令:screen -r php上面命令的意思就是恢复名为php的会话,如果你只创建了一个会话,直接如入命令screen -r就可以恢复了,如果不记得会话名称或者我直接输入screen创建的会话,怎么办呢,那就要用到下面的命令;4.查看已经创建的会话:screen -ls上面的命令的意思就是查看本机已经创建的会话,可以查看会话的名称和id,恢复时可以使用id代替名称;5.有时在恢复screen时会出现There is no screen to be resumed matching **,遇到这种情况咋办呢?输入命令screen -d ****然后再使用恢复命令恢复就ok了6.退出screen,使用screen会耗费一定的系统资源,所以当screen中的命令执行完毕之后,最好退出screen,直接输入命令exit就可以退出了。7.其它命令Ctrl + a,d #暂离当前会话 Ctrl + a,c #在当前screen会话中创建一个子会话 Ctrl + a,w #子会话列表 Ctrl + a,p #上一个子会话 Ctrl + a,n #下一个子会话 Ctrl + a,0-9 #在第0窗口至第9子会话间切换四、补充我使用screen是由于后台的python项目用到了selenium,这就要求不管是前台运行或者是后台运行 都必须保持ssh的稳定连接,一旦断开项目就会崩溃。这时候screen的作用就体现了,screen生成新的虚拟终端,将程序与当前终端的相关性剥离,当前终端断开连接,不影响程序的继续运行。五、文章来源作者:IT小C链接:https://www.jianshu.com/p/e91746ef4058来源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
2022年06月16日
236 阅读
0 评论
0 点赞
2022-05-25
.NET项目连接虚拟机VMware的SQL Server
不太喜欢Visual Studio和SQL Server,体积太大了,但是想以后桌面程序使用WPF或Winform开发,所以还是下回来了,顺便临近毕业了,大一大二挂科的都要清考不然毕业证拿不了,有同学的ASP.NET MVC挂了 我决定!捞一把....1.下载1.1 下载VsVs没得说,直接下载到主机 这里下的2022 latest version,不得不说 2022年的这版和Jetbrains系列的软件颜值都上升了有个小坑,使用vs installer默认不会携带.net framework的 MVC,需要自己勾选一下:1.2 下载SQLServer使用的是SQL Server2012,在学校上课的时候用的就是这个版本,害 反正就是CRUD,版本差别不大推个下载地址:{cloud title="SQL Server 2012最新版" type="default" url="https://www.onlinedown.net/soft/1229604.htm" password=""/}顺便附一个密钥[MICROSOFT SQL SERVER 2012 DEVELOPER 版序列号]:YQWTX-G8T4R-QW4XX-BVH62-GP68Y MICROSOFT.2022/05/25亲测可用1.3 安装Win7镜像::(泪) 安装了Vs后再安装SQL Server发现安装路径不能改变!!但是C盘又没有容量了,只好装个虚拟机了不过好在还发现了一个下载镜像的网站: https://next.itellyou.cn/ ,都是纯净版的,同样也是没有激活的。需要登录才能访问,也可以直接下载下方我提供的 Windows 7 Ultimate with Service Pack 1 (x64) - DVD (Chinese-Simplified)magnet:?xt=urn:btih:E86414F638E11104248108B155BE9408A8362509&dn=cn_windows_7_ultimate_with_sp1_x64_dvd_u_677408.iso&xl=34205573121.4 Navicat平常用的都是MySQL+SQLYog,发现Navicat可以连接SQLServer,那就不用切换到虚拟机了,果然整一个分享一个15.0.17版本的:{cloud title="Premium免安装版本,内附注册机" type="bd" url="https://pan.baidu.com/s/1sCXzwzA6fRtskNP8nKPO0w?pwd=p5sf " password="p5sf"/}2.主机连接虚拟机SQL Server这步就很烦,主机和虚拟机可以互相Ping通但就是连接不上,最后依照以下步骤解决问题.原文链接: https://blog.csdn.net/qq_33454111/article/details/885405211.首先保证主机和虚拟机互相能ping通如果不能ping通,可能是防火墙的问题,需要开启相应的入站规则。2.主机telnet虚拟机的1433端口如果出现黑屏,则表示连接得上如果连接不上,则在虚拟机中添加入站规则3.在虚拟机中登陆想要在主机登陆的账号4.开启允许远程连接到此服务器选项5.设置RemoteAccessEnabled属性为true6.修改sql server配置管理器属性配置完成后需要重新启动服务7.主机连接虚拟机sql server,图上的端口可加可不加3.编写程序项目中的数据库操作要求使用EntityFramework,数据库连接字符串: <connectionStrings> <!-- 连接本机的SQL Server --> <add name="conn" connectionString="Server=.;DataBase=Photography;Trusted_Connection=True;" providerName="System.Data.SqlClient" /> <!-- 连接服务器/虚拟机的SQL Server --> <add name="conn" connectionString="Server=192.168.191.129;DataBase=Photography;user id=sa;password=123456;" providerName="System.Data.SqlClient" /> </connectionStrings>留个小demo,以后再写.net估计就忘的差不多了,方便参考参考{cloud title="EF+IOC示例" type="bd" url="https://pan.baidu.com/s/1P8y5FrN6rqKv_iBUI2U05g?pwd=818f " password="818f"/}
2022年05月25日
242 阅读
0 评论
1 点赞
2022-05-23
vite+vue3+ts添加eslint+prettier项目规范
本文采用Yarn作为包管理器,开发环境使用WebStorm,如果使用Vsc可能需要额外安装插件或配置1.初始化项目# yarn yarn create @vitejs/app # or yarn create vite # npm npm init @vitejs/app这个模板是没有使用配置eslint和prettier的,接下来我们依次安装这些依赖。2.集成eslint# 首先安装 eslint yarn add eslint -D # 初始化eslint npx eslint --init然后选择这些选项,可以根据你的项目进行调整最后一步询问是否安装携带的依赖,选择No并Copy下来这些依赖,手动使用Yarn进行安装。yarn add eslint-plugin-vue@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest -D到这一步,我们就已经安装了相关的依赖了,并且得到一个已配置好的eslintrc.json文件:{ // set running environment is browser + es2021 + node, // else Eslint will report an error when encountering global objects such as Promises, Windows, etc "env": { "browser": true, "es2021": true, "node": true }, // extends the base eslint config "extends": [ "eslint:recommended", "plugin:vue/essential", "plugin:@typescript-eslint/recommended" ], // support ts latest features "parserOptions": { "ecmaVersion": "latest", "parser": "@typescript-eslint/parser", "sourceType": "module" }, // add vue and @typescript-eslint plugins, enhance eslint power "plugins": [ "vue", "@typescript-eslint" ], "rules": { } }然后为package.json增加一个lint命令{ "scripts":{ // lint当前项目中的文件并且开启自动修复 "lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx --fix", } }对.eslintrc.json进行如下修改:{ ... "extends": [ "eslint:recommended", -- "plugin:vue/essential", ++ "plugin:vue/vue3-recommended", "plugin:@typescript-eslint/recommended" ], // 新增,解析vue文件 "parser":"vue-eslint-parser", "parserOptions": { "ecmaVersion": "latest", "parser": "@typescript-eslint/parser", "sourceType": "module" }, ... }3.集成Prettieryarn add prettier -D然后在项目根目录创建一个配置文件:.prettierrc.json{ "useTabs": false, "tabWidth": 2, "printWidth": 80, "singleQuote": true, "trailingComma": "none", "semi": false }配置项很简单,名字就能知道是干嘛的,根据自己情况进行修改即可更多选项和配置方法参阅官方文档 官方的配置文档 下一步配置一个ignore文件,作用在对整个项目进行格式化时对某些文件进行忽略根目录下创建:.prettierignore/dist/* .local .output.js /node_modules/** **/*.svg **/*.sh /public/*然后在package.json中再增加一个命令 "prettier": "prettier --write ."3.解决eslint和prettier的冲突理想状态下,到这一步我们写代码的时候,eslint 和 prettier会相互协作,既美化我们的代码,也修复我们质量不过关的代码。然而现实总是不那么完美,我们会发现某些时候,eslint提示错误,我们修改了以后,屏幕会闪一下然后又恢复到报错状态,自动修复失效了。这是因为eslint 有一部分负责美化代码的规则和 prettier的规则冲突了。解决方案:用 eslint-config-prettier 提供的规则集来覆盖掉eslint冲突的规则,并用eslint-plugin-prettier来使eslint使用prettier的规则来美化代码。yarn add eslint-config-prettier eslint-plugin-prettier -D然后在 .eslintrc.json中extends的最后添加一个配置: "extends": [ "eslint:recommended", "plugin:vue/vue3-recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended" // 新增,必须放在最后面 ],然后我们重启一下IDE,就会发现冲突消失了,我们的自动修复和自动格式化也能相互协作了。文章来源/出处1.实战--为vite-vue3-ts项目添加 eslint + prettier + lint-staged 项目规范: https://juejin.cn/post/7043702363156119565
2022年05月23日
775 阅读
2 评论
0 点赞
2022-04-25
河北专升本-汇编程序设计-考前每日一题
本文目录{dotted startColor="#ff6c6c" endColor="#1989fa"/}2022年4月11日题目假设在内存中有两个带符号 字 数X和Y,计算X和Y差的绝对值(不考虑溢出情况),存放到内存Result单元。分析首先有三个带符号字数的定义在DATA区域实现计算差绝对值有三个思路总是以大数减去小数直接相减,对结果进去取补直接相减,对结果乘以-1答案思路一:总是以大数减去小数DATA SEGMENT X DW -4 Y DW -3 RESULT DW ? DATA ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATA START:MOV AX,DATA MOV DS,AX;finished initializing data segment MOV AX,X MOV BX,Y CMP AX,BX JGE NEXT;比较X是否大于等于Y的值(Compare whether the value of X is greater than the value of Y) XCHG AX,BX;Y大于X的值 - 交换AX和BX的值(Y is greater than X - Swap ax and BX values) NEXT:SUB AX,BX;AX=X-Y MOV RESULT,AX MOV AH,4CH INT 21H CODE ENDS END START思路二三:对结果进行取补或*-1DATA SEGMENT X DW -4 Y DW 3 RESULT DW ? DATA ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATA START:MOV AX,DATA MOV DS,AX;finished initializing data segment MOV AX,X MOV BX,Y SUB AX,BX;AX=X-Y ;方式1:判断AX是否为负数 ; TEST AX,AX ; JNS NEXT ;方式2:判断AX是否为负数 CMP AX,0 JG NEXT ; 结果的处理方式1:取补 ; NEG AX;AX=-AX ; 结果的处理方式2:乘以-1 MOV BX,-1 IMUL BX NEXT:MOV RESULT,AX MOV AH,4CH INT 21H CODE ENDS END START2022年4月12日题目在Data1 Data2单元分别有一个五字节数类似于(123456789ah),计算两个五字节数的和,存放进SUM单元。思路多字节数加法,用一个一个字节对应的去加,有进位的问题所以加法应使用ADC指令,第一对相加前应将CF进行清零操作。答案DATA SEGMENT DATA1 DB 12H,34H,56H,78H,9AH DATA2 DB 0ABH,0CDH,0EFH,12H,34H SUM DB 6 DUP(0);测试结果:BD 01 46 8B CE 00 DATA ENDS CODE SEGMENT ASSUME CS:CODE,DS:DATA START:MOV AX,DATA MOV DS,AX;initialize DS MOV CX,5 ;5字节循环5次 LEA SI,DATA1 LEA DI,DATA2;SI指向DATA1,DI指向DATA2 LEA BX,SUM;BX指向SUM CLC;进位标志位清0 AGAIN:MOV AL,[SI];AL从DATA1中读取一个字节 ADC AL,[DI];对应字节相加 MOV [BX],AL;将结果存入SUM INC SI INC DI INC BX;指向下一次字节 LOOP AGAIN ;5次结束,考虑还会有进位 ADC BYTE PTR [BX],0;将进位存入SUM MOV AH,4CH INT 21H CODE ENDS END START2022年4月13日题目(计算多个有符号数字节数的和)设在数据段Buf单元开始存放了五个字节的有符号数,求五个数的和并将和存入Sum字单元。2022年4月14日题目(8255十六位流水灯)已知8255的地址为200H-203H,其A口和B口共外接了16个共阳LED灯(送0亮送1灭),编程实现16个LED灯的流水灯功能,即按照PA0-PA1...-PA7-PB0-PB1...PB7-PA0...的顺序循环点亮。2022年4月15日题目在BUFF起始的单元依次存放了9个有符号数,判断其中负数的个数,如果大于5个在屏幕上显示'enough',如果小于5个大于0个显示‘not enough’,如果等于0个显示'zero'。4.16(难)键盘输入两个1位BCD数A和B,实现A-B的运算式,并显示在屏幕上。如'1-2=-1' 需考虑结果为负数。2022年4月16日(难)题目键盘输入两个1位BCD数A和B,实现A-B的运算式,并显示在屏幕上。如'1-2=-1' 需考虑结果为负数。2022年4月17日题目现有一2Mhz的信号源,通过8253实现以下功能:通道0每1ms输出一个负脉冲;通道1产生高电平500ms低电平500ms的方波信号;通道2接受到门控位信号时,输出一个宽度为5s的负脉冲且采用十进制计数模式。8253地址为0f8h-0fbh,完成上述功能。2022年4月18日题目使用0AH号功能输入一字符串,判断字符串中是否存在'get'单词,若存在将bh置位为全1,否则清零bh。2022年4月19日题目有两个字节变量A和B,完成以下功能:若A和B有一个为0,则清零另外一个;若A和B相等且不等于0,则将两个变量自增1;其余情况交换A和B两个变量的值。2022年4月20日题目键盘输入一个十六进制数(0-F无需判断),不使用乘法指令和查表指令,计算其对应平方值,存入BH单元。2022年4月21日题目不使用CBW和CWD指令,根据键盘输入的值来实现对应功能。如果键盘输入的是1,则模拟CBW的功能;如果不是1,则模拟CWD的功能。2022年4月22日题目DATA0开始的单元存放了100个类型为字的有符号数,将这些有符号数加负号后,存入DATA1开始的100个字单元。同时判定是否有超限的数,如果有将BH单元置为0FFH,否则清零BH单元。2022年4月23日题目键盘输入一个十六进制数(0-F),在屏幕上显示对应的十进制数。2022年4月24日题目数据段buff1开始的单元有一长度为100字节的字符串,将其逆序传送给buff2开始的单元。如buff1为"abcdef",则buff2为"fedcba"。2022年4月25日题目在字单元Year中存放了数据年,判断该年份是否为闰年,若为闰年则将Flag字节单元清零,否则将Flag单元置为全1。2022年4月26日题目键盘输入一个1-9的数,在屏幕上显示其对应的小写字母。即输入1显示a,输入2显示b以此类推。需要有判定在1-9上的判定程序,如果输入错误直接退出,如果输出正确则显示后可再次输入。2022年4月27日题目用两种方法实现以下功能:从1号功能从键盘输入10个单字符,然后在屏幕上逆序显示。2022年4月28日题目(做不出来也没事)键盘输入一个数(2-8),使用递归的方法实现输入数的阶乘,将阶乘值送入RESULT字单元。2022年4月29日题目Buff字符串以'$'结尾,将其中的小写字母转换成大写字母后,显示该字符串。(扩展可不做:将其中的大写转小写,小写转大写后显示该字符串)思路循环加判断 读入一个就处理一个 最后输出答案写的比较繁琐 感觉可以精简几条重复使用的指令DATA SEGMENT BUFF DB 'LiSongKun','$' DATA ENDS CODE SEGMENT ASSUME DS:DATA,CS:CODE START: MOV AX,DATA MOV DS,AX;DS指向DATA段 LEA SI,BUFF AGAIN: MOV AL,[SI] CMP AL,'$' ;判断是否到达字符串尾部 JZ DISP ;到$位就跳DISP结束循环 展示字符串 CMP AL,'a' ;非字符串结尾 先和 a 比较 JB L0 ;如果小于 a 那么有可能会是大写字母 转去校验大写字母 CMP AL,'z' ;执行到这里 AL 已知是大于a的 JA NEXT ;如果大于a 且 大于z 定不是字母 执行下次循环 JMP PROC1 ;反之则落在大写字母范围内 跳转去转小写 L0: CMP AL,'A' ;当前字符与A进行比较 JB NEXT ;如果小于A 肯定不是字母 就跳下一次循环 CMP AL,'Z' ;和Z比较 JA NEXT ;如果是大于Z的 那么就不会是字母了 大写A~Z在先前段已经判断过了 PROC1: XOR AL,20H ;标准统一大小写互换的指令 MOV [SI],AL ;转换后进行回送 NEXT: INC SI ;指针后移 JMP AGAIN ;再次循环 DISP: LEA DX,BUFF ;输出的操作 MOV AH,9 INT 21H MOV AH,4CH INT 21H CODE ENDS END START2022年5月7日题目已知8086有一可屏蔽中断,其对应中断类型码为8AH。已知8259单片使用且工作于正常eoi,普通全嵌套,边沿触发,非缓冲方式。该中断对应服务程序为INTQ,服务程序的功能为将200H端口内容送去字节BUF单元。阅读以前内容完成以下题目:1.该中断连接了8259的哪个引脚2.该中断的中断向量的地址是多少3.完成中断向量的设置4.完成8259的初始化5.完成中断服务程序的编写2022年5月8日题目内存X单元中有一7位二进制数据(以字节形式存放即D7位数据位无意义)。以偶校验的形式构成其校验位,并将X的D7位置位为校验位形式。2022年5月12日题目数据段有5组每组长度为20的字符串,键盘输入1-5中的任意数字,显示对应字符串内容。
2022年04月25日
340 阅读
1 评论
1 点赞
1
...
5
6
7