首页
文章分类
逆向网安
中英演讲
杂类教程
学习笔记
前端开发
汇编
数据库
.NET
服务器
Python
Java
PHP
Git
生活记录
读书笔记
作品发布
网上邻居
留言板
欣赏小姐姐
关于我
Search
登录
1
Js解密学习通视频秒过请求
1,830 阅读
2
学习强国部署查看docker容器输出和编写脚本清理输出
1,694 阅读
3
利用AList搭建家庭个人影音库
995 阅读
4
学习通学号登录简析
797 阅读
5
学习通签到QQ Bot开发及服务器部署(koishi+go-cqhttp)
501 阅读
Search
标签搜索
Laravel
前端
JavaScript
PHP
抓包
csharp
Fiddler
Vue
selenium
TypeScript
docker
Python
Java
爬虫
安卓逆向
ubuntu
SpringBoot
Web逆向
Git
winform
Hygge
累计撰写
44
篇文章
累计收到
339
条评论
首页
栏目
逆向网安
中英演讲
杂类教程
学习笔记
前端开发
汇编
数据库
.NET
服务器
Python
Java
PHP
Git
生活记录
读书笔记
作品发布
页面
网上邻居
留言板
欣赏小姐姐
关于我
用户登录
搜索到
5
篇与
的结果
2022-11-23
Winform的三层架构详细案例分析
写了几个Winform程序后发现一个类的行数直逼2000行...维护十分困难,想起来之前学过三层架构,转篇文章记录一下!三层架构将整个业务应用划分为:(1)界面UI层;(2)业务逻辑层;(3)数据访问层。对于复杂的系统分层可以让结构更加清晰,模块更加独立,便于维护。各层的任务:(1)数据访问层:负责数据库的操作。(2)业务逻辑层:实现功能模块的业务逻辑。(3)界面UI层:绘制界面,以及负责界面相关代码。(4)实体类:将数据库中的表转化为面向对象思想中的类。一、案例需求使用三层架构实现学生管理:(1)专业下拉框绑定专业表数据,网格控件绑定学生数据,并且点击"搜索"按钮可以多条件组合查询。(2)选中某一行,右键可以弹出"删除"菜单,点击"删除"菜单可以删除学生数据。(3)点击"新增"按钮,弹出新增窗体,在此窗体中完成学生的新增操作。(4)选中某一行,点击"编辑"按钮,弹出编辑窗体,在此窗体中完成数据的修改。备注:其中性别的单选框,以及爱好的多选框分别用两个Pannel容器包含。数据库准备:--专业 create table ProfessionInfo ( ProfessionID int primary key identity(1,1), --专业编号 ProfessionName varchar(50) not null unique --专业名称 ) --学生 create table StudentInfo ( StuID varchar(20) primary key, --学生学号 StuName varchar(50) not null, --学生姓名 StuAge int not null check(StuAge > 0 and StuAge < 130), --学生年龄 StuSex char(2) not null check(StuSex in('男','女')), --学生性别 StuHobby nvarchar(100), --爱好 ProfessionID int not null references ProfessionInfo(ProfessionID), --所属专业编号 ) --添加专业信息 insert into ProfessionInfo(ProfessionName) values('电子竞技') insert into ProfessionInfo(ProfessionName) values('软件开发') insert into ProfessionInfo(ProfessionName) values('医疗护理') --插入学生信息 insert into StudentInfo(StuID,StuName,StuAge,StuSex,StuHobby,ProfessionID) values('001','刘备',18,'男','',1) insert into StudentInfo(StuID,StuName,StuAge,StuSex,StuHobby,ProfessionID) values('002','关羽',20,'男','',2) insert into StudentInfo(StuID,StuName,StuAge,StuSex,StuHobby,ProfessionID) values('003','张飞',19,'男','',2) insert into StudentInfo(StuID,StuName,StuAge,StuSex,StuHobby,ProfessionID) values('004','孙尚香',17,'女','',3)二、项目结构(1)创建一个空白解决方案。(2)在解决方案中创建类库项目MyEntity代表"实体类"。(3)在解决方案中创建类库项目MyDAL代表"数据访问层"。(4)在解决方案中创建类库项目MyBLL代表"业务逻辑层"。(5)在解决方案中创建Windows窗体应用程序MyUI代表"界面UI层"。三、实体类编写在MyEntity项目中添加两个实体类,实体类代码如下:ProfessionInfoEntity: public class ProfessionInfoEntity { public ProfessionInfoEntity() { this.ProfessionID = 0; this.ProfessionName = ""; } public int ProfessionID { get; set; } //专业编号 public string ProfessionName { get; set; }//专业名称 }StudentInfoEntiy:public class StudentInfoEntiy { public StudentInfoEntiy() { this.StuID = ""; this.StuName = ""; this.StuAge = 0; this.StuSex = ""; this.StuHobby = ""; this.ProfessionID = 0; this.ProfessionName = ""; } public string StuID { get; set; } //学生学号 public string StuName { get; set; } //学生姓名 public int StuAge { get; set; } //学生年龄 public string StuSex { get; set; } //学生性别 public string StuHobby { get; set; } //学生爱好 public int ProfessionID { get; set; } //学生所属专业编号 public string ProfessionName { get; set; } //学生所属专业名称 }四、数据访问层编写(1)由于数据访问层需要使用实体类,所以需要添加实体类的引用。即在MyDAL项目上右键-->添加-->引用-->项目,在项目中勾选MyEntity项目。(2)将之前封装好的DBHelper文件复制到MyDAL项目中,并通过添加现有项,将DBHelper加入项目。(3)在MyDAL项目中添加两个类,类代码如下:ProfessionInfoDAL:public class ProfessionInfoDAL { #region 新增 public int Add(ProfessionInfoEntity entity) { string sql = "insert into ProfessionInfo(professionName) values(@professionName)"; DBHelper.PrepareSql(sql); DBHelper.SetParameter("ProfessionName",entity.ProfessionName); return DBHelper.ExecNonQuery(); } #endregion #region 删除 public int Delete(int id) { string sql = "delete from ProfessionInfo where
[email protected]
"; DBHelper.PrepareSql(sql); DBHelper.SetParameter("ProfessionID", id); return DBHelper.ExecNonQuery(); } #endregion #region 修改 public int Update(ProfessionInfoEntity entity) { string sql = "update ProfessionInfo set
[email protected]
where
[email protected]
"; DBHelper.PrepareSql(sql); DBHelper.SetParameter("professionName", entity.ProfessionName); DBHelper.SetParameter("ProfessionID", entity.ProfessionID); return DBHelper.ExecNonQuery(); } #endregion #region 列表 public List<ProfessionInfoEntity> List() { string sql = "select * from ProfessionInfo"; DataTable dt = new DataTable(); DBHelper.PrepareSql(sql); dt = DBHelper.ExecQuery(); List<ProfessionInfoEntity> list = new List<ProfessionInfoEntity>(); foreach (DataRow item in dt.Rows) { ProfessionInfoEntity entity = new ProfessionInfoEntity(); entity.ProfessionID = int.Parse(item["ProfessionID"].ToString()); entity.ProfessionName = item["ProfessionName"].ToString(); list.Add(entity); } return list; } #endregion #region 详情 public ProfessionInfoEntity Detail(int id) { string sql = "select * from ProfessionInfo where
[email protected]
"; DataTable dt = new DataTable(); DBHelper.PrepareSql(sql); DBHelper.SetParameter("ProfessionID", id); dt = DBHelper.ExecQuery(); if (dt.Rows.Count == 0) return null; ProfessionInfoEntity entity = new ProfessionInfoEntity(); entity.ProfessionID = int.Parse(dt.Rows[0]["ProfessionID"].ToString()); entity.ProfessionName = dt.Rows[0]["ProfessionName"].ToString(); return entity; } #endregion }StudentInfoDAL:public class StudentInfoDAL { #region 新增 public int Add(StudentInfoEntiy entity) { string sql = "insert into StudentInfo(StuID,StuName,StuAge,StuSex,StuHobby,ProfessionID) values(@StuID,@StuName,@StuAge,@StuSex,@StuHobby,@ProfessionID)"; DBHelper.PrepareSql(sql); DBHelper.SetParameter("StuID", entity.StuID); DBHelper.SetParameter("StuName", entity.StuName); DBHelper.SetParameter("StuAge", entity.StuAge); DBHelper.SetParameter("StuSex", entity.StuSex); DBHelper.SetParameter("StuHobby", entity.StuHobby); DBHelper.SetParameter("ProfessionID", entity.ProfessionID); return DBHelper.ExecNonQuery(); } #endregion #region 删除 public int Delete(string id) { string sql = "delete from StudentInfo where
[email protected]
"; DBHelper.PrepareSql(sql); DBHelper.SetParameter("StuID", id); return DBHelper.ExecNonQuery(); } #endregion #region 修改 public int Update(StudentInfoEntiy entity) { string sql = "update StudentInfo set
[email protected]
,
[email protected]
,
[email protected]
,
[email protected]
,
[email protected]
where
[email protected]
"; DBHelper.PrepareSql(sql); DBHelper.SetParameter("StuName", entity.StuName); DBHelper.SetParameter("StuAge", entity.StuAge); DBHelper.SetParameter("StuSex", entity.StuSex); DBHelper.SetParameter("StuHobby", entity.StuHobby); DBHelper.SetParameter("ProfessionID", entity.ProfessionID); DBHelper.SetParameter("StuID", entity.StuID); return DBHelper.ExecNonQuery(); } #endregion #region 列表 public List<StudentInfoEntiy> List() { string sql = "select * from StudentInfo"; DataTable dt = new DataTable(); DBHelper.PrepareSql(sql); dt = DBHelper.ExecQuery(); List<StudentInfoEntiy> list = new List<StudentInfoEntiy>(); foreach (DataRow item in dt.Rows) { StudentInfoEntiy entity = new StudentInfoEntiy(); entity.StuID = item["StuID"].ToString(); entity.StuName = item["StuName"].ToString(); entity.StuAge = int.Parse(item["StuAge"].ToString()); entity.StuSex = item["StuSex"].ToString(); entity.StuHobby = item["StuHobby"].ToString(); entity.ProfessionID = int.Parse(item["ProfessionID"].ToString()); list.Add(entity); } return list; } #endregion #region 详情 public StudentInfoEntiy Detail(string id) { string sql = "select * from StudentInfo where
[email protected]
"; DataTable dt = new DataTable(); DBHelper.PrepareSql(sql); DBHelper.SetParameter("StuID", id); dt = DBHelper.ExecQuery(); if (dt.Rows.Count == 0) return null; StudentInfoEntiy entity = new StudentInfoEntiy(); entity.StuID = dt.Rows[0]["StuID"].ToString(); entity.StuName = dt.Rows[0]["StuName"].ToString(); entity.StuAge = int.Parse(dt.Rows[0]["StuAge"].ToString()); entity.StuSex = dt.Rows[0]["StuSex"].ToString(); entity.StuHobby = dt.Rows[0]["StuHobby"].ToString(); entity.ProfessionID = int.Parse(dt.Rows[0]["ProfessionID"].ToString()); return entity; } #endregion }五、业务逻辑层编写(1)由于业务逻辑层需要使用实体类,所以需要添加实体类的引用。即在MyBLL项目上右键-->添加-->引用-->项目,在项目中勾选MyEntity项目。(2)由于业务逻辑层需要调用数据访问层,所以需要添加数据访问层的引用。即在MyBLL项目上右键-->添加-->引用-->项目,在项目中勾选MyDAL项目。(3)在MyBLL项目中添加两个类,类代码如下:ProfessionInfoBLL:public class ProfessionInfoBLL { ProfessionInfoDAL dal = new ProfessionInfoDAL(); #region 新增 public int Add(ProfessionInfoEntity entity) { return dal.Add(entity); } #endregion #region 删除 public int Delete(int id) { return dal.Delete(id); } #endregion #region 修改 public int Update(ProfessionInfoEntity entity) { return dal.Update(entity); } #endregion #region 列表 public List<ProfessionInfoEntity> List() { return dal.List(); } #endregion #region 详情 public ProfessionInfoEntity Detail(int id) { return dal.Detail(id); } #endregion }StudentInfoBLL:public class StudentInfoBLL { StudentInfoDAL dal = new StudentInfoDAL(); #region 新增 public int Add(StudentInfoEntiy entity) { return dal.Add(entity); } #endregion #region 删除 public int Delete(string id) { return dal.Delete(id); } #endregion #region 修改 public int Update(StudentInfoEntiy entity) { return dal.Update(entity); } #endregion #region 列表 public List<StudentInfoEntiy> List() { return dal.List(); } #endregion #region 详情 public StudentInfoEntiy Detail(string id) { return dal.Detail(id); } #endregion }六、界面UI层代码编写(1)由于界面UI层需要使用实体类,所以需要添加实体类的引用。即在MyUI项目上右键-->添加-->引用-->项目,在项目中勾选MyEntity项目。(2)由于界面UI层需要调用业务逻辑层,所以需要添加业务逻辑层的引用。即在MyUI项目上右键-->添加-->引用-->项目,在项目中勾选MyBLL项目。查询窗体界面及代码:(1)由于查询学生需要多条件组合查询,所以给数据访问层和业务逻辑层添加条件搜索的方法。给数据访问层MyDAL中StudentInfoDAL类添加方法:#region 条件查询 public List<StudentInfoEntiy> Search(StudentInfoEntiy searchObj) { string sql = "select * from StudentInfo inner join ProfessionInfo on StudentInfo.ProfessionID = ProfessionInfo.ProfessionID where 1 = 1"; if (searchObj.ProfessionID != 0) sql += " and StudentInfo.ProfessionID = " + searchObj.ProfessionID; if (!searchObj.StuName.Equals("")) sql += " and StuName like '%" + searchObj.StuName + "%'"; DataTable dt = new DataTable(); DBHelper.PrepareSql(sql); dt = DBHelper.ExecQuery(); List<StudentInfoEntiy> list = new List<StudentInfoEntiy>(); foreach (DataRow item in dt.Rows) { StudentInfoEntiy entity = new StudentInfoEntiy(); entity.StuID = item["StuID"].ToString(); entity.StuName = item["StuName"].ToString(); entity.StuAge = int.Parse(item["StuAge"].ToString()); entity.StuSex = item["StuSex"].ToString(); entity.StuHobby = item["StuHobby"].ToString(); entity.ProfessionID = int.Parse(item["ProfessionID"].ToString()); entity.ProfessionName = item["ProfessionName"].ToString(); list.Add(entity); } return list; } #endregion给业务逻辑层MyBLL中StudentInfoBLL类添加方法:#region 条件查询 public List<StudentInfoEntiy> Search(StudentInfoEntiy searchObj) { return dal.Search(searchObj); } #endregion(2)在此界面中多个功能需要调用业务逻辑层,定义两个业务逻辑层对象:ProfessionInfoBLL proBll = new ProfessionInfoBLL(); StudentInfoBLL stuBll = new StudentInfoBLL();(3)查询窗体绑定专业信息、绑定学生信息以及搜索功能代码:#region 绑定下拉框 private void BindPro() { List<ProfessionInfoEntity> list = new List<ProfessionInfoEntity>(); list = proBll.List(); list.Insert(0,new ProfessionInfoEntity { ProfessionID=0,ProfessionName="--请选择--"}); this.cmbPro.DataSource = list; this.cmbPro.DisplayMember = "ProfessionName"; this.cmbPro.ValueMember = "ProfessionID"; } #endregion #region 绑定学生数据 private void BindData() { StudentInfoEntiy searchObj = new StudentInfoEntiy(); searchObj.ProfessionID = int.Parse(this.cmbPro.SelectedValue.ToString()); searchObj.StuName = this.txtName.Text; this.dataGridView1.AutoGenerateColumns = false; this.dataGridView1.DataSource = stuBll.Search(searchObj); } #endregion #region 窗体加载 private void FrmSelect_Load(object sender, EventArgs e) { BindPro(); BindData(); } #endregion #region 搜索按钮 private void btSearch_Click(object sender, EventArgs e) { BindData(); } #endregion(4)删除菜单代码:private void 删除ToolStripMenuItem_Click(object sender, EventArgs e) { //添加是否确定删除的对话框 DialogResult result = MessageBox.Show("确定要删除数据吗,删除之后无法恢复!", "提示框", MessageBoxButtons.OKCancel, MessageBoxIcon.Question); if (result == DialogResult.Cancel) return; string stuid = this.dataGridView1.SelectedRows[0].Cells[0].Value.ToString(); if(stuBll.Delete(stuid) == 1) MessageBox.Show("删除成功!"); else MessageBox.Show("删除失败!"); BindData(); }新增窗体界面及代码:ProfessionInfoBLL proBll = new ProfessionInfoBLL(); StudentInfoBLL stuBll = new StudentInfoBLL(); #region 绑定下拉框 private void BindPro() { List<ProfessionInfoEntity> list = new List<ProfessionInfoEntity>(); list = proBll.List(); list.Insert(0, new ProfessionInfoEntity { ProfessionID = 0, ProfessionName = "--请选择--" }); this.cmbPro.DataSource = list; this.cmbPro.DisplayMember = "ProfessionName"; this.cmbPro.ValueMember = "ProfessionID"; } #endregion private void FrmAdd_Load(object sender, EventArgs e) { BindPro(); } private void btAdd_Click(object sender, EventArgs e) { //性别处理 string sex = ""; if (this.rbBoy.Checked == true) sex = this.rbBoy.Text; if (this.rbGirl.Checked == true) sex = this.rbGirl.Text; //爱好处理 string hobby = ""; foreach (CheckBox ck in this.panel2.Controls) { if (ck.Checked == true) { if (!hobby.Equals("")) hobby += ","; hobby += ck.Text; } } StudentInfoEntiy entity = new StudentInfoEntiy(); entity.StuID = this.txtId.Text; entity.StuName = this.txtName.Text; entity.StuAge = int.Parse(this.txtAge.Text); entity.StuSex = sex; entity.StuHobby = hobby; entity.ProfessionID = int.Parse(this.cmbPro.SelectedValue.ToString()); if (stuBll.Add(entity) == 1) MessageBox.Show("新增成功!"); else MessageBox.Show("新增失败!"); this.Close(); }编辑修改窗体界面及代码:public string StuID { get; set; } //学生编号 ProfessionInfoBLL proBll = new ProfessionInfoBLL(); StudentInfoBLL stuBll = new StudentInfoBLL(); #region 绑定下拉框 private void BindPro() { List<ProfessionInfoEntity> list = new List<ProfessionInfoEntity>(); list = proBll.List(); list.Insert(0, new ProfessionInfoEntity { ProfessionID = 0, ProfessionName = "--请选择--" }); this.cmbPro.DataSource = list; this.cmbPro.DisplayMember = "ProfessionName"; this.cmbPro.ValueMember = "ProfessionID"; } #endregion #region 绑定详情 private void BindDetail() { StudentInfoEntiy entity = new StudentInfoEntiy(); entity = stuBll.Detail(this.StuID); this.txtId.Text = entity.StuID; this.txtName.Text = entity.StuName; this.txtAge.Text = entity.StuAge.ToString(); this.cmbPro.SelectedValue = entity.ProfessionID; //性别处理 if (entity.StuSex.Equals("男")) this.rbBoy.Checked = true; else this.rbGirl.Checked = true; //爱好处理 string[] arrHobby = entity.StuHobby.Split(','); foreach (string hobby in arrHobby) { foreach (CheckBox ck in this.panel2.Controls) { if (ck.Text.Equals(hobby)) ck.Checked = true; } } } #endregion private void FrmEdit_Load(object sender, EventArgs e) { BindPro(); BindDetail(); } private void btUpdate_Click(object sender, EventArgs e) { //性别处理 string sex = ""; if (this.rbBoy.Checked == true) sex = this.rbBoy.Text; if (this.rbGirl.Checked == true) sex = this.rbGirl.Text; //爱好处理 string hobby = ""; foreach (CheckBox ck in this.panel2.Controls) { if (ck.Checked == true) { if (!hobby.Equals("")) hobby += ","; hobby += ck.Text; } } StudentInfoEntiy entity = new StudentInfoEntiy(); entity.StuID = this.txtId.Text; entity.StuName = this.txtName.Text; entity.StuAge = int.Parse(this.txtAge.Text); entity.StuSex = sex; entity.StuHobby = hobby; entity.ProfessionID = int.Parse(this.cmbPro.SelectedValue.ToString()); if (stuBll.Update(entity) == 1) MessageBox.Show("修改成功!"); else MessageBox.Show("修改失败!"); this.Close(); }查询窗体中"新增"和"编辑"按钮代码:private void btAdd_Click(object sender, EventArgs e) { FrmAdd frm = new FrmAdd(); frm.Show(); } private void btEdit_Click(object sender, EventArgs e) { string stuid = this.dataGridView1.SelectedRows[0].Cells[0].Value.ToString(); FrmEdit frm = new FrmEdit(); frm.StuID = stuid; frm.Show(); }本文来自博客园,作者:码农阿亮,转载请注明原文链接:https://www.cnblogs.com/wml-it/p/15967883.html
2022年11月23日
40 阅读
0 评论
0 点赞
2022-11-09
csharp调用OCR和DDDDOCR的DLL进行验证码识别
一、方式1:使用OCR.dll参考了一下精易论坛的这个易语言版的例程:https://bbs.125.la/forum.php?mod=viewthread&tid=14273813效果图:工具类代码:public class OCR { [DllImport("ocr.dll", EntryPoint = "init")] public static extern int Init(); [DllImport("ocr.dll", EntryPoint = "ocr")] public extern static string ocr(byte[] img, int imgLength); } }将ocr.dll放在项目工程的bin\Debug目录下即可。Winform程序中使用:// 1.OCR初始化,全局初始化一次即可,建议放在Program.cs的Main方法中 OCR.Init(); // 2.请求验证码图片 byte[] tmp = "https://passport2.chaoxing.com/num/code" .WithHeader("User-Agent", Constant.USER_AGENT) .WithCookies(out cookies) .GetBytesAsync().Result; // 3.调用DLL进行识别 code = OCR.ocr(tmp, tmp.Length);分流下载链接:{cloud title="OCR.DLL和易语言调用例程" type="bd" url="https://pan.baidu.com/s/1mmHUKscFIMghlm0l7ADS-A?pwd=xq82 " password="xq82 "/}二、方式2:使用DDDDOCR之前写过一篇python调用这个库的文章:记录一次调用OCR验证码识别库的过程,识别率不是很高,最终没有采用今天看到网络上有大神打成了DLL包:ddddocr 单dll版本,记录一下使用,算是多一种验证码的解决方案。效果图:工具类代码:public class OCR { [DllImport("ddocr_qs.dll", EntryPoint = "InitModel")] public static extern int Init(int threadnum); [DllImport("ddocr_qs.dll", EntryPoint = "Identify")] public extern static string ocr(byte[] img, int length); [DllImport("ddocr_qs.dll", EntryPoint = "FreeModel")] public extern static void FreeModel(); } }将ddocr_qs.dll放在项目工程的bin\Debug目录下即可。Winform程序中使用:// 1.OCR初始化,全局初始化一次即可,建议放在Program.cs的Main方法中 // 这里的参数是Thread Number OCR.Init(1); // 2.请求验证码图片 byte[] tmp = "https://passport2.chaoxing.com/num/code" .WithHeader("User-Agent", Constant.USER_AGENT) .WithCookies(out cookies) .GetBytesAsync().Result; // 3.调用DLL进行识别 code = OCR.ocr(tmp, tmp.Length);{cloud title="DDDDOCR_DLL和易语言调用例程" type="bd" url="https://pan.baidu.com/s/1yqY8uTIieeicYt2yow9ogw?pwd=py1l " password="py1l "/}三、附:查看DLL中的可用函数本小白第一次在csharp中调用DLL中的函数,刚拿到OCR.dll后不知道怎么写调用的代码于是上网查了一下,有推荐使用Dependency和Dependencies这两个工具,都有下载使用,感觉上还是后者更强打开GUI界面后直接将OCR.DLL拖入,可以看到函数名这样子但是参数和类型是看不到的 = =,似乎只能问开发者中获取引用1.C#调用论坛发布过的一个OCR.dll崩溃(易语言调用没问题):https://bbs.125.la/forum.php?mod=viewthread&tid=14451756&highlight=ocr.dll2.几百种网站的本地验证码识别源码(字母和数字通杀):https://bbs.125.la/forum.php?mod=viewthread&tid=142738133.ddddocr 单dll版本https://bbs.125.la/forum.php?mod=viewthread&tid=147328384.好工具推荐系列:VC++开发必备神器 -- Dependencies,查看依赖库DLL,支持win10,比depends更好用:https://blog.csdn.net/libaineu2004/article/details/896707265.Dependency Walker 2.2:http://www.dependencywalker.com/6.Dependencies GITHUB : https://github.com/lucasg/Dependencies
2022年11月09日
307 阅读
0 评论
1 点赞
2022-10-29
学习通学号登录简析
抓包分析序号参数名描述1fid学校ID2uname加密后的学号3password加密后的密码4numcode验证码除了这几个参数是变动的外,其他的都可以保持原样。判断一下学号和密码的加密特征0QgUbMUJj2usHikiqtb8HQ==22个字符加上两个等号 大概可能为..AES加密直接搜索请求的路径:/unitlogin,定位到login.js文件向上翻找一下// 对学号和密码进行加密 if(t == "true"){ let transferKey = "u2oh6Vu^HWe4_AES"; password = encryptByAES(password, transferKey); uname = encryptByAES(uname, transferKey); } // 使用的加密方法 function encryptByAES(message, key){ let CBCOptions = { iv: CryptoJS.enc.Utf8.parse(key), mode:CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }; let aeskey = CryptoJS.enc.Utf8.parse(key); let secretData = CryptoJS.enc.Utf8.parse(message); let encrypted = CryptoJS.AES.encrypt( secretData, aeskey, CBCOptions ); return CryptoJS.enc.Base64.stringify(encrypted.ciphertext); }这里就很明显了学号和密码的加密方式都是采用 AES 加密加密模式为CBCkey为:u2oh6Vu^HWe4_AESchsarp中复现请求体处理,需要对学号和密码加密后的文本进行URL编码:string requestBody = $"pid=-1&fid={schoolId}&uname={HttpUtility.UrlEncode(AESHelper.AesEncrypt(sn))}&numcode={verify}&password={HttpUtility.UrlEncode(AESHelper.AesEncrypt(password))}&refer=http%253A%252F%252Fi.chaoxing.com&t=true&hidecompletephone=0&doubleFactorLogin=0&independentId=0";AES加密工具类:using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; namespace OnlineCourseToolKit.Utils { /// <summary> /// AES加密解密 助手类 /// CBC加密模式 /// </summary> public class AESHelper { /// <summary> /// 默认密钥-长度32位 /// </summary> private const string Key = "u2oh6Vu^HWe4_AES"; /// <summary> /// 默认向量-长度16位 /// </summary> private const string Iv = "u2oh6Vu^HWe4_AES"; /// <summary> /// AES加密 /// </summary> /// <param name="str">需要加密字符串</param> /// <returns>加密后字符串</returns> public static string AesEncrypt(string str) { return Encrypt(str, Key); } /// <summary> /// AES解密 /// </summary> /// <param name="str">需要解密字符串</param> /// <returns>解密后字符串</returns> public static string AesDecrypt(string str) { return Decrypt(str, Key); } /// <summary> /// AES 加密 /// </summary> /// <param name="str">明文(待加密)</param> /// <param name="key">密文</param> /// <returns></returns> private static string Encrypt(string str, string key) { if (string.IsNullOrEmpty(str)) return null; Byte[] toEncryptArray = Encoding.UTF8.GetBytes(str); RijndaelManaged rm = new RijndaelManaged { Key = Encoding.UTF8.GetBytes(key), Mode = CipherMode.CBC, Padding = PaddingMode.PKCS7, IV = Encoding.UTF8.GetBytes(Iv) }; ICryptoTransform cTransform = rm.CreateEncryptor(); Byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length); return Convert.ToBase64String(resultArray, 0, resultArray.Length); } /// <summary> /// AES 解密 /// </summary> /// <param name="str">明文(待解密)</param> /// <param name="key">密文</param> /// <returns></returns> private static string Decrypt(string str, string key) { if (string.IsNullOrEmpty(str)) return null; Byte[] toEncryptArray = Convert.FromBase64String(str); RijndaelManaged rm = new RijndaelManaged { Key = Encoding.UTF8.GetBytes(key), Mode = CipherMode.ECB, Padding = PaddingMode.PKCS7, IV = Encoding.UTF8.GetBytes(Iv) }; ICryptoTransform cTransform = rm.CreateDecryptor(); Byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length); return Encoding.UTF8.GetString(resultArray); } } }
2022年10月29日
797 阅读
3 评论
2 点赞
2022-08-28
Js解密学习通视频秒过请求
前言之前在github找到一个python selenium刷视频和做测验的源码,挂服务器上问题太多了,最近闲下来破解一下网络请求版的刷视频和做测验。效果图:学习通担心的主要是秒过视频会造成学习时长过短的情况,为此没有使用秒过 而是持续请求模拟真实进度,虽然和selenium版的时长差不多,但是更稳定和便捷。视频时长都是完整的有计算的。视频播放解密先抓到记录视频播放的请求GET 请求参数全在URL里,rt,isdrag,_t 这几个参数没什么作用,不发送也可以获得响应除了enc外的参数都可以在其他请求中获得,本文重点分析enc首先找切入点,这个请求的构造是由播放器产生的,就会存在参数拼接的情况。我选择参数名isdrag全局搜索(当然要选特殊的啦,越特殊重复的结果就越少)_0x473cb3 = [_0x16dd5d[_0x5d818d(0x242)], '/', _0x16dd5d[_0x5d818d(0x1ee)], _0x5d818d(0x44c), _0x16dd5d[_0x5d818d(0x3af)], _0x5d818d(0x26d), _0x1d0594, _0x5d818d(0x396), _0x16dd5d[_0x5d818d(0x2e2)], _0x5d818d(0x20e), _0x8e40c2, _0x5d818d(0x437), _0x16dd5d['objectId'], _0x5d818d(0x36a), _0x16dd5d[_0x5d818d(0x20c)], _0x5d818d(0x3b1), _0x16dd5d[_0x5d818d(0x3ba)], _0x5d818d(0x1ca), _0x16dd5d[_0x5d818d(0x3bd)], '&isdrag=', _0x9aa55, '&view=pc', _0x5d818d(0x1ef), md5(_0x5dcd20), _0x5d818d(0x1ff), _0x16dd5d['rt'], _0x5d818d(0x2d3), _0x5d818d(0x2ab), new Date()[_0x5d818d(0x1d6)]()][_0x5d818d(0x3d8)]('');按照对应位置的话更直观一点'&isdrag=', _0x9aa55, '&view=pc', _0x5d818d(0x1ef), md5(_0x5dcd20), '&isdrag=', 3 , '&view=pc', '&enc', md5(_0x5dcd20), // enc = f60c04f7cf2820976479ba3758b7d142, // 32位,典型的md5加密,所以enc=md5(_0x5dcd20)继续查看_0x5dcd20var _0x5dcd20 = Ext[_0x5d818d(0x2a0)][_0x5d818d(0x3ae)](_0x56986f, _0x16dd5d[_0x5d818d(0x3af)], _0x16dd5d[_0x5d818d(0x3bd)], _0x16dd5d[_0x5d818d(0x3ba)] || '', _0x16dd5d[_0x5d818d(0x246)], _0x3a88e3, _0x5d818d(0x24b), _0x16dd5d[_0x5d818d(0x2e2)] * 0x3e8, _0x8e40c2) // 分析: Ext的二维对象里面是一个方法 小括号后面全是参数打个断点看一下在511行停下后转去控制台打印一下坐标和索引分别是什么String format [{0}][{1}][{2}][{3}][{4}][{5}][{6}][{7}] 59831731 199369846 154762960473479 85bf18fc7d1a0e4707c8b4c771580a53 0 d_yHJ!$pdA~5 767000 0_767 // 这里就很明显了,_0x5dcd20就是将一些参数填充到这个字符串的占位符中 // 如下: Ext[_0x5d818d(0x2a0)][_0x5d818d(0x3ae)](_0x56986f, _0x16dd5d[_0x5d818d(0x3af)], _0x16dd5d[_0x5d818d(0x3bd)], _0x16dd5d[_0x5d818d(0x3ba)] || '', _0x16dd5d[_0x5d818d(0x246)], _0x3a88e3, _0x5d818d(0x24b), _0x16dd5d[_0x5d818d(0x2e2)] * 0x3e8, _0x8e40c2) '[59831731][199369846][154762960473479][85bf18fc7d1a0e4707c8b4c771580a53][0][d_yHJ!$pdA~5][767000][0_767]'这个字符串中的参数,有些是固定不变的,会变动的是取这个_0x16dd5d数组的成员,基本都是可以在其他请求中取到的,没有加密信息。对这个字符串进行md5加密就可以得到enc解决无限debugger我们在对一些网站进行JS逆向分析过程中,很有可能会遇到网站禁止开发人员使用F12进行网页调试,如果你打开就会遇到无限debugger的这个情况,当我们遇到这种情况,我们该如何进行避免无限debugger弹出呢?解决方法:可以直接在控制台输入以下代码,可以有效的跳过无限debugger(Chrome浏览器)Function.prototype.constructor = function(){}核心代码while (currentProgress <= video_info.duration) { string enc = $"[{CurrentCourse.ClassId}][{vav.defaults.userid}][{point.jobid}][{point.objectId}][{currentProgress}000][d_yHJ!$pdA~5][{video_info.duration}000][0_{video_info.duration}]"; enc = GetMD5(enc);// 32位md5加密 string requestBody = $"clazzId={CurrentCourse.ClassId}&playingTime={currentProgress}&duration={video_info.duration}" + $"&clipTime=0_{video_info.duration}&objectId={vav.attachments[0].property.objectid}&otherInfo={vav.attachments[0].otherInfo}" + $"&courseId={CurrentCourse.CourseId}&jobid={vav.attachments[0].property.jobid}&userid={vav.defaults.userid}&view=pc&enc={enc}&dtype=Video"; // 跳过视频 VideoProgressVo jumpVideoResponse = await ($"https://mooc1.chaoxing.com/multimedia/log/a/{vav.defaults.cpi}/{video_info.dtoken}?{requestBody}") .WithHeader("Referer", "https://mooc1.chaoxing.com/ananas/modules/video/index.html") .WithHeader("User-Agent", "apifox/1.0.0 (https://www.apifox.cn)") .WithHeader("Content-Type", "application/json") .WithCookies(Cookies) .GetJsonAsync<VideoProgressVo>(); Log($"当前进度值:{currentProgress},总长度:{video_info.duration},状态:{jumpVideoResponse.isPassed}"); if (!jumpVideoResponse.isPassed) { if(currentProgress + 60 > video_info.duration) { currentProgress = video_info.duration; }else if(currentProgress == video_info.duration) { currentProgress += 1; }else { currentProgress += 60; } }else { Log("该视频已完成"); currentProgress = video_info.duration + 1; } await Task.Delay(60 * 1000); }引用1.JS调试的时候遇到无限debugger怎么办? : https://blog.csdn.net/qq_19309473/article/details/120573903
2022年08月28日
1,830 阅读
4 评论
3 点赞
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日
46 阅读
0 评论
0 点赞