阅读视图

发现新文章,点击刷新页面。

MyBatis-plus小课堂:@Select的参数传递

我正在参加「掘金·启航计划」

引言

  • @Select的参数传递
  • wrapper自定义sql: 使用条件构造器作为参数

I 预备知识

1.1 JDBC

Java Database Connectivity):一种用于执行 SQL 语句的 Java API,它由一组用 Java 编程语言编写的类和接口组成,JDBC 可做三件事:

  • 与数据库建立连接,
  • 发送 SQL 语句,
  • 处理结果。

MyBatis和JDBC最显著的区别是SQL语句配置化,通过xml文件定义SQL语句。

MyBatis is a first class persistence framework with support for custom SQL, stored procedures and advanced mappings. MyBatis eliminates almost all of the JDBC code and manual setting of parameters and retrieval of results. MyBatis can use simple XML or Annotations for configuration and map primitives, Map interfaces and Java POJOs (Plain Old Java Objects) to database records.

1.2 MyBatis的xml配置文件可用自己定义的数据类型

@Select(“select * from Type where id = #{id, jdbcType=BIGINT} and code= #{code, jdbcType=VARCHAR}”)  
Type selectTypeById(@Param(“id”) Long id, @Param(“code”) String code);

Associated JDBC type can be specified by two means:

  • Adding a jdbcType attribute to the typeHandler element (for example: jdbcType=“VARCHAR”).
 <select id='getMeetingnoByCompanyid' parameterType="java.lang.Integer"
        resultType="java.lang.String">
        select a.meetingno
        from xxx a
        where a.companyid = #{companyid, jdbcType=BIGINT}
</select>



  • Adding a @MappedJdbcTypes annotation to your TypeHandler class specifying the list of JDBC types to associate it with. This annotation will be ignored if the jdbcType attribute as also been specified.

1.3 MyBatis JdbcType 与Oracle、MySql数据类型对应关系

Mybatis JdbcType Oracle MySql
JdbcType ARRAY
JdbcType BIGINT BIGINT
JdbcType BINARY
JdbcType BIT BIT
JdbcType BLOB BLOB TEXT
JdbcType BOOLEAN
JdbcType CHAR CHAR CHAR
JdbcType CLOB CLOB CLOB–>修改为TEXT
JdbcType CURSOR
JdbcType DATE DATE DATE
JdbcType DECIMAL DECIMAL DECIMAL
JdbcType DOUBLE NUMBER DOUBLE
JdbcType FLOAT FLOAT FLOAT
JdbcType INTEGER INTEGER INTEGER
JdbcType LONGVARBINARY
JdbcType LONGVARCHAR LONG VARCHAR
JdbcType NCHAR NCHAR
JdbcType NCLOB NCLOB
JdbcType NULL
JdbcType NUMERIC NUMERIC/NUMBER NUMERIC/
JdbcType NVARCHAR
JdbcType OTHER
JdbcType REAL REAL REAL
JdbcType SMALLINT SMALLINT SMALLINT
JdbcType STRUCT
JdbcType TIME TIME
JdbcType TIMESTAMP TIMESTAMP TIMESTAMP/DATETIME
JdbcType TINYINT TINYINT
JdbcType UNDEFINED
JdbcType VARBINARY
JdbcType VARCHAR VARCHAR VARCHAR

1.4 Mybatis JdbcType

www.mybatis.org/mybatis-3/a…

public enum JdbcType {
    ARRAY(2003),
    BIT(-7),
    TINYINT(-6),
    SMALLINT(5),
    INTEGER(4),
    BIGINT(-5),
    FLOAT(6),
    REAL(7),
    DOUBLE(8),
    NUMERIC(2),
    DECIMAL(3),
    CHAR(1),
    VARCHAR(12),
    LONGVARCHAR(-1),
    DATE(91),
    TIME(92),
    TIMESTAMP(93),
    BINARY(-2),
    VARBINARY(-3),
    LONGVARBINARY(-4),
    NULL(0),
    OTHER(1111),
    BLOB(2004),
    CLOB(2005),
    BOOLEAN(16),
    CURSOR(-10),
    UNDEFINED(-2147482648),
    NVARCHAR(-9),
    NCHAR(-15),
    NCLOB(2011),
    STRUCT(2002),
    JAVA_OBJECT(2000),
    DISTINCT(2001),
    REF(2006),
    DATALINK(70),
    ROWID(-8),
    LONGNVARCHAR(-16),
    SQLXML(2009),
    DATETIMEOFFSET(-155),
    TIME_WITH_TIMEZONE(2013),
    TIMESTAMP_WITH_TIMEZONE(2014);

    public final int typeCode;
    private static final Map<Integer, JdbcType> CODE_MAP = new ConcurrentHashMap(100, 1.0F);

    private JdbcType(int code) {
        this.typeCode = code;
    }

    public static JdbcType valueOf(int code) {
        return (JdbcType)CODE_MAP.get(code);
    }

    static {
        JdbcType[] var0 = values();
        int var1 = var0.length;

        for(int var2 = 0; var2 < var1; ++var2) {
            JdbcType type = var0[var2];
            CODE_MAP.put(type.typeCode, type);
        }

    }
}

II @Select的参数传递

2.1 普通类型传递

案例1

@Select(“select * from Type where id = #{id, jdbcType=BIGINT} and code= #{code, jdbcType=VARCHAR}”)  
Type selectTypeById(@Param(“id”) Long id, @Param(“code”) String code);

案例2

    @Select("select an.* from sys_announcement an  ${ew.customSqlSegment} and an.id not in (select  a.id from sys_announcement a  inner join sys_announcement_read r on r.announcement_id=a.id where r.user_id = #{user_id,jdbcType=BIGINT})  order by an.create_time desc")
    List<SysAnnouncement> listUnRead(@Param(Constants.WRAPPER) LambdaQueryWrapper<SysAnnouncement> lambda,@Param("user_id") Long userId);

使用

    List<SysAnnouncement> listUnRead(@Param(Constants.WRAPPER) LambdaQueryWrapper<SysAnnouncement> lambda,@Param("user_id") Long userId);

2.2 使用条件构造器作为参数

  1. mapper.java/Service.java定义接口方法
  2. 添加 @Param(Constants.WRAPPER)形参和${ew.customSqlSegment}值参

${ew.customSqlSegment}值参 以where关键字开头,@Select语句如果有其他查询条件,必须放在${ew.customSqlSegment}之后。

    @Select("select  a.* from sys_announcement a  inner join t_sys_announcement_read r on r.announcement_id=a.id  ${ew.customSqlSegment} order by a.create_time desc")
    List<SysAnnouncement> listRead(@Param(Constants.WRAPPER) LambdaQueryWrapper<SysAnnouncement> lambda);

使用

        LambdaQueryWrapper<SysAnnouncement> lambda = new LambdaQueryWrapper<>();
        lambda.apply(input.getSendChannel() != null, "an.send_channel like {0}", "%"+input.getSendChannel()+"%");
        lambda.apply(input.getStartTime() != null, "an.create_time > {0}", input.getStartTime());
        lambda.apply(input.getEndTime() != null, "an.create_time > {0}", input.getEndTime());
        lambda.apply(input.getState() != null, "an.state = {0}", input.getState());
                list = tSysAnnouncementService.listUnRead(lambda, LoginHelper.getUserId());

2.3 案例

mybatis-plus小课堂:多表查询【案例篇】(apply 拼接 in SQL,来查询从表某个范围内的数据)kunnan.blog.csdn.net/article/det…

数据库小技能:mysql的DB规范

我正在参加「掘金·启航计划」

I DB 规范

MySQL 支持的类型大致可以分为:数值日期/时间字符串(字符)类型。

1.1 字段名类型

字段名 类型 备注
id bigint 主键雪花ID
create_time datetime 创建时间
create_id bigint 创建用户id
update_time datetime 修改时间
update_id bigint 修改用户id
数量类型 int
金额类型 decimal(10,2)数据库存元
枚举类型 varchar(n)大写
布尔类型 varchar(n)是1否0
字符串类型 varchar(n)
费率 decimal(10,4)费率可能2个小数位以上要给4个小数位
alter table t_voucher_coupon
add create_time datetime null comment '创建时间';
alter table t_voucher_coupon
add update_time datetime null comment '更新时间';
alter table t_voucher_coupon
add create_id bigint null comment '创建用户id';
alter table t_voucher_coupon
add update_id bigint null comment '修改用户id';
alter table t_equipment_position add create_name varchar(100) null comment '创建人';
alter table t_equipment_position add update_name varchar(100) null comment '操作人';

TMapper.xml

        <result column="create_name" property="createName" />
        <result column="update_name" property="updateName" />

entity

    @ApiModelProperty(value = "创建人")
    @TableField("create_name")
    private String createName;
    
    @ApiModelProperty(value = "操作人")
    @TableField("update_name")
    private String updateName;

代理商信息

alter table t_voucher_merchant
add facilitator_id bigint not null comment '代理商id(直属)';

alter table t_voucher_merchant
add facilitator_name varchar(500) null comment '代理商名称(直属)';

alter table t_voucher_merchant
add facilitator_top_id bigint null comment '一级代理商id';

alter table t_voucher_merchant
add facilitator_top_name varchar(500) null comment '一级代理商名称';


1.2 字段命名

同一个字段在各个表做到统一

XXX_no XXX编号
XXX_id xxxid
company_id 企业号
merchant_id 商户号
merchant_code 第三方商户号
merchant_name 商户名称
facilitator_id 代理商id(直属)
facilitator_name 代理商名称(直属)
facilitator_top_id 一级代理商id
facilitator_top_name 一级代理商名称
trade_no 交易流水号
trade_no3 三方交易流水号
out_trade_no 下游交易流水号
office_id 微信支付宝交易流水号
sref_no 参考号
remark 说明,备注
tags_type 对象类型(PT:平台,JG:机构,DLS:代理商,QY:企业,SH:商户)
terminal_code 交易终端号
state 状态
base_rate 基础费率
extra_rate 附加费率
base_fee 基础手续费
extra_fee 附加手续费
province 省份id
province_name 省份名称

1.3 表名模块前缀

trans_ 交易
report_trans_ 交易报表
mer_ 商户
fac_ 服务商
risk_ 风控
sys_ 系统
policy_ 政策
tms_ 终端

II 简单的excel 导入导出

用阿里的easyexcel

see also

数据库小技能:字段处理(BigDecimal 元转分,与0比较大小,bigDecimal格式化注解)

我正在参加「掘金·启航计划」

引言

数据库小技能:mysql的DB规范ianhttps://blog.csdn.net/z929118967/article/details/129803014

I 字段前处理:检测

@JsonFormat注解:主要是后台到前台的时间格式的转换

@DataFormat注解:主要是前后到后台的时间格式的转换

    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
    private Date blog_date;// 博客发布日期

1.1 格式校验

多个代理商编号时,请用英文逗号,分隔

try {
            facilitatorIds4input= Arrays.asList(input.getFacilitatorIds().split(","))
                    .stream().map(s -> Long.parseLong(s)).collect(Collectors.toList());
        } catch (Exception e) {
            throw CommonException.create(ServerResponse.createByError("代理商id,多个代理商编号时,请用英文逗号,分隔"));
        }

使用注解进行格式校验

public class ApiPubSigPayRequest extends ApiBaseRequest{
    @NotBlank(message = "订单日期不能为空")
    @Pattern(regexp = "(^[0-9]*$)",message = "订单日期格式有误")
    private String orderDate;//订单日期 yyyyMMddHHmmss

    @DecimalMin(value = "1" , message = "订单金额必须大于1分")
    private Long payAmt;//订单金额 分

    @DecimalMin(value = "1" , message = "订单金额必须大于1分")
    private Long totAmt;//总金额 分

    @NotBlank(message = "交易码不能为空")
    private String transCode;//交易码

    @NotBlank(message = "微信/支付宝小程序appId不能为空")
    private String subAppid;

    @NotBlank(message = "微信/支付宝用户id不能为空")
    private String subOpenid;

    private String subject;//订单标题

}

1.2 检测唯一性

如果唯一性的其中一个字段时可选的,统一保存""空串,便于比较。

        if(position.getEquipmentSn() ==null){// 统一保存""空串,便于比较
            position.setEquipmentSn("");
        }
        if (!(position.getMerchantId().equals(input.getMerchantId()) && position.getEquipmentSn().equals(input.getEquipmentSn()))) {
            // 检测唯一性
            if (getPosition(input.getMerchantId(), input.getEquipmentSn()) != null) {
                throw CommonException.create(ServerResponse.createByError("规则已经存在"));
            }
        }

1.3 日期范围查询

请求参数LocalDate

    @ApiModelProperty(value = "开始时间")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate startTime;

    @ApiModelProperty(value = "结束时间")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate endTime;

LocalDate 转LocalDateTime

        if (input.getStartTime() != null) {
            lambda.apply("create_time > {0}",input.getStartTime().atStartOfDay());
        }
        if (input.getEndTime() != null) {
            lambda.apply("create_time < {0}",input.getEndTime().plusDays(1).atStartOfDay());
        }

II 字段后处理

2.1 List转换成用逗号分割的字符串

            String exidsStr = exFacilitator.stream().map(i->i.getFacilitatorId().toString()).collect(Collectors.joining(","));

2.2 按照枚举分组数据

        List<RiskFacilitatorNetLimitRateDto> rates = new ArrayList<>();
        // 按照结算周期枚举分组
        List<ESettlementCycle> cycles = Arrays.asList(ESettlementCycle.class.getEnumConstants());
        cycles.forEach(item -> {
            RiskFacilitatorNetLimitRateDto dto = new RiskFacilitatorNetLimitRateDto();
            dto.setSettlementCycle(item);
            dto.setRateList(rateList.stream().filter(i -> i.getSettlementCycle() == item).collect(Collectors.toList()));
            rates.add(dto);
        });
        return rates;

III BigDecimal处理

3.1 BigDecimal与0比较大小

如果0表示不限制 ,推微信总上限。

        if(activity.getMaxAmountByDay().compareTo(BigDecimal.ZERO)==0){//BigDecimal与0比较大小 -1 小于0,1 大于0
            stockUseRule.setMaxAmountByDay(stockUseRule.getMaxAmount());
        }else{
            stockUseRule.setMaxAmountByDay(activity.getMaxAmountByDay().multiply(new BigDecimal(100)).longValue());
        }

3.2 BigDecimal 元转分

        stockUseRule.setMaxAmount((activity.getMaxAmount().multiply(new BigDecimal(100)).longValue()));//乘以100(单位:分)

3.3 自定义DecimalFormat注解

新增bigDecimal格式化注解CustomDecimalFormat,默认格式#.###,可用于费率字段。

blog.csdn.net/z929118967/…

import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
//Jackson的注解继承注解
 @JacksonAnnotationsInside
// 为该注解指定一个自定义的json序列化解析器
@JsonSerialize(using = DecimalFormatSerialize.class)
public @interface CustomDecimalFormat {
    //0 整数或小数部分少位数补0
    //# 开头的0和末尾的0不显示,只要有可能就把数字拉上这个位置。
//    % 乘100然后加%
//     .小数分隔符
//     ,:整数分隔符

    String value() default "#.###";
    boolean showDecimalSeparator() default true;
    boolean zeroDisplay() default false;
}

iOS小技能:UITableView的适配 (iOS10/iOS14/iOS16.0)

本文正在参加「金石计划」

引言

如果按照开发规范写代码,不会存在关于UITableView的适配问题。

  • 如果按照规范使用UITableViewHeaderFooterView,就不会存在iOS16横竖屏切换场景的页脚问题。
  • 如果按照规范添加UITableViewCell的子试图到UITableViewCellContentView,就不会存在iOS14被遮挡的问题。
  • 如果将tableView:viewForFooterInSection: 和tableView:heightForFooterInSection:方法都注释掉,就不会存在iOS10即使heightForFooterInSection高度返回0 也会显示视图的问题。

I iOS16.0 横竖屏切换适配

调用完转屏方法以后,view需要重新更新frame

1.1 获取当前屏幕横竖屏状态

  • (UIInterfaceOrientation)interfaceOrientation {// 获取当前屏幕横竖屏状态 if (@available(iOS 13.0, *)) { return [UIApplication sharedApplication].windows.firstObject.windowScene.interfaceOrientation; } return [[UIApplication sharedApplication] statusBarOrientation]; }

1.2 iOS16.0调完转屏方法后,需要重新更新view的frame

问题:从电子签名界面回来之后,页脚的frame不正确

- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
        UIButton *nextBtn = [ControlManager getButtonWithFrame:CGRectMake(KWratio(30), KWratio(25), kWidth - KWratio(60), KWratio(45)) fontSize:displayFontSize(15.0f) title:@"提交" titleColor:[UIColor whiteColor] image:[UIImage new] backgroundColor:BASE_RED_COLOR cornerRadius:KWratio(5)];
return xxx;
}

原因:iOS16.0调用完转屏方法以后,如果不是使用自动布局,view需要重新更新frame。

解决方法:自定义页脚和页眉(UITableViewHeaderFooterView代替 titleForHeaderInSection)

1.3 自定义页脚

UITableViewHeaderFooterView代替 titleForHeaderInSection

blog.csdn.net/z929118967/…

  1. 注册FooterView
        [_tableView registerClass:[CRMNextBtnHeaderFooterView class] forHeaderFooterViewReuseIdentifier:@"CRMNextBtnHeaderFooterView"];
  1. 返回FooterView
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
    CRMNextBtnHeaderFooterView *footerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:@"CRMNextBtnHeaderFooterView"];

//    footerView.models = self.viewModel.passwordRuleDescModel;

    return footerView;
}
  1. 实现FooterView

