和钉钉打卡迟到说拜拜

### 前言
对于上班族来说,上班打卡是一件很难受的事情.因为迟到就意味着要扣钱.很多时候,我们都会因为堵在高架桥、挤不上地铁、睡过头而错过打卡时间,每天心心念念的工资就这么眼睁睁的被扣掉,真的很不甘心.如果你也有类似的经历,请参考本文,本文将带你脱离苦海.(安卓用户请自行离开,去买一部几百块的二手iPhone拿来专门打卡吧)
### Payload
#### Mac用户
对于Mac用户来说,可以使用Apple官方开发工具Xcode进行实现。

- 1.进入Mac App Store安装Xcode

![Mac App Stor](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491410/c2fae344-1e2d-8dba-f1c7-1a846f44a6a8.png)

- 2.安装好以后打开它,并登陆

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491410/c0293bd6-3188-89e2-0c19-27d09aef71d6.png)

- 3.访问Apple开发者注册为开发者

https://developer.apple.com/

![Apple开发者](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491410/1618dc5c-085f-a553-0bb6-74d8a091f3a3.png)

登陆以后进行注册开发者步骤,一直到最后一步要求付款688人民币界面关闭即可,无需付款

- 4.新建Xcode工程
![新建Xcode工程](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491410/3be6936b-023c-43e8-8f1a-3a744c02c96b.png)

![新建iOS App工程](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491410/63c79824-958d-311d-1a0e-defbd32eb80b.png)

![填写工程相关选项](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491410/94a5de92-54bd-f75b-2ea2-429aa2325999.png)
- 5.随后你新建一个飘逸算法的文件

![新建文件](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491410/776f19e8-ca89-f2f8-4d7f-f8844bc4442c.png)

取名叫`LocationTransform`

文件内容如下:

