分类

类型:
不限 游戏开发 计算机程序开发 Android开发 网站开发 笔记总结 其他
评分:
不限 10 9 8 7 6 5 4 3 2 1
原创:
不限
年份:
不限 2018 2019 2020 2021

技术文章列表

  • 基于SSM的超市订单管理系统

    1 系统需求分析超市订单管理系统是一个专为连锁店、超市等商业场所提供订单管理平台的系统。该系统的目标是建立一个订单管理平台,为需要合理规划超市供应链、供应商以及工作人员提供的便捷的平台。该系统的主要业务需求包括记录并维护某超市的供应商信息,以及该超市与供应商之间的交易订单信息,包括三种角色,系统管理员经理,普通员工。
    1.1 系统功能分析本系统主要的功能是实现超市订单管理功能,以便为超市、连锁店提供以及其他负责人提供订单详情、联系方式等,系统的主要功能有以下五个方面:

    登录/注销:管理员可以在网站上登录浏览,离开时注销并退出
    订单管理:管理员可以浏览所有订单信息,并且通过点击查看了解订单详情信息
    供应商管理:管理员可以在网站浏览所有供应商信息,并在在与其他供应商达成合作之后,添加相关供应商信息,并且通过点击查看了解他们的联系方式等
    用户管理:管理员可以管理所有超市员工用户,对用户进行增删改查,对于离职或其他原因的未工作用户给予注销管理
    密码修改:管理员可对自己的账号密码进行修改,填写对应之前的正确密码以及新密码之后,即完成相关修改密码操作
    搜索功能:在以上管理界面中,均允许了管理员根据关键字进行搜索,要求搜索框中输入的字段必须完全包含在物品名称中,否则无法查询


    1.2 系统功能需求根据系统功能要求,该超市订单管理系统以管理员为中心的用户角色,可以将系统分解成几个模块来分别设计应用程序界面,如图 1.1所示。

    1.3 系统性能需求超市订单管理系统的开发是在Window10平台上,以SSM为架构,采用MySQL 作为数据库管理系统管理后台数据库。本系统是超市信息管理建设中必不可少的一部分,它实现了现代管理信息系统的大部分功能需要。使用本系统可以使超市管理更加方便快捷,合理的页面设计也使得这个用户充分享受到基于Internet管理信息系统的优越。本系统开发说明:
    1.3.1 功能完备在开发初期,查看了大量关于电子商务,管理信息系统,J2EE等方面的资料,同时借鉴了很多其他电子商务网站和管理信息的流程。经过总结,确定了满足需求分析的基本模块。系统总体设计上实现了整个系统模块的划分,系统主要包含5大模块,分别是:订单管理信息,供应商管理,用户管理,修改密码,登陆退出系统,基本上实现了综合管理系统的所有功能。 
    1.3.2 界面友好系统用户登陆到管理页面后,每页有导航和引领的作用。系统具有自适应的能力,同时导航条方便快捷的引导用户进行各种合理的操作。
    1.3.3 管理科学本系统一开始就从管理学的角度做出了详细细致的考虑,后来有参考了电子商务管理等,最后才做出了系统总体设计,因此可以讲该系统是较为科学的。
    系统的性能需求主要表现在数据库中的各个表需要频繁地被插入、删除以及更新。对于用户来说,系统地响应时间不宜太长,否则会降低用户体验。为此要求我们建立良好的表结构,加上足够的存储空间以及硬件性能。
    2 可行性分析2.1 研究前提随着我国经济情况的日新月异,飞速发展,涌现出许许多多的超市和便利店。越来越多的人喜欢到超市购物,超市里销售的商品也呈现出多种多样的变化趋势。我们开发一个超市订单管理系统,它可以对仓储各环节实施全过程控制管理,对整个进货、退货、盘点等各个环节的规范化作业,控制整个过程的正常运行。去掉了手工书写票据和送到机房输入的步骤,解决库房信息陈旧滞后的弊病,方便了仓库管理人员对物品的放置和调配,提高了工作效率。
    该系统容易被接受,具有简单易学性,便于管理等功能,是对超市订单管理的一种有效工具。
    2.2 设计要求2.2.1 安全性超市订单管理增强对产品规范的审计,重点确定该项目中需要审计的产品。买家只能针对卖家允许公开的信息进行查阅。买家只享受对自己账号内数据的查阅权,与定后处理权,订货支付权,申请退货权,不允许偷窥其他人。卖家只能针对买家允许公开的信息进行查阅。卖家只享受对自己账号内数据的查阅权,发货权,退款相应处理权,不允许偷窥其他人。
    2.2.2 系统性能管理员登录查看超市供应商与超市员工用户管理,可以进行增、删、改、查等操作。超市订单系统可以使超市的管理趋于正规化、现代化和系统化。本项目的产品可以达到以下目标:

    提高工作效率,减少返工
    业务流程的流水线化
    符合相关标准和规则
    与目前的应用产品相比较,提高了可用性或减少了失效程度

    2.2.3 可扩展性所有信息呈现,操作完全由打开的网页呈现并完成。本系统所占有的是超市市场,它追求的是简单、易学、易用,能够更好地解决管理人员的负担,能够辅助超市有效的管理物品。对于订单管理系统的用户,可满足对订单管理的需求,且此种需求被接受并且满足,其系统便可以推广。

    3 数据库设计3.1 数据库需求分析经过对超市管理系统的调查分析,得出用户的需求大致如下:

    管理员可以在系统中对订单、供应商以及用户进行增、删、改、查的处理
    管理员需要输入账号密码登录,并且可以增添新的管理员

    如下是利用数据流图方法对数据库做需求分析:
    第一步:由用户的需求,可以得到顶层数据流图如图3.1.1所示。

    第二步:超市订单管理系统的第1层数据流图如图3.1.2所示。

    第三步:超市订单管理系统的第2层数据库流图——订单管理的细化数据流图如图3.1.3所示。

    第四步:超市订单管理系统的第2层数据流库——供应商管理的细化数据流图如图3.1.4所示。

    第五步:超市订单管理系统的第2层数据流库——用户管理的细化数据流图如图3.1.5所示。

    根据如上的数据流程图,可以列出以下记录超市订单管理所需的数据项和数据结构:

    管理员:管理员ID、管理员姓名、管理员密码、管理员性别、管理员角色、管理员出生日期、管理员电话、管理员住址
    订单:订单编码、商品名称、供应商名称、订单金额、是否付款
    供应商:供应商编码、供应商名称、联系人、联系电话、微信

    3.2 数据库概念结构设计本系统一共有用户、供应商、订单、角色、地址这五个基本实体。
    管理员可以对应多个订单,而一个订单只能对应于一个管理员。管理员可以管理多个供应商,而一个供应商只能对应于一个管理员。一个供应商可以对应多条订单,但一条订单只能对应于一个供应商。此外,有一个用户对应一个角色,一个角色对应多个用户;一个地址对应多个订单,一个订单对应一个地址。数据库表之间的关系如下:


    用户:主键ID、用户编码、用户名称、用户密码、性别、出生日期、手机、地址、用户角色、创建者、创建时间、更新者、更新时间、用户头像、工作照
    账单:订单编号、订单编码、商品名称、商品描述、商品单位、商品数量、商品总额、是否支付、创建者、创建时间、更新者、更新时间、供应商ID
    供应商:供应商ID、供货商编码、供货商名称、供应商详细描述、供应商联系人、联系电话、地址、微信、创建者、创建时间、更新时间、更新者、营业执照、组织机构代码证
    地址:主键ID、联系人姓名、收货地址明细、邮编、联系人电话、创建者、创建日期、修改者、修改时间、用户ID
    角色:角色编号、角色编码、角色名称、创建者、创建时间、修改者、修改时间

    3.3 数据库逻辑结构设计将概念结构设计中的各个模型转化为DBMS支持的表结构,同时保持不会出现插入异常、删除异常和修改异常,表结构应该做到符合3NF。根据系统 E-R 图,需要设计4个数据表来存放信息。在本系统中,一共有五个实体,实体转化为数据库模型为如下所示:
    一对多联系转化为一个关系模式:

    用户—订单(用户编号,订单编号)
    供货商—订单(供货商编号,订单编号)
    用户—身份(用户编号,身份编号)
    用户—地址(用户编号)

    利用以上关系模式得到的所有数据表如下所示:
    用户表(smbms_user)

    数据项:主键ID、用户编码、用户名称、用户密码、性别、出生日期、手机、地址、用户角色、创建者、创建时间、更新者、更新时间、用户头像、工作照
    说明:用户ID是唯一的用户标识,使此表的主键。如表3.3.1所示。




    列名
    数据类型
    数据长度
    可否为空
    备注




    Id
    bigint
    20
    Not null
    主键ID


    userCode
    varchar
    15
    Not null
    用户编码


    userName
    varchar
    15
    Not null
    用户名称


    userPassword
    varchar
    15
    Not null
    用户密码


    gender
    int
    10

    性别


    birthday
    date


    出生日期


    phone
    varchar
    15

    手机


    address
    varchar
    30

    地址


    userRole
    int
    10

    用户角色


    createdBy
    bigint
    20

    创建者


    creationDate
    datetime


    创建时间


    modifyBy
    bigint
    20

    更新者


    modifyDate
    datetime


    更新时间


    idPicPath
    varchar
    300

    用户头像


    workPicPath
    varchar
    300

    工作照



    供应商表(smbms_provider)

    数据项:供应商ID、供货商编码、供货商名称、供应商详细描述、供应商联系人、联系电话、地址、微信、创建者、创建时间、更新时间、更新者、营业执照、组织机构代码证
    说明:这张表标识的是超市管理信息系统中商品供应商的信息列表,供应商ID是该表的主键
    编号方法:商品供应商ID采用自动生成方式,如表3.3.2所示。




    列名
    数据类型
    数据长度
    可否为空
    备注




    Id
    Bigint
    20
    Not null
    供货商ID(主键)


    proCode
    Varchar
    20
    Not null
    供货商编码


    proName
    varchar
    20
    Not null
    供货商名称


    ProDesc
    varchar
    50

    供应商详细描述


    proContact
    varchar
    20
    Not null
    供货商联系人


    proPhone
    Varchar
    20
    Not null
    联系电话


    ProAddress
    Varchar
    50
    Not null
    供货商地址


    proFax
    varchar
    20

    微信


    CreateBy
    bigint
    20

    创建者


    CreatationDate
    datetime


    创建时间


    modifyDate
    datetime


    更新时间


    modifyBy
    bigint
    20

    更新者


    companyLicPicPath
    varchar
    300

    营业执照


    orgCodePicPath
    varchar
    300

    组织机构代码证



    订单表(smbms_bill)

    数据项:订单编号、订单编码、商品名称、商品描述、商品单位、商品数量、商品总额、是否支付、创建者、创建时间、更新者、更新时间、供应商ID
    说明:这张表标识的是超市管理信息系统订单信息列表,订单ID是该表的主键
    编号方法:订单ID采用自动生成方式,供应商ID与供应商表中供应商ID一一对应,如表3.3.3所示。




    列名
    数据类型
    数据长度
    可否为空
    备注




    Id
    bigint
    20
    Not null
    订单ID(主键)


    billCode
    varchar
    20
    Not null
    订单编码


    ProductName
    Varchar
    20
    Not null
    商品名称


    ProductDescent
    Varchar
    50
    Not null
    商品描述


    ProductUnit
    Varchar
    10
    Not null
    商品单位


    ProductCount
    Decimal
    20,2
    Not null
    商品数量


    totalPrice
    Decimal
    20,2
    Not null
    商品总额


    isPayment
    int
    10
    Not null
    是否支付


    createdBy
    bigint
    20

    创建者


    creationDate
    Datetime


    创建时间


    modifyBy
    bigint
    20

    更新者


    modifyDate
    datetime


    更新时间


    providerID
    Int
    20

    供应商ID



    身份表(smbms_role)

    数据项:角色编号、角色编码、角色名称、创建者、创建时间、修改者、修改时间
    说明:这张表标识的是超市订单管理信息系统中用户身份列表,身份编号是该表的主键
    编号方法:用户身份编号与用户表中的员工身份编号一一对应,如表3.3.4所示。




    列名
    数据类型
    数据长度
    可否为空
    备注




    Id
    bigint
    20
    Not null
    角色ID(主键)


    RoleCode
    varchar
    15
    Not null
    角色编码


    roleName
    Varchar
    15
    Not null
    角色名称


    createdBy
    bigint
    20

    创建者


    creationDate
    datetime


    创建时间


    modifyBy
    bigint
    20

    修改者


    modifyDate
    datetime


    修改时间



    地址表(smbms_address)

    数据项:主键ID、联系人姓名、收货地址明细、邮编、联系人电话、创建者、创建日期、修改者、修改时间、用户ID
    编号方法:用户ID与用户表中的用户ID一一对应,如表3.3.5所示。




    列名
    数据类型
    数据长度
    可否为空
    备注




    Id
    bigint
    20
    Not null
    主键ID(主键)


    Contact
    varchar
    15
    Not null
    联系人姓名


    addressDesce
    Varchar
    50
    Not null
    收货地址明细


    postcode
    Varchar
    15

    邮编


    Tel
    Varchar
    20
    Not null
    联系人电话


    createdBy
    bigint
    20

    创建者


    creationDate
    Datetime


    创建时间


    modifyBy
    bigint
    20

    修改者


    modifyDate
    datetime


    修改时间


    userID
    Bigint
    20

    用户ID



    数据库连接利用了SSM框架的底层的MyBatis,建立了实体类与MySQL之间映射关系,从而实现数据持久化、封装数据库连接等操作。
    3.4 数据库物理结构设计3.4.1 选择关系模式的存取方式对数据库逻辑结构设计中建立的表结构,供应商表的供应商编号属性唯一决定每一个供应商元组,所以对供应商表建立以供应商编号为主关键字的索引。同理,对管理员关系模式、订单关系模式也采用类似的索引存取方法。
    3.4.2 数据表存储结构设计本系统的所有数据表均存放在物理磁盘中。用户表、供应商表和订单表的结构是相对稳定的,表中的已有记录是要长期保存的,在此基础上系统会相应用户的操作对数据表进行增、删、改、查等操作。

    3.5 数据库的建立3.5.1 数据库的建立创建数据库
    create database smbms;USE smbms;
    创建表smbms_address
    DROP TABLE IF EXISTS `smbms_address`;CREATE TABLE `smbms_address` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `contact` varchar(15) COLLATE utf8_unicode_ci NOT NULL COMMENT '联系人姓名', `addressDesc` varchar(50) COLLATE utf8_unicode_ci NOT NULL COMMENT '收货地址明细', `postCode` varchar(15) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '邮编', `tel` varchar(20) COLLATE utf8_unicode_ci NOT NULL COMMENT '联系人电话', `createdBy` bigint(20) DEFAULT NULL COMMENT '创建者', `creationDate` datetime DEFAULT NULL COMMENT '创建时间', `modifyBy` bigint(20) DEFAULT NULL COMMENT '修改者', `modifyDate` datetime DEFAULT NULL COMMENT '修改时间', `userId` bigint(20) DEFAULT NULL COMMENT '用户ID', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
    创建表smbms_bill
    DROP TABLE IF EXISTS `smbms_bill`;CREATE TABLE `smbms_bill` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `billCode` varchar(20) COLLATE utf8_unicode_ci NOT NULL COMMENT '账单编码', `productName` varchar(20) COLLATE utf8_unicode_ci NOT NULL COMMENT '商品名称', `productDesc` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '商品描述', `productUnit` varchar(10) COLLATE utf8_unicode_ci NOT NULL COMMENT '商品单位', `productCount` decimal(20,2) NOT NULL COMMENT '商品数量', `totalPrice` decimal(20,2) NOT NULL COMMENT '商品总额', `isPayment` int(10) NOT NULL COMMENT '是否支付(1:未支付 2:已支付)', `createdBy` bigint(20) DEFAULT NULL COMMENT '创建者(userId)', `creationDate` datetime DEFAULT NULL COMMENT '创建时间', `modifyBy` bigint(20) DEFAULT NULL COMMENT '更新者(userId)', `modifyDate` datetime DEFAULT NULL COMMENT '更新时间', `providerId` int(20) DEFAULT NULL COMMENT '供应商ID', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
    创建表smbms_provider
    DROP TABLE IF EXISTS `smbms_provider`;CREATE TABLE `smbms_provider` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `proCode` varchar(20) COLLATE utf8_unicode_ci NOT NULL COMMENT '供应商编码', `proName` varchar(20) COLLATE utf8_unicode_ci NOT NULL COMMENT '供应商名称', `proDesc` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '供应商详细描述', `proContact` varchar(20) COLLATE utf8_unicode_ci NOT NULL COMMENT '供应商联系人', `proPhone` varchar(20) COLLATE utf8_unicode_ci NOT NULL COMMENT '联系电话', `proAddress` varchar(50) COLLATE utf8_unicode_ci NOT NULL COMMENT '地址', `proFax` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '微信', `createdBy` bigint(20) DEFAULT NULL COMMENT '创建者(userId)', `creationDate` datetime DEFAULT NULL COMMENT '创建时间', `modifyDate` datetime DEFAULT NULL COMMENT '更新时间', `modifyBy` bigint(20) DEFAULT NULL COMMENT '更新者(userId)', `companyLicPicPath` varchar(300) DEFAULT NULL COMMENT '营业执照', `orgCodePicPath` varchar(300) DEFAULT NULL COMMENT '组织机构代码证', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
    创建表smbms_role
    DROP TABLE IF EXISTS `smbms_role`;CREATE TABLE `smbms_role` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `roleCode` varchar(15) COLLATE utf8_unicode_ci NOT NULL COMMENT '角色编码', `roleName` varchar(15) COLLATE utf8_unicode_ci NOT NULL COMMENT '角色名称', `createdBy` bigint(20) DEFAULT NULL COMMENT '创建者', `creationDate` datetime DEFAULT NULL COMMENT '创建时间', `modifyBy` bigint(20) DEFAULT NULL COMMENT '修改者', `modifyDate` datetime DEFAULT NULL COMMENT '修改时间', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
    创建表smbms_user
    DROP TABLE IF EXISTS `smbms_user`;CREATE TABLE `smbms_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `userCode` varchar(15) COLLATE utf8_unicode_ci NOT NULL COMMENT '用户编码', `userName` varchar(15) COLLATE utf8_unicode_ci NOT NULL COMMENT '用户名称', `userPassword` varchar(15) COLLATE utf8_unicode_ci NOT NULL COMMENT '用户密码', `gender` int(10) DEFAULT 2 COMMENT '性别(1:女、 2:男)', `birthday` date DEFAULT NULL COMMENT '出生日期', `phone` varchar(15) COLLATE utf8_unicode_ci NOT NULL COMMENT '手机', `address` varchar(30) COLLATE utf8_unicode_ci NOT NULL COMMENT '地址', `userRole` int(10) DEFAULT NULL COMMENT '用户角色(取自角色表-角色id)', `createdBy` bigint(20) DEFAULT NULL COMMENT '创建者(userId)', `creationDate` datetime DEFAULT NULL COMMENT '创建时间', `modifyBy` bigint(20) DEFAULT NULL COMMENT '更新者(userId)', `modifyDate` datetime DEFAULT NULL COMMENT '更新时间', `idPicPath` varchar(300) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '用户头像', `workPicPath` varchar(300) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '工作照', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
    3.5.2 初始数据的输入数据表创建成功后,数据库中还没有实际的数据。为了保证外部键能使用,数据需要提前输入,如用户编码、用户姓名、订单名称和供应商等等。具体插入语句如下:
    向smbms_address表插入数据
    insert into `smbms_address`(`id`,`contact`,`addressDesc`,`postCode`,`tel`,`createdBy`,`creationDate`,`modifyBy`,`modifyDate`,`userId`) values (1,'王丽','北京市东城区东交民巷44号','100010','13678789999',1,'2020-04-13 00:00:00',NULL,NULL,1),(2,'张红丽','北京市海淀区丹棱街3号','100000','18567672312',1,'2020-04-13 00:00:00',NULL,NULL,1),(3,'任志强','北京市东城区美术馆后街23号','100021','13387906742',1,'2020-04-13 00:00:00',NULL,NULL,1),(4,'曹颖','北京市朝阳区朝阳门南大街14号','100053','13568902323',1,'2020-04-13 00:00:00',NULL,NULL,2),(5,'李慧','北京市西城区三里河路南三巷3号','100032','18032356666',1,'2020-04-13 00:00:00',NULL,NULL,3),(6,'王国强','北京市顺义区高丽营镇金马工业区18号','100061','13787882222',1,'2020-04-13 00:00:00',NULL,NULL,3);
    向smbms_bill表插入数据
    insert into `smbms_bill`(`id`,`billCode`,`productName`,`productDesc`,`productUnit`,`productCount`,`totalPrice`,`isPayment`,`createdBy`,`creationDate`,`modifyBy`,`modifyDate`,`providerId`) values (1,'BILL2016_001','洗发水、护发素','日用品-洗发、护发','瓶','500.00','25000.00',2,1,'2020-06-14 13:02:03',NULL,NULL,13),(2,'BILL2016_002','香皂、肥皂、药皂','日用品-皂类','块','1000.00','10000.00',2,1,'2020-03-23 04:20:40',NULL,NULL,13),(3,'BILL2016_003','大豆油','食品-食用油','斤','300.00','5890.00',2,1,'2020-05-14 13:02:03',NULL,NULL,6),(4,'BILL2016_004','橄榄油','食品-进口食用油','斤','200.00','9800.00',2,1,'2020-04-10 03:12:13',NULL,NULL,7),(5,'BILL2016_005','洗洁精','日用品-厨房清洁','瓶','500.00','7000.00',2,1,'2020-05-14 13:02:03',NULL,NULL,9),(6,'BILL2016_006','美国大杏仁','食品-坚果','袋','300.00','5000.00',2,1,'2020-04-14 06:08:09',NULL,NULL,4),(7,'BILL2016_007','沐浴液、精油','日用品-沐浴类','瓶','500.00','23000.00',1,1,'2020-07-01 10:10:22',NULL,NULL,14),(8,'BILL2016_008','不锈钢盘碗','日用品-厨房用具','个','600.00','6000.00',2,1,'2020-04-14 05:12:13',NULL,NULL,14),(9,'BILL2016_009','塑料杯','日用品-杯子','个','350.00','1750.00',2,1,'2020-02-04 11:40:20',NULL,NULL,14),(10,'BILL2016_010','豆瓣酱','食品-调料','瓶','200.00','2000.00',2,1,'2020-01-29 05:07:03',NULL,NULL,8),(11,'BILL2016_011','海之蓝','饮料-国酒','瓶','50.00','10000.00',1,1,'2020-04-14 16:16:00',NULL,NULL,1),(12,'BILL2016_012','芝华士','饮料-洋酒','瓶','20.00','6000.00',1,1,'2020-06-09 17:00:00',NULL,NULL,1),(13,'BILL2016_013','长城红葡萄酒','饮料-红酒','瓶','60.00','800.00',2,1,'2020-04-14 15:23:00',NULL,NULL,1),(14,'BILL2016_014','泰国香米','食品-大米','斤','400.00','5000.00',2,1,'2020-05-09 15:20:00',NULL,NULL,3),(15,'BILL2016_015','东北大米','食品-大米','斤','600.00','4000.00',2,1,'2020-05-14 14:00:00',NULL,NULL,3),(16,'BILL2016_016','可口可乐','饮料','瓶','2000.00','6000.00',2,1,'2020-03-27 13:03:01',NULL,NULL,2),(17,'BILL2016_017','脉动','饮料','瓶','1500.00','4500.00',2,1,'2020-05-10 12:00:00',NULL,NULL,2),(18,'BILL2016_018','哇哈哈','饮料','瓶','2000.00','4000.00',2,1,'2020-06-24 15:12:03',NULL,NULL,2);
    向smbms_provider表插入数据
    insert into `smbms_provider`(`id`,`proCode`,`proName`,`proDesc`,`proContact`,`proPhone`,`proAddress`,`proFax`,`createdBy`,`creationDate`,`modifyDate`,`modifyBy`) values(1,'BJ_GYS001','北京三木堂商贸有限公司','长期合作伙伴,主营产品:茅台、五粮液、郎酒、酒鬼酒、泸州老窖、赖茅酒、法国红酒等','张国强','13566667777','北京市丰台区育芳园北路','010-58858787',1,'2020-03-21 16:52:07',NULL,NULL),(2,'HB_GYS001','石家庄帅益食品贸易有限公司','长期合作伙伴,主营产品:饮料、水饮料、植物蛋白饮料、休闲食品、果汁饮料、功能饮料等','王军','13309094212','河北省石家庄新华区','0311-67738876',1,'2020-04-13 04:20:40',NULL,NULL),(3,'GZ_GYS001','深圳市泰香米业有限公司','初次合作伙伴,主营产品:良记金轮米,龙轮香米等','郑程瀚','13402013312','广东省深圳市福田区深南大道6006华丰大厦','0755-67776212',1,'2020-03-21 16:56:07',NULL,NULL),(4,'GZ_GYS002','深圳市喜来客商贸有限公司','长期合作伙伴,主营产品:坚果炒货.果脯蜜饯.天然花茶.营养豆豆.特色美食.进口食品.海味零食.肉脯肉','林妮','18599897645','广东省深圳市福龙工业区B2栋3楼西','0755-67772341',1,'2020-03-22 16:52:07',NULL,NULL),(5,'JS_GYS001','兴化佳美调味品厂','长期合作伙伴,主营产品:天然香辛料、鸡精、复合调味料','徐国洋','13754444221','江苏省兴化市林湖工业区','0523-21299098',1,'2020-02-22 16:52:07',NULL,NULL),(6,'BJ_GYS002','北京纳福尔食用油有限公司','长期合作伙伴,主营产品:山茶油、大豆油、花生油、橄榄油等','马莺','13422235678','北京市朝阳区珠江帝景1号楼','010-588634233',1,'2020-03-21 17:52:07',NULL,NULL),(7,'BJ_GYS003','北京国粮食用油有限公司','初次合作伙伴,主营产品:花生油、大豆油、小磨油等','王驰','13344441135','北京大兴青云店开发区','010-588134111',1,'2020-04-13 00:00:00',NULL,NULL),(8,'ZJ_GYS001','慈溪市广和绿色食品厂','长期合作伙伴,主营产品:豆瓣酱、黄豆酱、甜面酱,辣椒,大蒜等农产品','薛圣丹','18099953223','浙江省宁波市慈溪周巷小安村','0574-34449090',1,'2020-01-21 06:02:07',NULL,NULL),(9,'GX_GYS001','优百商贸有限公司','长期合作伙伴,主营产品:日化产品','李立国','13323566543','广西南宁市秀厢大道42-1号','0771-98861134',1,'2020-03-21 19:52:07',NULL,NULL),(10,'JS_GYS002','南京火头军信息技术有限公司','长期合作伙伴,主营产品:不锈钢厨具等','陈女士','13098992113','江苏省南京市浦口区浦口大道1号新城总部大厦A座903室','025-86223345',1,'2020-03-25 16:52:07',NULL,NULL),(11,'GZ_GYS003','广州市白云区美星五金制品厂','长期合作伙伴,主营产品:海绵床垫、坐垫、靠垫、海绵枕头、头枕等','梁天','13562276775','广州市白云区钟落潭镇福龙路20号','020-85542231',1,'2020-01-21 06:12:17',NULL,NULL),(12,'BJ_GYS004','北京隆盛日化科技','长期合作伙伴,主营产品:日化环保清洗剂,家居洗涤专卖、洗涤用品网、墙体除霉剂、墙面霉菌清除剂等','孙欣','13689865678','北京市大兴区旧宫','010-35576786',1,'2020-01-21 12:51:11',NULL,NULL),(13,'SD_GYS001','山东豪克华光联合发展有限公司','长期合作伙伴,主营产品:洗衣皂、洗衣粉、洗衣液、洗洁精、消杀类、香皂等','吴洪转','13245468787','山东济阳济北工业区仁和街21号','0531-53362445',1,'2020-01-28 10:52:07',NULL,NULL),(14,'JS_GYS003','无锡喜源坤商行','长期合作伙伴,主营产品:日化品批销','周一清','18567674532','江苏无锡盛岸西路','0510-32274422',1,'2020-04-23 11:11:11',NULL,NULL),(15,'ZJ_GYS002','乐摆日用品厂','长期合作伙伴,主营产品:各种中、高档塑料杯,塑料乐扣水杯(密封杯)、保鲜杯(保鲜盒)、广告杯、礼品杯','王世杰','13212331567','浙江省金华市义乌市义东路','0579-34452321',1,'2020-06-22 10:01:30',NULL,NULL);
    向smbms_role表插入数据
    insert into `smbms_role`(`id`,`roleCode`,`roleName`,`createdBy`,`creationDate`,`modifyBy`,`modifyDate`) values (1,'SMBMS_ADMIN','系统管理员',1,'2020-01-01 00:00:00',NULL,NULL),(2,'SMBMS_MANAGER','经理',1,'2020-02-02 00:01:00',NULL,NULL),(3,'SMBMS_EMPLOYEE','普通员工',1,'2020-02-03 00:00:00',NULL,NULL);
    向smbms_user表插入数据
    insert into `smbms_user`(`id`,`userCode`,`userName`,`userPassword`,`gender`,`birthday`,`phone`,`address`,`userRole`,`createdBy`,`creationDate`,`modifyBy`,`modifyDate`) values (1,'admin','系统管理员','1234567',1,'1983-10-10','13688889999','山东省日照市东港区成府路207号',1,1,'2020-03-21 16:52:07',NULL,NULL),(2,'liming','李明','0000000',2,'1983-12-10','13688884457','山东省日照市东港区前门东大街9号',2,1,'2020-03-01 00:00:00',NULL,NULL),(5,'hanlubiao','韩路彪','0000000',2,'2001-06-05','18567542321','山东省日照市东港区北辰中心12号',2,1,'2020-02-11 19:52:09',NULL,NULL),(6,'zhanghua','张华','0000000',1,'1980-06-15','13544561111','山东省日照市东港区学院路61号',3,1,'2020-02-11 10:51:17',NULL,NULL),(7,'wangyang','王洋','0000000',2,'2001-12-31','13444561124','山东省青岛市三二二区西二旗辉煌国际16层',3,1,'2020-06-11 19:09:07',NULL,NULL),(8,'zhaoyan','赵燕','0000000',1,'1999-03-07','18098764545','山东省青岛市东科区回龙观小区10号楼',3,1,'2020-04-21 13:54:07',NULL,NULL),(10,'sunlei','孙磊','0000000',2,'1998-01-04','13387676765','山东省日照市朝阳区管庄新月小区12楼',3,1,'2020-05-06 10:52:07',NULL,NULL),(11,'sunxing','孙兴','0000000',2,'1997-03-12','13367890900','北京市朝阳区建国门南大街10号',3,1,'2020-01-09 16:51:17',NULL,NULL),(12,'zhangchen','张晨','0000000',1,'1986-03-28','18098765434','朝阳区管庄路口北柏林爱乐三期13号楼',3,1,'2019-06-09 05:52:37',1,'2020-04-14 14:15:36'),(13,'dengchao','邓超','0000000',2,'1981-11-04','13689674534','北京市海淀区北航家属院10号楼',3,1,'2020-07-01 08:02:47',NULL,NULL),(14,'yangguo','杨过','0000000',2,'1989-01-01','13388886623','北京市朝阳区北苑家园茉莉园20号楼',3,1,'2020-02-01 03:52:07',NULL,NULL),(15,'zhaomin','赵敏','0000000',1,'1989-12-04','18099897657','山东省临沂市昌平区天通苑3区12号楼',2,1,'2020-01-12 12:02:12',NULL,NULL);
    此外,本系统中所用到的用户性别和用户身份代码如表3.5.1至表3.5.2所示。
    用户性别代码表



    代码
    说明




    1



    2




    用户身份代码



    代码
    说明




    1
    系统管理员


    2
    经理


    3
    普通员工



    4 各功能模块的设计与实现4.1 系统开发条件4.1.1 开发语言系统使用的开发语言是Java。Java具有简单性、面向对象、分布式、健壮性、安全性、平台独立与可移植性、多线程等特点。Java可以编写桌面应用程序、Web应用程序、分布式系统应用程序等。正是因为Java语言拥有如此诸多的优秀特性,所以我们选择了它作为开发超市订单管理系统,使得整个开发、调试过程更加高效。
    4.1.2 开发框架超市订单管理系统以SSM架构作为支撑,分为表现层、业务层和持久层三层,实现后台数据更新。该架构由Spring MVC、Spring和MyBatis三个开源框架整合而成,用于开发结构合理,性能优越,代码健壮的应用程序。

    4.1.3 前端框架由于本系统是Web应用,所以使用了HTML5+CSS3+JavaScript的方式实现前端页面。实现过程中参考了Bootstrap前端开发框架。Bootstrap是Twitter退出的一个用于前端开发的开源工具包。在设计前端页面时,参考了Bootstrap的相关开源代码。
    4.1.4 集成开发环境编程所使用的集成开发环境是Eclipse,是著名的跨平台的自由集成开发环境(IDE)。Eclipse 是一个开放源代码的、基于Java的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。本次系统便选用了Eclipse作为开发平台。
    4.1.5 Web应用服务器Tomcat由Apache、Sun和其他一些公司及个人共同开发而成。由于有了Sun的参与和支持,最新的Servlet和JSP规范可以在Tomcat中得到体现。因为Tomcat技术先进、性能稳定,因而成为目前比较流行的Web应用服务器。本次系统选用的便是Tomcat作为应用服务器。
    4.1.6 数据库管理系统本系统使用的数据库管理系统是MySQL Community。MySQL是一个关系型数据库管理系统,由瑞典MySQL AB公司开发。在WEB应用方面,MySQL是最好的RDBMS (Relational Database Management System,关系数据库管理系统)应用软件。
    系统中的数据库以及数据库中的所有关系模式都使用MySQL进行处理。
    4.2 用户界面设计完成数据库创建和功能说明以后,我们进行下一步工作,即设计用户界面,完成了系统要求的 5 项主要功能。
    我们把超市订单管理系统的窗体分成5个主要部分,如下图所示。
    4.2.1 订单管理
    4.2.2 供应商管理
    4.2.3 用户管理
    4.2.4 修改密码
    4.2.5 登录注销
    4.3 功能模块说明5.3.1 订单信息添加、查询、修改与删除订单信息查看:为了对订单浏览信息,能够实现浏览的功能是十分必要的。管理员输入需要搜索的相应信息,点击查看按钮后系统将寻找到的数据展示到网页中。

    订单信息添加:作为超市订单管理系统,订单信息的管理是很重要的。每当采购部门增加新的订单时,订单信息就要增加。超市也可能因为其它原因增加订单信息,订单添加模块都可以做出快捷的解决方案。管理员输入相应的信息,点击提交后系统将数据保存到数据库中。

    订单信息修改:根据订单编号可以查询订单详细信息,然后修改订单的所有信息。系统从数据库中读取数据并显示到页面上,管理员修改数据后,点击修改按钮,系统将更新表中的数据。

    订单信息删除:根据订单编号可以删除该订单的信息。管理员选择需要删除订单名称并点击删除按钮,系统将从数据库中删除相应数据。
    订单信息查询:在成千上万种商品种,如果人为寻找某一个商品肯定是不可能的,只有通过商品信息查询模块才能为用户或管理人员解决这个难题。根据订单名称可以查询该订单的信息。管理员输入订单名称并点击查询按钮,系统将从数据库中查询相应的数据并显示到页面上。

    5.3.2 供应商信息添加、查询、修改与删除供应商查询界面:供应商查询界面提供了供应商的信息,可根据供应商名称的关键字进行筛选查询,并提供了添加供应商、查看供应商详细信息、修改供应商信息、删除供应商的功能。

    供应商查看详情界面:在供应商查询界面点击具体供应商操作列表的查看按钮,可以查看供应商的具体信息,包括:供货商编码、供货商名称、详细描述、联系人、联系电话、地址、微信。

    供应商修改页面:若供应商信息变动,管理员可通过供应商信息修改功能对供应商信息进行更新,更新后的数据将保存到数据库中。

    商品供应商信息删除:企业倒闭或者经营策略的改变,当它对超市商品的供应没有作用时,商品供应商厂家信息的删除是正常的。管理员输入供应商名称查询数据表中的数据并显示到页面上,点击删除后系统将表中的相应数据删除。
    供应商添加界面:与供应商达成交易后,管理员在供应商添加页面填写供应商具体信息,填写完毕点击提交,添加后的数据将保存到数据库中。

    5.3.3 用户信息添加、查询、修改与删除用户管理页面:通过输入用户名和身份查询用户。当不记得用户名的具体名字时,只输入用户名的其中一个字,会检索出所有带这个字的用户,方便管理员查询管理。点击右边链接添加用户,会连接到相关网页添加用户信息。点击操作里的查看、修改等可以进行相应的改、删、查操作。

    用户信息删除:当企业员工离职时,或者经过一段时间后,会发现用户表中一些信息时无用的,用户删除模块可以解决这样的问题。
    添加用户信息:填写用户相关信息,下面有两个按钮,可以选择重置或者提交。

    5.3.4 修改密码为了系统的安全,用户的应该只有用户个人才能修改,这不仅保证了整个公司的利益也保护了个人隐私。用户在输入相应的用户编号,填写旧密码以及新密码后,点击提交,重置密码成功。发现输入错误时,可以手动删除或者点击重置按钮,重新填写。

    修改用户密码成功后,会弹出修改用户密码成功页面,如图4.3.14所示。

    5.3.5 登录/注销输入用户名以及用户密码登录进入超市订单管理界面,可以查看管理信息。管理员可以对相关数据进行增、改、查等操作,也可以注销退出系统。
    5 实训总结5.1 所遇困难在实现本系统时遇到的困难主要体现在两个方面,一是系统的前端页面的设计,二是怎样Web与数据库实现交互。
    系统前端页面的设计困难的解决是通过参考著名的前端框架Bootstrap实现的。Bootstrap框架提供了许多精美的组建、布局,还开放了源代码供参考。在此基础上我们还加入了一些利用JavaScript代码实现的美化效果,使得前端设计更加美观。
    实体Web与数据库交互的解决得益于SSM框架的三层Spring MVC、Spring和MyBatis,能够分离处理数据库与Web层的视图,从而达到交互的目的。
    此外,在编写后端的时候,变量的大小写、系统配置也是困难重重。好在,在反复编写之后,迅速熟悉的技巧,能够让页面自由切换。系统配置更是反复在网上求证,得以解决。
    5.2 实验心得这一次合作开发超市订单管理系统,从开始选择课题的困惑到最终完成了一个我们还算满意的作品,使我学到了很多东西。从设计数据库到编写后台代码,链接数据库,在网页上显示,令人印象深刻。反复查阅资料,启动Tomcat到凌晨0点,都是藏着对这次项目的努力。其实,从一开始选择哪个题目是否用SSM框架来开发我一直也犹豫过,像国内势头正旺的ThinkPHP,易学易用,完善的中文开发文档,遇到问题或者bug可以非常容易的在中文社区得到解答。但是我最后选择了SSM框架,不仅仅因为它广泛,而是我希望能够挑战自己。经过这一个周的磨练,我最大的收获除了学到了真正可以应用的知识外,更重要的是学会了项目合作开发的经验。
    15 留言 2020-08-05 15:32:05 奖励46点积分
  • mysql 与 oralce 的区别

    一、宏观上
    Oracle是大型的数据库而Mysql是中小型数据库;Mysql是开源的,Oracle是收费的,且价格昂贵
    Oracle支持大并发,大访问量,是OLTP的最好的工具
    安装占用的内存也是有差别,Mysql安装完成之后占用的内存远远小于Oracle所占用的内存,并且Oracle越用所占内存也会变多

    二、微观上
    对于事务的支持

    Mysql对于事务默认是不支持的,只是有某些存储引擎中如:innodb可以支持;而Oracle对于事物是完全支持的
    并发性

    什么是并发性?并发性是OLTP(On-Line Transaction Processing联机事务处理过程)数据库最重要的特性,并发性涉及到资源的获取、共享与锁定Mysql以表锁为主,对资源锁定的力度很大,如果一个session对一个表加锁时间过长,会让其他session无法更新此表的数据Oracle使用行级锁,对资源锁定的力度要小很多,只是锁定sql需要的资源,并且加锁是在数据库中的数据行上,不依赖于索引。所以oracle对并发性的支持要好很多
    数据的持久性

    Oracle保证提交的事务均可以恢复,因为Oracle把提交的sql操作线写入了在线联机日志文件中,保存到磁盘上,如果出现数据库或者主机异常重启,重启Oracle可以靠联机在线日志恢复客户提交的数据Mysql默认提交sql语句,但是如果更新过程中出现db或者主机重启的问题,也可能会丢失数据
    事务隔离级别

    MySQL是repeatable read的隔离级别,而Oracle是read commited的隔离级别,同时二者都支持serializable串行化事务隔离级别,可以实现最高级别的读一致性。每个session提交后其他session才能看到提交的更改。Oracle通过在undo表空间中构造多版本数据块来实现读一致性,每个session 查询时,如果对应的数据块发生变化,Oracle会在undo表空间中为这个session构造它查询时的旧的数据块MySQL没有类似Oracle的构造多版本数据块的机制,只支持read commited的隔离级别。一个session读取数据时,其他session不能更改数据,但可以在表最后插入数据。session更新数据时,要加上排它锁,其他session无法访问数据
    提交方式

    Oracle默认不自动提交,需要手动提交。Mysql默认自动提交
    逻辑备份

    Mysql逻辑备份是要锁定数据,才能保证备份的数据是一致的,影响业务正常的DML(数据操纵语言Data Manipulation Language)使用;Oracle逻辑备份时不锁定数据,且备份的数据是一致的
    sql语句的灵活性

    mysql对sql语句有很多非常实用而方便的扩展,比如limit功能(分页),insert可以一次插入多行数据;Oracle在这方面感觉更加稳重传统一些,Oracle的分页是通过伪列和子查询完成的,插入数据只能一行行的插入数据
    数据复制

    MySQL:复制服务器配置简单,但主库出问题时,丛库有可能丢失一定的数据。且需要手工切换丛库到主库Oracle:既有推或拉式的传统数据复制,也有dataguard的双机或多机容灾机制,主库出现问题是,可以自动切换备库到主库,但配置管理较复杂
    分区表和分区索引

    MySQL的分区表还不太成熟稳定;Oracle的分区表和分区索引功能很成熟,可以提高用户访问db的体验
    售后与费用

    Oracle是收费的,出问题找客服;Mysql是免费的的,开源的,出问题自己解决
    权限与安全

    Oracle的权限与安全概念比较传统,中规中矩;MySQL的用户与主机有关,感觉没有什么意义,另外更容易被仿冒主机及ip有可乘之机
    性能诊断方面

    Oracle有各种成熟的性能诊断调优工具,能实现很多自动分析、诊断功能。比如awr、addm、sqltrace、tkproof等 ;MySQL的诊断调优方法较少,主要有慢查询日志
    2 留言 2021-04-20 08:39:06 奖励24点积分
  • 利用inf2cat根据inf文件生成cat文件

    背景Inf2Cat (Inf2Cat.exe) 是一个命令行工具,该工具确定驱动程序包的 INF 文件是否可以针对指定的 Windows 版本列表进行数字签名。如果可以,那么 Inf2Cat 会生成适用于指定 Windows 版本的未签名的目录文件 CAT。
    Inf2Cat 工具检查驱动程序包的 INF 文件是否存在结构错误,并验证驱动程序包是否可以进行数字签名。只有当 INF 文件中引用的所有文件都存在并且源文件位于正确的位置时,驱动程序包才能被签名。如果 INF 文件无法签名或包含结构错误,驱动程序包可能未正确安装,或者可能在安装期间错误地显示驱动程序签名警告对话框。
    对于 WDM 驱动程序的安装,都需要用到 INF 文件。其中,有些 WDM 驱动安装,需要根据 INF 生成 CAT 文件,这样,驱动程序方可安装,例如键盘、鼠标等设备驱动程序。
    本文主要介绍使用 WDK 自带的 Inf2Cat.exe 工具,根据驱动程序的 INF 文件,生成 CAT 文件。
    实现过程Inf2Cat.exe 的使用命令为:
    Inf2Cat /driver:PackagePath/os:WindowsVersionList [/nocat] [/verbose] [other switches]
    其中,参数的含义为:

    /driver:PackagePath:指定包含驱动程序包的 INF 文件的目录路径。如果指定的目录包含多个驱动程序包的 INF 文件,那么 Inf2Cat 会为每个驱动程序包创建目录文件注意:可以使用 /drv: 开关来代替 /driver: 开关
    /os:WindowsVersionList:将 Inf2Cat 配置为验证驱动程序包的 INF 文件是否符合由 WindowsVersionList 指定的 Windows 版本的签名要求。WindowsVersionList 是一个逗号分隔列表,其中包含以下一个或多个版本标识符




    Windows version
    Version identifier




    Windows 8.1 x86 Edition
    6_3_X86


    Windows 8.1 x64 Edition
    6_3_X64


    Windows 8.1 ARM Edition
    6_3_ARM


    Windows Server 2012 R2
    Server6_3_X64


    Windows 8 x64 Edition
    8_X64


    Windows 8 x86 Edition
    8_X86


    Windows 8 ARM Edition
    8_ARM


    Windows Server 2012
    Server8_X64


    Windows Server 2008 R2 x64 Edition
    Server2008R2_X64


    Windows Server 2008 R2 Itanium Edition
    Server2008R2_IA64


    Windows 7 x64 Edition
    7_X64


    Windows 7 x86 Edition
    7_X86


    Windows Server 2008 x64 Edition
    Server2008_X64


    Windows Server 2008 Itanium Edition
    Server2008_IA64


    Windows Server 2008 x86 Edition
    Server2008_X86


    Windows Vista x64 Edition
    Vista_X64


    Windows Vista x86 Edition
    Vista_X86


    Windows Server 2003 x64 Edition
    Server2003_X64


    Windows Server 2003 Itanium Edition
    Server2003_IA64


    Windows Server 2003 x86 Edition
    Server2003_X86


    Windows XP x64 Edition
    XP_X64


    Windows XP x86 Edition
    XP_X86


    Windows 2000
    2000



    注意:说明从 Windows Server 2008 R2 开始,Windows Server 操作系统将不再支持基于 x86 的平台。
    Inf2Cat 忽略版本标识符字符串的字母字符的大小写。 例如,vista_x64 和 Vista_X64 都是 Windows Vista x64 Edition 的有效标识符。

    /nocat:将 Inf2Cat 配置为验证驱动程序包是否符合指定的 Windows 版本的签名要求,而不生成目录文件
    /verbose:将 Inf2Cat 配置为在命令窗口中显示详细信息
    other switches:将 Inf2Cat 配置为向文件中添加 DRM 级别目录属性或 PE 目录属性或者向文件中添加页面哈希。若要获得详细信息,请使用 /? 开关

    程序测试现在,我们对 C:\Users\用户名\Desktop\DriverTest 目录下的 DriverTest.inf 和 DriverTest.sys 驱动文件生成对应的 .cat 文件。其中,该程序包的 inf 文件中的 inf 版本部分仅包含以下 CatalogFile 指令:
    [Version]. . .CatalogFile = DriverTest.cat. . .
    对于该示例,以下 Inf2Cat 命令将验证是否可以针对 Win7、Win8、Win8.1 的 64 位版本,为驱动程序包进行签名。如果可以针对这些版本对程序包进行签名,那么 Inf2Cat 将创建未签名的目录文件 DriverTest.cat。
    在运行窗口中输入 cmd,打开命令行窗口,依次输入下述命令:
    // 切换到 WDK 中的 inf2cat.exe 程序目录cd C:\Program Files (x86)\Windows Kits\8.1\bin\x86// 运行 inf2cat.exe 生成 cat 文件inf2cat.exe /driver:C:\Users\用户名\Desktop\DriverTest /os:7_x64,8_x64,6_3_X64
    其中,C:\Program Files (x86)\Windows Kits\8.1\bin\x86 是 inf2cat.exe 程序所在的目录路径;C:\Users\用户名\Desktop\DriverTest 为程序包路径。
    运行后,成功生成 drivertest.cat 文件。运行结果如下所示:

    总结在使用 Inf2Cat.exe 程序生成 cat 文件的时候,要求 inf 文件中,Version 版本部分一定指定 CatalogFile 文件。否则,Inf2Cat 会报错。
    同时,也要注意系统的版本的标识符,可以参考上述表格。
    特别要注意的是,当我们对驱动程序签名的时候,除了对 .sys 驱动程序签名之外,还需要对生成的 .cat 文件进行签名。否则,加载驱动会出错。
    1 留言 2021-05-13 08:47:21 奖励36点积分
  • 基于 WFP 实现的网络监控

    WFP 全称 Windows Filtering Platform,即 Windows 过滤平台。随着网络的高速发展,网络安全问题越来越受到重视,同时随着 WindowsOS 的快速更新换代,以往的网络过滤框架已经不能满足需要,于是导致了 WFP 的出现。WFP 是 VISTA 中引入的 API 集,也是从 VISTA 系统后新增的一套系统 API 和服务,在新版的操作系统中,开发人员可以通过这套 API 集将 Windows 防火墙嵌入到开发软件中,可以恰到好去的处理 Windows 防火墙的一些设置。
    WFP 为网络数据包过滤提供了架构支持,是微软在 VISTA 之后,替代之前的基于包过滤的防火墙设计,如 Transport DriverInterface(TDI)过滤、Network Driver InterfaceSpecification(NDIS)过滤、Winsocklayered Service Providers(LSP)。
    在 VISITA 及以后的系统中,系统防火墙的过滤钩子驱动不再适用,只能使用 WFP。WFP 允许程序员编写代码和操作系统的网络协议栈进行交互,同时在网络数据到达最后的归宿前,将数据进行过滤,拦截,修改等。流程如下图所示。

    Filter Engine 是 WFP 的核心组件,用来过滤 TCP/IP 协议的网络数据。在 TCP/IP 协议栈中存在 Filtering Layer,把网络数据传递到 Filter Engine 中处理。如果 Filtering Layer 中 Filter 的所有过滤条件都满足,Filter Engine 就会执行 Filter 指定的过滤操作。其中,Filter 可以指定 Callout 去完成特定的过滤操作。Callout 是 WFP 的功能拓展,驱动程序需要将 Callout 注册到 Filter Engine 中,这样 Filter Engine 才能调用 Callout 函数去处理网络数据。
    接下来,本文将介绍基于 WFP 实现监控系统上网络连接情况,并阻止指定进程建立通信连接。
    实现过程在调用 WFP 函数开发程序之前,先来介绍下程序所需要包含的头文件以及导入的库函数。
    要使用 WFP 框架,就需要向驱动程序中加入头文件以及导入库文件,头文件有:
    #include <fwpsk.h>#include <fwpmk.h>
    在链接器中添加库文件 fwpkclnt.lib 和 uuid.lib 库文件:
    属性-->链接器-->输入-->附加依赖库,添加fwpkclnt.lib和uuid.lib库文件
    由于程序使用的是 NDIS6,所以,需要在预处理器中添加预处理指令:
    属性-->C/C++ -->预处理器,添加“NDIS_SUPPORT_NDIS6”
    经过上述的设置,接下来,就可以进行 WFP 开发了。
    在驱动程序创建好驱动设备之后,就可以调用 FwpsCalloutRegister 函数向 Filter Engine 注册一个 Callout,即使 Filter Engine 还没有启动。FwpsCalloutRegister 函数的最后一个参数是一个 GUID 的数据类型,该数值表示 Callout 的 Key,代表了一个 Callout,具有唯一性。
    其中,WFP 一次性要注册的 Callout 函数不是 1 个,而是 3 个:

    notifyFn:负责处理 notifications
    classifyFn:负责处理 classifications
    flowDeleteFn:负责处理 flow deletions,是可选的

    为了便于理解,可以认为 Callout 函数相当于回调函数,classifyFn 相当于 pre 事前回调,notifyFn 和 flowDeleteFn 相当于事后回调函数。
    WFP API 是面向会话(Session)的,大多数函数调用是在会话的上下文中进行。驱动程序可以通过调用 FwpmEngineOpen 函数创建新会话,调用 FwpmEngineClose 函数来结束会话。
    WFP API 同时具有事务性,大多数函数调用是在事务的上下文中进行。驱动程序可以调用 FwpmTransactionBegin 函数开始事务,调用 FwpmTransactionCommit 函数提交事务,调用 FwpmTransactionAbort 来终止事务。
    驱动程序中,每个会话只能进行一个事务。如果在第一个事务提交或者中止之前就开始第二个事务,程序则会返回错误。
    那么,一个 WFP 框架驱动程序大体是这样子的:

    首先,调用 FwpsCalloutRegister 函数根据驱动设备对象向 Filter Engine 注册一个 Callout,指明 Callout Key 以及 3 个 Callout 函数 notifyFn、classifyFn 和 flowDeleteFn
    然后,调用 FwpmEngineOpen 函数创建一个 WFP 会话句柄,并调用 FwpmTransactionBegin 函数开始事务
    接着,创建过滤点。先调用 FwpmCalloutAdd 函数将前面注册好的 Callout 添加到会话,注意 Callout Key 要保持一致;再调用 FwpmFilterAdd 函数添加 Filter,注意设置过滤层和 Callout Key。本文要实现的是过滤进程联网的功能,而且联网一般都是用 IPV4 协议,所以过滤条件标志设置为 FWPM_LAYER_ALE_AUTH_CONNECT_V4。同时,必须为这个过滤条件标志指定一个 GUID,该 GUID 值任意,只要在系统范围内不重复
    最后,调用 FwpmTransactionCommit 函数提交事务,让上述操作开始生效

    经过上述 4 个步骤,就可以完成 Callout 的注册以及设置过滤条件。当满足所有过滤条件的数据包出现的时候,系统便会调用 Callout 函数 notifyFn 进行处理。
    当程序成功注册回调函数之后,就可以在 notifyFn 函数中实现对网络连接情况进行监控,还能对连接进行控制。其中,回调函数第 1 个参数 FWPS_INCOMING_VALUES0 中存储着网络连接的 IP、端口、协议等信息;第 2 个参数 FWPS_INCOMING_METADATA_VALUES0 存储着进程 ID、路径等信息;第 3 个参数 FWPS_CLASSIFY_OUT0 还可以控制是允许连接还是拒绝连接。
    当程序不用 WFP 的时候,就要调用 FwpmFilterDeleteById、FwpmCalloutDeleteById 以及 FwpsCalloutUnregisterById 函数把添加的过滤器对象和回调函数删除掉,并调用 FwpmEngineClose 关闭 WFP 会话。
    注册 Callout 的实现代码如下所示。
    // 注册CalloutNTSTATUS RegisterCallout( PDEVICE_OBJECT pDevObj, IN const GUID *calloutKey, IN FWPS_CALLOUT_CLASSIFY_FN classifyFn, IN FWPS_CALLOUT_NOTIFY_FN notifyFn, IN FWPS_CALLOUT_FLOW_DELETE_NOTIFY_FN flowDeleteNotifyFn, OUT ULONG32 *calloutId){ NTSTATUS status = STATUS_SUCCESS; FWPS_CALLOUT sCallout = { 0 }; // 设置Callout sCallout.calloutKey = *calloutKey; sCallout.classifyFn = classifyFn; sCallout.flowDeleteFn = flowDeleteNotifyFn; sCallout.notifyFn = notifyFn; // 注册Callout status = FwpsCalloutRegister(pDevObj, &sCallout, calloutId); if (!NT_SUCCESS(status)) { ShowError("FwpsCalloutRegister", status); return status; } return status;}
    创建过滤点的实现代码如下所示。
    // 设置过滤点NTSTATUS SetFilter( IN const GUID *layerKey, IN const GUID *calloutKey, OUT ULONG64 *filterId, OUT HANDLE *engine){ HANDLE hEngine = NULL; NTSTATUS status = STATUS_SUCCESS; FWPM_SESSION session = { 0 }; FWPM_FILTER mFilter = { 0 }; FWPM_CALLOUT mCallout = { 0 }; FWPM_DISPLAY_DATA mDispData = { 0 }; // 创建Session session.flags = FWPM_SESSION_FLAG_DYNAMIC; status = FwpmEngineOpen(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &hEngine); if (!NT_SUCCESS(status)) { ShowError("FwpmEngineOpen", status); return status; } // 开始事务 status = FwpmTransactionBegin(hEngine, 0); if (!NT_SUCCESS(status)) { ShowError("FwpmTransactionBegin", status); return status; } // 设置Callout参数 mDispData.name = L"MY WFP TEST"; mDispData.description = L"WORLD OF DEMON"; mCallout.applicableLayer = *layerKey; mCallout.calloutKey = *calloutKey; mCallout.displayData = mDispData; // 添加Callout到Session中 status = FwpmCalloutAdd(hEngine, &mCallout, NULL, NULL); if (!NT_SUCCESS(status)) { ShowError("FwpmCalloutAdd", status); return status; } // 设置过滤器参数 mFilter.action.calloutKey = *calloutKey; mFilter.action.type = FWP_ACTION_CALLOUT_TERMINATING; mFilter.displayData.name = L"MY WFP TEST"; mFilter.displayData.description = L"WORLD OF DEMON"; mFilter.layerKey = *layerKey; mFilter.subLayerKey = FWPM_SUBLAYER_UNIVERSAL; mFilter.weight.type = FWP_EMPTY; // 添加过滤器 status = FwpmFilterAdd(hEngine, &mFilter, NULL, filterId); if (!NT_SUCCESS(status)) { ShowError("FwpmFilterAdd", status); return status; } // 提交事务 status = FwpmTransactionCommit(hEngine); if (!NT_SUCCESS(status)) { ShowError("FwpmTransactionCommit", status); return status; } *engine = hEngine; return status;}
    notifyFn 函数的实现代码如下所示。
    // 回调函数#if (NTDDI_VERSION >= NTDDI_WIN8) VOID NTAPI classifyFn( _In_ const FWPS_INCOMING_VALUES0* inFixedValues, _In_ const FWPS_INCOMING_METADATA_VALUES0* inMetaValues, _Inout_opt_ void* layerData, _In_opt_ const void* classifyContext, _In_ const FWPS_FILTER2* filter, _In_ UINT64 flowContext, _Inout_ FWPS_CLASSIFY_OUT0* classifyOut )#elif (NTDDI_VERSION >= NTDDI_WIN7) VOID NTAPI classifyFn( _In_ const FWPS_INCOMING_VALUES0* inFixedValues, _In_ const FWPS_INCOMING_METADATA_VALUES0* inMetaValues, _Inout_opt_ void* layerData, _In_opt_ const void* classifyContext, _In_ const FWPS_FILTER1* filter, _In_ UINT64 flowContext, _Inout_ FWPS_CLASSIFY_OUT0* classifyOut )#else VOID NTAPI classifyFn( _In_ const FWPS_INCOMING_VALUES0* inFixedValues, _In_ const FWPS_INCOMING_METADATA_VALUES0* inMetaValues, _Inout_opt_ void* layerData, _In_ const FWPS_FILTER0* filter, _In_ UINT64 flowContext, _Inout_ FWPS_CLASSIFY_OUT0* classifyOut )#endif{ /* 。WFP 的回调函数里提供了丰富的信息,这是 WFP 最大的优点, 不用我们为获得各种相关信息而绞尽脑汁。 比如在 FWPM_LAYER_ALE_AUTH_CONNECT_V4 的回调函数里,我们能获得进程 ID、进程路径、本地、远 程的 IP 地址/端口号以及协议代码。 但最爽的是此回调函数的最后一个参数,能让我们指定一个值,决定是 放行还是拦截. */ ULONG ulLocalIp = inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_LOCAL_ADDRESS].value.uint32; UINT16 uLocalPort = inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_LOCAL_PORT].value.uint16; ULONG ulRemoteIp = inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_REMOTE_ADDRESS].value.uint32; UINT16 uRemotePort = inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_REMOTE_PORT].value.uint16; KIRQL kCurrentIrql = KeGetCurrentIrql(); ULONG64 processId = inMetaValues->processId; UCHAR szProcessPath[256] = { 0 }; CHAR szProtocalName[256] = { 0 }; RtlZeroMemory(szProcessPath, 256); ULONG i = 0; // 获取进程路径 for (i = 0; i < inMetaValues->processPath->size; i++) { // 里面是宽字符存储的 szProcessPath[i] = inMetaValues->processPath->data[i]; } // 允许连接 classifyOut->actionType = FWP_ACTION_PERMIT; // 禁止指定进程网络连接 if (NULL != wcsstr((PWCHAR)szProcessPath, L"tcpclient.exe")) { KdPrint(("TCPClient.exe[FWP_ACTION_BLOCK]\n")); // 拒绝连接 classifyOut->actionType = FWP_ACTION_BLOCK; classifyOut->rights = classifyOut->rights & (~FWPS_RIGHT_ACTION_WRITE); classifyOut->flags = classifyOut->flags | FWPS_CLASSIFY_OUT_FLAG_ABSORB; } // 协议判断 ProtocalIdToName(inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_PROTOCOL].value.uint16, szProtocalName); // 显示 DbgPrint("Protocal=%s, LocalIp=%u.%u.%u.%u:%d, RemoteIp=%u.%u.%u.%u:%d, IRQL=%d, PID=%I64d, Path=%S\n", szProtocalName, (ulLocalIp >> 24) & 0xFF, (ulLocalIp >> 16) & 0xFF, (ulLocalIp >> 8) & 0xFF, (ulLocalIp)& 0xFF, uLocalPort, (ulRemoteIp >> 24) & 0xFF, (ulRemoteIp >> 16) & 0xFF, (ulRemoteIp >> 8) & 0xFF, (ulRemoteIp)& 0xFF, uRemotePort, kCurrentIrql, processId, (PWCHAR)szProcessPath);}
    测试在 64 位 Windows 10 系统下,直接加载并运行上述驱动程序,成功获取计算机上的网络连接情况,并成功阻止 TCPClient.exe 进程的网络连接,如下图所示。

    小结WFP 实现起来比较复杂,建议大家一边阅读配套的示例代码,一边结合本文的讲解来理解。
    在 WFP 程序开发之前,项目工程要记得包含 fwpsk.h 头文件以及 fwpmk.h 头文件,在链接器中导入 fwpkclnt.lib 以及 uuid.lib 库文件,同时在预处理器中添加 NDIS_SUPPORT_NDIS6 宏,以支持 NDIS6 的使用。
    对于 WFP 框架的实现流程较为固定,主要包括打开 WFP 引擎会话、确认引擎的过滤权限、注册回调函数以及提交事务并启动回调等。在注册回调函数的过程中,要注意指明过滤条件。
    0 留言 2021-05-12 09:22:40 奖励38点积分
  • 基于C#的chart控件实现的调查问卷统计系统

    一、实验目的和要求
    掌握C#的基本语法
    掌握C#的图表控件Chart的用法
    掌握C#的工具栏控件的使用
    掌握多表联合查询的使用方法

    二、实验内容和原理编写一个调查问卷统计系统

    三、实验环境
    硬件:PC机
    软件:windowsXP、VS2008

    四、算法描述及实验步骤
    添加toolStrip工具栏,在工具栏中添加一个toolStripLabel、两个toolStripComboBox分别为选择调查类型、返回调查图表信息
    在主体部分添加三个charts图表控件、控件的类型为饼图、折线图、柱状图
    当用户选择相应的调查类型、返回调查图表查询数据库、显示相应的图表

    五、调试过程错误: {““frompeople”附近有语法错误。”}

    错误原因:SQL语句中的from关键字与表名字未隔开

    错误:{“对象名 ‘class’ 无效。”}

    错误原因:数据库连接错误

    六、实验结果你最喜欢的明星+饼图

    以下忽略你最喜欢的明星+折线图、你最喜欢的明星+柱图;
    你最喜欢的课程+折线图

    以下忽略你最喜欢的课程+饼图、你最喜欢的课程+柱图;
    你最喜欢的水果+柱图

    以下忽略你最喜欢的水果+饼图、你最喜欢的水果+折线图;
    七、总结通过这个实验,我了解到了掌握C#的图表控件Chart的用法,掌握C#的工具栏控件的使用。
    Chart的用法笔记:
    控件设置Series名称显示的位置:chart1中的legends集合,找到其中的Docking和Alignment属性。其中Docking是图例的停靠位置,默认值是right,有四个位置可选,可根据需要调整。Alignment属性是图例的对齐方式,默认near,表示在靠近图表的地方,有三个值near,center,far可选,这里不介绍了。还有一个DockedToChartArea属性,可以把名称放到图表里面去,不过这样会和部分数据混在一起,这里就不这么设置了,保持默认值NotSet.
    2 留言 2020-06-04 10:18:20 奖励38点积分
  • 上传资源,获取积分 精华

    上传资源,获取积分“WRITE-BUG技术共享平台”是一个专注于校园计算机技术交流共享的平台,面向的主要目标群体是我们计算机相关专业的大学生。在平台上,大家既可以交流学校课内学习的心得体会,也可以分享自己课外积累的技术经验。
    为了充实平台的资源库,更好地服务于各位同学,平台决定推出“众源计划”,有偿征集同学们自己计算机专业的作业、课程设计或是毕业设计等资源。“众源计划”的主要目的是创建一个具有一定规模的“技术资源库”,资源库里的每一份资源,都必须有详细的开发文档和可编译的源代码。
    作业、课程设计或是毕业设计等资源是同学们自己辛苦付出的成果,也是自己技术进步的见证。这部分资源通常都有详细的开发文档和完整的程序源代码,能够帮助其他初学者更好地消化和吸收将要学习的技术,降低学习门槛。所以,平台决定积分奖励征集这些资源。
    具体要求奖励方式
    资源上传并审核通过后,根据资源质量,奖励每贴 10 - 100 点积分
    上传流程
    会员登录自己的账号上传资源
    资源上传后,管理员会在 24 小时之内审核资源
    审核通过后,管理员会立即发放奖励积分至所对应账户

    审核重点
    重点审核资源是否具有详细的文档和完整的源代码
    审查资源是否原创,切勿重复提交

    资源要求“众源计划”仅对两类资源进行积分奖励征集,分别是“课内资源”和“课外资源”,各类资源具体要求如下所示。

    课内资源

    内容范围:计算机相关专业课内的毕业设计、课程设计、小学期、大作业等课程内开发的程序,程序包括游戏、PC程序、APP、网站或者其他软件形式
    内容要求:资源必须要包括完整的程序源代码和详细的开发文档或报告

    课外资源

    内容范围:计算机相关专业的课外自己主导研究游戏、项目、竞赛、个人研究等,区别于课程设计和毕业设计等课内资源
    内容要求:资源必须要包括完整的程序源代码和详细的开发文档或报告


    使用须知** 在上传资源前,请花两分钟阅读 “使用须知” 部分内容 **
    47 留言 2019-01-24 09:26:15 奖励100点积分
  • 以太坊智能合约 OPCODE 逆向之理论基础篇


    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/640/

    最近在学习 solidity 逆向方面的知识,看到这篇博文讲得很好,故转载分享!
    一、基础二、IO2.1 stack2.2 mem2.3 storage三、变量3.1 全局变量的储存模型3.1.1 定长变量3.1.2 映射变量3.1.3 变长变量3.1.4 结构体四、函数4.1 两种调用函数的方式4.2 调用函数4.3 主函数中的函数4.4 回退函数和payable4.5 函数参数4.6 变量类型的分辨五、智能合约代码结构5.1 部署合约5.2 创建合约代码总结附录(操作码对应含义和GAS)Ethereum VM (EVM) Opcodes and Instruction ReferenceNotesTableInstruction DetailsADDPUSHXCALL在我们对etherscan等平台上合约进行安全审查时,常常会遇到没有公布Solidity源代码的合约,只能获取到合约的OPCODE,所以一个智能合约的反编译器对审计无源码的智能合约起到了非常重要的作用。
    目前在互联网上常见的反编译工具只有porosity[1],另外在Github上还找到另外的反编译工具ethdasm[2],经过测试发现这两个编译器都有许多bug,无法满足我的工作需求。因此我开始尝试研究并开发能满足我们自己需求的反编译工具,在我看来如果要写出一个优秀的反汇编工具,首先需要有较强的OPCODE逆向能力,本篇Paper将对以太坊智能合约OPCODE的数据结构进行一次深入分析。
    一、基础智能合约的OPCODE是在EVM(Ethereum Virtual Machine)中进行解释执行,OPCODE为1字节,从0x00 - 0xff代表了相对应的指令,但实际有用的指令并没有0xff个,还有一部分未被使用,以便将来的扩展。
    具体指令可参考Github[3]上的OPCODE指令集,每个指令具体含义可以参考相关文档[4]。
    二、IO在EVM中不存在寄存器,也没有网络IO相关的指令,只存在对栈(stack)、内存(mem)、存储(storage)的读写操作。
    2.1 stack使用的push和pop对栈进行存取操作,push后面会带上存入栈数据的长度,最小为1字节,最大为32字节,所以OPCODE从0x60-0x7f分别代表的是push1-push32。
    PUSH1会将OPCODE后面1字节的数据放入栈中,比如字节码是0x6060代表的指令就是PUSH1 0x60。
    除了PUSH指令,其他指令获取参数都是从栈中获取,指令返回的结果也是直接存入栈中。
    2.2 mem内存的存取操作是MSTORE和MLOAD:

    MSTORE(arg0, arg1)从栈中获取两个参数,表示MEM[arg0:arg0+32] = arg1
    MLOAD(arg0)从栈中获取一个参数,表示PUSH32(MEM[arg0:arg0+32])

    因为PUSH指令,最大只能把32字节的数据存入栈中,所以对内存的操作每次只能操作32字节。
    但是还有一个指令MSTORE8,只修改内存的1个字节。

    MSTORE(arg0, arg1)从栈中获取两个参数,表示MEM[arg0] = arg1
    内存的作用一般是用来存储返回值,或者某些指令有处理大于32字节数据的需求。
    比如: SHA3(arg0, arg1)从栈中获取两个参数,表示SHA3(MEM[arg0:arg0+arg1]),SHA3对内存中的数据进行计算sha3哈希值,参数只是用来指定内存的范围。
    2.3 storage上面的stack和mem都是在EVM执行OPCODE的时候初始化,但是storage是存在于区块链中,我们可以类比为计算机的存储磁盘。
    所以,就算不执行智能合约,我们也能获取智能合约storage中的数据:
    eth.getStorageAt(合约地址, slot) # 该函数还有第三个参数,默认为"latest",还可以设置为"earliest"或者"pending",具体作用本文不做分析
    storage用来存储智能合约中所有的全局变量,使用SLOAD和SSTORE进行操作:

    SSTORE(arg0, arg1)从栈中获取两个参数,表示eth.getStorageAt(合约地址, arg0) = arg1
    SLOAD(arg0)从栈中获取一个参数,表示PUSH32(eth.getStorageAt(合约地址, arg0))

    三、变量智能合约的变量从作用域可以分为三种:全局公有变量(public)、全局私有变量(private)和局部变量。

    全局变量和局部变量的区别:全局变量储存在storage中,而局部变量是被编译进OPCODE中,在运行时,被放在stack中,等待后续使用
    公有变量和私有变量的区别:公有变量会被编译成一个constant函数,后面会分析函数之前的区别

    因为私有变量也是储存在storage中,而storage是存在于区块链当中,所以相当于私有变量也是公开的,所以不要想着用私有变量来储存啥不能公开的数据。
    3.1 全局变量的储存模型不同类型的变量在storage中储存的方式也是有区别的,下面对各种类型的变量的储存模型进行分析。
    solidity内存地址结构

    3.1.1 定长变量第一种我们归类为定长变量,所谓的定长变量,也就是该变量在定义的时候,其长度就已经被限制住了。
    比如定长整型(int/uint……)、地址(address)、定长浮点型(fixed/ufixed……)、定长字节数组(bytes1-32)。
    这类的变量在storage中都是按顺序储存:
    uint a; // slot = 0address b; // 1ufixed c; // 2bytes32 d; // 3## a == eth.getStorageAt(contract, 0)d == eth.getStorageAt(contract, 3)
    上面举的例子,除了address的长度是160bits,其他变量的长度都是256bits,而storage是256bits对齐的,所以都是一个变量占着一块storage,但是会存在连续两个变量的长度不足256bits的情况:
    address a; // slot = 0uint8 b; // 0address c; // 1uint16 d; // 1
    在opcode层面:

    获取a的值得操作是:SLOAD(0) & 0xffffffffffffffffffffffffffffffffffffffff
    获取b值得操作是:SLOAD(0) // 0x10000000000000000000000000000000000000000 & 0xff
    获取d值得操作是: SLOAD(1) // 0x10000000000000000000000000000000000000000 & 0xffff

    因为b的长度+a的长度不足256bits,变量a和b是连续的,所以他们在同一块storage中,然后在编译的过程中进行区分变量a和变量b,但是后续在加上变量c,长度就超过了256bits,因此把变量c放到下一块storage中,然后变量d跟在c之后。
    从上面我们可以看出,storage的储存策略一个是256bits对齐,一个是顺序储存。(并没有考虑到充分利用每一字节的储存空间,我觉得可以考虑把d变量放到b变量之后)。
    3.1.2 映射变量mapping(address => uint) a;
    映射变量就没办法想上面的定长变量按顺序储存了,因为这是一个键值对变量,EVM采用的机制是:
    SLOAD(sha3(key.rjust(64, "0")+slot.rjust(64, "0")))
    比如: a["0xd25ed029c093e56bc8911a07c46545000cbf37c6"]首先计算sha3哈希值:
    >>> from sha3 import keccak_256>>> data = "d25ed029c093e56bc8911a07c46545000cbf37c6".rjust(64, "0")>>> data += "00".rjust(64, "0")>>> keccak_256(data.encode()).hexdigest()'739cc24910ff41b372fbcb2294933bdc3108bd86ffd915d64d569c68a85121ec'# a["0xd25ed029c093e56bc8911a07c46545000cbf37c6"] == SLOAD("739cc24910ff41b372fbcb2294933bdc3108bd86ffd915d64d569c68a85121ec")
    我们也可以使用以太坊客户端直接获取:
    > eth.getStorageAt(合约地址, "739cc24910ff41b372fbcb2294933bdc3108bd86ffd915d64d569c68a85121ec")
    还有slot需要注意一下:
    address public a; // slot = 0mapping(address => uint) public b; // slot = 1uint public d; // slot = 1mapping(address => uint) public c; // slot = 3
    根据映射变量的储存模型,或许我们真的可以在智能合约中隐藏私密信息,比如,有一个secret,只有知道key的人才能知道secret的内容,我们可以b[key] = secret, 虽然数据仍然是储存在storage中,但是在不知道key的情况下却无法获取到secret。
    不过,storage是存在于区块链之中,目前我猜测是通过智能合约可以映射到对应的storage,storage不可能会初始化256*256bits的内存空间,那样就太消耗硬盘空间了,所以可以通过解析区块链文件,获取到storage全部的数据。
    上面这些仅仅是个人猜想,会作为之后研究以太坊源码的一个研究方向。
    3.1.3 变长变量变长变量也就是数组,长度不一定,其储存方式有点像上面两种的结合
    uint a; // slot = 0uint[] b; // 1uint c; // 2
    数组任然会占用对应slot的storage,储存数组的长度(b.length == SLOAD(1))。
    比如我们想获取b[1]的值,会把输入的index和SLOAD(1)的值进行比较,防止数组越界访问。
    然后计算slot的sha3哈希值:
    >>> from sha3 import keccak_256>>> slot = "01".rjust(64, "0")>>> keccak_256(slot.encode()).hexdigest()'20ec45d096f1fa2aeff1e3da8a84697d90109524958ed4be9f6d69e37a9140a4'#b[X] == SLOAD('20ec45d096f1fa2aeff1e3da8a84697d90109524958ed4be9f6d69e37a9140a4' + X)# 获取b[2]的值> eth.getStorageAt(合约地址, "20ec45d096f1fa2aeff1e3da8a84697d90109524958ed4be9f6d69e37a9140a6")
    在变长变量中有两个特例:string和bytes。
    字符串可以认为是字符数组,bytes是byte数组,当这两种变量的长度在0-31时,值储存在对应slot的storage上,最后一字节为长度*2|flag, 当flag = 1,表示长度>31,否则长度<=31
    下面进行举例说明:
    uint i; // slot = 0string a = "c"*31; // 1SLOAD(1) == "c*31" + "00" | 31*2 == "636363636363636363636363636363636363636363636363636363636363633e"
    当变量的长度大于31时,SLOAD(slot)储存length*2|flag,把值储存到sha3(slot):
    uint i; // slot = 0string a = "c"*36; // 1SLOAD(1) == 36*2|1 == 0x49SLOAD(SHA3("01".rjust(64, "0"))) == "c"*36
    3.1.4 结构体结构体没有单独特殊的储存模型,结构体相当于变量数组,下面进行举例说明:
    struct test { uint a; uint b; uint c;}address g;Test e;// 上面变量在storage的储存方式等同于address g;uint a;uint b;uint c;
    四、函数4.1 两种调用函数的方式下面是针对两种函数调用方式说明的测试代码,发布在测试网络上: https://ropsten.etherscan.io/address/0xc9fbe313dc1d6a1c542edca21d1104c338676ffd#code
    pragma solidity ^0.4.18;contract Test { address public owner; uint public prize; function Test() { owner = msg.sender; } function test1() constant public returns (address) { return owner; } function test2(uint p) public { prize += p; }}
    整个OPCODE都是在EVM中执行,所以第一个调用函数的方式就是使用EVM进行执行OPCODE:
    # 调用test1> eth.call({to: "0xc9fbe313dc1d6a1c542edca21d1104c338676ffd", data: "0x6b59084d"})"0x0000000000000000000000000109dea8b64d87a26e7fe9af6400375099c78fdd"> eth.getStorageAt("0xc9fbe313dc1d6a1c542edca21d1104c338676ffd", 0)"0x0000000000000000000000000109dea8b64d87a26e7fe9af6400375099c78fdd"
    第二种方式就是通过发送交易:
    # 调用test2> eth.getStorageAt("0xc9fbe313dc1d6a1c542edca21d1104c338676ffd", 1)"0x0000000000000000000000000000000000000000000000000000000000000005"> eth.sendTransaction({from: eth.accounts[0], to: "0xc9fbe313dc1d6a1c542edca21d1104c338676ffd", data: "0xcaf446830000000000000000000000000000000000000000000000000000000000000005"})> eth.getStorageAt("0xc9fbe313dc1d6a1c542edca21d1104c338676ffd", 1)"0x000000000000000000000000000000000000000000000000000000000000000a"
    这两种调用方式的区别有两个:

    使用call调用函数是在本地使用EVM执行合约的OPCODE,所以可以获得返回值
    通过交易调用的函数,能修改区块链上的storage

    一个调用合约函数的交易(比如 https://ropsten.etherscan.io/tx/0xab1040ff9b04f8fc13b12057f9c090e0a9348b7d3e7b4bb09523819e575cf651)的信息中,是不存在返回值的信息,但是却可以修改storage的信息(一个交易是怎么修改对应的storage信息,是之后的一个研究方向)。
    而通过call调用,是在本地使用EVM执行OPCODE,返回值是存在MEM中return,所以可以获取到返回值,虽然也可以修改storage的数据,不过只是修改你本地数据,不通过发起交易,其他节点将不会接受你的更改,所以是一个无效的修改,同时,本地调用函数也不需要消耗gas,所以上面举例中,在调用信息的字典里,不需要from字段,而交易却需要指定(设置from)从哪个账号消耗gas。
    4.2 调用函数EVM是怎么判断调用哪个函数的呢?下面使用OPCODE来进行说明。
    每一个智能合约入口代码是有固定模式的,我们可以称为智能合约的主函数,上面测试合约的主函数如下:
    PS: Github[5]上面有一个EVM反汇编的IDA插件。
    [ 0x0] | PUSH1 | ['0x80'][ 0x2] | PUSH1 | ['0x40'][ 0x4] | MSTORE | None[ 0x5] | PUSH1 | ['0x4'][ 0x7] | CALLDATASIZE | None[ 0x8] | LT | None[ 0x9] | PUSH2 | ['0x61'][ 0xc] | JUMPI | None[ 0xd] | PUSH4 | ['0xffffffff'][ 0x12] | PUSH29 | ['0x100000000000000000000000000000000000000000000000000000000'][ 0x30] | PUSH1 | ['0x0'][ 0x32] | CALLDATALOAD | None[ 0x33] | DIV | None[ 0x34] | AND | None[ 0x35] | PUSH4 | ['0x6b59084d'][ 0x3a] | DUP2 | None[ 0x3b] | EQ | None[ 0x3c] | PUSH2 | ['0x66'][ 0x3f] | JUMPI | None[ 0x40] | DUP1 | None[ 0x41] | PUSH4 | ['0x8da5cb5b'][ 0x46] | EQ | None[ 0x47] | PUSH2 | ['0xa4'][ 0x4a] | JUMPI | None[ 0x4b] | DUP1 | None[ 0x4c] | PUSH4 | ['0xcaf44683'][ 0x51] | EQ | None[ 0x52] | PUSH2 | ['0xb9'][ 0x55] | JUMPI | None[ 0x56] | DUP1 | None[ 0x57] | PUSH4 | ['0xe3ac5d26'][ 0x5c] | EQ | None[ 0x5d] | PUSH2 | ['0xd3'][ 0x60] | JUMPI | None[ 0x61] | JUMPDEST | None[ 0x62] | PUSH1 | ['0x0'][ 0x64] | DUP1 | None[ 0x65] | REVERT | None
    反编译出来的代码就是:
    def main(): if CALLDATASIZE >= 4: data = CALLDATA[:4] if data == 0x6b59084d: test1() elif data == 0x8da5cb5b: owner() elif data == 0xcaf44683: test2() elif data == 0xe3ac5d26: prize() else: pass raise
    PS:因为个人习惯问题,反编译最终输出没有选择对应的Solidity代码,而是使用Python。
    从上面的代码我们就能看出来,EVM是根据CALLDATA的前4字节来确定调用的函数的,这4个字节表示的是函数的sha3哈希值的前4字节:
    > web3.sha3("test1()")"0x6b59084dfb7dcf1c687dd12ad5778be120c9121b21ef90a32ff73565a36c9cd3"> web3.sha3("owner()")"0x8da5cb5b36e7f68c1d2e56001220cdbdd3ba2616072f718acfda4a06441a807d"> web3.sha3("prize()")"0xe3ac5d2656091dd8f25e87b604175717f3442b1e2af8ecd1b1f708bab76d9a91"# 如果该函数有参数,则需要加上各个参数的类型> web3.sha3("test2(uint256)")"0xcaf446833eef44593b83316414b79e98fec092b78e4c1287e6968774e0283444"
    所以可以去网上找个哈希表映射[6],这样有概率可以通过hash值,得到函数名和参数信息,减小逆向的难度。
    4.3 主函数中的函数上面给出的测试智能合约中只有两个函数,但是反编译出来的主函数中,却有4个函数调用,其中两个是公有函数,另两个是公有变量。
    智能合约变量/函数类型只有两种,公有和私有,公有和私有的区别很简单,公有的是能别外部调用访问,私有的只能被本身调用访问。
    对于变量,不管是公有还是私有都能通过getStorageAt访问,但是这是属于以太坊层面的,在智能合约层面,把公有变量给编译成了一个公有函数,在这公有函数中返回SLOAD(slot),而私有函数只能在其他函数中特定的地方调用SLOAD(slot)来访问。
    在上面测试的智能合约中, test1()函数等同于owner(),我们可以来看看各自的OPCODE:
    ; test1(); 0x66: loc_66[ 0x66] | JUMPDEST | None[ 0x67] | CALLVALUE | None[ 0x68] | DUP1 | None[ 0x69] | ISZERO | None[ 0x6a] | PUSH2 | ['0x72'][ 0x6d] | JUMPI | None[ 0x6e] | PUSH1 | ['0x0'][ 0x70] | DUP1 | None[ 0x71] | REVERT | None; 0x72: loc_72[ 0x72] | JUMPDEST | None[ 0x73] | POP | None[ 0x74] | PUSH2 | ['0x7b'][ 0x77] | PUSH2 | ['0xfa'][ 0x7a] | JUMP | None; 0xFA: loc_fa[ 0xfa] | JUMPDEST | None[ 0xfb] | PUSH1 | ['0x0'][ 0xfd] | SLOAD | None[ 0xfe] | PUSH20 | ['0xffffffffffffffffffffffffffffffffffffffff'][ 0x113] | AND | None[ 0x114] | SWAP1 | None[ 0x115] | JUMP | None; 0x7B: loc_7b[ 0x7b] | JUMPDEST | None[ 0x7c] | PUSH1 | ['0x40'][ 0x7e] | DUP1 | None[ 0x7f] | MLOAD | None[ 0x80] | PUSH20 | ['0xffffffffffffffffffffffffffffffffffffffff'][ 0x95] | SWAP1 | None[ 0x96] | SWAP3 | None[ 0x97] | AND | None[ 0x98] | DUP3 | None[ 0x99] | MSTORE | None[ 0x9a] | MLOAD | None[ 0x9b] | SWAP1 | None[ 0x9c] | DUP2 | None[ 0x9d] | SWAP1 | None[ 0x9e] | SUB | None[ 0x9f] | PUSH1 | ['0x20'][ 0xa1] | ADD | None[ 0xa2] | SWAP1 | None[ 0xa3] | RETURN | None
    和owner()函数进行对比:
    ; owner(); 0xA4: loc_a4[ 0xa4] | JUMPDEST | None[ 0xa5] | CALLVALUE | None[ 0xa6] | DUP1 | None[ 0xa7] | ISZERO | None[ 0xa8] | PUSH2 | ['0xb0'][ 0xab] | JUMPI | None[ 0xac] | PUSH1 | ['0x0'][ 0xae] | DUP1 | None[ 0xaf] | REVERT | None; 0xB0: loc_b0[ 0xb0] | JUMPDEST | None[ 0xb1] | POP | None[ 0xb2] | PUSH2 | ['0x7b'][ 0xb5] | PUSH2 | ['0x116'][ 0xb8] | JUMP | None; 0x116: loc_116[ 0x116] | JUMPDEST | None[ 0x117] | PUSH1 | ['0x0'][ 0x119] | SLOAD | None[ 0x11a] | PUSH20 | ['0xffffffffffffffffffffffffffffffffffffffff'][ 0x12f] | AND | None[ 0x130] | DUP2 | None[ 0x131] | JUMP | None; 0x7B: loc_7b[ 0x7b] | JUMPDEST | None[ 0x7c] | PUSH1 | ['0x40'][ 0x7e] | DUP1 | None[ 0x7f] | MLOAD | None[ 0x80] | PUSH20 | ['0xffffffffffffffffffffffffffffffffffffffff'][ 0x95] | SWAP1 | None[ 0x96] | SWAP3 | None[ 0x97] | AND | None[ 0x98] | DUP3 | None[ 0x99] | MSTORE | None[ 0x9a] | MLOAD | None[ 0x9b] | SWAP1 | None[ 0x9c] | DUP2 | None[ 0x9d] | SWAP1 | None[ 0x9e] | SUB | None[ 0x9f] | PUSH1 | ['0x20'][ 0xa1] | ADD | None[ 0xa2] | SWAP1 | None[ 0xa3] | RETURN | None
    所以我们可以得出结论:
    address public a;会被编译成(==)function a() public returns (address) { return a;}#address private a;function c() public returns (address) { return a;}等同于下面的变量定义(≈)address public c;
    公有函数和私有函数的区别也很简单,公有函数会被编译进主函数中,能通过CALLDATA进行调用,而私有函数则只能在其他公有函数中进行调用,无法直接通过设置CALLDATA来调用私有函数。
    4.4 回退函数和payable在智能合约中,函数都能设置一个payable,还有一个特殊的回退函数,下面用实例来介绍回退函数。
    比如之前的测试合约加上了回退函数:
    function() { prize += 1;}则主函数的反编译代码就变成了:
    def main(): if CALLDATASIZE >= 4: data = CALLDATA[:4] if data == 0x6b59084d: return test1() elif data == 0x8da5cb5b: return owner() elif data == 0xcaf44683: return test2() elif data == 0xe3ac5d26: return prize() assert msg.value == 0 prize += 1 exit()当CALLDATA和该合约中的函数匹配失败时,将会从抛异常,表示执行失败退出,变成调用回退函数。
    每一个函数,包括回退函数都可以加一个关键字: payable,表示可以给该函数转帐,从OPCODE层面讲,没有payable关键字的函数比有payable的函数多了一段代码:
    JUMPDEST | NoneCALLVALUE | NoneDUP1 | NoneISZERO | NonePUSH2 | ['0x8e']JUMPI | NonePUSH1 | ['0x0']DUP1 | NoneREVERT | None
    反编译成python,就是:
    assert msg.value == 0
    REVERT是异常退出指令,当交易的金额大于0时,则异常退出,交易失败。
    4.5 函数参数函数获取数据的方式只有两种,一个是从storage中获取数据,另一个就是接受用户传参,当函数hash表匹配成功时,我们可以知道该函数的参数个数,和各个参数的类型,但是当hash表匹配失败时,我们仍然可以获取该函数参数的个数,因为获取参数和主函数、payable检查一样,在OPCODE层面也有固定模型:
    比如上面的测试合约,调动test2函数的固定模型就是:main -> payable check -> get args -> 执行函数代码。
    获取参数的OPCODE如下:
    ; 0xAF: loc_af[ 0xaf] | JUMPDEST | None[ 0xb0] | POP | None[ 0xb1] | PUSH2 | ['0xd1'][ 0xb4] | PUSH20 | ['0xffffffffffffffffffffffffffffffffffffffff'][ 0xc9] | PUSH1 | ['0x4'][ 0xcb] | CALLDATALOAD | None[ 0xcc] | AND | None[ 0xcd] | PUSH2 | ['0x18f'][ 0xd0] | JUMP | None

    函数test2的参数p = CALLDATA[4:4+0x20]
    如果有第二个参数,则是arg2 = CALLDATA[4+0x20:4+0x40],以此类推

    所以智能合约中,调用函数的规则就是data = sha3(func_name)[:4] + *args。
    但是,上面的规则仅限于定长类型的参数,如果参数是string这种不定长的变量类型时,固定模型仍然不变,但是在从calldata获取数据的方法,变得不同了,定长的变量是通过调用CALLDATALOAD,把值存入栈中,而string类型的变量,因为长度不定,会超过256bits的原因,使用的是calldatacopy把参数存入MEM。
    可以看看function test3(string a) public {}函数获取参数的代码:
    ; 0xB2: loc_b2[ 0xb2] | JUMPDEST | None[ 0xb3] | POP | None[ 0xb4] | PUSH1 | ['0x40'][ 0xb6] | DUP1 | None[ 0xb7] | MLOAD | None[ 0xb8] | PUSH1 | ['0x20'][ 0xba] | PUSH1 | ['0x4'][ 0xbc] | DUP1 | None[ 0xbd] | CALLDATALOAD | None[ 0xbe] | DUP1 | None[ 0xbf] | DUP3 | None[ 0xc0] | ADD | None[ 0xc1] | CALLDATALOAD | None[ 0xc2] | PUSH1 | ['0x1f'][ 0xc4] | DUP2 | None[ 0xc5] | ADD | None[ 0xc6] | DUP5 | None[ 0xc7] | SWAP1 | None[ 0xc8] | DIV | None[ 0xc9] | DUP5 | None[ 0xca] | MUL | None[ 0xcb] | DUP6 | None[ 0xcc] | ADD | None[ 0xcd] | DUP5 | None[ 0xce] | ADD | None[ 0xcf] | SWAP1 | None[ 0xd0] | SWAP6 | None[ 0xd1] | MSTORE | None[ 0xd2] | DUP5 | None[ 0xd3] | DUP5 | None[ 0xd4] | MSTORE | None[ 0xd5] | PUSH2 | ['0xff'][ 0xd8] | SWAP5 | None[ 0xd9] | CALLDATASIZE | None[ 0xda] | SWAP5 | None[ 0xdb] | SWAP3 | None[ 0xdc] | SWAP4 | None[ 0xdd] | PUSH1 | ['0x24'][ 0xdf] | SWAP4 | None[ 0xe0] | SWAP3 | None[ 0xe1] | DUP5 | None[ 0xe2] | ADD | None[ 0xe3] | SWAP2 | None[ 0xe4] | SWAP1 | None[ 0xe5] | DUP2 | None[ 0xe6] | SWAP1 | None[ 0xe7] | DUP5 | None[ 0xe8] | ADD | None[ 0xe9] | DUP4 | None[ 0xea] | DUP3 | None[ 0xeb] | DUP1 | None[ 0xec] | DUP3 | None[ 0xed] | DUP5 | None[ 0xee] | CALLDATACOPY | None[ 0xef] | POP | None[ 0xf0] | SWAP5 | None[ 0xf1] | SWAP8 | None[ 0xf2] | POP | None[ 0xf3] | PUSH2 | ['0x166'][ 0xf6] | SWAP7 | None[ 0xf7] | POP | None[ 0xf8] | POP | None[ 0xf9] | POP | None[ 0xfa] | POP | None[ 0xfb] | POP | None[ 0xfc] | POP | None[ 0xfd] | POP | None[ 0xfe] | JUMP | None
    传入的变长参数是一个结构体:
    struct string_arg { uint offset; uint length; string data;}
    offset+4表示的是当前参数的length的偏移,length为data的长度,data就是用户输入的字符串数据。
    当有多个变长参数时: function test3(string a, string b) public {}。
    calldata的格式如下: sha3(func)[:4] + a.offset + b.offset + a.length + a.data + b.length + b.data
    翻译成py代码如下:
    def test3(): offset = data[4:0x24] length = data[offset+4:offset+4+0x20] a = data[offset+4+0x20:length] offset = data[0x24:0x24+0x20] length = data[offset+4:offset+4+0x20] b = data[offset+4+0x20:length]
    因为参数有固定的模型,因此就算没有从hash表中匹配到函数名,也可以判断出函数参数的个数,但是要想知道变量类型,只能区分出定长、变长变量,具体是uint还是address,则需要从函数代码,变量的使用中进行判断。
    4.6 变量类型的分辨在智能合约的OPCDOE中,变量也是有特征的。
    比如一个address变量总会 & 0xffffffffffffffffffffffffffffffffffffffff:
    PUSH1 | ['0x0']SLOAD | NonePUSH20 | ['0xffffffffffffffffffffffffffffffffffffffff']AND | None
    上一篇说的mapping和array的储存模型,可以根据SHA3的计算方式知道是映射变量还是数组变量。
    再比如,uint变量因为等同于uint256,所以使用SLOAD获取以后不会再进行AND计算,但是uint8却会计算& 0xff。
    所以我们可以SLOAD指令的参数和后面紧跟的计算,来判断出变量类型。
    五、智能合约代码结构5.1 部署合约在区块链上,要同步/发布任何信息,都是通过发送交易来进行的,用之前的测试合约来举例,合约地址为:0xc9fbe313dc1d6a1c542edca21d1104c338676ffd,创建合约的交易地址为::0x6cf9d5fe298c7e1b84f4805adddba43e7ffc8d8ffe658b4c3708f42ed94d90ed。
    查看下该交易的相关信息:
    > eth.getTransaction("0x6cf9d5fe298c7e1b84f4805adddba43e7ffc8d8ffe658b4c3708f42ed94d90ed"){ blockHash: "0x7f684a294f39e16ba1e82a3b6d2fc3a1e82ef023b5fb52261f9a89d831a24ed5", blockNumber: 3607048, from: "0x0109dea8b64d87a26e7fe9af6400375099c78fdd", gas: 171331, gasPrice: 1000000000, hash: "0x6cf9d5fe298c7e1b84f4805adddba43e7ffc8d8ffe658b4c3708f42ed94d90ed", input: "0x608060405234801561001057600080fd5b5060008054600160a060020a0319163317905561016f806100326000396000f3006080604052600436106100615763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416636b59084d81146100665780638da5cb5b146100a4578063caf44683146100b9578063e3ac5d26146100d3575b600080fd5b34801561007257600080fd5b5061007b6100fa565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b3480156100b057600080fd5b5061007b610116565b3480156100c557600080fd5b506100d1600435610132565b005b3480156100df57600080fd5b506100e861013d565b60408051918252519081900360200190f35b60005473ffffffffffffffffffffffffffffffffffffffff1690565b60005473ffffffffffffffffffffffffffffffffffffffff1681565b600180549091019055565b600154815600a165627a7a7230582040d052fef9322403cb3c1de27683a42a845e091972de4c264134dd575b14ee4e0029", nonce: 228, r: "0xa08f0cd907207af4de54f9f63f3c9a959c3e960ef56f7900d205648edbd848c6", s: "0x5bb99e4ab9fe76371e4d67a30208aeac558b2989a6c783d08b979239c8221a88", to: null, transactionIndex: 4, v: "0x2a", value: 0}
    我们可以看出来,想一个空目标发送OPCODE的交易就是创建合约的交易,但是在交易信息中,却不包含合约地址,那么合约地址是怎么得到的呢?
    function addressFrom(address _origin, uint _nonce) public pure returns (address) { if(_nonce == 0x00) return address(keccak256(byte(0xd6), byte(0x94), _origin, byte(0x80))); if(_nonce <= 0x7f) return address(keccak256(byte(0xd6), byte(0x94), _origin, byte(_nonce))); if(_nonce <= 0xff) return address(keccak256(byte(0xd7), byte(0x94), _origin, byte(0x81), uint8(_nonce))); if(_nonce <= 0xffff) return address(keccak256(byte(0xd8), byte(0x94), _origin, byte(0x82), uint16(_nonce))); if(_nonce <= 0xffffff) return address(keccak256(byte(0xd9), byte(0x94), _origin, byte(0x83), uint24(_nonce))); return address(keccak256(byte(0xda), byte(0x94), _origin, byte(0x84), uint32(_nonce))); // more than 2^32 nonces not realistic }
    智能合约的地址由创建合约的账号和nonce决定,nonce用来记录用户发送的交易个数,在每个交易中都有该字段,现在根据上面的信息来计算下合约地址:
    # 创建合约的账号 from: "0x0109dea8b64d87a26e7fe9af6400375099c78fdd",# nonce: 228 = 0xe4 => 0x7f < 0xe4 < 0xff>>> sha3.keccak_256(binascii.unhexlify("d7" + "94" + "0109dea8b64d87a26e7fe9af6400375099c78fdd" + "81e4")).hexdigest()[-40:]'c9fbe313dc1d6a1c542edca21d1104c338676ffd'
    5.2 创建合约代码一个智能合约的OPCODE分为两种,一个是编译器编译好后的创建合约代码,还是合约部署好以后runtime代码,之前我们看的,研究的都是runtime代码,现在来看看创建合约代码,创建合约代码可以在创建合约交易的input数据总获取,上面已经把数据粘贴出来了,反汇编出指令如下:
    ; 0x0: main[ 0x0] | PUSH1 | ['0x80'][ 0x2] | PUSH1 | ['0x40'][ 0x4] | MSTORE | None[ 0x5] | CALLVALUE | None[ 0x6] | DUP1 | None[ 0x7] | ISZERO | None[ 0x8] | PUSH2 | ['0x10'][ 0xb] | JUMPI | None[ 0xc] | PUSH1 | ['0x0'][ 0xe] | DUP1 | None[ 0xf] | REVERT | None----------------------------------------------------------------; 0x10: loc_10[ 0x10] | JUMPDEST | None[ 0x11] | POP | None[ 0x12] | PUSH1 | ['0x0'][ 0x14] | DUP1 | None[ 0x15] | SLOAD | None[ 0x16] | PUSH1 | ['0x1'][ 0x18] | PUSH1 | ['0xa0'][ 0x1a] | PUSH1 | ['0x2'][ 0x1c] | EXP | None[ 0x1d] | SUB | None[ 0x1e] | NOT | None[ 0x1f] | AND | None[ 0x20] | CALLER | None[ 0x21] | OR | None[ 0x22] | SWAP1 | None[ 0x23] | SSTORE | None[ 0x24] | PUSH2 | ['0x24f'][ 0x27] | DUP1 | None[ 0x28] | PUSH2 | ['0x32'][ 0x2b] | PUSH1 | ['0x0'][ 0x2d] | CODECOPY | None[ 0x2e] | PUSH1 | ['0x0'][ 0x30] | RETURN | None
    代码逻辑很简单,就是执行了合约的构造函数,并且返回了合约的runtime代码,该合约的构造函数为:
    function Test() { owner = msg.sender;}

    因为没有payable关键字,所以开头是一个check代码assert msg.value == 0
    然后就是对owner变量的赋值,当执行完构造函数后,就是把runtime代码复制到内存中:
    CODECOPY(0, 0x32, 0x24f) # mem[0:0+0x24f] = CODE[0x32:0x32+0x24f]
    最后在把runtime代码返回: return mem[0:0x24f]

    在完全了解合约是如何部署的之后,也许可以写一个OPCODE混淆的CTF逆向题。
    总结通过了解EVM的数据结构模型,不仅可以加快对OPCODE的逆向速度,对于编写反编译脚本也有非常大的帮助,可以对反编译出来的代码进行优化,使得更加接近源码。
    附录(操作码对应含义和GAS)Ethereum VM (EVM) Opcodes and Instruction ReferenceThis reference consolidates EVM opcode information from the yellow paper, stack exchange, solidity source, parity source, evm-opcode-gas-costs and Manticore.
    New issues and contributions are welcome, and are covered by bounties from Trail of Bits. Join us in #ethereum on the Empire Hacking Slack to discuss Ethereum security tool development.
    NotesThe size of a “word” in EVM is 256 bits.
    The gas information is a work in progress. If an asterisk is in the Gas column, the base cost is shown but may vary based on the opcode arguments.
    Table


    Opcode
    Name
    Description
    Extra Info
    Gas




    0x00
    STOP
    Halts execution
    -
    0


    0x01
    ADD
    Addition operation
    -
    3


    0x02
    MUL
    Multiplication operation
    -
    5


    0x03
    SUB
    Subtraction operation
    -
    3


    0x04
    DIV
    Integer division operation
    -
    5


    0x05
    SDIV
    Signed integer division operation (truncated)
    -
    5


    0x06
    MOD
    Modulo remainder operation
    -
    5


    0x07
    SMOD
    Signed modulo remainder operation
    -
    5


    0x08
    ADDMOD
    Modulo addition operation
    -
    8


    0x09
    MULMOD
    Modulo multiplication operation
    -
    8


    0x0a
    EXP
    Exponential operation
    -
    10*


    0x0b
    SIGNEXTEND
    Extend length of two’s complement signed integer
    -
    5


    0x0c - 0x0f
    Unused
    Unused
    -


    0x10
    LT
    Less-than comparison
    -
    3


    0x11
    GT
    Greater-than comparison
    -
    3


    0x12
    SLT
    Signed less-than comparison
    -
    3


    0x13
    SGT
    Signed greater-than comparison
    -
    3


    0x14
    EQ
    Equality comparison
    -
    3


    0x15
    ISZERO
    Simple not operator
    -
    3


    0x16
    AND
    Bitwise AND operation
    -
    3


    0x17
    OR
    Bitwise OR operation
    -
    3


    0x18
    XOR
    Bitwise XOR operation
    -
    3


    0x19
    NOT
    Bitwise NOT operation
    -
    3


    0x1a
    BYTE
    Retrieve single byte from word
    -
    3


    0x1b
    SHL
    Shift Left
    EIP145
    3


    0x1c
    SHR
    Logical Shift Right
    EIP145
    3


    0x1d
    SAR
    Arithmetic Shift Right
    EIP145
    3


    0x20
    KECCAK256
    Compute Keccak-256 hash
    -
    30*


    0x21 - 0x2f
    Unused
    Unused


    0x30
    ADDRESS
    Get address of currently executing account
    -
    2


    0x31
    BALANCE
    Get balance of the given account
    -
    400


    0x32
    ORIGIN
    Get execution origination address
    -
    2


    0x33
    CALLER
    Get caller address
    -
    2


    0x34
    CALLVALUE
    Get deposited value by the instruction/transaction responsible for this execution
    -
    2


    0x35
    CALLDATALOAD
    Get input data of current environment
    -
    3


    0x36
    CALLDATASIZE
    Get size of input data in current environment
    -
    2*


    0x37
    CALLDATACOPY
    Copy input data in current environment to memory
    -
    3


    0x38
    CODESIZE
    Get size of code running in current environment
    -
    2


    0x39
    CODECOPY
    Copy code running in current environment to memory
    -
    3*


    0x3a
    GASPRICE
    Get price of gas in current environment
    -
    2


    0x3b
    EXTCODESIZE
    Get size of an account’s code
    -
    700


    0x3c
    EXTCODECOPY
    Copy an account’s code to memory
    -
    700*


    0x3d
    RETURNDATASIZE
    Pushes the size of the return data buffer onto the stack
    EIP 211
    2


    0x3e
    RETURNDATACOPY
    Copies data from the return data buffer to memory
    EIP 211
    3


    0x3f
    EXTCODEHASH
    Returns the keccak256 hash of a contract’s code
    EIP 1052
    700


    0x40
    BLOCKHASH
    Get the hash of one of the 256 most recent complete blocks
    -
    20


    0x41
    COINBASE
    Get the block’s beneficiary address
    -
    2


    0x42
    TIMESTAMP
    Get the block’s timestamp
    -
    2


    0x43
    NUMBER
    Get the block’s number
    -
    2


    0x44
    DIFFICULTY
    Get the block’s difficulty
    -
    2


    0x45
    GASLIMIT
    Get the block’s gas limit
    -
    2


    0x46
    CHAINID
    Returns the current chain’s EIP-155 unique identifier
    EIP 1344
    2


    0x47 - 0x4f
    Unused
    -


    0x50
    POP
    Remove word from stack
    -
    2


    0x51
    MLOAD
    Load word from memory
    -
    3*


    0x52
    MSTORE
    Save word to memory
    -
    3*


    0x53
    MSTORE8
    Save byte to memory
    -
    3


    0x54
    SLOAD
    Load word from storage
    -
    200


    0x55
    SSTORE
    Save word to storage
    -
    20000**


    0x56
    JUMP
    Alter the program counter
    -
    8


    0x57
    JUMPI
    Conditionally alter the program counter
    -
    10


    0x58
    GETPC
    Get the value of the program counter prior to the increment
    -
    2


    0x59
    MSIZE
    Get the size of active memory in bytes
    -
    2


    0x5a
    GAS
    Get the amount of available gas, including the corresponding reduction the amount of available gas
    -
    2


    0x5b
    JUMPDEST
    Mark a valid destination for jumps
    -
    1


    0x5c - 0x5f
    Unused
    -


    0x60
    PUSH1
    Place 1 byte item on stack
    -
    3


    0x61
    PUSH2
    Place 2-byte item on stack
    -
    3


    0x62
    PUSH3
    Place 3-byte item on stack
    -
    3


    0x63
    PUSH4
    Place 4-byte item on stack
    -
    3


    0x64
    PUSH5
    Place 5-byte item on stack
    -
    3


    0x65
    PUSH6
    Place 6-byte item on stack
    -
    3


    0x66
    PUSH7
    Place 7-byte item on stack
    -
    3


    0x67
    PUSH8
    Place 8-byte item on stack
    -
    3


    0x68
    PUSH9
    Place 9-byte item on stack
    -
    3


    0x69
    PUSH10
    Place 10-byte item on stack
    -
    3


    0x6a
    PUSH11
    Place 11-byte item on stack
    -
    3


    0x6b
    PUSH12
    Place 12-byte item on stack
    -
    3


    0x6c
    PUSH13
    Place 13-byte item on stack
    -
    3


    0x6d
    PUSH14
    Place 14-byte item on stack
    -
    3


    0x6e
    PUSH15
    Place 15-byte item on stack
    -
    3


    0x6f
    PUSH16
    Place 16-byte item on stack
    -
    3


    0x70
    PUSH17
    Place 17-byte item on stack
    -
    3


    0x71
    PUSH18
    Place 18-byte item on stack
    -
    3


    0x72
    PUSH19
    Place 19-byte item on stack
    -
    3


    0x73
    PUSH20
    Place 20-byte item on stack
    -
    3


    0x74
    PUSH21
    Place 21-byte item on stack
    -
    3


    0x75
    PUSH22
    Place 22-byte item on stack
    -
    3


    0x76
    PUSH23
    Place 23-byte item on stack
    -
    3


    0x77
    PUSH24
    Place 24-byte item on stack
    -
    3


    0x78
    PUSH25
    Place 25-byte item on stack
    -
    3


    0x79
    PUSH26
    Place 26-byte item on stack
    -
    3


    0x7a
    PUSH27
    Place 27-byte item on stack
    -
    3


    0x7b
    PUSH28
    Place 28-byte item on stack
    -
    3


    0x7c
    PUSH29
    Place 29-byte item on stack
    -
    3


    0x7d
    PUSH30
    Place 30-byte item on stack
    -
    3


    0x7e
    PUSH31
    Place 31-byte item on stack
    -
    3


    0x7f
    PUSH32
    Place 32-byte (full word) item on stack
    -
    3


    0x80
    DUP1
    Duplicate 1st stack item
    -
    3


    0x81
    DUP2
    Duplicate 2nd stack item
    -
    3


    0x82
    DUP3
    Duplicate 3rd stack item
    -
    3


    0x83
    DUP4
    Duplicate 4th stack item
    -
    3


    0x84
    DUP5
    Duplicate 5th stack item
    -
    3


    0x85
    DUP6
    Duplicate 6th stack item
    -
    3


    0x86
    DUP7
    Duplicate 7th stack item
    -
    3


    0x87
    DUP8
    Duplicate 8th stack item
    -
    3


    0x88
    DUP9
    Duplicate 9th stack item
    -
    3


    0x89
    DUP10
    Duplicate 10th stack item
    -
    3


    0x8a
    DUP11
    Duplicate 11th stack item
    -
    3


    0x8b
    DUP12
    Duplicate 12th stack item
    -
    3


    0x8c
    DUP13
    Duplicate 13th stack item
    -
    3


    0x8d
    DUP14
    Duplicate 14th stack item
    -
    3


    0x8e
    DUP15
    Duplicate 15th stack item
    -
    3


    0x8f
    DUP16
    Duplicate 16th stack item
    -
    3


    0x90
    SWAP1
    Exchange 1st and 2nd stack items
    -
    3


    0x91
    SWAP2
    Exchange 1st and 3rd stack items
    -
    3


    0x92
    SWAP3
    Exchange 1st and 4th stack items
    -
    3


    0x93
    SWAP4
    Exchange 1st and 5th stack items
    -
    3


    0x94
    SWAP5
    Exchange 1st and 6th stack items
    -
    3


    0x95
    SWAP6
    Exchange 1st and 7th stack items
    -
    3


    0x96
    SWAP7
    Exchange 1st and 8th stack items
    -
    3


    0x97
    SWAP8
    Exchange 1st and 9th stack items
    -
    3


    0x98
    SWAP9
    Exchange 1st and 10th stack items
    -
    3


    0x99
    SWAP10
    Exchange 1st and 11th stack items
    -
    3


    0x9a
    SWAP11
    Exchange 1st and 12th stack items
    -
    3


    0x9b
    SWAP12
    Exchange 1st and 13th stack items
    -
    3


    0x9c
    SWAP13
    Exchange 1st and 14th stack items
    -
    3


    0x9d
    SWAP14
    Exchange 1st and 15th stack items
    -
    3


    0x9e
    SWAP15
    Exchange 1st and 16th stack items
    -
    3


    0x9f
    SWAP16
    Exchange 1st and 17th stack items
    -
    3


    0xa0
    LOG0
    Append log record with no topics
    -
    375


    0xa1
    LOG1
    Append log record with one topic
    -
    750


    0xa2
    LOG2
    Append log record with two topics
    -
    1125


    0xa3
    LOG3
    Append log record with three topics
    -
    1500


    0xa4
    LOG4
    Append log record with four topics
    -
    1875


    0xa5 - 0xaf
    Unused
    -


    0xb0
    JUMPTO
    Tentative libevmasm has different numbers
    EIP 615


    0xb1
    JUMPIF
    Tentative
    EIP 615


    0xb2
    JUMPSUB
    Tentative
    EIP 615


    0xb4
    JUMPSUBV
    Tentative
    EIP 615


    0xb5
    BEGINSUB
    Tentative
    EIP 615


    0xb6
    BEGINDATA
    Tentative
    EIP 615


    0xb8
    RETURNSUB
    Tentative
    EIP 615


    0xb9
    PUTLOCAL
    Tentative
    EIP 615


    0xba
    GETLOCAL
    Tentative
    EIP 615


    0xbb - 0xe0
    Unused
    -


    0xe1
    SLOADBYTES
    Only referenced in pyethereum
    -
    -


    0xe2
    SSTOREBYTES
    Only referenced in pyethereum
    -
    -


    0xe3
    SSIZE
    Only referenced in pyethereum
    -
    -


    0xe4 - 0xef
    Unused
    -


    0xf0
    CREATE
    Create a new account with associated code
    -
    32000


    0xf1
    CALL
    Message-call into an account
    -
    Complicated


    0xf2
    CALLCODE
    Message-call into this account with alternative account’s code
    -
    Complicated


    0xf3
    RETURN
    Halt execution returning output data
    -
    0


    0xf4
    DELEGATECALL
    Message-call into this account with an alternative account’s code, but persisting into this account with an alternative account’s code
    -
    Complicated


    0xf5
    CREATE2
    Create a new account and set creation address to sha3(sender + sha3(init code)) % 2**160
    -


    0xf6 - 0xf9
    Unused
    -
    -


    0xfa
    STATICCALL
    Similar to CALL, but does not modify state
    -
    40


    0xfc
    TXEXECGAS
    Not in yellow paper FIXME
    -
    -


    0xfd
    REVERT
    Stop execution and revert state changes, without consuming all provided gas and providing a reason
    -
    0


    0xfe
    INVALID
    Designated invalid instruction
    -
    0


    0xff
    SELFDESTRUCT
    Halt execution and register account for later deletion
    -
    5000*



    Instruction DetailsADDTakes two words from stack, adds them, then pushes the result onto the stack.
    Pseudocode: push(s[0]+s[1])
    PUSHXThe following X bytes are read from PC, placed into a word, then this word is pushed onto the stack.
    CALL
    1 留言 2020-08-29 11:27:20 奖励30点积分
  • 基于 Jupyter notebook 完成的一个数据分析

    用Jupyter notebook完成一个数据分析任务,达到以下基本要求:

    可以使用推荐的数据集之一,或自选数据集
    数据分析目标可以自定,但要有明确的目标
    笔记中包括程序代码、输出数据和分析报告

    一、Jupyter notebook的安装与配置Jupyter Notebook是基于网页的用于交互计算的应用程序。其可被应用于全过程计算:开发、文档编写、运行代码和展示结果。简而言之,Jupyter Notebook是以网页的形式打开,可以在网页页面中直接编写代码和运行代码,代码的运行结果也会直接在代码块下显示的程序。如在编程过程中需要编写说明文档,可在同一个页面中直接编写,便于作及时的说明和解释。
    1.1 安装下载Jupyter notebook的网站地址如下:https://link.zhihu.com/?target=https%3A//try.jupyter.org/
    安装Jupyter Notebook的前提是需要安装了Python。在cmd命令中输入:pip install jupyter即可安装完毕。
    1.2 配置在终端中输入以下命令:
    jupyter notebook
    执行命令之后,在终端中将会显示一系列notebook的服务器信息,同时浏览器将会自动启动Jupyter Notebook。
    浏览器地址栏中默认地将会显示:http://localhost:8888 。其中,“localhost”指的是本机,“8888”则是端口号。打开主页面后,如下图所示,接下来就可以进行操作。
    二、数据处理任务为求得某个地区的商品店的月营业额是与店铺的面积相关性大,还是与该店距离车站距离的相关性大,需要我们以店铺面积、距离车站的距离、以及月营业额建立线性回归方程,并求解该方程和相关系数。
    具体数据如下表所示:



    店铺名称
    店铺的面积
    距离最近的车站
    月营业额




    梦之丘总店
    10
    80
    469


    寺锦站大厦店
    8
    0
    366


    曾根店
    8
    200
    371


    桥本大街店
    5
    200
    208


    桔梗町店
    7
    300
    246


    邮政局前店
    8
    230
    297


    水道町站前店
    7
    40
    363


    六条站大厦店
    9
    0
    436


    若叶川店
    6
    330
    198


    美里店
    9
    180
    364



    三、准备的多元线性回归方程的变量的表格数据从左到右,依次为店铺面积、距离车站距离和月营业额。
    四、梯度下降算法求解多元线性回归方程1.导入基本库、数据,并为变量赋值
    import numpy as npfrom matplotlib import pyplot as pltfrom mpl_toolkits.mplot3d import Axes3Ddata=np.genfromtxt('D:/面积距离车站数据.csv',delimiter=',')x_data=data[:,:-1]y_data=data[:,2]
    2.定义系数初始值以及学习率和迭代次数
    #定义学习率、斜率、截据#设方程为y=theta1*x1+theta2*x2+theta0lr=0.00001theta0=0theta1=0theta2=0#定义最大迭代次数,因为梯度下降法是在不断迭代更新k与bepochs=10000
    3.定义最小二乘法函数-损失函数(代价函数)
    #定义最小二乘法函数-损失函数(代价函数)def compute_error(theta0,theta1,theta2,x_data,y_data): totalerror=0 for i in range(0,len(x_data)):#定义一共有多少样本点 totalerror=totalerror+(y_data[i]-(theta1*x_data[i,0]+theta2*x_data[i,1]+theta0))**2 return totalerror/float(len(x_data))/2
    通过代价方程,求解迭代过程中的错误率,直观看出求解误差。
    4.定义梯度下降算法求解线性回归方程系数python函数
    #梯度下降算法求解参数def gradient_descent_runner(x_data,y_data,theta0,theta1,theta2,lr,epochs): m=len(x_data) for i in range(epochs): theta0_grad=0 theta1_grad=0 theta2_grad=0 for j in range(0,m): theta0_grad-=(1/m)*(-(theta1*x_data[j,0]+theta2*x_data[j,1]+theta2)+y_data[j]) theta1_grad-=(1/m)*x_data[j,0]*(-(theta1*x_data[j,0]+theta2*x_data[j,1]+theta0)+y_data[j]) theta2_grad-=(1/m)*x_data[j,1]*(-(theta1*x_data[j,0]+theta2*x_data[j,1]+theta0)+y_data[j]) theta0=theta0-lr*theta0_grad theta1=theta1-lr*theta1_grad theta2=theta2-lr*theta2_grad return theta0,theta1,theta2
    该函数的主要功能就是利用梯度下降算法进行系数的求解,通过多次的迭代,完成近视值的求解。
    5.代用函数,进行系数求解,并打印
    #进行迭代求解theta0,theta1,theta2=gradient_descent_runner(x_data,y_data,theta0,theta1,theta2,lr,epochs)print('结果:迭代次数:{0} 学习率:{1}之后 a0={2},a1={3},a2={4},代价函数为{5}'.format(epochs,lr,theta0,theta1,theta2,compute_error(theta0,theta1,theta2,x_data,y_data)))print("多元线性回归方程为:y=",theta1,"X1+",theta2,"X2+",theta0)
    6.画出回归方程线性拟合图
    #画图ax=plt.figure().add_subplot(111,projection='3d')ax.scatter(x_data[:,0],x_data[:,1],y_data,c='r',marker='o')x0=x_data[:,0]x1=x_data[:,1]#生成网格矩阵x0,x1=np.meshgrid(x0,x1)z=theta0+theta1*x0+theta2*x1#画3d图ax.plot_surface(x0,x1,z)ax.set_xlabel('area')ax.set_ylabel('distance')ax.set_zlabel("Monthly turnover")plt.show()
    通过梯度下降算法求解的系数可用通过代价方程看出误差,误差还是很大的,基本不是太精确。
    五、最小二乘法法求解多元线性回归方程1.导入本次例题所需要的库
    import numpy as npimport matplotlib.pyplot as pltimport pandas as pdimport seaborn as sns%matplotlib inline
    numpy库用来做矩阵运算的。pandas库用来导入表格数据文件的。matplotlib库用来画拟合回归曲线图的。seaborn库是matplotlib的子库。
    2.通过导入的数据,对自变量X1、X2和因变量Y赋值
    data = np.genfromtxt("D:/面积距离车站数据.csv",delimiter=",")X1=data[0:10,0]#自变量温度X2=data[0:10,1]#因变量销售量Y=data[0:10,2]#自变量温度
    3.通过原理,需要将自变量X1、X2,因变量Y的值转换为矩阵模式
    (1)因变量(月销售额)Y进行矩阵化的代码:
    #将因变量赋值给矩阵Y1Y1=np.array([Y]).T
    (2)自变量X1(店铺面积0)、X2(距离车站的距离)、进行矩阵化变为原理的X矩阵的代码:
    #为自变量系数矩阵X赋值X11=np.array([X1]).TX22=np.array([X2]).TA=np.array([[1],[1],[1],[1],[1],[1],[1],[1],[1],[1]])#创建系数矩阵B=np.hstack((A,X11))#将矩阵a与矩阵X11合并为矩阵bX=np.hstack((B,X22))#将矩阵b与矩阵X22合并为矩阵X
    (3)求X矩阵的转置矩阵==X_==矩阵
    #求矩阵X的转置矩阵X_=X.T
    (4)求矩阵X与他的==转置矩阵的X_==的乘积
    X_X=np.dot(X_,X)
    (5)求矩阵X与他的转置矩阵的X_的乘积的逆矩阵
    X_X_=np.linalg.inv(X_X)
    (6)求解系数矩阵W
    #求解系数矩阵W,分别对应截距b、a1、和a2W=np.dot(np.dot((X_X_),(X_)),Y1)
    (7)将系数矩阵W转换为系数a1、a2和截距b,并输入线性回归方程
    b=W[0][0]a1=W[1][0]a2=W[2][0]print("系数a1=",a1)print("系数a2=",a2)print("截距为=",b)print("多元线性回归方程为:y=",a1,"X1+",a2,"X2+",b)
    (8)画出线性回归分析图
    data1=pd.read_excel('D:\面积距离车站数据.xlsx')sns.pairplot(data1, x_vars=['area','distance'], y_vars='Y', height=3, aspect=0.8, kind='reg') plt.show()
    4.求解判定系数R2
    (1)求解月销售额的总值,以及平均值y1
    #求月销售量Y的和以及平均值y1sumy=0#因变量的和y1=0#因变量的平均值for i in range(0,len(Y)): sumy=sumy+Y[i]y1=sumy/len(Y)
    (2)求解月销售额y减去它平均值的和y_y1
    #求月销售额y-他的平均值的和y_y1=0#y-y1的值的和for i in range(0,len(Y)): y_y1=y_y1+(Y[i]-y1)print("销售量-销售量平均值的和为:",y_y1)
    (3)通过上面系数和截距,求出预测的月销售额sales1
    sales1=a1x1+a2x2+b#求预测值sales1sales1=[]for i in range(0,len(Y)): sales1.append(a1*X1[i]+a2*X2[i]+b)
    (4)求预测月销售额的平均值y2
    #求预测值的平均值y2y2=0sumy2=0for i in range(len(sales1)): sumy2=sumy2+sales1[i]y2=sumy2/len(sales1)
    (5)求预测月销售额减去其平均值的和y11_y2
    #求预测值-平均值的和y11_y2y11_y2=0for i in range(0,len(sales1)): y11_y2=y11_y2+(sales1[i]-y2)print("预测销售值-预测销售平均值的和为:",y11_y2)
    (6)求月销售额y减去他的平均值的平方和Syy
    #求月销售额y-他的平均值的平方和Syy=0#y-y1的值的平方和for i in range(0,len(Y)): Syy=Syy+((Y[i]-y1)*(Y[i]-y1))print("Syy=",Syy)
    (7)求预测月销售额减去其平均的平方和Sy1y1
    #求y1-y1平均的平方和Sy1y1=0for i in range(0,len(sales1)): Sy1y1=Sy1y1+((sales1[i]-y2)*(sales1[i]-y2))print("Sy1y1=",Sy1y1)
    (8)求(y1-y1平均)*(y-y平均)的和Syy1
    #(y1-y1平均)*(y-y平均)Syy1=0for i in range(0,len(sales1)): Syy1=Syy1+((Y[i]-y1)*(sales1[i]-y2))print("Syy1=",Syy1)
    5.通过上述步骤的求解,整理,求解判定系数R2
    #求RR=Syy1/((Syy*Sy1y1)**0.5)R2=R*Rprint("判定系数R2=",R2)
    六、总结将上述结果使用excel做回归分析。通过对比结果可以知道,最小二乘法求解线性回归方程的精度优于梯度下降算法,这也是梯度下降算法的确定,求解精度太低,求解还需要很多次的迭代。
    0 留言 2021-05-09 09:54:26 奖励32点积分
  • mvc 下常见错误

    mvc模式下一些常见的错误及分析cause: java.sql.SQLSyntaxErrorException: Unknow database 'javaee'

    javax.el.PropertyNotFoundException: 类型[java.lang.String]上找不到属性[id]

    javax.el.PropertyNotFoundException: 类型[com.bjsxt.pojo.Flower]上找不到属性[id]

    注意:了这个错误类型和上面一个是不一致的。
    java.langNullPointerException

    注意: NullPointerException算的上是我们最常见的错误之一的,碰到这个异常不要着急,慢慢往下看,找到自己能看懂的代码(自己写的类),找到该类的确定第多少行,上下看看分析哪个地方可能会出现null,那就基本上确定是它了。
    0 留言 2021-05-08 07:42:12 奖励36点积分
  • 内存快速搜索遍历 精华

    背景相比很多人都用过内存搜索软件 Cheat Engine 吧,它里面提供了强大进程内存搜索功能,搜索速度很快,搜索结果也很精确。我之前对内存搜索也稍微专研了一下,是因为当时需要写一个小程序,那个小程序的功能就是可以搜索出指定进程指定值的内存地址,这个CE就能做,只不过是要在自己的程序里实现内存的搜索。
    内存的遍历搜索,说难也不难,说容易也不容易。因为你可以做得比较简单,也可以做得比较完美,这主要是在搜索效率上的区别而已。简单的搜索方法就是直接暴力搜索内存,直接从0地址搜索到0x7FFFFFFF地址处,因为低 2GB 进程空间是用户空间。然后,匹配值就可以了。难点的,就是过滤掉一些内存地址,不用搜索。例如,进程加载基址之前的地址空间,就可以不用搜索等等,以此来缩小搜索的范围,提升搜索效率。
    本文就是对内存遍历实现快速搜索,当然,这肯定不会是最快的搜索方式,只是相对的快速。我们也是通过加载基址,以及内存空间地址信息,缩小搜索的范围,提升搜索效率。现在,就把实现过程整理成文档,分享给大家。
    函数介绍VirtualQueryEx 函数
    查询地址空间中内存地址的信息。
    函数声明
    DWORD VirtualQueryEx( HANDLE hProcess, LPCVOID lpAddress, PMEMORY_BASIC_INFORMATION lpBuffer, DWORD dwLength);
    参数

    hProcess:进程句柄。lpAddress:查询内存的地址。lpBuffer:指向MEMORY_BASIC_INFORMATION结构的指针,用于接收内存信息。dwLength:MEMORY_BASIC_INFORMATION结构的大小。
    返回值

    返回值是信息缓冲区中返回的实际字节数。如果函数失败,返回值是 0。为了获得更多的错误信息,调用GetLastError。

    MEMORY_BASIC_INFORMATION 结构体
    结构体声明
    typedef struct _MEMORY_BASIC_INFORMATION { PVOID BaseAddress; // 区域基地址 PVOID AllocationBase; // 分配基地址 DWORD AllocationProtect; // 区域被初次保留时赋予的保护属性 SIZE_T RegionSize; // 区域大小(以字节为计量单位) DWORD State; // 状态(MEM_FREE、MEM_RESERVE或 MEM_COMMIT) DWORD Protect; // 保护属性 DWORD Type; // 类型} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;
    成员

    BaseAddress:与lpAddress参数的值相同,但是四舍五入为页面的边界值。AllocationBas:指明用VirtualAlloc函数分配内存区域的基地址。lpAddress在该区域之内。AllocationProtect:指明该地址空间区域被初次保留时赋予该区域的保护属性。PAGE_READONLY:只读属性,如果试图进行写操作,将引发访问违规。如果系统区分只读、执行两种属性,那么试图在该区域执行代码也将引发访问违规。PAGE_READWRITE:允许读写。PAGE_EXECUTE:只允许执行代码,对该区域试图进行读写操作将引发访问违规。PAGE_EXECUTE_READ:允许执行和读取。PAGE_EXECUTE_READWRITE:允许读写和执行代码。PAGE_EXECUTE_WRITECOPY:对于该地址空间的区域,不管执行什么操作,都不会引发访问违规。如果试图在该页面上的内存中进行写入操作,就会将它自己的私有页面(受页文件的支持)拷贝赋予该进程。PAGE_GUARD:在页面上写入一个字节时使应用程序收到一个通知(通过一个异常条件)。PAGE_NOACCESS:禁止一切访问。PAGE_NOCACHE:停用已提交页面的高速缓存。一般情况下最好不要使用该标志,因为它主要是供需要处理内存缓冲区的硬件设备驱动程序的开发人员使用的。RegionSize 用于指明内存块从基地址即BaseAddress开始的所有页面的大小(以字节为计量单位)这些页面与含有用LpAddress参数设定的地址的页面拥有相同的保护属性、状态和类型。State:用于指明所有相邻页面的状态。MEM_COMMIT:指明已分配物理内存或者系统页文件。MEM_FREE:空闲状态。该区域的虚拟地址不受任何内存的支持。该地址空间没有被保留。该状态下AllocationBase、AllocationProtect、Protect和Type等成员均未定义。MEM_RESERVE:指明页面被保留,但是没有分配任何物理内存。该状态下Protect成员未定。Protect:用于指明所有相邻页面(内存块)的保护属性。这些页面与含有拥有相同的保属性、状态和类型。意义同AllocationProtect。Type:用于指明支持所有相邻页面的物理存储器的类型(MEM_IMAGE,MEM_MAPPED或MEM_PRIVATE)。这些相邻页面拥有相同的保护属性、状态和类型。如果是Windows 98,那么这个成员将总是MEM_PRIVATE 。MEM_IMAGE:指明该区域的虚拟地址原先受内存映射的映像文件(如.exe或DLL文件)的支持,但也许不再受映像文件的支持。例如,当写入模块映像中的全局变量时,“写入时拷贝”的机制将由页文件来支持特定的页面,而不是受原始映像文件的支持。MEM_MAPPED:该区域的虚拟地址原先是受内存映射的数据文件的支持,但也许不再受数据文件的支持。例如,数据文件可以使用“写入时拷贝”的保护属性来映射。对文件的任何写入操作都将导致页文件而不是原始数据支持特定的页面。MEM_PRIVATE:指明该内存区域是私有的。不被其他进程共享。

    ReadProcessMemory 函数
    在指定的进程中从内存区域读取数据。 要读取的整个区域必须可访问或操作失败。
    函数声明
    BOOL WINAPI ReadProcessMemory( _In_ HANDLE hProcess, _In_ LPCVOID lpBaseAddress, _Out_ LPVOID lpBuffer, _In_ SIZE_T nSize, _Out_ SIZE_T *lpNumberOfBytesRead);
    参数

    hProcess [in]具有正在读取的内存的进程的句柄。 句柄必须具有进程的PROCESS_VM_READ访问权限。lpBaseAddress [in]指向从中读取的指定进程中的基地址的指针。 在发生任何数据传输之前,系统将验证指定大小的基地址和内存中的所有数据都可访问以进行读取访问,如果不可访问,则该函数将失败。lpBuffer [out]指向从指定进程的地址空间接收内容的缓冲区的指针。nSize [in]要从指定进程读取的字节数。lpNumberOfBytesRead [out]指向一个变量的指针,该变量接收传输到指定缓冲区的字节数。 如果lpNumberOfBytesRead为NULL,则该参数将被忽略。
    返回值

    如果函数成功,则返回值不为零。如果函数失败,返回值为0(零)。 要获取扩展错误信息,请调用GetLastError。

    实现原理实现内存的快速搜索,我们主要是缩小搜索的范围,来提升的搜索效率。缩小搜索范围的方法有,一是过滤掉进程加载基址之前的内存地址;二是获取内存空间地址信息,把内存状态不是 MEM_COMMIT 以及保护属性没有读权限的内存区域都过滤掉。
    所以,获取进程加载基址以及获取内存空间地址信息比较关键。
    大概分为以下 7 个步骤:

    首先,我们调用 OpenProcess 函数根据进程 PID 打开进程,并获取进程的句柄,进程句柄的权限为 PROCESS_ALL_ACCESS
    然后,我们根据进程句柄,获取指定进程的加载基址。对于进程加载基址的获取,我们使用的是 EnumProcessModules 函数来获取。其它的的进程基址获取方法,可以参考本站上其他网友写的“获取指定进程的加载基址”这篇文章
    接着,以进程加载基址作为内存搜索的起始地址,调用 VirtualQueryEx 函数查询地址空间中内存地址的信息,然后将内存页面状态不是MEM_COMMIT 过滤掉,即过滤掉没有分配物理内存或者系统页文件。同时,也把没有读权限的页面属性保护都过滤掉
    通过内存地址的信息过滤之后,我们就可以调用 ReadProcessMemory 函数把对应的内存区域读取到自己进程的缓冲区中
    接着,我们就可以匹配内存,搜索指定的内存,并获取制定进程内存地址
    然后,获取下一块内存区域的起始地址,继续重复上面3、4、5步操作,直到满足退出条件
    最后,我们就释放内存,并关闭进程句柄

    这样,就是先了内存的快速遍历搜索。
    编码实现// 搜索内存BOOL SearchMemory(DWORD dwProcessId, PVOID pSearchBuffer, DWORD dwSearchBufferSize){ // 根据PID, 打开进程获取进程句柄 HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId); if (NULL == hProcess) { ShowError("OpenProcess"); return FALSE; } // 获取进程加载基址 HMODULE hModule = NULL; ::EnumProcessModules(hProcess, &hModule, sizeof(HMODULE), NULL); // 把加载基址作为遍历内存的起始地址, 开始遍历 BYTE *pSearchAddress = (BYTE *)hModule; MEMORY_BASIC_INFORMATION mbi = {0}; DWORD dwRet = 0; BOOL bRet = FALSE; BYTE *pTemp = NULL; DWORD i = 0; BYTE *pBuf = NULL; while (TRUE) { // 查询地址空间中内存地址的信息 ::RtlZeroMemory(&mbi, sizeof(mbi)); dwRet = ::VirtualQueryEx(hProcess, pSearchAddress, &mbi, sizeof(mbi)); if (0 == dwRet) { break; } // 过滤内存空间, 根据内存的状态和保护属性进行过滤 if ((MEM_COMMIT == mbi.State) && (PAGE_READONLY == mbi.Protect || PAGE_READWRITE == mbi.Protect || PAGE_EXECUTE_READ == mbi.Protect || PAGE_EXECUTE_READWRITE == mbi.Protect)) { // 申请动态内存 pBuf = new BYTE[mbi.RegionSize]; ::RtlZeroMemory(pBuf, mbi.RegionSize); // 读取整块内存 bRet = ::ReadProcessMemory(hProcess, mbi.BaseAddress, pBuf, mbi.RegionSize, &dwRet); if (FALSE == bRet) { ShowError("ReadProcessMemory"); break; } // 匹配内存 for (i = 0; i < (mbi.RegionSize - dwSearchBufferSize); i++) { pTemp = (BYTE *)pBuf + i; if (RtlEqualMemory(pTemp, pSearchBuffer, dwSearchBufferSize)) { // 显示搜索到的地址 printf("0x%p\n", ((BYTE *)mbi.BaseAddress + i)); } } // 释放内存 delete[]pBuf; pBuf = NULL; } // 继续对下一块内存区域进行遍历 pSearchAddress = pSearchAddress + mbi.RegionSize; } // 释放内存, 关闭句柄 if (pBuf) { delete[]pBuf; pBuf = NULL; } ::CloseHandle(hProcess); return TRUE;}
    程序测试我们直接运行程序,对 520.exe 进程进行搜索,搜索值为 0x00905A4D 的地址都哪些,程序成功列举所有的地址。

    总结这个程序,通过以进程加载基址为搜索起始地址、判断地址内存空间信息过滤一些内存状态不是 MEM_COMMIT 以及保护属性没有读权限的内存区域,以此来缩小搜索的范围,提升搜索的效率。
    大家注意理解 VirtualQueryEx 函数以及 ReadProcessMemory 函数的参数使用方式,同时也要注意不同进程内存空间的转换。
    参考参考自《Windows黑客编程技术详解》一书
    3 留言 2018-12-20 12:16:28 奖励25点积分
  • 前后端接口规范

    前后端接口规范随着前后端分离越来越普遍, 后端接口规范也就越来越重要了. 一套良好的接口规范可以提升工作效率, 减少沟通障碍.
    通常我们都会采用 REST 方式来提供接口, 使用 JSON 来传输数据.



    名词
    含义




    前端
    Web前端, APP端, 桌面端等一切属于用户界面的这一层


    后端
    即服务器端, 指一切属于用户界面之下的这一层


    前后端接口
    前端与后端进行数据交互的统称, 也叫做数据接口, 属于一种远程调用, 一般指前端通过HTTP(ajax)请求获取到的数据或者执行的某项操作. 为确保前后端(工程师)的协作沟通, 一般由前端和后端一起来定义接口的规范, 规范的内容一般包含接口的地址, 接口的输入参数和输出的数据格式(结构), 最终由后端来实现这些规范, 为前端提供符合规范的接口



    [前端] -------- ^ | |前后端接口 | |-------- [后端]前后端接口协作流程在开发之前一定要先定义好接口规范, 至于接口应该由前端来定还是后端来定, 这个还得看公司的具体情况, 但一定要让前后端都确认无误, 特别是接口协商要点.
    以免出现前后端分离之后最容易出现的扯皮现象. 特别是当你碰到做事不主动(无责任感)的后端, 什么都要前端来催. 比如什么接口又缺了一个字段没有提供啦, 什么又少了一个接口啦, 等等诸如此类. 后端不去熟悉业务, 也不看界面原型和需求, 只管把接口做完, 任务完成就万事大吉了, 每天除了等前端通知哪里要修改, 自己就像没事人一样.
    所以说定好接口, 前后端一起来确认好接口是多么的重要, 不然你就等着干着急吧. 当然了, 想一次性完美地将所有接口都定义出来, 有点不太现实, 需要调整的情况在所难免, 所以还是希望后端能够主动一点, 前后端沟通的时候就轻松得多, 大家的效率就都提高了.
    准备环境
    安装 Node.js安装 puer-mock
    接口规范由前端(APP端)和后端一起协定接口规范的内容, 确定每一个接口的地址(URL), 输入(request)和输出(response), 必要的时候详细注释每一个字段的含义和数据类型.
    具体需要定义哪些接口, 可以按照下面的思路来整理

    资源接口: 系统涉及到哪些资源, 按照 RESTful 方式定义的细粒度接口操作接口: 页面涉及到哪些操作, 例如修改购物车中商品的数量, 更换优惠券等等, 也可以使用 RESTful 方式来定义页面接口: 页面涉及到太多接口, 如果是一个个地调用, 会需要很多次请求, 有可以影响到前端的性能和用户感知(特别是首屏的体验), 因此可能需要将这些接口的数据合并到一起, 作成一个聚合型接口提供给前端来使用
    接口协商要点
    接口必须返回统一的数据结构, 参考后端接口通用规范中接口返回的数据结构接口查询不到数据时, 即空数据的情况下返回给前端怎样的数据
    建议返回非 null 的对应数据类型初始值, 例如对象类型的返回空对象({}), 数组类型的返回空数组([]), 其他原始数据类型(string/number/boolean…)也使用对应的默认值这样可以减少前端很多琐碎的非空判断, 直接使用接口中的数据例如: result.fieldName如果 result 为 null, 可想而知会报错 Uncaught TypeError: Cannot read property 'fieldName' of null
    调用接口业务失败的常用错误码, 例如未授权时调用需要授权的接口返回 "status": 1接口需要登录时如何处理, 特别是同时涉及到 Web 端/微信端/App 端, 需要前端针对运行环境判断如何跳转到登录页面返回数据中图片 URL 是完整的还是部分的
    http://a.res.com/path/to/img.png 这就是完整的, 前端直接使用这个 URL/path/to/img.png 这就是部分的, 一般省略域名部分, 前端需要自己拼接后才能使用 'http://a.res.com' + '/path/to/img.png'
    返回数据中页面跳转的 URL 是给完整的还是部分的
    内部页面返回部分的, 或者只给ID, 由前端自己拼接, 例如只给出商品ID, 让前端自己拼接商品详情页的 URL外部页面返回完整的, 例如广告位要跳转去谷歌
    返回数据中日期的格式, 是使用时间戳还是格式化好的文字
    对于需要前端再次处理的日期值(例如根据日期计算倒计时), 可以使用时间戳(简单暴力), 例如: 1458885313711, 或者参考 Date.prototype.toJSON 提供 ISO 标准格式(例如需要考虑时区时)对于纯展示用的日期值, 推荐返回为格式化好的文字, 例如: 2017年1月1日
    对于大数字(例如 Java 的 long 类型), 返回给前端时需要设置为字符串类型, 否则 JavaScript 会发生溢出, 造成得到的数值错误
    例如: 返回 JSON 数据 {"id": 362909601374617692} 前端拿到的值却是: 362909601374617660
    分页参数和分页信息
    如何限制只返回 N 条数据(limit 参数)如何控制每页的数据条数(pageSize 参数)如何加载某一页的数据(page 参数)
    第一页是从 0 开始还是从 1 开始
    如何避免无限滚动加载可能出现的重复数据(采用 lastId 分页方式, 来避免传统分页方式的弊端)
    假设数据是按照新增时间倒序排列的首先加载 2 页的数据等了很久期间新增了很多数据再获取第 3 页数据此时就可能出现重复数据的情况, 因为新增的数据都排在最前面, 后面会接着已经加载过数据
    分页信息包含什么(total, page, pageSize)分页信息何时表明已经是最后一页了
    请求某页数据时返回的数据条数 < pageSize请求某页数据时返回的数据条数 = 0如果碰巧最后一页有 pageSize 条数据, 前端无法通过数据条数来判断已经处于最后一页了


    接口定义所有的接口定义在项目前端静态文件目录的 _mockserver.json 文件中, 启动 puer-mock 服务, 即可使用这些接口获得符合规范的假数据, 也可以查看接口文档.
    具体 puer-mock 的详细使用手册和 _mockserver.json 如何配置接口请参考 puer-mock 项目, 或者参考项目中已经配置好的其他接口.
    接口协作由于接口规范的定义和接口的实际实现是分开的两个部分, 而且涉及到多人协作, 因此在开发过程中可能出现接口规范与实现不同步, 最终造成实际的接口不符合规范的定义, 接口规范就会慢慢失去存在的意义.
    为了尽量避免这种问题, 后端在实现接口的过程中应该确保与接口规范保持一致, 一旦出现分歧, 必须同步修改接口规范, 尽可能保持沟通.
    接口文档(示例)
    后端接口通用规范接口地址和请求方式接口根路径 - Root Endpoint 推荐为: http://api.yourdomain.com 或者 http://yourdomain.com/api
    接口地址即接口的 URL, 定义时使用相对路径(即不用带上域名信息), 建议分模块来定义, 推荐 REST 风格, 例如

    GET /user/:id 表示获取用户信息POST /user 表示新增用户
    接口参数向接口传递参数时, 如果是少量参数可以作为 URL query string 追加到接口的 URL 中, 或者作为 Content-Type: application/x-www-form-urlencoded 放在请求体(body)中(即表单提交的方式)
    对于复杂的接口参数(例如嵌套了多层的数据结构), 推荐在 HTTP 请求体(body)中包含一个 JSON 字符串作为接口的参数, 并设置 Content-Type: application/json; charset=utf-8.
    例如
    查询 VIP 用户的接口
    POST /users?limit=10 HTTP/1.1Content-Type: application/json; charset=utf-8{ "name": "hanmeimei", "isVip": true}
    接口返回的数据结构返回的响应体类型推荐为 Content-Type: application/json; charset=utf-8, 返回的数据包含在 HTTP 响应体中, 是一个 JSON Object. 该 Object 可能包含 3 个字段 data, status, statusInfo
    HTTP/1.1 200 OKContent-Type: application/json; charset=utf-8{ "data": {}, "status": 0, "statusInfo": { "message": "给用户的提示信息", "detail": "用于排查错误的详细错误信息" }}



    字段名
    字段说明




    data
    业务数据;必须是任意 JSON 数据类型(number/string/boolean/object/array);推荐始终返回一个 object (即再包一层)以便于扩展字段;例如: 用户数据应该返回 {"user":{"name":"test"}}, 而不是直接为 {"name":"test"}


    status
    状态码:必须是 >= 0 的 JSON Number 整数.<ul><li>0 表示请求处理成功, 此时可以省略 status 字段, 省略时和为 0 时表示同一含义;非 0 表示发生错误时的错误码, 此时可以省略 data 字段, 并视情况输出 statusInfo 字段作为补充信息


    statusInfo
    状态信息:必须是任意 JSON 数据类型;推荐始终返回一个 object 包含 message 和 detail 字段;message 字段作为接口处理失败时, 给予用户的友好的提示信息, 即所有给用户的提示信息都统一由后端来处理;detail 字段用来放置接口处理失败时的详细错误信息. 只是为了方便排查错误, 前端无需使用;



    例如:

    接口处理成功时接口返回的数据
    { "data": "api result" "status": 0}
    接口处理失败时接口返回的数据
    { "status": 1, "statusInfo": { "message": "服务器正忙", "detail": { "exception": "java.util.List" } }}

    这样我们就可以非常容易地通过判断 status 来处理数据了:
    if (!response.status) { // status 为 0 或者没有 status 字段时表示接口成功返回了数据 console.log(response.data);} else { // 失败 console.error(response.status, response.statusInfo); // 统一由服务端返回给用户的提示信息 alert(response.statusInfo.message);}
    错误码规范: status 字段该如何取值采用前后端分离开发模式的项目越来越多, 前端负责调用后端的接口来展现界面, 如果有界面显示异常, 需要有快速方便的手段来排查线上错误和定位出职责范围
    综合了经验总结和行业实践, 最简单有效的手段是制定出一套统一的错误码规范, 协助多方人员来排查出接口的错误
    例如:

    用户发现错误, 可以截错误码的图, 就能够提供有效的信息帮助开发人员排查错误
    测试人员发现错误, 可以通过错误码, 快速定位是前端的问题还是后端接口的问题

    因此我们确定提示信息规范为: 当后端接口调用出错时, 接口提供一个用户可以理解的错误提示, 前端展示给用户错误提示和错误码, 给予用户反馈
    对于错误码的规范, 参考行业实践, 大致有两种方案

    做显性的类型区分, 快速定位错误的类别, 例如通过字母划分类型: A101, B131
    Standard ISO Response Codes
    固定位数, 设定区间(例如手机号码, 身份证号码)来划分不同的错误类型
    HTTP Status Code DefinitionsSystem Error Codes

    具体实践如下:

    错误码固定长度, 以区间来划分错误类型(例如 HTTP 的状态码)
    例如: 10404 表示 HTTP 请求 404 错误, 20000 表示 API 调用失败, 30000 代表业务错误, 31000 表示业务A错误, 32000 表示业务B错误
    错误码可不固定长度, 以首字母来划分错误类型, 可扩展性更好, 但实际运作还是需要划分区间
    例如: H404 表示 HTTP 请求 404 错误, A100 表示 API 调用失败, B100 表示业务A错误, B200 表示业务B错误

    关于错误分类的原则, 我们可以根据发送请求的最终状态来划分

    发送失败(即请求根本就没有发送出去)发送成功
    HTTP 异常状态(例如 404/500…)HTTP 正常状态(例如 200)
    接口调用成功接口调用失败(业务错误, 即接口规范中 status 非 0 的情况)


    最终规范错误码可不固定长度, 整体格式为: 字母+数字, 字母作为错误类型, 可扩展性更好, 数字建议划分区间来细分错误
    例如:

    A for API: API 调用失败(请求发送失败)的错误, 例如 A100 表示 URL 非法H for HTTP, HTTP 异常状态的错误, 例如 H404 表示 HTTP 请求404错误B for backend or business, 接口调用失败的错误, 例如 B100 业务A错误, B200 业务B错误C for Client: 客户端错误, 例如 C100 表示解析 JSON 失败
    发送 HTTP 请求 ┌───────────┴───────────┐ 发送成功¹ 发送失败² │ │ ┌──────────┴──────────┐ A 例如: A100 获得 HTTP 响应 无法获得 HTTP 响应³ │ │ HTTP status A 例如: A200 ┌──────────┴──────────┐ HTTP 成功(200-300) HTTP 异常 │ | {data, status, statusInfo} H${HTTP status} 例如: H404 ┌───────────┴───────────┐ 接口调用成功(status:0) 接口调用失败 ┌────────┴────────┐ |客户端处理出错 客户端处理正常 B${status}${statusInfo.message} 例如: B100 | C 例如: C100- 发送成功¹: 服务端收到了 HTTP 请求并返回了 HTTP 响应- 发送失败²: HTTP 请求没有发送出去(例如由于跨域被浏览器拦截不允许发送), 未到达服务端(即服务端没有收到这个 HTTP 请求)- 无法获得 HTTP 响应³: 服务端收到了请求并返回了响应, 但客户端由于某些原因无法获得 HTTP 响应, 例如请求的超时处理机制
    统一错误提示
    错误日志
    接口调用出错(${错误码}) ${HTTP 方法} ${HTTP URL} ${请求参数} ${请求选项} ${请求返回结果}例如: 接口调用出错(H404) GET https://domain.com {foo: bar} {option1: 'test'} {status: 404}
    给用户的提示消息(参考自 QQ 的错误提示消息)
    提示消息(错误码: xxx)提示消息和错误码之间用换行隔开错误码整块内容建议弱化使用灰色字例如

    规范实现: weapp-backend-api
    接口实现建议
    接口实现的大方向建议遵循 RESTful 风格HTTP 动词: 获取数据用 GET, 新增/修改/发送数据用 POST
    例如: 获取用户数据的接口用 GET, 修改用户数据的接口用 POST
    对于资源的操作类型, 使用 HTTP 动词来指定, 减少接口 URL 的数量
    例如: GET /contact 获取联系人, POST /contact 新增/修改联系人
    对外的 ID 字段使用字符串类型
    特别核心数据的 ID 字段, 不要使用自增的数字类型, 建议使用无规则的字符串类型(例如UUID), 避免核心数据被轻易抓取避免使用大数字类型(Long), 因为前端可能承载不了这个精度而溢出得到另外一个数值例如: Java 中的 Long 类型的数值: 362909601374617692, 作为 JSON 数据返回给前端, 前端拿到的值变成了 362909601374617660
    接口字段建议同时给出 ID 字段和用于显示字段, 前端提交数据时只提交 ID 字段
    例如: {"gender": 1, "genderText": "男"}
    图片的 URL 建议返回完整的 URL
    例如: {"pic": "https://domain.com/a.png"}
    时间字段建议同时返回时间戳的原始值(或 ISO 标准格式)和用于统一显示的格式化文本
    由后端接口集中控制各端的显示, 提供的原始值兼顾前端的自定义显示或者计算(例如倒计时)的需求避免每个端(例如H5/APP/小程序)都需要对时间做统一的格式化实现, 一旦需要调整, 需要各个端都调整一遍例如: {"createTime": 1543195480357, "createTimeText": "2018年11月26日"}
    统一分页的数据格式
    分页请求的参数和分页结果的数据结构

    注意
    Version跨域
    CORSJSONP
    避免中文乱码
    参考
    E-JSON数据传输标准有范云协作 让项目的协作姿势更有范儿


    交互阶段说明
    交互设计师根据产品方的需求对产品进行行为设计和界面设计的阶段,主要产出物为交互设计稿开发工程师需要做的事情是针对产品需求、交互设计稿中的内容进行技术评审,为产品方、交互设计师提供可行技术实现解决方案,对于多种不同解决方案需针对各种解决方案做分析说明,务必准确传达各种方案的优缺点,并根据需求给出建议方案
    系统设计说明
    各端开发工程师针对产品需求说明、交互设计稿开始设计系统架构、拆分子系统、划分子系统模块、协调端与端之间的接口规范,这个阶段各端根据实际情况输出若干系统设计说明书等文档除此之外更重要的是输出端与端之间通信的接口规范,而这个规范则可以借助 NEI 平台 来完成
    编码阶段说明
    开发工程师根据系统设计阶段的输出,用代码来实现这样的系统,包括技术方案的选型、项目框架的搭建、工具及环境的配置等其中有些工作可以借助于有范云协作提供的自动化工具 NEI-Toolkit 来完成,比如项目的初始结构代码、在 NEI平台 上定义好的接口规范等
    自测阶段说明
    各个端的工程师验证自己编写的代码的正确性,按角色不同,测试方式也有所有不同对于前端和移动端工程师来说,主要是需要测试各种可能的值会不会影响界面展示对于服务端工程师来说,主要是测试提供给客户端工程师使用的接口的正确性,对于不同的输入参数是否返回了预期的结果
    联调阶段说明
    主要是连测试环境进行测试对于前端和移动端工程师来说,主要是需要将本地容器提供的接口换成测试环境的接口
    测试阶段说明
    开发工程师开发完成后提测的过程,是产品上线前的最后环节测试工程师会对接 NEI 平台生成接口测试用例代码并集成到自动化测试平台运行,如果NEI平台的接口定义与实际提测的项目不符则此次提测失败,需由开发对照 NEI 平台检查接口实现情况,所以可以保证 NEI 平台上的接口定义始终与线上保持一致


    客户端API请求规范



    参数名
    说明




    imei
    国际移动设备身份码


    imsi
    客户端用户标识


    t
    TIMESTAMP,请求的时间戳


    appkey
    由服务端颁发的appkey


    sign
    md5签名串。为了减轻非法恶意请求,每次来自APP的请求都需要对请求参数进行签名以实现安全认证


    lng
    手机上获取的经度


    lat
    手机上获取的纬度


    ci
    渠道标识,格式为:channelId@应用名平台客户端版本,例如:1001@nzaom_android_1.0,其中1001表示应用宝




    GitHub API | 微博API | 淘宝开放平台 API
    JSend | JSON API | JSON Schema | JSON-RPC | JWT | OAuth




    Type
    Description
    Required Keys
    Optional Keys




    success
    All went well, and (usually) some data was returned.
    status, data



    fail
    There was a problem with the data submitted, or some pre-condition of the API call wasn’t satisfied
    status, data



    error
    An error occurred in processing the request, i.e. an exception was thrown
    status, message
    code, data




    Google JSON Style Guide
    最佳实践:更好的设计你的 REST API | RESTful API 设计指南 | Best Practices for Designing a Pragmatic RESTful API | HTTP API Design Guide | The RESTful Cookbook | RESTful API 编写指南
    Restlet Studio - Web IDE for API design | Swagger | ReDoc | RAML | API Blueprint
    1 留言 2021-04-27 09:47:26 奖励36点积分
  • ~“WRITE-BUG校园开源社区”交流群 ~

    为方便各位小伙伴的交流学习,平台创建了一个校园开源社区微信交流群
    群内的成员均是国内各大高校计算机相关专业的大学生(清华、人大、北邮… 硕士、博士都有哦!),快来扫码加入吧 ~(PS:本群组建的目的是方便各大高校计算机专业学生对课内专业技术交流和讨论!欢迎愿意交流、学习的同学加入,“伸手党”请自觉屏蔽!)

    若人数达到上限,不能直接扫码加群,可先加客服小姐姐微信,再拉您进群哦~
    (PS:客服小姐姐微信二维码如下↓,请备注:“WRITE-BUG技术共享平台”,否则不予通过哦)
    1 留言 2021-04-23 08:30:38
  • MySQL 数据库 SQL 语句的语法介绍

    一、MySQL——常用命令1.1 创建数据库(Create)mysql> create database db_name; -- 创建数据库mysql> show databases; -- 显示所有的数据库mysql> drop database db_name; -- 删除数据库mysql> use db_name; -- 选择数据库mysql> create table tb_name (字段名 varchar(20), 字段名 char(1)); -- 创建数据表模板mysql> show tables; -- 显示数据表mysql> desc tb_name; -- 显示表结构mysql> drop table tb_name; -- 删除表
    例如:创建学生表
    create table Student( Sno char(10) primary key, Sname char(20) unique, Ssex char(2), Sage smallint, Sdept char(20));
    1.2 插入数据(Insert)insert into 语句用于向表格中插入新的行:
    /*第一种形式无需指定要插入数据的列名,只需提供被插入的值即可:*/mysql> insert into tb_name values (value1,value2,value3,...);/*第二种形式需要指定列名及被插入的值:*/mysql> insert into tb_name (column1,column2,column3,...) values (value1,value2,value3,...);
    例如:插入数据
    mysql> insert into Student values ( 20180001,张三,男,20,CS);mysql> insert into Student values ( 20180002,李四,男,19,CS);mysql> insert into Student (Sno,Sname,Ssex,Sage,Sdept) values ( 20180003,王五,男,18,MA);mysql> insert into Student (Sno,Sname,Ssex,Sage,Sdept) values ( 20180004,赵六,男,20,IS);
    1.3 查询数据(Select)select语句除了可以查看数据库中的表格和视图的信息外,还可以查看 SQL Server的系统信息、复制、创建数据表。其查询功能强大,是SQL语言的灵魂语句,也是SQL中使用频率最高的语句。
    基本select语句:一个基本的select语句可分解成三个部分:查找什么数据(select)、从哪里查找(from)、查找的条件是什么(where)。
    select 语句的一般格式如下:
    select <目标列表达式列表>[into 新表名]from 表名或视图名[where <条件>][group by <分组表达式>][having <条件>][order by <排序表达式>[ASC|DESC]]
    1.3.1 查询指定的列1.查询表中所有列
    在select语句指定列的位置上使用*号时,表示查询表的所有列。
    select * from tb_name
    2.查询表中指定的列
    查询多列时,列名之间要用逗号隔开。
    select tb_name.<字符型字段>,<字符型字段>... from tb_name;
    3.指定查询结果中的列标题
    通过指定列标题(也叫列别名)可使输出结果更容易被人理解。

    指定列标题时,可在列名之后使用AS子句;也可使用:列别名=<表达式>的形式指定列标题
    AS子句的格式为:列名或计算表达式 [AS] 列标题

    select <字符型字段> as 列标题1,<字符型字段> as 列标题2, <字符型字段> as 列标题3 from bt_name;
    4.查询经过计算的列(即表达式的值)
    使用select对列进行查询时,不仅可以直接以列的原始值作为结果,而且还可以将列值进行计算后所得值作为查询结果,即select子句可以查询表达式的值,表达式可由列名、常量及算术运算符组成。
    查询结果计算列显示“无列名”,一般要给计算列加列标题。
    其中:表达式中可以使用的运算符有:加+、减-、乘*、除/、取余% 。
    select <字符型字段>,<字符型字段>,列标题 = <字符型字段> * n from tb_name;
    1.3.2 选择行:选择表中的部分行或全部行作为查询的结果 select [all|distinct] [top n[percent]]<目标列表达式列表> from 表名
    1. 消除查询结果中的重复行
    对于关系数据库来说,表中的每一行都必须是不同的(即无重复行)。但当对表进行查询时若只选择其中的某些列,查询结果中就可能会出现重复行。
    在select语句中使用distinct关键字可以消除结果集中的重复行。
    select distinct <字符型字段>[,<字符型字段>,...] from tb_name;
    2. 限制查询结果中的返回行数
    使用top选项可限制查询结果的返回行数,即返回指定个数的记录数。
    其中:n是一个正整数,表示返回查询结果集的前n行;若带percent关键字,则表示返回结果集的前n%行。
    celect top n from tb_name; /*查询前 n 的数据*/celect top n percent from tb_name; /*查询前 n% tb_name的数据*/
    1.3.3 查询满足条件的行: 用where子句实现条件查询通过where子句实现,该子句必须紧跟在From子句之后
    select [all|distinct] [top n[percent]]<目标列表达式列表> from 表名 where <条件>;



    运算符
    运算符标识




    比较运算符
    <=,<,=,>,>=,!=,<>,!>,!<


    范围运算符
    between… and,not between… and


    列举运算符
    in,not in


    模糊匹配运算符
    like,not like


    空值运算符
    is null,is not null


    逻辑运算符
    and,or,not



    说明:在查询条件中可使用以下运算符或表达式
    1.使用比较运算符
    select * from tb_name where <字符型字段> >=n;
    2.指定范围
    用于指定范围的关键字有两个:between…and和 not between…and。
    select * from tb_name where [not] between <表达式1> and <表达式2>;
    其中:between关键字之后的是范围的下限(即低值),and关键字之后的是范围的上限(即高值)。
    用于查找字段值在(或不在)指定范围的行。
    3.使用列举
    使用in关键字可以指定一个值的集合,集合中列出所有可能的值,当表达式的值与集合中的任一元素个匹配时,即返回true,否则返回false。
    select * from tb_name where <字符型字段> [not] in(值1,值2,...,值n);
    4.使用通配符进行模糊查询
    可用like 子句进行字符串的模糊匹配查询,like子句将返回逻辑值(true或False)。
    like子句的格式:
    select * from tb_name where <字符型字段> [not] like <匹配串>;
    其含义是:查找指定字段值与匹配串相匹配的记录。匹配串中通常含有通配符%和_(下划线)。
    其中: %:代表任意长度(包括0)的字符串
    5.使用null的查询
    当需要判定一个表达式的值是否为空值时,使用 is null关键字。
    当不使用not时,若表达式的值为空值,则返回true,否则返回false;当使用not时,结果刚好相反。
    select * from tb_name where <字符型字段> is [not] null;
    6.多重条件查询(使用逻辑运算符)
    逻辑运算符and(与:两个条件都要满足)和or(或:满足其中一个条件即可)可用来联接多个查询条件。and的优先级高于or,但若使用括号可以改变优先级。
    select * from tb_name where <字符型字段> = 'volues' and <字符型字段> > n;
    1.3.4 对查询结果排序order by子句可用于对查询结果按照一个或多个字段的值(或表达式的值)进行升序(ASC)或降序(DESC)排列,默认为升序。
    order by {排序表达式[ASC|DESC]}[,...n];
    其中:排序表达式既可以是单个的一个字段,也可以是由字段、函数、常量等组成的表达式,或一个正整数。
    模板:
    select * from tb_name order by <排序表达式> <排序方法>;
    1.3.5 使用统计函数(又称集函数,聚合函数)在对表进行检索时,经常需要对结果进行计算或统计,T-SQL提供了一些统计函数(也称集函数或聚合函数),用来增强检索功能。统计函数用于计算表中的数据,即利用这些函数对一组数据进行计算,并返回单一的值。
    常用统计函数表



    函数名
    功能




    AVG
    求平均值


    count
    求记录个数,返回int类型整数


    max
    求最大值


    min
    求最小值


    sum
    求和



    1. SUM和AVG
    功能:求指定的数值型表达式的和或平均值。
    select avg(<字符型字段>) as 平均数,sum(<字符型字段>) as 总数 from tb_name where <字符型字段> ='字符串';
    2. Max和Min
    功能:求指定表达式的最大值或最小值。
    select max(<字符型字段>) as 最大值,min(<字符型字段>) as 最小值 from tb_name;
    3. count
    该函数有两种格式:count(*)和count([all]|[distinct] 字段名),为避免出错,查询记录个数一般使用count(*),而查询某字段有几种取值用count(distinct 字段名)。

    (1) count(*)功能:统计记录总数
    select count(*) as 总数 from tb_name;
    (2) count([all]|[distinct] 字段名)功能:统计指定字段值不为空的记录个数,字段的数据类型可以是text、image、ntext、uniqueidentifier之外的任何类型
    select count(<字符型字段>) as 总数 from tb_name;

    1.3.6 对查询结果分组group by子句用于将查询结果表按某一列或多列值进行分组,列值相等的为一组,每组统计出一个结果。该子句常与统计函数一起使用进行分组统计。
    group by 分组字段[,...n][having <条件表达式>];
    1.在使用group by子句后
    select列表中只能包含:group by子句中所指定的分组字段及统计函数。
    2.having子句的用法
    having子句必须与group by 子句配合使用,用于对分组后的结果进行筛选(筛选条件中常含有统计函数)。
    3. 分组查询时不含统计函数的条件
    通常使用where子句;含有统计函数的条件,则只能用having子句。
    select <字符型字段>,count(*) as 列标题 from tb_name where <字符型字段>='字符串' group by <字符型字段>;
    4、修改数据(Update)
    Update 语句用于修改表中的数据。
    update tb_name set 列名称 = 新值 where 列名称 = 某值;
    5、删除数据(Delete)
    删除单行
    delete from tb_name where 列名称 = 某值;
    删除所有行,可以在不删除表的情况下删除所有的行。这意味着表的结构、属性和索引都是完整的:
    delete * from tb_name 或 delete from tb_name;
    二、MySQL——alter命令alter add命令用来增加表的字段。
    alter add命令格式:alter table 表名 add字段 类型 其他;
    例如,在表MyClass中添加了一个字段passtest,类型为int(4),默认值为0:
    mysql> alter table MyClass add passtest int(4) default '0';
    添加两个字段:
    mysql> alter table Person add age int,add address varchar(11);
    删除两个字段:
    mysql> alter table Person drop column age,drop column address;
    修改字段的注释:
    mysql> alter table `student` modify column `id` comment '学号';
    加索引
    mysql> alter table 表名 add index 索引名 (字段名1[,字段名2 …]);
    mysql> alter table employee add index emp_name (name);
    加主关键字的索引
    mysql> alter table 表名 add primary key (字段名);
    mysql> alter table employee add primary key(id);
    加唯一限制条件的索引
    mysql> alter table 表名 add unique 索引名 (字段名);
    mysql> alter table employee add unique emp_name2(cardnumber);
    删除某个索引
    mysql> alter table 表名 drop index 索引名;
    mysql>alter table employee drop index emp_name;
    添加字段
    mysql> ALTER TABLE table_name ADD field_name field_type;
    修改原字段名称及类型
    mysql> ALTER TABLE table_name CHANGE old_field_name new_field_name field_type;
    删除字段
    MySQL ALTER TABLE table_name DROP field_name;
    三、MySQL — 应用学生-课程数据库:

    学生表:Student(Sno,Sname,Ssex,Sage,Sdept)
    课程表:Course(Cno,Cname,Cpno,Ccredit)
    学生选课表:SC(Sno,Cno,Grade)

    3.1 建立一个“学生”表Studentcreate table Student( Sno char(9) peimary key, /*列级完整性约束条件,Sno是主码*/ Sname char(20) unique, /* Sname取唯一值*/ Ssex char(2), Sage smallint, Sdept char(20));
    3.2 建立一个“课程”表Coursecreate table Course( Sno char(4) primary key, /*列级完整性约束条件,Cname不能取空值*/ Sname char(40) not null, /*Cpno的含义是先修课*/ Cpno char(4) Ccredit smallint, foreign key (Cpnoo) references Course(Cno) /*表级完整性约束条件,Cpno是外码,被参照表是Course,被参照列是Cno*/);
    3.3 建立学生选课表SCcreate table SC( Sno char(9), Cno char(4), Grade smallint, frimary key (Sno,Cno), /*主码由两个属性构成,必须作为表级完整性进行定义*/ foreign key (Sno) references Student(Sno), /*表级完整性约束条件,Sno是外码,被参照表是Student*/ foreign key (Cno) references Course(Cno) /*表级完整性约束条件,Cno是外码,被参照表是Course */);
    0 留言 2021-04-26 09:03:56 奖励50点积分
  • 2020 版 IDEA 汉化与切换为英化


    害,容我先吐槽吐槽,我这大半天时间用来解决 IDEA 2017 版本占 CPU 高达 80% 的问题,还又时不时来个未响应。然鹅–就是各种办法都试了,结果一打开,还是原样子。没办法,我卸载了 2017 版本,直接安装了 2020 版本。不过我安装的是汉化过后的版本,但是还是英文好,所以又查了半天。
    咳咳,进入正题 ~~

    IDEA 英汉切换英切汉
    将 resources_zh_CN_IntelliJIDEA_2020_r1.jar(我会上传的哈)复制,然后打开 IntelliJ IDEA 2020.1\lib 文件,在空白处粘贴,重新打开IDEA即可。


    IDEA汉切英
    那更简单啦,直接去 IntelliJ IDEA 2020.1\lib 文件下,删除 resources_zh_CN_IntelliJIDEA_2020_r1.jar,然后重新打开 IDEA 就好啦。

    这是网盘链接哈链接:https://pan.baidu.com/s/1aoS_Tok37CZOiYQQvPN2Ag提取码:or4u
    0 留言 2021-04-23 08:21:26 奖励40点积分
  • mysql 之死锁问题

    一、死锁死锁是指两个或两个以上的事务在执行过程中,因锁资源而造成的一种互相等待的现象。若无外力作用,事物都将无法推进下去。
    解决随所问题的方法

    超时,即两个事务互相等待时,当一个等待时间超过设置的某一阈值时,其中一个事务进行回滚,另一个等待的事务就能继续进行
    wait-for graph(等待图)的方式进行死锁检测

    二、wait-for graphwait-for graph 要求数据库保存两种信息:锁的信息链表和事务等待链表。通过这两类链表可一构造出一张图。若这个图中存在回路,就代表存在死锁。
    举个栗子:下图所示,事务 t1 指向 t2 表示

    事务t1等待事务t2所占用的资源
    事务t1最终等待事务t2所占用的资源,也就是事物之间在等待相同资源,而事务t1发生在事务t2的后面


    在 Transaction Wait Lists 中可以看到共有 4 个事务 t1、t2、t3、t4,故在 wait-for graph 中应该有 4 个节点。而事务 t2 对 row1 占用 X 锁,事务 t1 对 row2 占用 S 锁。事务 t1 需要等待事务 t2 中 row1 的资源,因此在 wait-for graph 中有条从节点 t1 指向节点 t2。事务 t2 需要等待事务 t1、t4 所占用的 row2 对象,故而存在节点 t2 到节点 t1、t4 的边。同样,存在节点 t3 到节点 t1、t2、t4 的边,因此最终的 wait-for graph 如图所示:

    从图中可以发现存在回路 (t1, t2),因此存在死锁。通常来说 InnoDB 存储引擎选择回滚 undo 量最小的事务。
    wait-for graph 的死锁检测通常采用深度优先的算法实现。
    三、死锁的示例创建测试表 t
    CREATE TABLE t (a INT PRIMARY KEY);INSERT INTO t SELECT 1;INSERT INTO t SELECT 2;INSERT INTO t SELECT 4;INSERT INTO t SELECT 5;
    1. AB-BA 死锁,即 A 等待 B,B 在等待 A

    可以看到,发生了死锁。死锁的原因是会话 A 和 B 的资源在互相等待。大多数的死锁 InnoDB 存储引擎本身可以侦测到,并不需要认为进行干预。
    2. 当事务 A 持有了带插入记录的下一个纪录的 X 锁,但是在等待队列中存在一个 S 锁的请求,则可能会发生死锁

    可以看到,会话 A 中已经对记录 4 持有了 X 锁,但是会话 A 中插入记录 3 时会导致死锁发生。这个问题的产生是由于会话 B 中请求记录 4 的 S 锁而发生等待,但之前请求的锁对于主键值记录 1、2 都已经成功,若在事件点 5 能插入记录,那么会话 B 在获得记录 4 持有的 S 锁后,还需要向后获取记录 3 的记录,这样就显得有点不合理。因此 InnoDB 存储引擎在记录 5 主动选择死锁,而回滚得是 undo log 记录大的事务,这与 AB-BA 死锁的处理方式有所不同。
    0 留言 2021-04-23 08:57:43 奖励46点积分
显示 0 到 15 ,共 15 条
eject