@interface CRMNextBtnHeaderFooterView : UITableViewHeaderFooterView

@property (nonatomic , weak) UIButton *nextBtn;

@property (nonatomic , weak) UIButton *cancelBtn;


@end

II 解决UITableViewCell兼容问题(iOS14适配)

kunnan.blog.csdn.net/article/det…

2.1 问题分析

iOS14 UITableViewCell的子试图不能点击或者滑动等手势响应问题,发现有问题的cell基本都是直接添加到Cell的,通过Xcode自带的DebugViewHierarchy视图分析发现问题的原因是:被系统自带的UITableViewCellContentView遮挡在底部了

2.2 解决方案

需要改规范的做法

cell.contentView.addSubView(tempView1)

全局修改适配

//
//  UITableViewCell+CRMaddSubView.m
//  Housekeeper
//
//  Created by mac on 2020/9/18.
//  Copyright © 2020 QCT. All rights reserved.
//

#import "UITableViewCell+CRMaddSubView.h"

@implementation UITableViewCell (CRMaddSubView)
+ (void)load {
    // Swizzle addSubView
    [UITableViewCell sensorsdata_swizzleMethod:@selector(addSubview:) withMethod:@selector(kunnan_addSubview:)];
    
}

- (void)kunnan_addSubview:(UIView *)view {

    
    
    if  ([view isKindOfClass:NSClassFromString(@"UITableViewCellContentView")]) {//允许 addSubView UITableViewCellContentView 
        
        [self kunnan_addSubview:view];//实现方法,因为已经进行了 swizzle,相当于调用原来的方法
        

        
    } else {//
        
        [self.contentView addSubview:view];
        
    }



}