```
//
//  LocationTransform.swift
//  SimulateLocation
//
//  Created by 清水川崎 on 2017/3/14.
//  Copyright © 2017年 清水川崎. All rights reserved.
//

import Foundation

/**
 *  Struct transform coordinate between earth(WGS-84) and mars in china(GCJ-02).
 */
public struct LocationTransform {
    
    static let EARTH_R: Double = 6378137.0
    
    static func isOutOfChina(lat: Double, lng: Double) -> Bool {
        
        if lng < 72.004 || lng > 137.8347 {
            return true
        }
        if lat < 0.8293 || lat > 55.8271 {
            return true
        }
        return false
    }
    
    static func transform(x: Double, y: Double) -> (lat: Double, lng: Double) {
        
        let xy = x * y
        let absX = sqrt(fabs(x))
        let xPi = x * Double.pi
        let yPi = y * Double.pi
        let d = 20.0 * sin(6.0 * xPi) + 20.0 * sin(2.0 * xPi)
        
        var lat = d
        var lng = d
        
        lat += 20.0 * sin(yPi) + 40.0 * sin(yPi / 3.0)
        lng += 20.0 * sin(xPi) + 40.0 * sin(xPi / 3.0)
        
        lat += 160.0 * sin(yPi / 12.0) + 320 * sin(yPi / 30.0)
        lng += 150.0 * sin(xPi / 12.0) + 300 * sin(xPi / 30.0)
        
        lat *= 2.0 / 3.0
        lng *= 2.0 / 3.0
        
        lat += -100 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * xy + 0.2 * absX
        lng += 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * xy + 0.1 * absX
        
        return (lat, lng)
    }
    
    static func delta(lat: Double, lng: Double) -> (dLat: Double,  dLng: Double) {
        let ee = 0.00669342162296594323
        let radLat = lat / 180.0 * Double.pi
        var magic = sin(radLat)
        magic = 1 - ee * magic * magic
        let sqrtMagic = sqrt(magic)
        var (dLat, dLng) = transform(x: lng - 105.0, y: lat - 35.0)
        dLat = (dLat * 180.0) / ((EARTH_R * (1 - ee)) / (magic * sqrtMagic) * Double.pi)
        dLng = (dLng * 180.0) / (EARTH_R / sqrtMagic * cos(radLat) * Double.pi)
        return (dLat, dLng)
    }
    
    /**
     *  wgs2gcj convert WGS-84 coordinate(wgsLat, wgsLng) to GCJ-02 coordinate(gcjLat, gcjLng).
     */
    public static func wgs2gcj(wgsLat: Double, wgsLng: Double) -> (gcjLat: Double, gcjLng: Double) {
        if isOutOfChina(lat: wgsLat, lng: wgsLng) {
            return (wgsLat, wgsLng)
        }
        let (dLat, dLng) = delta(lat: wgsLat, lng: wgsLng)
        return (wgsLat + dLat, wgsLng + dLng)
    }
    
    /**
     *  gcj2wgs convert GCJ-02 coordinate(gcjLat, gcjLng) to WGS-84 coordinate(wgsLat, wgsLng).
     *  The output WGS-84 coordinate's accuracy is 1m to 2m. If you want more exactly result, use gcj2wgs_exact.
     */
    public static func gcj2wgs(gcjLat: Double, gcjLng: Double) -> (wgsLat: Double, wgsLng: Double) {
        if isOutOfChina(lat: gcjLat, lng: gcjLng) {
            return (gcjLat, gcjLng)
        }
        let (dLat, dLng) = delta(lat: gcjLat, lng: gcjLng)
        return (gcjLat - dLat, gcjLng - dLng)
    }
    
    /**
     *  gcj2wgs_exact convert GCJ-02 coordinate(gcjLat, gcjLng) to WGS-84 coordinate(wgsLat, wgsLng).
     *  The output WGS-84 coordinate's accuracy is less than 0.5m, but much slower than gcj2wgs.
     */
    public static func gcj2wgs_exact(gcjLat: Double, gcjLng: Double) -> (wgsLat: Double, wgsLng: Double) {
        let initDelta = 0.01, threshold = 0.000001
        var (dLat, dLng) = (initDelta, initDelta)
        var (mLat, mLng) = (gcjLat - dLat, gcjLng - dLng)
        var (pLat, pLng) = (gcjLat + dLat, gcjLng + dLng)
        var (wgsLat, wgsLng) = (gcjLat, gcjLng)
        for _ in 0 ..< 30 {
            (wgsLat, wgsLng) = ((mLat + pLat) / 2, (mLng + pLng) / 2)
            let (tmpLat, tmpLng) = wgs2gcj(wgsLat: wgsLat, wgsLng: wgsLng)
            (dLat, dLng) = (tmpLat - gcjLat, tmpLng - gcjLng)
            if (fabs(dLat) < threshold) && (fabs(dLng) < threshold) {
                return (wgsLat, wgsLng)
            }
            if dLat > 0 {
                pLat = wgsLat
            } else {
                mLat = wgsLat
            }
            if dLng > 0 {
                pLng = wgsLng
            } else {
                mLng = wgsLng
            }
        }
        return (wgsLat, wgsLng)
    }
    
    /**
     *  Distance calculate the distance between point(latA, lngA) and point(latB, lngB), unit in meter.
     */
    public static func Distance(latA: Double, lngA: Double, latB: Double, lngB: Double) -> Double {
        let arcLatA = latA * Double.pi / 180
        let arcLatB = latB * Double.pi / 180
        let x = cos(arcLatA) * cos(arcLatB) * cos((lngA-lngB) * Double.pi/180)
        let y = sin(arcLatA) * sin(arcLatB)
        var s = x + y
        if s > 1 {
            s = 1
        }
        if s < -1 {
            s = -1
        }
        let alpha = acos(s)
        let distance = alpha * EARTH_R
        return distance
    }
}

extension LocationTransform {
    
    public static func gcj2bd(gcjLat: Double, gcjLng: Double) -> (bdLat: Double, bdLng: Double) {
        if isOutOfChina(lat: gcjLat, lng: gcjLng) {
            return (gcjLat, gcjLng)
        }
        let x = gcjLng, y = gcjLat
        let z = sqrt(x * x + y * y) + 0.00002 * sin(y * Double.pi)
        let theta = atan2(y, x) + 0.000003 * cos(x * Double.pi)
        let bdLng = z * cos(theta) + 0.0065
        let bdLat = z * sin(theta) + 0.006
        return (bdLat, bdLng)
    }
    
    public static func bd2gcj(bdLat: Double, bdLng: Double) -> (gcjLat: Double, gcjLng: Double) {
        if isOutOfChina(lat: bdLat, lng: bdLng) {
            return (bdLat, bdLng)
        }
        let x = bdLng - 0.0065, y = bdLat - 0.006
        let z = sqrt(x * x + y * y) - 0.00002 * sin(y * Double.pi)
        let theta = atan2(y, x) - 0.000003 * cos(x * Double.pi)
        let gcjLng = z * cos(theta)
        let gcjLat = z * sin(theta)
        return (gcjLat, gcjLng)
    }
    
    public static func wgs2bd(wgsLat: Double, wgsLng: Double) -> (bdLat: Double, bdLng: Double) {
        let (gcjLat, gcjLng) = wgs2gcj(wgsLat: wgsLat, wgsLng: wgsLng)
        return gcj2bd(gcjLat: gcjLat, gcjLng: gcjLng)
    }
    
    public static func bd2wgs(bdLat: Double, bdLng: Double) -> (wgsLat: Double, wgsLng: Double) {
        let (gcjLat, gcjLng) = bd2gcj(bdLat: bdLat, bdLng: bdLng)
        return gcj2wgs(gcjLat: gcjLat, gcjLng: gcjLng)
    }
}

```

修改`AppDelegate.swift`内容为如下:

```
//
//  AppDelegate.swift
//  lat
//
//  Created by xue on 11/4/2019.
//  Copyright © 2019 清水川崎. All rights reserved.
//

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        let location = LocationTransform.gcj2wgs(gcjLat:LAT, gcjLng:LON)
        print(location)
        return true
    }

    func applicationWillResignActive(_ application: UIApplication) {
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
    }

    func applicationDidEnterBackground(_ application: UIApplication) {
        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    }

    func applicationWillEnterForeground(_ application: UIApplication) {
        // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
    }

    func applicationDidBecomeActive(_ application: UIApplication) {
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    }

    func applicationWillTerminate(_ application: UIApplication) {
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    }


}


```
在新建一个GPX文件,随便取名(不能有中文)

![gpx文件](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491410/affe4542-7ad0-5388-0f16-7b2cc58a9729.png)


修改GPX文件为如下内容

```

<?xml version="1.0" encoding="UTF-8" ?>
<gpx version="1.1"
    creator="GMapToGPX 6.4j - http://www.elsewhere.org/GMapToGPX/"
    xmlns="http://www.topografix.com/GPX/1/1"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">

<wpt lat="lat" lon="lon">

    </wpt>
</gpx>

```

随后连接iPhone和电脑(信任，允许都选是，联网等待大概15分钟安装开发者模式文件),修改运行加载文件设置

![Scheme](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491410/cf5c408d-580a-bb2a-39e9-e715a3a5b070.png)

随后选择你的gpx文件
![选择gpx](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491410/e72f97e4-a800-5b32-fa84-7264005829c9.png)

- 6.获取想要修改定位地点的坐标系

访问高德控制台API(钉钉、高德都是阿里系的app)

https://lbs.amap.com/console/show/picker

![高德控制台API](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491410/f390bb8c-101d-c3a2-5161-d20cae97918f.png)

输入你想要定位的地方，例如香港轩尼诗道

![香港轩尼诗道](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491410/70553f29-959c-4726-3907-87ac3945f554.png)

随后获得坐标系为`114.172914,22.275128`

进入Xcode工程中AppDelegate,用22.275128替换LON,114.172914替换LAT

随后点击三角进行调试

![调试](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491410/0b8eab17-4ee2-b52a-841f-64d7233bf822.png)

PS:第一次调试可能耗时较长,且可能遇到无法信任app问题,解决以后再调试一次即可

无法信任app问题请参考:https://jingyan.baidu.com/article/0964eca2487f928285f536fe.html

随后控制台会返回真实坐标系(由于某些原因我国默认坐标系为加密坐标系)

![坐标系](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491410/d24529bf-047d-9b4e-0984-4f2367bfb8b0.png)

拿着真实坐标系，到gpx文件中进行替换坐标

![gpx替换坐标](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491410/9b570f7e-3cce-2087-50ed-7ffe88897b2e.png)

再次调试工程,即可更换坐标为香港轩尼诗道

使用WeChat\百度地图\Find My iPhone(iCloud)等日常app做一次测试

![WeChat](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491410/41d5d315-30ba-18b4-2dc6-2765516d5fec.png)

![百度地图](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491410/8ca48eab-9bf7-4f02-a2b7-1ac0f93cb490.png)

![Find My iPhone(iCloud)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491410/5b2ffb14-681b-cc7b-3549-448a285a3567.png)

连Apple自己的App都骗过了,害怕绕不过钉钉？再牛逼的app也是基于iOS SDK下面的几个Kit,例如UIKit、MapKit等
#### Win用户
前面的步骤都是在照顾Mac用户,现在来说Win用户

Win用户步骤更为简单粗暴,直接安装爱思助手,连接iPhone到手机等待爱思助手连接到iPhone

![爱思助手](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491410/a0b00bf0-5f18-9ee0-5e15-ce9825776747.png)

点击工具箱,找到虚拟定位

![虚拟定位](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491410/e4b8b953-b048-3423-c2ab-38151b05fa20.png)

输入坐标系或点击你要的定位,点击蓝色的修改虚拟定位即可

![修改定位](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491410/d04dc8c3-1dac-6536-a3ba-05ce6a270deb.png)

![修改定位成功](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491410/580c52b9-b645-fa9e-d384-d3b60b40aeac.png)

还是使用百度地图做下检测

![百度地图](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491410/a6c86384-eec6-a042-e986-da81a864665e.png)

### 以上两种方法,尤其是第一种100%不会被钉钉后台检测到,也不需要越狱,第二种方式朋友使用至今没有收到任何钉钉后台告警

如果你要还原真实坐标系,重启你的iPhone即可.那么可以尝试购买一部二手iPhone专门用于打卡,反正只需几百块而已.虚拟定位第一种方式我使用至今,几乎一周自动还原一次真实坐标系.使用超过两年,从未被后台警告过.完全可以bypass任何定位打卡设备.而WI-FI定位识别的朋友,可以使用openwrt固件的路由模拟公司路由Mac地址,修改WI-FI名称为公司的WI-FI名称即可破解,过程这里不再赘述.

# PS:我司比较人性化,上班不打卡.😁😁😁