@end

III iOS10 系统关于UITableView的适配问题

因为目前基本找不到iOS10的真机了,要测试iOS10 可以下载模拟器。

  1. tableView numberOfRowsInSection:QCTReceiptsubFilterViewSection4KeyTypeTitle] 的使用执行顺序在iOS10的很特殊,不能在在方法中 - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section调用,否则会造成死循环。
  2. heightForFooterInSection 在iOS10 即使高度返回0 也会显示视图

3.1 代理方法的执行顺序

tableView:numberOfRowsInSection 不能在方法中 - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{ 调用,否则会造成死循环。

3.2 尾部视图的展示

在iOS10 中不显示viewForFooterInSection的正确做法是,将tableView:viewForFooterInSection: 和tableView:heightForFooterInSection:方法都注释掉


//- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{
//    
//    return 0;
//    
//    return kAdjustRatio(92+10);
//}
//
//- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section{
//     *footerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:@""];
//    
//    footerView.type =  self.type;
//    footerView.models = self.viewModel.datas[section];
//    return footerView;
//}

iOS小技能:短信验证码的Checklist、格式校验、获取验证码处理流程(限制60s)

本文正在参加「金石计划」

前言

短信验证码处理的实际应用:注册登陆界面获取验证码功能

  1. 获取验证码(限制60s):获取成功之后验证码输入框得到焦点,并开始计时器
  2. 格式校验
  3. 短信验证码的Checklist

I、实际应用的例子: 获取验证码(限制60s)

1.1 点击获取验证码处理

请求接口获取验证码,获取成功之后,开始到60S倒计时

  • 监听点击事件,并处理获取成功之后的倒计时
        UITapGestureRecognizer *cutTap = [[UITapGestureRecognizer alloc] init];
        [[cutTap rac_gestureSignal] subscribeNext:^(id x) {
            
            
            NSLog(@"获取验证码");
            
            
            if(weakSelf.model.rightBtnblock){
                
                // 参数为block
                
                
                weakSelf.model.rightBtnblock(
                                              
                                              
                                              ^(id  _Nonnull sender) {
                                
                //                                  []
                                                  [self.textF becomeFirstResponder];
                                                  [self startCountdown];//test code

                                                  
                            }// 参数
                                           );
                
            }
            
            
            
            
            
        }];
        [tmpView addGestureRecognizer:cutTap];
        

  • 请求短信验证码:SendValidateCode

 
/** 本方法的参数类型时block,是获取完短信验证码的回调
 ,回调主要是执行60S倒计时
 
 */
- (void)SendValidateCode:(void (^)(id sender))tmp {

        [QCTNetworkHelper Post:post parameters:params success:^(id  _Nonnull responseObj) {
            
            
            if(tmp){// 开始计时器
                tmp(nil);
            }
//
            
            
            
        } failure:nil bizFailure:nil isShowLoadingDataGif:YES];
        

1.2、计时方法处理

  • 去掉输入框的内容,短信验证码获取成功调用这个方法


#pragma mark - 计时方法, 去掉输入框的内容,短信验证码获取成功调用这个方法
//开始倒计时
- (void)startCountdown {
//
//    //1\ 清空文本输入框
    [self updateText:nil]; //
//
    _textF.userInteractionEnabled = YES;
    
    self.countdownTime = 60;
    
    self.rightBtn.enabled = NO;
    
    
    [self.rightBtn setTitle:[NSString stringWithFormat:@"%dS",self.countdownTime] forState:UIControlStateDisabled];
    if (_timer) {
        [_timer invalidate];
        _timer = nil;
    }
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}


//
//结束倒计时
- (void)stopCountdown {
    if (_timer) {
        [_timer invalidate];
        _timer = nil;
    }
    [self.rightBtn setTitle:@"重新发送" forState:UIControlStateNormal];
//    //    self.renewBtn.userInteractionEnabled = YES;
    self.rightBtn.enabled = YES;
    
    self.rightBtn.layer.borderColor = RGB(255,83,85).CGColor;
    [self.rightBtn setTitleColor:RGB(255,83,85) forState:UIControlStateNormal];
    
}



//
- (void)countdownAction:(NSTimer *)timer {
    self.countdownTime --;
    if (self.countdownTime < 0) {
        self.countdownTime = 0;
        [self stopCountdown];
    } else {
        [self.rightBtn setTitle:[NSString stringWithFormat:@"%dS",self.countdownTime] forState:UIControlStateDisabled];
    }
}
//
//
//
//
//
- (NSTimer *)timer {
    if (_timer == nil) {
        _timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(countdownAction:) userInfo:nil repeats:YES];
    }
    return _timer;
}


II 格式正则校验

2.1 短信验证码格式正则校验

+ (BOOL)isSMSshouldChangeCharactersInRange:(NSString*)str{
    //匹配以0开头的数字
    
    //^\d{6}$"
    
//    NSPredicate * predicate0 = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",@"^[0][0-9]+$"];
    
    
    //
    NSPredicate * predicate1 = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",@"^[0-9]{0,6}$"];
    
    
    
    
    return  [predicate1 evaluateWithObject:str] ? YES : NO;
    
    
}


2.2 金额校验格式正则校验

需求背景:商户进件新增、编辑、变更;费率设置支持到小数点后3位,如微信支付宝费率0.385;d0费率0.015

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
    NSInteger len = range.length > 0?([textField.text length] - range.length): ([textField.text length] + [string length]);
    
    if(len > 20){// 限制20位
        return false;
    }
    
    NSMutableString * futureString = [NSMutableString stringWithString:textField.text];
    if(range.length > 0){//允许删除
        [futureString replaceCharactersInRange:range withString:string];
    }else if(range.length<=0){
        [futureString  insertString:string atIndex:range.location];
    }
    if(futureString.length<1){//允许删除全部
        return YES;
    }
    if(jjkFdTextF == textField){// 支持2位小数
        return [QCT_Common isAmoutshouldChangeCharactersInRange:futureString.copy ];
    }
    //支持三位小数
    return [QCT_Common isAmoutshouldChangeCharactersInRange3:futureString.copy];
    
}

支持三位小数

/**
 前端商户费率填写支持三位小数
 */
+ (BOOL)isAmoutshouldChangeCharactersInRange3:(NSString*)str{
    //匹配以0开头的数字
    NSPredicate * predicate0 = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",@"^[0][0-9]+$"];
    //匹配两位小数、整数
    NSPredicate * predicate1 = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",@"^(([1-9]{1}[0-9]*|[0])\.?[0-9]{0,3})$"];
    return ![predicate0 evaluateWithObject:str] && [predicate1 evaluateWithObject:str] ? YES : NO;
    
}

III、短信验证码的Checklist

  • 一次一用
  • 发送频率控制(建议60s获取一次)
  • 验证码有效期(建议60s内有效,发短信时进行友好提示)
  • 杂度(短信验证码建议6位数字)
  • 安全提示:是否是个人自己操作等风险提示信息
  • 在前端校验(客户端的校验只能作为辅助手段,很容易被绕过),必须使用服务端代码对输入数据进行最终校验
  • 短信验证码需要限制频率使用

例如:每天一个手机号码只允许发送5次,防止被黑客恶意消耗短信

  • 不同场景的短信验证码不可通用
  • 单个短信验证码限制有效验证次数
  • 验证码需要对应手机号不可通用

iOS小技能:【设备日志查看工具】syslog、deviceconsole和socat

本文正在参加「金石计划」

前言

本文介绍iOS设备日志查看工具syslog、deviceconsole和socat,如果上述工具都不满意,你也可以使用Mac系统自带的console控制台进行查看。

>

I syslog

1.1 安装syslog

在cydia搜索syslogd to /var/log/syslog安装即可

1.2 syslog用法

syslog是把系统日志写入到/var/log/syslog文件里,用法很简单,执行tail -f /var/log/syslog就能看到了

如果需要过滤某一应用的日志,只需加上grep即可,比如过滤微信

tail -f /var/log/syslog |grep WeChat

II socat

2.1 安装

  • 在iOS设备安装

使用 APT 0.6 Transitional 安装socat 几乎所有流行的黑客工具都可以在 BigBoss Recommendation tools这个包中找到 ( APT 0.6 Transitional, Git, GNU Debugger, less, make, unzip, wget 和 SQLite 3.x)

apt-get install socat

如果找不到安装包的时候,运行一下 apt-get update, 获得最新的包列表.

2.2 连接到系统日志的sock文件

socat - UNIX-CONNECT:/var/run/lockdown/syslog.sock

  • 进入到命令行交互界面,这时可以输入help查看帮助
iPhone:~ root# socat - UNIX-CONNECT:/var/run/lockdown/syslog.sock

========================
ASL is here to serve you
> 

2.3 日志的查看

输入watch查看,输入stop停止

2.4 清除日志文件数据

cat /dev/null >/var/log/syslog

III deviceconsole

自从iOS8之后,我就习惯使用Mac系统自带的console ,后来发现有些同事的Mac中console 版本低,没有device 选项;于是乎,就推荐他们使用deviceconsole

  • deviceconsole --help
➜  bin git:(master) ✗ deviceconsole --help
Usage: deviceconsole [options]
Options:
 -d      Include connect/disconnect messages in standard out
 -u <udid>    Show only logs from a specific device
 -p <process name>  Show only logs from a specific process

Control-C to disconnect
Mail bug reports and suggestions to <ryan.petrich@medialets.com>
  • knlog -help

    Usage: knlog [options]
    Options:
    -i | --case-insensitive     Make filters case-insensitive
    -f | --filter <string>      Filter include by single word occurrences (case-sensitive)
    -x | --exclude <string>     Filter exclude by single word occurrences (case-sensitive)
    -p | --process <string>     Filter by process name (case-sensitive)
    -u | --udid <udid>          Show only logs from a specific device
    -s | --simulator <version>  Show logs from iOS Simulator
         --debug                Include connect/disconnect messages in standard out
         --use-separators       Skip a line between each line
         --force-color          Force colored text
         --message-only          Display only level and message
    Control-C to disconnect
    
  • 编译之后的可执行文件 knlog

IV shell 脚本完成对日志文件的提取之sed 学习

sed 是一种在线编辑器,它一次处理一行内容。Sed主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等。

文件内容并没有改变,除非你使用重定向存储输出。

4.1 ## sed的参数

    sed [-nefr] [动作]
选项与参数:
-n :使用安静(silent)模式。在一般 sed 的用法中,所有来自 STDIN 的数据一般都会被列出到终端上。但如果加上 -n 参数后,则只有经过sed 特殊处理的那一行(或者动作)才会被列出来。
-e :直接在命令列模式上进行 sed 的动作编辑;
-f :直接将 sed 的动作写在一个文件内, -f filename 则可以运行 filename 内的 sed 动作;
-r :sed 的动作支持的是延伸型正规表示法的语法。(默认是基础正规表示法语法)
-i :直接修改读取的文件内容,而不是输出到终端。

动作说明: [n1[,n2]]function
n1, n2 :不见得会存在,一般代表『选择进行动作的行数』,举例来说,如果我的动作是需要在 10 到 20 行之间进行的,则『 10,20[动作行为] 』

function:
a :新增, a 的后面可以接字串,而这些字串会在新的一行出现(目前的下一行)~
c :取代, c 的后面可以接字串,这些字串可以取代 n1,n2 之间的行!
d :删除,因为是删除啊,所以 d 后面通常不接任何咚咚;
i :插入, i 的后面可以接字串,而这些字串会在新的一行出现(目前的上一行);
p :列印,亦即将某个选择的数据印出。通常 p 会与参数 sed -n 一起运行~
s :取代,可以直接进行取代的工作哩!通常这个 s 的动作可以搭配正规表示法!例如 1,20s/old/new/g 
————————————————
版权声明:本文为CSDN博主「iOS逆向」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/z929118967/article/details/49489527

4.2 数据的搜寻并替换

`sed 's/要被取代的字串/新的字串/g'`

    /etc>ifconfig -a | grep inet 
        inet 172.16.49.224 netmask 0xffffff00 broadcast 172.16.49.255
        inet 192.168.10.4 netmask 0xffffff00 broadcast 192.168.10.255
        inet 127.0.0.1 netmask 0xff000000 broadcast 127.255.255.255
        inet6 ::1%1/0
————————————————
版权声明:本文为CSDN博主「iOS逆向」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/z929118967/article/details/49489527

see also

更多服务和咨询请关注#公号:iOS逆向

iOS小技能:核心动画(CoreAnimation)

本文正在参加「金石计划」

引言

预备知识1: RootLayer

在创建一个UIView对象时,UIView内部会自动创建一个图层(即CALayer对象);通过UIView对象的layer属性可以访问这个层,通常称这个层为RootLayer。 iOS小技能:CALayerblog.csdn.net/z929118967/…

预备知识2. 关于CALayer(可移植性) QuartzCore框架和CoreGraphics框架是可以跨平台使用的,在iOS和Mac OS X上都能使用,但是UIKit只能在iOS中使用。所以: 为了保证可移植性,QuartzCore不能使用UIImage、UIColor,只能使用CGImageRef、CGColorRef

  • CALayer是定义在QuartzCore框架中的(CoreAnimation)

  • CGImageRef、CGColorRef两种数据类型是定义在CoreGraphics框架中的

  • UIColor、UIImage是定义在UIKit框架中的

本文核心内容:

  • CALayer的position、anchorPoint属性的作用
  • 核心动画基本概念
  • 基本动画
  • 关键帧动画
  • 动画组
  • 转场动画

I 隐式动画

所有的非Root Layer,也就是手动创建的CALayer对象,都存在着隐式动画。 隐式动画:当对非Root Layer的部分属性进行修改时,默认会自动产生一些动画效果;而这些属性称为可动画属性(Animatable Properties)。 几个常见的Animatable Properties:

  1. bounds:用于设置CALayer的宽度和高度。修改这个属性会产生缩放动画
  2. backgroundColor:用于设置CALayer的背景色。修改这个属性会产生背景色的渐变动画
  3. position:用于设置CALayer的位置。修改这个属性会产生平移动画效果

关闭隐式动画(使用CATransation关闭隐式动画): 可以通过动画事务(CATransaction)关闭默认的隐式动画效果


[CATransaction begin];
[CATransaction setDisableActions:YES];
self.myview.layer.position = CGPointMake(10, 10);
[CATransaction commit];

II 核心动画

核心动画的执行过程都是在后台操作的,不会阻塞主进程;核心动画直接作用于CALayer,而非UIView

2.1 是所有动画对象的父类

负责控制动画的持续时间和速度,是个抽象类,不能直接使用,应该使用它具体的子类

CAAnimation的继承结构

2.2 属性

duration:动画的持续时间 repeatCount:重复次数,无限循环可以设置HUGE_VALF或者MAXFLOAT repeatDuration:重复时间 nremovedOnCompletion:默认为YES,代表动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态。如果想让图层保持显示动画执行后的状态,那就设置为NO,不过还要设置fillMode为kCAFillModeForwards fillMode:决定当前对象在非active时间段的行为。比如动画开始之前或者动画结束之后 beginTime:可以用来设置动画延迟执行时间,若想延迟2s,就设置为CACurrentMediaTime()+2,CACurrentMediaTime()为图层的当前时间 timingFunction:速度控制函数,控制动画运行的节奏 delegate:动画代理

(粗体代表来自CAMediaTiming协议的属性)

2.3 动画填充模式(fillMode )

填充模式的枚举值

要想fillMode有效,最好设置removedOnCompletion = NO



CA_EXTERN NSString * const kCAFillModeForwards//当动画结束后,layer会一直保持着动画最后的状态
CA_EXTERN NSString * const kCAFillModeBackwards//在动画开始前,只需要将动画加入了一个layer,layer便立即进入动画的初始状态并等待动画开始。
CA_EXTERN NSString * const kCAFillModeBoth//上面两个的合成,动画加入后开始之前,layer便处于动画初始状态,动画结束后layer保持动画最后的状态
CA_EXTERN NSString * const kCAFillModeRemoved //这个是默认值,也就是说当动画开始前和动画结束后,动画对layer都没有影响,动画结束后,layer会恢复到之前的状态

速度控制函数(CAMediaTimingFunction)


1.kCAMediaTimingFunctionLinear(线性):匀速,给你一个相对静态的感觉
2.kCAMediaTimingFunctionEaseIn(渐进):动画缓慢进入,然后加速离开
3.kCAMediaTimingFunctionEaseOut(渐出):动画全速进入,然后减速的到达目的地
4.kCAMediaTimingFunctionEaseInEaseOut(渐进渐出):动画缓慢的进入,中间加速,然后减速的到达目的地。这个是默认的动画行为。

动画的代理方法

@interface NSObject (CAAnimationDelegate)
/* Called when the animation begins its active duration. */
- (void)animationDidStart:(CAAnimation *)anim;
/* Called when the animation either completes its active duration or
 
 * is removed from the object it is attached to (i.e. the layer). 'flag'
 
 * is true if the animation reached the end of its active duration
 
 * without being removed. */
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;
@end

III 开发步骤

  1. 获取到CALayer(CoreAnimation是直接作用在CALayer上的,并非UIView。)
  2. 初始化CAAnimation对象,并设置一些动画相关属性
  3. 通过调用CALayer的addAnimation: forKey: 方法来添加CAAnimation到CALayer中,这样就能开始执行动画了。
  4. 通过调用CALayer的removeAnimationforKey: 方法来停止CALayer的动画。

CALayer上动画的暂停和恢复



#pragma mark 暂停CALayer的动画
-(void)pauseLayer:(CALayer*)layer
{
CFTimeInterval pausedTime =
[layer convertTime:CACurrentMediaTime() fromLayer:nil];
//让CALayer的时间停止走动
  layer.speed = 0.0;
//让CALayer的时间停留在pausedTime这个时刻
layer.timeOffset = pausedTime;
 
}
 
 
 
 
 
 
 
#pragma mark 恢复CALayer的动画
 
 
-(void)resumeLayer:(CALayer*)layer{
CFTimeInterval pausedTime = layer.timeOffset;
//1. 让CALayer的时间继续行走
  layer.speed = 1.0;
//2. 取消上次记录的停留时刻
  layer.timeOffset = 0.0;
//3. 取消上次设置的时间
  layer.beginTime = 0.0;    
//4. 计算暂停的时间(这里也可以用CACurrentMediaTime()-pausedTime)
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
//5. 设置相对于父坐标系的开始时间(往后退timeSincePause)
  layer.beginTime = timeSincePause;
 
 

IV CAAnimation的子类

4.1 CAPropertyAnimation

是CAAnimation的子类,也是个抽象类,要想创建动画对象,应该使用它的两个子类:

  1. CABasicAnimation
  2. CAKeyframeAnimation

keyPath属性说明:通过指定CALayer的一个属性名称为keyPath(NSString类型),并且对CALayer的这个属性的值进行修改,达到相应的动画效果。 比如,指定@“position”为keyPath,就修改CALayer的position属性的值,以达到平移的动画效果

4.2 CABasicAnimation(基本动画)


是CAPropertyAnimation的子类

属性说明:

  1. fromValue:keyPath相应属性的初始值
  2. toValue:keyPath相应属性的结束值

动画过程说明:

随着动画的进行,在长度为duration的持续时间内,keyPath相应属性的值从fromValue渐渐地变为toValuekeyPath内容是CALayer的可动画Animatable属性。

如果fillMode=kCAFillModeForwards同时removedOnComletion=NO,那么在动画执行完毕后,图层会保持显示动画执行后的状态。但在实质上,图层的属性值还是动画执行前的初始值,并没有真正被改变。

4.3 CAKeyFrameAnimation(关键帧动画)

也是CAPropertyAnimation的子类,与CABasicAnimation的区别是:CABasicAnimation只能从一个数值(fromValue)变到另一个数值(toValue),而CAKeyframeAnimation会使用一个NSArray保存这些数值。

CABasicAnimation可看做是只有2个关键帧的CAKeyframeAnimation

属性说明:

  1. values:上述的NSArray对象。里面的元素称为“关键帧”(keyframe)。动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧
  2. path:可以设置一个CGPathRef、CGMutablePathRef,让图层按照路径轨迹移动。path只对CALayer的anchorPoint和position起作用。如果设置了path,那么values将被忽略
  3. keyTimes:可以为对应的关键帧指定对应的时间点,其取值范围为0到1.0,keyTimes中的每一个时间值都对应values中的每一帧。如果没有设置keyTimes,各个关键帧的时间是平分的

4.4 CAAnimationGroup(动画组)

动画组,是CAAnimation的子类,可以保存一组动画对象,将CAAnimationGroup对象加入层后,组中所有动画对象可以同时并发运行 1.属性说明:

nanimations:用来保存一组动画对象的NSArray
默认情况下,一组动画对象是同时运行的,也可以通过设置动画对象的beginTime属性来更改动画的开始时间

4.5 CATransition(转场动画)


CATransition是CAAnimation的子类,用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果。iOS比Mac OS X的转场动画效果少一点

UINavigationController就是通过CATransition实现了将控制器的视图推入屏幕的动画效果

动画属性:

  1. type:动画过渡类型
  2. subtype:动画过渡方向
  3. startProgress:动画起点(在整体动画的百分比)
  4. endProgress:动画终点(在整体动画的百分比)

  1. 使用UIView动画函数实现转场动画——单视图

+ (void)transitionWithView:(UIView *)view duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0);
 

duration:动画的持续时间

view:需要进行转场动画的视图

options:转场动画的类型

animations:将改变视图属性的代码放在这个block中

completion:动画结束后,会自动调用这个block

  1. 使用UIView动画函数实现转场动画——双视图

+ (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0); // toView added to fromView.superview, fromView removed from its superview
 

duration:动画的持续时间

options:转场动画的类型

animations:将改变视图属性的代码放在这个block中

completion:动画结束后,会自动调用这个block

4.6 CADisplayLink (时钟机制)

CADisplayLink是一种以屏幕刷新频率触发的时钟机制,每秒钟执行大约60次左右 CADisplayLink是一个计时器,可以使绘图代码与视图的刷新频率保持同步,而NSTimer无法确保计时器实际被触发的准确时间。

使用方法:

  1. 定义CADisplayLink并制定触发调用方法
  2. 将显示链接link添加到主运行循环队列

通常保障一个View只拥有一个link

- (CADisplayLink *)link{
 
    if (nil == _link) {
 
        _link = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];
        [self.link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    }
 
    return _link;
 
}
- (void)startRotating{
 
    /** 核心动画的缺点是改变不了真是的属性*/
 
//    CABasicAnimation *animation = [CABasicAnimation animation];
 
//    //设置动画对象属性
 
//   
 
//    [self.roattionImageView.layer addAnimation:animation forKey:nil];
 
    [self.link setPaused:NO];
 
     
}
 
 
- (void) update{
 
    [self.roattionImageView setTransform:CGAffineTransformRotate(self.roattionImageView.transform, angle2Radian(45/60.0))];//60 次转45弧度,每次转45/60 弧度
 
    
}
 
- (void) stopRotating{
 
    [self.link setPaused:YES];
}
 

V iOS Core Animation&CALayer 的使用例子

kunnan.blog.csdn.net/article/det…

iOS小技能:1、yalu102 激活了之后,无法连接ssh的解决方案 2、Reveal的基本使用3、Passionfruit 的实现原理

本文正在参加「金石计划」

前言

  1. 无法连接ssh的解决方案
  2. Reveal的安装、配置及使用
  3. Passionfruit 的实现原理
  4. Mac上Nodejs环境搭建

I 使用yalu102 激活了之后,无法连接ssh的解决方案

yalu102此次越狱工具默认安装了 SSH.采用了 Dropbear 取代 Openssh。#/usr/local/bin/dropbear

-部署安装使用yalu102时,修改dropbear.plist的信息: ProgramArguments的127.0.0.1:22 直接改为22。

如果部署完成,直接修改沙盒的信息的话,记得重启设备。

1.1 获取直接修改对应的配置信息

  • ps -e |grep yalu*
iPhone:~ root# ps -e |grep yalu*
 1174 ??         0:00.80 /var/containers/Bundle/Application/B831448D-BCD0-4F29-BDA6-9FC03903D30C/yalu102.app/yalu102
  • plutil plist 内容
iPhone:/var/containers/Bundle/Application/B831448D-BCD0-4F29-BDA6-9FC03903D30C/yalu102.app root# plutil dropbear.plist
{
   KeepAlive = 1;
   Label = ShaiHulud;
   Program = "/usr/local/bin/dropbear";
   ProgramArguments =     (
       "/usr/local/bin/dropbear",
       "-F",
       "-R",
       "-p",
       22
   );
   RunAtLoad = 1;
}
  • dropbear 参数
 iPhone:/var/containers/Bundle/Application/B831448D-BCD0-4F29-BDA6-9FC03903D30C/yalu102.app root# ps -e |grep dropbear
  228 ??         0:00.05 /usr/local/bin/dropbear -F -R -p 22

1.2 利用wget 安装scp( 解决:sh: scp: command not found)

#两台服务器都要安装scp才能传文件
#wget + 空格 + 要下载文件的url路径
# cydia里面安装wget
#  安装scp,默认安装在当前目录
wget mila432.com/scp
ldid -S scp
# chmod +x scp
chmod 777 scp
mv scp /usr/bin/scp
# 或者使用curl: curl -O mila432.com/scp ./scp
  • dyld: Library not loaded: /usr/lib/libssl.0.9.8.dylib 重新安装openssl
  • 浏览器下载scp
 find . -name "scp" 

II Reveal的安装、配置及使用

目前自己经常使用的是AFlexLoader

2.1. Reveal Loader安装


首先我们打款越狱设备的Cydia,然后在搜索中输入Reveal Loader,并且进行安装即可,下方是安装后的效果。这一步比较简单,安装后重启SpringBoard即可。

iPhone:~ root# cd /Library/RHRevealLoader
-sh: cd: /Library/RHRevealLoader: No such file or directory

进行第二步骤,导入libReveal.dylib

2.2.导入libReveal.dylib


Mac上的Reveal自带了两个库,一个是libReveal.dylib,一个是Reveal.framework。 在未越狱的设备上使用的是后者,本文使用的是前者。

这两个文件位于Reveal中的iOS Library中。Reveal菜单->Help->Show Reveal Library in Finder ->iOS Library。通过上述目录就可以找到我们需要的文件。

 /Users/devzkn/Downloads/kevin-software/ios-Reverse_Engineering/Reveal/Reveal.app/Contents/SharedSupport/iOS-Libraries/libReveal.dylib

mkdir

devzkndeMacBook-Pro:python-client devzkn$ ssh iphone
iPhone:~ root# mkdir /Library/RHRevealLoader
iPhone:~ root# cd /Library/RHRevealLoader
iPhone:/Library/RHRevealLoader root

scp

devzkndeMacBook-Pro:python-client devzkn$ scp  /Users/devzkn/Downloads/kevin-software/ios-Reverse_Engineering/Reveal/Reveal.app/Contents/SharedSupport/iOS-Libraries/libReveal.dylib iphone:/Library/RHRevealLoader/

iPhone:~ root# ls -l /Library/RHRevealLoader/libReveal.dylib
-rwxr-xr-x 1 root admin 3850232 Oct 19 16:17 /Library/RHRevealLoader/libReveal.dylib

2.3、Reveal的使用


  • 选择Reveal的App

在设置中找到Reveal的配置项,在该配置项中我们可以去选择要Reveal的App, 当然对于越狱手机,手机上安装的所有App都可以Reveal。当然也包括从AppStore下载的,也包括iOS系统自带的

  • 查看app的UI层级

Mac上Reveal查看设备上App的UI层级时是不需要使用USB进行连接的,但要保证你的iOS设备与你的Mac在同一个局域网内

  • 注意事项

记得打开对应的app,保证你查看的app 处于运行状态

III Passionfruit 的实现原理

  • 我在使用Passionfruit 的时候,安装步骤碰到的问题是fatal error: 'frida-core.h' file not found,具体的请看Q&A。

  • 安全审计的工具 我觉得iNalyzer 已经过时了,推荐这款Passionfruit;

  • Passionfruit 通过frida注入代码到目标应用实现了个“动态分析iOS应用”的图形界面。

3.1 实现原理

Passionfruit 通过 frida 注入代码到目标应用实现功能,再通过 node.js 服务端消息代理与浏览器通信,用户通过访问网页即可对 App 实现常规的检测任务。 在这里插入图片描述

3.2 安装

3.2.1 安装前准备

  • brew install libimobiledevice
devzkndeMacBook-Pro:passionfruit devzkn$ brew install libimobiledevice

  • brew install yarn
devzkndeMacBook-Pro:passionfruit devzkn$  brew install yarn

  • install npm
brew install npm

3.2.2 运行 npm install 根据 package.json 文件安装依赖。

  • save frida then $ npm install
devzkndeMacBook-Pro:passionfruit devzkn$ npm install
> Passionfruit@0.0.3 postinstall /Users/devzkn/code/demo/passionfruit
> cd gui && (yarn || npm install)
yarn install v1.3.2
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 📃  Building fresh packages...
✨  Done in 231.08s.
up to date in 235.627s

  • npm run build
devzkndeMacBook-Pro:passionfruit devzkn$ npm run build

> Passionfruit@0.0.3 build /Users/devzkn/code/demo/passionfruit
> frida-compile agent -o _agent.js && cd gui && (yarn run build || npm run build)

yarn run v1.3.2

  • npm start
devzkndeMacBook-Pro:passionfruit devzkn$ npm start

> Passionfruit@0.0.3 start /Users/devzkn/code/demo/passionfruit
> cross-env NODE_ENV=production node .

listening on http://localhost:31337

3.3 Q&A

  • prebuild-install http 404 https://github.com/frida/frida/releases/download/10.6.13/frida-v10.6.13-node-v59-darwin-x64.tar.gz
devzkndeMacBook-Pro:passionfruit devzkn$ npm install --save frida@latest

> frida@10.6.28 install /Users/devzkn/code/demo/passionfruit/node_modules/frida
> prebuild-install || node-gyp rebuild

prebuild-install info begin Prebuild-install version 2.3.0
prebuild-install info looking for local prebuild @ prebuilds/frida-v10.6.28-node-v59-darwin-x64.tar.gz
prebuild-install info looking for cached prebuild @ /Users/devzkn/.npm/_prebuilds/https-github.com-frida-frida-releases-download-10.6.28-frida-v10.6.28-node-v59-darwin-x64.tar.gz
prebuild-install http request GET https://github.com/frida/frida/releases/download/10.6.28/frida-v10.6.28-node-v59-darwin-x64.tar.gz
prebuild-install http 200 https://github.com/frida/frida/releases/download/10.6.28/frida-v10.6.28-node-v59-darwin-x64.tar.gz
prebuild-install info downloading to @ /Users/devzkn/.npm/_prebuilds/https-github.com-frida-frida-releases-download-10.6.28-frida-v10.6.28-node-v59-darwin-x64.tar.gz.47369-6d6afc3ef5581.tmp
prebuild-install info renaming to @ /Users/devzkn/.npm/_prebuilds/https-github.com-frida-frida-releases-download-10.6.28-frida-v10.6.28-node-v59-darwin-x64.tar.gz
prebuild-install info unpacking @ /Users/devzkn/.npm/_prebuilds/https-github.com-frida-frida-releases-download-10.6.28-frida-v10.6.28-node-v59-darwin-x64.tar.gz
prebuild-install info unpack resolved to /Users/devzkn/code/demo/passionfruit/node_modules/frida/build/Release/frida_binding.node
prebuild-install info unpack required /Users/devzkn/code/demo/passionfruit/node_modules/frida/build/Release/frida_binding.node successfully
prebuild-install info install Successfully installed prebuilt binary!
+ frida@10.6.28
added 32 packages in 18.574s

  • Unable to launch iOS app: timeout

启动应用程序失败之后,装置就重启了。这个问题 有点类似Failed to spawn: unable to launch iOS app: timeout

临时解决方式: 手动启动app ,还是可以正常分析的

更多具体的分析请看这里: frida Failed to spawn 解决方案

IV Mac上Nodejs环境搭建

  1. 安装包安装:nodejs下载网址 http://nodejs.cn/download/
  2. 二进制安装:
brew install nodejs

brew install npm

see also

修改汇编的方式: 选中行,选择菜单栏的Modify > Assemble Instruction...,将jne修改成je,然后点击Assemble and Go Next。

公众号:iOS逆向

JavaScript预备知识

本文正在参加「金石计划」

前言

一个有具体功能的完整网页,一般由3部分组成:

  1. HTML(内容和结构): HyperText Markup Language,超文本标记语言。用来结构化网页内容并赋予内容含义,例如定义段落、标题和数据表,或在页面中嵌入图片和视频。

  2. css(样式): Cascading Style Sheets层叠样式表是一种样式规则语言,允许我们精确地设计HTML的样式,例如设置背景颜色和字体,在多个列中布局内容。

  3. JavaScript(交互效果) :JavaScript 是一种符合ECMAScript规范的脚本编程语言,可以用来创建动态更新的内容,控制多媒体,制作图像动画。

脚本语言是为了缩短传统编程语言从编写-编译-运行这个过程而开发的一种简单类型语言。

I 预备知识

1.1 术语:解释(interpret)和 编译 (compile)

编译原理 : https://blog.csdn.net/z929118967/article/details/123778003 在解释型语言中,代码自上而下运行,且实时返回运行结果。

js代码由浏览器执行前,不需要将其转化为其他形式,代码将直接以文本格式(text form)被接收和处理。

编译型语言需要先将代码转化(编译)成另一种形式才能运行。

C++,Objective C都是编译语言,必须先通过编译器生成机器码,然后才能由计算机运行。

Objective-C与swift都采用Clang作为编译器前端,编译器前端主要进行语法分析,语义分析,生成中间代码,在这个过程中,会进行类型检查,如果发现错误或者警告会标注出来在哪一行。

在这里插入图片描述 编译器后端会进行机器无关的代码优化,生成机器语言,并且进行机器相关的代码优化,根据不同的系统架构生成不同的机器码。

在这里插入图片描述

1.2 语言特点

  1. JavaScript 是轻量级解释型语言。

浏览器接受到 JavaScript 代码,并以代码自身的文本格式运行它。

技术上,几乎所有 JavaScript 转换器都运用了一种叫做即时编译(just-in-time compiling)的技术;当 JavaScript 源代码被执行时,它会被编译成二进制的格式,使代码运行速度更快。尽管如此,JavaScript 仍然是一门解释型语言,因为编译过程发生在代码运行中,而非之前。

JavaScript能被浏览器进行解释,是一种解释性语言。受浏览器的影响,在不同的浏览器可能表现的效果不一样,存在浏览器差异。

  1. 与大多数编程语言不同,JavaScript 没有输入或输出的概念。它是一个在宿主环境(host environment)下运行的脚本语言,任何与外界沟通的机制都是由宿主环境提供的。

浏览器是最常见的宿主环境,但Node.js 的服务器端环境中也包含 JavaScript 解释器,所以JavaScript 也可用作服务器端语言。

  1. JavaScript的语法来源于 Java 和 C,所以这两种语言的许多语法特性同样适用于 JavaScript。
  2. JavaScript 是一种“动态类型语言”(弱类型数据语言),这意味着不需要指定变量将包含什么数据类型,如果你声明一个变量并给它一个带引号的值,浏览器就会知道它是一个字符串:
let myString = 'Hello';
//提供了一个函数typeof用于检测数据属于哪个类型
//1.typeof 变量名
//2.typeof(变量名)
typeof myString;
  1. JavaScript 通过原型链而不是类来支持面向对象编程 ,JavaScript 同样支持函数式编程。

函数也可以被保存在变量中,并且像其他对象一样被传递。


document.querySelector('html').onclick = function() {
    alert('别戳我,我怕疼。');
}

document.querySelector('html').addEventListener('click', () => {
  alert('别戳我,我怕疼。');
});

//Longhand
function add(num1, num2) {
   return num1 + num2;
}
//Shorthand 箭头函数
const add = (num1, num2) => num1 + num2;
//`匿名函数`: 因为它没有名字,匿名函数还有另一种我们称之为`箭头函数`的写法,箭头函数使用` () => `代替 `function ()`:

函数式编程思想:把操作尽量写成一系列嵌套的函数或者方法调用。

函数式编程特点:每个方法必须有返回值(本身对象),把函数或者Block当做参数,block参数(需要操作的值)block返回值(操作结果)

iOS小技能:链式编程在iOS开发中的应用https://blog.csdn.net/z929118967/article/details/75219317

1.3 JavaScript的使用场所

使用场所:任何的HTML页面、所有的动态页面,通过 DOM API动态修改 HTML 和 CSS 来更新用户界面(user interface)。

没有动态更新内容的网页叫做“静态”页面,所显示的内容不会改变。

  1. 前端验证,通过验证提高数据的完整性以及安全性。
<input type="submit" value="确定" class="guessSubmit">

const guessSubmit = document.querySelector('.guessSubmit');
guessSubmit.addEventListener('click', checkGuess);
function checkGuess() {
//...
}
  1. 操纵html元素,响应用户的各种操作,提高用户体验性。
<input type="text" id="guessField" class="guessField">

const guessField = document.querySelector('.guessField');
  guessField.focus();//让光标在页面加载完毕时自动放置于 <input> 输入框内,这意味着玩家可以马上开始第一次猜测,而无需点击输入框。提高了可用性,为使用户能投入游戏提供一个良好的视觉线。
  1. ajax核心技术之一

ajax: 在浏览器中运行的js脚本,通过http请求异步地访问服务器组件,服务器组件返回xml文件或者json格式的数据,js接收后通过解析xml或json来局部刷新页面,提高用户体验。

  1. 获取浏览器的一些相关信息

1.4 脚本调用策略

HTML 元素是按其在页面中出现的次序调用的,如果用 JavaScript 来管理页面上的元素(更精确的说法是使用 文档对象模型 DOM),若 JavaScript 加载于欲操作的 HTML 元素之前,则代码将出错。

  1. 内部 JavaScript的解决方案
//监听浏览器的 "DOMContentLoaded" 事件,即 HTML 文档体加载、解释完毕事件
//可能会带来显著的性能损耗
document.addEventListener("DOMContentLoaded", function() {
//  . . .
});
  1. 外部JavaScript的解决方案(推荐)

async 属性可以解决调用顺序问题,它告知浏览器在遇到<script>元素时不要中断后续 HTML 内容的加载。

不依赖于本页面的其它任何脚本时,async 是最理想的选择。

<script src="script.js" async></script>

defer 属性,脚本将按照在页面中出现的顺序加载和运行:

<!--添加 defer 属性的脚本将按照在页面中出现的顺序加载-->
<script defer src="js/vendor/jquery.js"></script>

<script defer src="js/script2.js"></script>

<script defer src="js/script3.js"></script>

脚本调用策略小结:

  • 如果脚本无需等待页面解析,且无依赖独立运行,那么应使用 async。
  • 如果脚本需要等待页面解析,且依赖于其它脚本,调用这些脚本时应使用 defer,将关联的脚本按所需顺序置于 HTML 中。

II 应用程序接口(Application Programming Interfaces)

在这里插入图片描述

  1. 第三方 API 并没有默认嵌入浏览器中,一般要从网上取得它们的代码和信息,比如地图 API 可以在网站嵌入定制的地图。

  2. 浏览器 API 内建于 web 浏览器中,它们可以将数据从周边计算机环境中筛选出来,还可以做实用的复杂工作,比如DOM API。

  • 文档对象模型 API(Document Object Model Application Programming Interfaces ) 能通过创建、移除和修改 HTML,为页面动态应用新样式等手段来操作 HTML 和 CSS。
  • 地_理位置 API(Geolocation API) 获取地_理信息。
  • 画布(Canvas) 和 WebGL API 可以创建生动的 2D 和 3D 图像。
  • HTMLMediaElementWebRTC 等影音类 API 。

2.1 BOM(Browser Object Model,浏览器对象模型)

整个浏览器窗口是一个顶层window对象

  1. 函数 alert() 警告框 prompt() 对话框 confirm() 确认框 window.open("URL"); setTimeout();超时之后调用目标函数 clearTimeout();超时之后清除目标函数 focus() 获得焦点 setInterval(,) (以毫秒计)调用执行函数/表达式 setInterval(code,millisec[,"lang"]) clearInterval() 取消对 code 的周期性执行,由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的参数。
//setInterval() 方法可按照指定的周期(以毫秒计)来调用函数或计算表达式。
//setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的参数。
window.setInterval(function() {}, 18000)//18S

前端小技能:利用action-type按钮事件实现批量删除 https://blog.csdn.net/z929118967/article/details/123222483

  1. 浏览器对象 Navigator对象
  1. 属性: appName、 appVersion 、 History 历史记录对象
  2. 函数:go(url);
  1. 地址栏对象 Location

属性:href 通过改变地址栏访问目标地址

  1. 文档对象 Document

2.2 DOM(Document Object Model,文档对象模型)

由W3C定义的一组规范一组API 用来操作HTML对象

  1. 直接获得标签对象

1) document.getElementById("id属性值") 通过ID来获得对应的标签对象

    <iframe id="ifr" width=400 height=500></iframe>
        document.getElementById("ifr").src = url[j];

2) document.getElementsByTagName("标签名称") 根据标签名称来获得一组标签,返回数组对象 3) document.getElementsByName("name属性值");根据name属性值来获得一组标签,返回数组对象

  1. 间接获得标签对象

1) 父标签.childNodes 获得当前标签的所有孩子节点,返回数组对象 2) 父标签.firstChild 获得第一个孩子节点 3) 父标签.lastChild 获得最后一个孩子节点 4) 标签.nextSibling 获得下一个兄弟节点 5) 标签.previousSibling 获得前一个兄弟节点

  1. 创建节点对象

1) document.createElement("标签名称") 创建一个对应的标签对象 2) document.createTextNode("文本值"); 创建一个文本节点对象

  1. 操作标签

1) 父标签.appendChild(子节点); 将一个标签追加到父标签当中 2) 父标签.removeChild(子节点); 删除子节点 3) 父标签.insertBefore(newElement,targetElement); 在目标元素之前插入一个新元素 4) 父标签.replaceChild(newElement,oldElement); 用新元素替换掉旧的元素

  1. 其他操作

1)标签.style.样式属性 = 属性值; h1.style.backgroundColor = "red"; 2) 标签.innerHTML 获得或者设置元素的标签体 3) 文本节点.nodeValue 获得文本节点值 4) 标签.parentNode 获得父节点 5) this 表示当前标签对象

see also

公众号:iOS逆向

demo: https://codepen.io/zhangkn/pen/BaLyerq?editors=1111

JavaScript小技能: Ajax

本文正在参加「金石计划」

I 术语

1.1 术语:箭头函数

匿名函数: 因为它没有名字

//Longhand
function add(num1, num2) {
   return num1 + num2;
}

匿名函数还有另一种我们称之为箭头函数的写法,箭头函数使用() =>代替 function ()

//Shorthand 箭头函数
const add = (num1, num2) => num1 + num2;

1.2 术语:回调函数和Promise链

  1. 回调函数是一个被传递到另一个函数中的会在适当的时候被调用的函数,如事件处理程序就是一种特殊类型的回调函数。

由于嵌套回调导致处理错误变得非常困难,代码也更难阅读和调试,所以JavaScript 中的异步编程是基于 Promise实现。

  1. Promise是一个由异步函数返回的可以向我们指示当前操作所处的状态的对象。在基于 Promise 的 API 中,异步函数会启动操作并返回 Promise 对象。然后你可以将处理函数附加到 Promise 对象上,当操作完成时(成功或失败),这些处理函数将被执行。

在 Promise 返回给调用者的时候,操作往往还没有完成,但 Promise 对象可以让我们操作最终完成时对其进行处理(无论成功还是失败)。

例子:fetch() 一个基于 Promise 的、用于替代 XMLHttpRequest 的方法。

const fetchPromise = fetch('https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json');

console.log(fetchPromise);
//将一个处理函数传递给 Promise 的 then() 方法。
fetchPromise.then( response => {
  console.log(`已收到响应:${response.status}`);
});

console.log("已发送请求……");

"pending" 状态意味着操作仍在进行中。

  1. Promise 链:链式使用 Promise
const fetchPromise = fetch('https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json');

fetchPromise
  .then( response => {
    return response.json();//json() 也是异步的,response.json() 返回的是 Promise对象
  })
  .then( json => {
    console.log(json[0].name);
  });

Promise 对象提供了一个 catch() 方法来支持错误处理。当异步操作成功时,传递给 then() 的处理函数被调用,而当异步操作失败时,传递给 catch() 的处理函数被调用。

如果将 catch() 添加到 Promise 链的末尾,它就可以在任何异步函数失败时被调用。于是,我们就可以将一个操作实现为几个连续的异步函数调用,并在一个地方处理所有错误。

const fetchPromise = fetch('bad-scheme://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json');

fetchPromise
  .then( response => {
    if (!response.ok) {
      throw new Error(`HTTP 请求错误:${response.status}`);
    }
    return response.json();
  })
  .then( json => {
    console.log(json[0].name);
  })
  .catch( error => {
    console.error(`无法获取产品列表:${error}`);
  });

1.3 JSON(JavaScript Object Notation)

是一种轻量级的数据交换格式。它基于 ECMAScript(European Computer Manufacturers Association, 欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。

  1. JSON用于在网站上表示和传输数据
////通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,获取数据。
//创建一个新的 XMLHttpRequest 并监听它的 loadend 事件

request.open('GET', requestURL);
request.responseType = 'text'; // now we're getting a string!
request.send();
//以事件处理程序属性形式关联事件处理器
request.onload = function() {
  var superHeroesText = request.response; // get the string from the response
  var superHeroes = JSON.parse(superHeroesText); // convert it to an object
  populateHeader(superHeroes);
}
//通过DOM Level 2 Events 函数 addEventListener()关联事件处理器
  xhr.addEventListener('loadend', () => {
    log.textContent = `${log.textContent}完成!状态码:${xhr.status}`;
  });


  1. JSON 是 JS 对象的字符串表示法,它使用文本表示一个 JS 对象的信息,本质是一个字符串。
//JSON 格式创建一个对象
   var oo = {
        name:"hello",
        age:123,
        getName:function(){
       return oo.name;
            }
       
    }

  1. JSON 和 JS 对象互转: 利用浏览器内建的 JSON进行转换数据
//parse(): 以文本字符串形式接受 JSON 对象作为参数,并返回相应的对象。
var obj = JSON.parse('{"a": "Hello", "b": "World"}'); //结果是 {a: 'Hello', b: 'World'}
//stringify(): 接收一个对象作为参数,返回一个对应的 JSON 字符串。
var json = JSON.stringify({a: 'Hello', b: 'World'}); //结果是 '{"a": "Hello", "b": "World"}'

  1. 深拷贝多级对象
//深拷贝多级对象
const cloneObj = JSON.parse(JSON.stringify(obj));//JSON.stringify 对象的时候,包含 function, undefined or NaN 值的属性会从对象中移除。
//Shorthand for single level object
let obj = {x: 20, y: 'hello'};
const cloneObj = {...obj};
//————————————————
//版权声明:本文为CSDN博主「iOS逆向」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
//原文链接:https://blog.csdn.net/z929118967/article/details/126142071

II Ajax(Asynchronous JavaScript and XML)

Ajax是一种在2005年由Google推广开来的编程模式,可以创建更好、更快以及更友好的web应用程序。

在这里插入图片描述Asynchronous JavaScript and XML基于javascript和HTTP请求,以异步地方式实现局部HTML的刷新。

2.1 从服务器获取数据

  1. XMLHttpRequest (通常缩写为 XHR)现在是一个相当古老的技术 - 它是在 20 世纪 90 年代后期由微软发明的,并且已经在相当长的时间内跨浏览器进行了标准化。

因为微软 Edge 浏览器的受宠, IE 已经没人管了

let request = new XMLHttpRequest();
request.open('GET', url);
request.responseType = 'text';

request.onload = function() {
  poemDisplay.textContent = request.response;
};

request.send();

  1. Fetch API 基本上是 XHR 的一个现代替代品——它是最近在浏览器中引入的,它使异步 HTTP 请求在 JavaScript 中更容易实现
fetch(url).then(function(response) {
  response.text().then(function(text) {
    poemDisplay.textContent = text;
  });
});

2.2 缓存问题

如果是get方式请求,浏览器会将请求的数据缓存起来,如果下次访问的地址没变,浏览器就不会发送真正的请求,会将缓存的数据显示给用户。

解决方式:

  1. 可以在地址后面加上一个随机数。
  2. 使用post方式发送请求。
❌