├── .gitignore ├── .project ├── .settings └── org.eclipse.buildship.core.prefs ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── autorun │ │ ├── CheckAllowTest.java │ │ ├── ExampleInstrumentedTest.java │ │ └── NoSSLv3SocketFactory.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ ├── com │ │ │ └── example │ │ │ │ └── autorun │ │ │ │ ├── data │ │ │ │ ├── LoginDataSource.java │ │ │ │ ├── LoginRepository.java │ │ │ │ ├── Result.java │ │ │ │ └── model │ │ │ │ │ └── LoggedInUser.java │ │ │ │ ├── helper │ │ │ │ ├── AESUtil.java │ │ │ │ ├── App.java │ │ │ │ ├── CheckAllow.java │ │ │ │ └── SystemUtil.java │ │ │ │ └── ui │ │ │ │ └── login │ │ │ │ ├── LoggedInUserView.java │ │ │ │ ├── LoginActivity.java │ │ │ │ ├── LoginFormState.java │ │ │ │ ├── LoginResult.java │ │ │ │ ├── LoginViewModel.java │ │ │ │ └── LoginViewModelFactory.java │ │ └── org │ │ │ └── runrun │ │ │ ├── App.java │ │ │ ├── UniRunMain.java │ │ │ ├── entity │ │ │ ├── AppConfig.java │ │ │ ├── Location.java │ │ │ ├── NewRecordBody.java │ │ │ ├── Response.java │ │ │ ├── ResponseType │ │ │ │ ├── ClubInfo.java │ │ │ │ ├── JoinClubResult.java │ │ │ │ ├── MyActivityItem.java │ │ │ │ ├── NewRecordResult.java │ │ │ │ ├── RunStandard.java │ │ │ │ ├── SchoolBound.java │ │ │ │ ├── SignInTf.java │ │ │ │ ├── SportsClassStudentLearnClockingV0.java │ │ │ │ └── UserInfo.java │ │ │ └── SignInOrSignBackBody.java │ │ │ ├── run │ │ │ └── Request.java │ │ │ └── utils │ │ │ ├── FileUtil.java │ │ │ ├── HTTP │ │ │ ├── CustomCookieStore.java │ │ │ ├── HttpUtil.java │ │ │ ├── HttpUtil2.java │ │ │ └── HttpUtilEntity.java │ │ │ ├── JsonUtils.java │ │ │ ├── MD5Utils.java │ │ │ ├── SignUtils.java │ │ │ └── TrackUtils.java │ ├── res │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ │ └── activity_login.xml │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── values-night │ │ │ └── themes.xml │ │ ├── values │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── themes.xml │ │ └── xml │ │ │ └── network_security_config.xml │ └── resources │ │ ├── map.json │ │ └── map2.json │ └── test │ ├── java │ └── com │ │ └── example │ │ └── autorun │ │ ├── CheckAllowTest.java │ │ └── ExampleUnitTest.java │ └── resources │ ├── map.json │ ├── template.json │ ├── test.js │ └── 路径标记点图.png ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | /app/release/ 17 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | AutoRun1 4 | Project AutoRun1 created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.buildship.core.gradleprojectbuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.buildship.core.gradleprojectnature 16 | 17 | 18 | 19 | 1636629667783 20 | 21 | 30 22 | 23 | org.eclipse.core.resources.regexFilterMatcher 24 | node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | arguments= 2 | auto.sync=false 3 | build.scans.enabled=false 4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) 5 | connection.project.dir= 6 | eclipse.preferences.version=1 7 | gradle.user.home= 8 | java.home=D\:/Program Files (x86)/AdoptOpenJDK/jdk-11.0.11.9-hotspot 9 | jvm.arguments= 10 | offline.mode=false 11 | override.workspace.settings=true 12 | show.console.view=true 13 | show.executions.view=true 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AutoRun 2 | fuck unirun 3 | 4 | ## 坐标拾取 5 | 6 | 使用高德的坐标拾取:[高德坐标拾取1](https://lbs.gaode.com/console/show/picker) or [高德坐标拾取2](https://lbs.amap.com/tools/picker) 7 | 8 | 9 | ## 分析过程 10 | 11 | https://www.jysafe.cn/4707.air 12 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /release/ 3 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | compileSdkVersion 30 7 | buildToolsVersion "30.0.3" 8 | 9 | defaultConfig { 10 | applicationId "com.example.autorun" 11 | minSdkVersion 22 12 | targetSdkVersion 31 13 | versionCode 1 14 | versionName "1.3.0" 15 | multiDexEnabled true 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | buildFeatures { 31 | viewBinding true 32 | } 33 | packagingOptions { 34 | resources { 35 | excludes += ['META-INF/DEPENDENCIES', 'androidsupportmultidexversion.txt'] 36 | } 37 | } 38 | namespace 'com.example.autorun' 39 | 40 | } 41 | 42 | dependencies { 43 | 44 | implementation 'androidx.appcompat:appcompat:1.3.0' 45 | implementation 'com.google.android.material:material:1.3.0' 46 | implementation 'androidx.annotation:annotation:1.2.0' 47 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 48 | implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1' 49 | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1' 50 | testImplementation 'junit:junit:4.13.2' 51 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 52 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 53 | compileOnly 'org.projectlombok:lombok:1.18.24' 54 | annotationProcessor 'org.projectlombok:lombok:1.18.24' 55 | api 'com.github.ok2c.hc5.android:httpclient-android:0.1.1' 56 | implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.3' 57 | implementation 'com.android.support:multidex:1.0.3' 58 | // https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 59 | implementation 'org.apache.commons:commons-lang3:3.12.0' 60 | // https://mvnrepository.com/artifact/org.gavaghan/geodesy 61 | implementation 'org.gavaghan:geodesy:1.1.3' 62 | // implementation 'ch.qos.logback:logback-classic:1.2.3' 63 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/autorun/CheckAllowTest.java: -------------------------------------------------------------------------------- 1 | package com.example.autorun; 2 | 3 | import com.example.autorun.helper.CheckAllow; 4 | 5 | import org.junit.Test; 6 | 7 | public class CheckAllowTest { 8 | @Test 9 | public void test(){ 10 | System.out.println("=============="); 11 | new CheckAllow().run(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/autorun/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.example.autorun; 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4; 4 | 5 | import org.junit.runner.RunWith; 6 | 7 | 8 | /** 9 | * Instrumented test, which will execute on an Android device. 10 | * 11 | * @see Testing documentation 12 | */ 13 | @RunWith(AndroidJUnit4.class) 14 | public class ExampleInstrumentedTest { 15 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/autorun/NoSSLv3SocketFactory.java: -------------------------------------------------------------------------------- 1 | package com.example.autorun; 2 | 3 | import java.io.IOException; 4 | import java.net.InetAddress; 5 | import java.net.Socket; 6 | 7 | import javax.net.ssl.HttpsURLConnection; 8 | import javax.net.ssl.SSLSocket; 9 | import javax.net.ssl.SSLSocketFactory; 10 | 11 | public class NoSSLv3SocketFactory extends SSLSocketFactory { 12 | private final SSLSocketFactory delegate; 13 | 14 | public NoSSLv3SocketFactory() { 15 | this.delegate = HttpsURLConnection.getDefaultSSLSocketFactory(); 16 | } 17 | 18 | public NoSSLv3SocketFactory(SSLSocketFactory delegate) { 19 | this.delegate = delegate; 20 | } 21 | 22 | @Override 23 | public String[] getDefaultCipherSuites() { 24 | // return delegate.getDefaultCipherSuites(); 25 | return new String[]{ 26 | "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256" 27 | }; 28 | } 29 | 30 | @Override 31 | public String[] getSupportedCipherSuites() { 32 | // return delegate.getSupportedCipherSuites(); 33 | 34 | return new String[]{ 35 | "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256" 36 | }; 37 | } 38 | 39 | private Socket makeSocketSafe(Socket socket) { 40 | if (socket instanceof SSLSocket) { 41 | String[] protocols = { 42 | "TLSv1.1", 43 | "TLSv1.2" 44 | }; 45 | ((SSLSocket) socket).setEnabledProtocols(protocols); 46 | } 47 | return socket; 48 | } 49 | 50 | @Override 51 | public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { 52 | return makeSocketSafe(delegate.createSocket(s, host, port, autoClose)); 53 | } 54 | 55 | @Override 56 | public Socket createSocket(String host, int port) throws IOException { 57 | return makeSocketSafe(delegate.createSocket(host, port)); 58 | } 59 | 60 | @Override 61 | public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { 62 | return makeSocketSafe(delegate.createSocket(host, port, localHost, localPort)); 63 | } 64 | 65 | @Override 66 | public Socket createSocket(InetAddress host, int port) throws IOException { 67 | return makeSocketSafe(delegate.createSocket(host, port)); 68 | } 69 | 70 | @Override 71 | public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { 72 | return makeSocketSafe(delegate.createSocket(address, port, localAddress, localPort)); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/autorun/data/LoginDataSource.java: -------------------------------------------------------------------------------- 1 | package com.example.autorun.data; 2 | 3 | import com.example.autorun.data.model.LoggedInUser; 4 | 5 | import java.io.IOException; 6 | 7 | /** 8 | * Class that handles authentication w/ login credentials and retrieves user information. 9 | */ 10 | public class LoginDataSource { 11 | 12 | public Result login(String username, String password) { 13 | 14 | try { 15 | // TODO: handle loggedInUser authentication 16 | LoggedInUser fakeUser = 17 | new LoggedInUser( 18 | java.util.UUID.randomUUID().toString(), 19 | "Jane Doe"); 20 | return new Result.Success<>(fakeUser); 21 | } catch (Exception e) { 22 | return new Result.Error(new IOException("Error logging in", e)); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/autorun/data/LoginRepository.java: -------------------------------------------------------------------------------- 1 | package com.example.autorun.data; 2 | 3 | import com.example.autorun.data.model.LoggedInUser; 4 | 5 | /** 6 | * Class that requests authentication and user information from the remote data source and 7 | * maintains an in-memory cache of login status and user credentials information. 8 | */ 9 | public class LoginRepository { 10 | 11 | private static volatile LoginRepository instance; 12 | 13 | private LoginDataSource dataSource; 14 | 15 | // If user credentials will be cached in local storage, it is recommended it be encrypted 16 | // @see https://developer.android.com/training/articles/keystore 17 | private LoggedInUser user = null; 18 | 19 | // private constructor : singleton access 20 | private LoginRepository(LoginDataSource dataSource) { 21 | this.dataSource = dataSource; 22 | } 23 | 24 | public static LoginRepository getInstance(LoginDataSource dataSource) { 25 | if (instance == null) { 26 | instance = new LoginRepository(dataSource); 27 | } 28 | return instance; 29 | } 30 | 31 | 32 | private void setLoggedInUser(LoggedInUser user) { 33 | this.user = user; 34 | // If user credentials will be cached in local storage, it is recommended it be encrypted 35 | // @see https://developer.android.com/training/articles/keystore 36 | } 37 | 38 | public Result login(String username, String password) { 39 | // handle login 40 | Result result = dataSource.login(username, password); 41 | if (result instanceof Result.Success) { 42 | setLoggedInUser(((Result.Success) result).getData()); 43 | } 44 | return result; 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/autorun/data/Result.java: -------------------------------------------------------------------------------- 1 | package com.example.autorun.data; 2 | 3 | /** 4 | * A generic class that holds a result success w/ data or an error exception. 5 | */ 6 | public class Result { 7 | // hide the private constructor to limit subclass types (Success, Error) 8 | private Result() { 9 | } 10 | 11 | @Override 12 | public String toString() { 13 | if (this instanceof Result.Success) { 14 | Result.Success success = (Result.Success) this; 15 | return "Success[data=" + success.getData().toString() + "]"; 16 | } else if (this instanceof Result.Error) { 17 | Result.Error error = (Result.Error) this; 18 | return "Error[exception=" + error.getError().toString() + "]"; 19 | } 20 | return ""; 21 | } 22 | 23 | // Success sub-class 24 | public final static class Success extends Result { 25 | private T data; 26 | 27 | public Success(T data) { 28 | this.data = data; 29 | } 30 | 31 | public T getData() { 32 | return this.data; 33 | } 34 | } 35 | 36 | // Error sub-class 37 | public final static class Error extends Result { 38 | private Exception error; 39 | 40 | public Error(Exception error) { 41 | this.error = error; 42 | } 43 | 44 | public Exception getError() { 45 | return this.error; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/autorun/data/model/LoggedInUser.java: -------------------------------------------------------------------------------- 1 | package com.example.autorun.data.model; 2 | 3 | /** 4 | * Data class that captures user information for logged in users retrieved from LoginRepository 5 | */ 6 | public class LoggedInUser { 7 | 8 | private String userId; 9 | private String displayName; 10 | 11 | public LoggedInUser(String userId, String displayName) { 12 | this.userId = userId; 13 | this.displayName = displayName; 14 | } 15 | 16 | public String getDisplayName() { 17 | return displayName; 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/autorun/helper/AESUtil.java: -------------------------------------------------------------------------------- 1 | package com.example.autorun.helper; 2 | 3 | 4 | import android.util.Base64; 5 | 6 | import java.nio.charset.StandardCharsets; 7 | 8 | import javax.crypto.Cipher; 9 | import javax.crypto.spec.SecretKeySpec; 10 | 11 | 12 | /** 13 | * @Author jiyec 14 | * @Date 2021/9/5 10:18 15 | * @Version 1.0 16 | **/ 17 | public class AESUtil { 18 | 19 | // 加密 20 | public static String Encrypt(String sSrc, String sKey) throws Exception { 21 | if (sKey == null) { 22 | System.out.print("Key为空null"); 23 | return null; 24 | } 25 | // 判断Key是否为16位 26 | if (sKey.length() != 16) { 27 | System.out.print("Key长度不是16位"); 28 | return null; 29 | } 30 | byte[] raw = sKey.getBytes("utf-8"); 31 | SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); 32 | Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");//"算法/模式/补码方式" 33 | cipher.init(Cipher.ENCRYPT_MODE, skeySpec); 34 | byte[] encrypted = cipher.doFinal(sSrc.getBytes("utf-8")); 35 | return Base64.encodeToString(encrypted, 1);//此处使用BASE64做转码功能,同时能起到2次加密的作用。 36 | } 37 | 38 | // 解密 39 | public static String Decrypt(String sSrc, String sKey) throws Exception { 40 | try { 41 | // 判断Key是否正确 42 | if (sKey == null) { 43 | System.out.print("Key为空null"); 44 | return null; 45 | } 46 | // 判断Key是否为16位 47 | if (sKey.length() != 16) { 48 | System.out.print("Key长度不是16位"); 49 | return null; 50 | } 51 | byte[] raw = sKey.getBytes(StandardCharsets.UTF_8); 52 | SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); 53 | Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); 54 | cipher.init(Cipher.DECRYPT_MODE, skeySpec); 55 | byte[] encrypted1 = Base64.decode(sSrc, 1);//先用base64解密 56 | try { 57 | byte[] original = cipher.doFinal(encrypted1); 58 | String originalString = new String(original, StandardCharsets.UTF_8); 59 | return originalString; 60 | } catch (Exception e) { 61 | System.out.println(e.toString()); 62 | return null; 63 | } 64 | } catch (Exception ex) { 65 | System.out.println(ex.toString()); 66 | return null; 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/autorun/helper/App.java: -------------------------------------------------------------------------------- 1 | package com.example.autorun.helper; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.view.View; 7 | import android.widget.ProgressBar; 8 | import android.widget.TextView; 9 | 10 | import androidx.appcompat.widget.TintContextWrapper; 11 | 12 | import com.example.autorun.ui.login.LoginActivity; 13 | import com.fasterxml.jackson.core.type.TypeReference; 14 | 15 | import org.apache.hc.core5.http.ParseException; 16 | import org.runrun.entity.AppConfig; 17 | import org.runrun.entity.Location; 18 | import org.runrun.entity.NewRecordBody; 19 | import org.runrun.entity.Response; 20 | import org.runrun.entity.ResponseType.NewRecordResult; 21 | import org.runrun.entity.ResponseType.RunStandard; 22 | import org.runrun.entity.ResponseType.SchoolBound; 23 | import org.runrun.entity.ResponseType.SignInTf; 24 | import org.runrun.entity.ResponseType.UserInfo; 25 | import org.runrun.entity.SignInOrSignBackBody; 26 | import org.runrun.run.Request; 27 | import org.runrun.utils.FileUtil; 28 | import org.runrun.utils.JsonUtils; 29 | import org.runrun.utils.TrackUtils; 30 | 31 | import java.io.IOException; 32 | import java.io.InputStream; 33 | import java.text.SimpleDateFormat; 34 | import java.util.*; 35 | 36 | import lombok.Setter; 37 | 38 | /** 39 | * Hello world! 40 | * 41 | */ 42 | public class App extends Thread 43 | { 44 | AppConfig config; 45 | @Setter 46 | private InputStream mapInput; 47 | 48 | @Setter 49 | private TextView resultArea; 50 | @Setter 51 | ProgressBar loadingProgressBar; 52 | public static String ERROR; 53 | @Setter 54 | private String type; 55 | private final StringBuffer token; 56 | 57 | public App(AppConfig config) { 58 | this.config = config; 59 | token = config.getToken(); 60 | } 61 | 62 | // 跑步 63 | @SuppressLint("DefaultLocale") 64 | public void runRun() throws IOException, ParseException { 65 | 66 | appendMsg("开始"); 67 | // ==========配置 START============== 68 | String phone = config.getPhone(); 69 | String password = config.getPassword(); 70 | int schoolSite = 0; // 0航空港,1龙泉暂不支持 71 | long runDistance = config.getDistance(); // 路程米 72 | int runTime = config.getRunTime(); // 时间分钟 73 | 74 | // 型号仓库: https://github.com/KHwang9883/MobileModels 75 | // ==========配置 END============== 76 | 77 | if (config.getBrand().length() == 0) { 78 | appendMsg("请配置手机型号信息"); 79 | return; 80 | } 81 | // 计算平均配速,防止跑太快 82 | double average = 1.0 * runTime / runDistance * 1000; 83 | 84 | if(Double.isNaN(average)){ 85 | appendMsg("输入不正确"); 86 | return; 87 | } 88 | if (average < 6) { 89 | String[] notice = { 90 | "我认为这种事情是不可能的", 91 | "太快了", 92 | "要死了", 93 | "你正在自毁", 94 | "你正在自残", 95 | "你得锻炼正造成身体上的损伤", 96 | "六分是养身", 97 | "七分是自娱", 98 | "八分是治愈" 99 | }; 100 | appendMsg("八分是治愈,七分是自娱,六分是养身,五分是自伤,四分是自残,三分是自毁。"); 101 | appendMsg(String.format("你的配速是:%.2f 分钟/公里, %s", average, notice[(int) average])); 102 | return; 103 | } 104 | appendMsg(String.format("平均配速:%.2f\n", average)); 105 | 106 | // if(config.getRunTime() > 0)return; 107 | 108 | Request request = new Request(token.toString(), config); 109 | appendMsg("开始登录"); 110 | Response userInfoResponse = request.login(phone, password); 111 | UserInfo userInfo = userInfoResponse.getResponse(); 112 | if(userInfo == null ) { 113 | appendMsg("登录失败"); 114 | return; 115 | } 116 | long userId = userInfo.getUserId(); 117 | if (userId != -1) { 118 | token.delete(0, token.length()); 119 | token.append(request.getToken()); 120 | 121 | appendMsg("获取跑步标准"); 122 | RunStandard runStandard = request.getRunStandard(userInfo.getSchoolId()); 123 | appendMsg("获取学校经纬度区域信息"); 124 | SchoolBound[] schoolBounds = request.getSchoolBound(userInfo.getSchoolId()); 125 | 126 | appendMsg("生成跑步数据"); 127 | // 新增跑步数据 128 | NewRecordBody recordBody = new NewRecordBody(); 129 | recordBody.setUserId(userId); 130 | recordBody.setAppVersions(config.getAppVersion()); 131 | recordBody.setBrand(config.getBrand()); 132 | recordBody.setMobileType(config.getMobileType()); 133 | recordBody.setSysVersions(config.getSysVersion()); 134 | recordBody.setRunDistance(runDistance); 135 | recordBody.setRunTime(runTime); 136 | recordBody.setYearSemester(runStandard.getSemesterYear()); 137 | recordBody.setRealityTrackPoints(schoolBounds[schoolSite].getSiteBound() + "--"); 138 | 139 | // 今天日期 年-月-日 140 | @SuppressLint("SimpleDateFormat") SimpleDateFormat sdf = new SimpleDateFormat(); 141 | sdf.applyPattern("yyyy-MM-dd"); 142 | Date date = new Date(); 143 | String formatTime = sdf.format(date); 144 | recordBody.setRecordDate(formatTime); 145 | 146 | // 生成跑步数据 147 | String tack = genTack(runDistance); 148 | recordBody.setTrackPoints(tack); 149 | 150 | //发送数据 151 | appendMsg("提交跑步数据"); 152 | String result = request.recordNew(recordBody); 153 | Response response = JsonUtils.string2Obj(result, new TypeReference>() { 154 | }); 155 | appendMsg(""); 156 | appendMsg("返回原始数据:" + result); 157 | appendMsg("解析数据:"); 158 | appendMsg("跑步结果:" + response.getCode() + " - " + response.getMsg()); 159 | NewRecordResult response1 = response.getResponse(); 160 | appendMsg("生成的跑步ID:" + response1.getRecordId()); 161 | appendMsg("结果状态:" + response1.getResultStatus()); 162 | appendMsg("结果描述:" + response1.getResultDesc()); 163 | appendMsg("超速警告次数:" + response1.getOverSpeedWarn()); 164 | appendMsg("警告内容:" + response1.getWarnContent()); 165 | } else { 166 | appendMsg("用户Id获取失败"); 167 | } 168 | } 169 | 170 | // 签到/签退 171 | public void runSignInOrBack() throws IOException { 172 | String phone = config.getPhone(); 173 | String password = config.getPassword(); 174 | Request request = new Request(token.toString(), config); 175 | Response userInfoResponse = request.getUserInfo(); 176 | //更新token 177 | if(userInfoResponse.getCode() != 10000) { 178 | appendMsg("token无效,更新"); 179 | userInfoResponse = request.login(phone, password); 180 | token.delete(0, token.length()); 181 | token.append(request.getToken()); 182 | } 183 | UserInfo userInfo = userInfoResponse.getResponse(); 184 | 185 | if (userInfo != null) { 186 | Long studentId = userInfo.getStudentId(); 187 | SignInTf signInTf = request.getSignInTf(String.valueOf(studentId)); 188 | appendMsg("待签到俱乐部:{}" + signInTf.toString()); 189 | String signStatus = signInTf.getSignStatus(); 190 | String signInStatus = signInTf.getSignInStatus(); 191 | String signBackStatus = signInTf.getSignBackStatus(); 192 | 193 | if ("1".equals(signInStatus) && "1".equals(signBackStatus)) { 194 | appendMsg("未知状态"); 195 | return ; 196 | } 197 | 198 | String signType; 199 | if ("1".equals(signStatus)) { 200 | // 可签到 201 | signType = "1"; 202 | } else if ("1".equals(signInStatus) && "2".equals(signStatus)) { 203 | // 可签退 204 | signType = "2"; 205 | } else { 206 | appendMsg("非可签到签退状态,或没有可签到项目"); 207 | return ; 208 | } 209 | 210 | SignInOrSignBackBody signInOrSignBackBody = new SignInOrSignBackBody( 211 | signInTf.getActivityId(), 212 | signInTf.getLatitude(), 213 | signInTf.getLongitude(), 214 | signType, 215 | studentId); 216 | 217 | Response signInOrSignBack = request.signInOrSignBack(signInOrSignBackBody); 218 | appendMsg("签到结果:"); 219 | appendMsg(signInOrSignBack.getMsg()); 220 | } else { 221 | appendMsg("用户信息获取失败"); 222 | } 223 | } 224 | public void run(){ 225 | try{ 226 | if("run".equals(type)) { 227 | runRun(); 228 | }else if("signInOrBack".equals(type)){ 229 | runSignInOrBack(); 230 | }else{ 231 | appendMsg("未知操作"); 232 | } 233 | }catch (Exception e){ 234 | e.printStackTrace(); 235 | String msg; 236 | if(e instanceof RuntimeException) { 237 | StackTraceElement traceElement = e.getStackTrace()[0]; 238 | msg = e.getMessage() + "\n异常来源:" +traceElement.getClassName() + " - line:" + traceElement.getLineNumber(); 239 | }else{ 240 | msg = e.getMessage(); 241 | } 242 | appendMsg(msg); 243 | }finally { 244 | stopLoading(); 245 | } 246 | } 247 | 248 | public void appendMsg(String msg){ 249 | Context context = resultArea.getContext(); 250 | 251 | Activity activity = null; 252 | if(context instanceof LoginActivity) { 253 | activity = (Activity) context; 254 | }else if(context instanceof TintContextWrapper){ 255 | activity = (Activity)((TintContextWrapper) context).getBaseContext(); 256 | } 257 | if(activity != null) 258 | activity.runOnUiThread(new Runnable() { 259 | @Override 260 | public void run() { 261 | resultArea.append("\n" + msg); 262 | } 263 | }); 264 | } 265 | 266 | public String genTack(long distance) { 267 | if(mapInput == null) 268 | mapInput = org.runrun.App.class.getResourceAsStream("/map.json"); 269 | String json = FileUtil.ReadFile(mapInput); 270 | try { 271 | mapInput.close(); 272 | } catch (IOException e) { 273 | e.printStackTrace(); 274 | } 275 | if (json.length() == 0) { 276 | System.out.println("配置读取失败"); 277 | return null; 278 | } 279 | Location[] locations = JsonUtils.string2Obj(json, Location[].class); 280 | return TrackUtils.gen(distance, locations); 281 | } 282 | 283 | public void stopLoading(){ 284 | Context context = resultArea.getContext(); 285 | // 4.4 TintContextWrapper 286 | // 5.1 LoginActivity 287 | 288 | Activity activity = null; 289 | if(context instanceof LoginActivity) { 290 | activity = (Activity) context; 291 | }else if(context instanceof TintContextWrapper){ 292 | activity = (Activity)((TintContextWrapper) context).getBaseContext(); 293 | } 294 | 295 | if(activity != null) 296 | activity.runOnUiThread(new Runnable() { 297 | @Override 298 | public void run() { 299 | loadingProgressBar.setVisibility(View.INVISIBLE); 300 | } 301 | }); 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/autorun/helper/CheckAllow.java: -------------------------------------------------------------------------------- 1 | package com.example.autorun.helper; 2 | 3 | import android.os.Build; 4 | import android.os.Looper; 5 | import android.util.Base64; 6 | import android.util.Log; 7 | import android.widget.TextView; 8 | 9 | import org.runrun.utils.HTTP.HttpUtil2; 10 | import org.runrun.utils.JsonUtils; 11 | 12 | import java.nio.charset.StandardCharsets; 13 | import java.util.Date; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | import java.util.function.Consumer; 17 | 18 | import lombok.AllArgsConstructor; 19 | import lombok.Data; 20 | import lombok.EqualsAndHashCode; 21 | import lombok.NoArgsConstructor; 22 | import lombok.extern.slf4j.Slf4j; 23 | 24 | @Slf4j 25 | @Data 26 | @EqualsAndHashCode(callSuper=false) 27 | @AllArgsConstructor 28 | @NoArgsConstructor 29 | public class CheckAllow extends Thread{ 30 | private static String TAG = CheckAllow.class.getSimpleName(); 31 | private String androidId = null; 32 | private String uuid = null; 33 | private String apkVersion = null; 34 | private TextView resultArea; 35 | private Consumer> consumer; 36 | 37 | @Override 38 | public void run() { 39 | super.run(); 40 | 41 | try { 42 | // Log.i(TAG, "s = " + s); 43 | long time = new Date().getTime(); 44 | Map data = new HashMap<>(); 45 | data.put("time", time); 46 | data.put("type", "unirun"); 47 | Log.i(TAG, "serial: " + SystemUtil.getSerial()); 48 | Log.i(TAG, "android id: " + androidId); 49 | data.put("serial", SystemUtil.getSerial()); 50 | data.put("android_id", androidId); 51 | data.put("device_brand", SystemUtil.getDeviceBrand()); 52 | data.put("system_model", SystemUtil.getSystemModel()); 53 | data.put("system_version", SystemUtil.getSystemVersion()); 54 | data.put("apk_version", apkVersion); 55 | String checkStr = JsonUtils.obj2String(data); 56 | String key = "unirun1234554321"; 57 | Log.i(TAG, "info:" + checkStr); 58 | checkStr = AESUtil.Encrypt(checkStr, key); 59 | // Log.i(TAG, checkStr); 60 | // checkStr = AESUtil.Decrypt(checkStr, key); 61 | // Log.i(TAG, checkStr); 62 | String reqStr = Base64.encodeToString(checkStr.getBytes(StandardCharsets.UTF_8), 1); 63 | 64 | byte[] s1 = new HttpUtil2().doPostJson2Byte("http://task.jysafe.cn/task/unirun3.php", null, reqStr); 65 | String s = new String(s1); 66 | Log.i(TAG, "s = " + s); 67 | String result = AESUtil.Decrypt(s, key); 68 | Map ret = JsonUtils.string2Obj(result, Map.class); 69 | 70 | // Log.i(TAG, "result = " + result); 71 | long retTime = (long)ret.get("time"); 72 | 73 | if(!(retTime == time && "ok".equals(ret.get("result")))) 74 | System.exit(-1); 75 | if (ret.containsKey("message") && consumer != null) { 76 | Looper.prepare(); 77 | 78 | String message = (String) ret.get("message"); 79 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 80 | consumer.accept(ret); 81 | } 82 | resultArea.append("\n" + message); 83 | Looper.loop(); 84 | } 85 | } catch (Exception e) { 86 | e.printStackTrace(); 87 | System.exit(-1); 88 | } 89 | } 90 | public String toBinary(String str){ 91 | char[] strChar = str.toCharArray(); 92 | StringBuilder result= new StringBuilder(); 93 | for (char c : strChar) { 94 | result.append(Integer.toBinaryString(c)).append(" "); 95 | } 96 | return result.toString(); 97 | } 98 | //将二进制字符串转换成int数组 99 | public int[] BinstrToIntArray(String binStr) { 100 | char[] temp=binStr.toCharArray(); 101 | int[] result=new int[temp.length]; 102 | for(int i=0;i= 23) { 86 | if (!Settings.System.canWrite(context)) { 87 | Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS, 88 | Uri.parse("package:" + context.getPackageName())); 89 | context.startActivity(intent); 90 | } else { 91 | uniqueIdentificationCode = Settings.System.getString(context.getContentResolver(), "uniqueIdentificationCode"); 92 | } 93 | } else { 94 | //获取系统配置文件中的数据,第一个参数固定的,但是需要上下文,第二个参数是之前保存的Key,第三个参数表示如果没有这个key的情况的默认值 95 | uniqueIdentificationCode = Settings.System.getString(context.getContentResolver(), "uniqueIdentificationCode"); 96 | } 97 | 98 | if (TextUtils.isEmpty(uniqueIdentificationCode)) { 99 | uniqueIdentificationCode = System.currentTimeMillis() + UUID.randomUUID().toString().substring(20).replace("-", ""); 100 | //设置系统配置文件中的数据,第一个参数固定的,但是需要上下文,第二个参数是保存的Key,第三个参数是保存的value 101 | Settings.System.putString(context.getContentResolver(), "uniqueIdentificationCode", uniqueIdentificationCode); 102 | } 103 | Log.i("getDeviceUuidId", "getDeviceUuidId: uniqueIdentificationCode = " + uniqueIdentificationCode); 104 | 105 | return uniqueIdentificationCode; 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/autorun/ui/login/LoggedInUserView.java: -------------------------------------------------------------------------------- 1 | package com.example.autorun.ui.login; 2 | 3 | /** 4 | * Class exposing authenticated user details to the UI. 5 | */ 6 | class LoggedInUserView { 7 | private final String displayName; 8 | //... other data fields that may be accessible to the UI 9 | 10 | LoggedInUserView(String displayName) { 11 | this.displayName = displayName; 12 | } 13 | 14 | String getDisplayName() { 15 | return displayName; 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/autorun/ui/login/LoginActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.autorun.ui.login; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.SharedPreferences; 8 | import android.content.pm.PackageManager; 9 | import android.net.Uri; 10 | import android.os.Bundle; 11 | import android.provider.Settings; 12 | import android.text.Editable; 13 | import android.text.TextWatcher; 14 | import android.text.method.ScrollingMovementMethod; 15 | import android.util.Log; 16 | import android.view.View; 17 | import android.view.inputmethod.EditorInfo; 18 | import android.widget.Button; 19 | import android.widget.EditText; 20 | import android.widget.ProgressBar; 21 | import android.widget.TextView; 22 | import android.widget.Toast; 23 | 24 | import androidx.activity.result.ActivityResultLauncher; 25 | import androidx.activity.result.contract.ActivityResultContracts; 26 | import androidx.annotation.StringRes; 27 | import androidx.appcompat.app.AlertDialog; 28 | import androidx.appcompat.app.AppCompatActivity; 29 | import androidx.lifecycle.ViewModelProvider; 30 | 31 | import com.example.autorun.R; 32 | import com.example.autorun.databinding.ActivityLoginBinding; 33 | import com.example.autorun.helper.App; 34 | import com.example.autorun.helper.CheckAllow; 35 | import com.example.autorun.helper.SystemUtil; 36 | 37 | import org.runrun.entity.AppConfig; 38 | 39 | import java.io.InputStream; 40 | import java.lang.reflect.Field; 41 | import java.util.Objects; 42 | 43 | 44 | public class LoginActivity extends AppCompatActivity { 45 | 46 | private static String TAG = LoginActivity.class.getSimpleName(); 47 | public static final String PREFS_NAME = LoginActivity.class.getName(); 48 | public static final String IS_LOCAL = "IS_LOCAL"; 49 | public static final String HOSTS_URI = "HOST_URI"; 50 | public static final String NET_HOST_FILE = "net_hosts"; 51 | public static final String MAP_PREFIX = "地图:"; 52 | AppConfig appConfig = new AppConfig(); 53 | private LoginViewModel loginViewModel; 54 | private ActivityLoginBinding binding; 55 | 56 | ActivityResultLauncher selectFileLauncher = registerForActivityResult( 57 | new ActivityResultContracts.StartActivityForResult(), 58 | result -> { 59 | Log.i(TAG, String.format("code:%d", result.getResultCode())); 60 | if (result.getResultCode() == RESULT_OK) { 61 | // There are no request codes 62 | Intent data = result.getData(); 63 | assert data != null; 64 | setUriByPREFS(data); 65 | } 66 | } 67 | ); 68 | @SuppressLint("SetTextI18n") 69 | @Override 70 | public void onCreate(Bundle savedInstanceState) { 71 | super.onCreate(savedInstanceState); 72 | 73 | SharedPreferences settings = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); 74 | SharedPreferences.Editor editor = settings.edit(); 75 | 76 | binding = ActivityLoginBinding.inflate(getLayoutInflater()); 77 | setContentView(binding.getRoot()); 78 | 79 | loginViewModel = new ViewModelProvider(this, new LoginViewModelFactory()) 80 | .get(LoginViewModel.class); 81 | 82 | final EditText usernameEditText = binding.username; 83 | final EditText passwordEditText = binding.password; 84 | final EditText appVersionEditText = binding.inputAppVersion; 85 | final EditText inputDistance = binding.inputDistance; 86 | final EditText inputTimeEditText = binding.inputTime; 87 | final TextView resultArea = binding.result; 88 | final EditText mapFileArea = binding.mapFile; 89 | final Button loginButton = binding.login; 90 | final Button loadMapButton = binding.loadMap; 91 | final Button signInButton = binding.signInOrBack; 92 | final ProgressBar loadingProgressBar = binding.loading; 93 | 94 | usernameEditText.setText(settings.getString("phone", null)); 95 | passwordEditText.setText(settings.getString("password", null)); 96 | 97 | resultArea.setMovementMethod(ScrollingMovementMethod.getInstance()); 98 | resultArea.setHorizontallyScrolling(true); 99 | resultArea.setVerticalScrollBarEnabled(true); 100 | resultArea.setFocusable(true); 101 | 102 | loginViewModel.getLoginFormState().observe(this, loginFormState -> { 103 | if (loginFormState == null) { 104 | return; 105 | } 106 | loginButton.setEnabled(loginFormState.isDataValid()); 107 | signInButton.setEnabled(loginFormState.isDataValid()); 108 | if (loginFormState.getUsernameError() != null) { 109 | usernameEditText.setError(getString(loginFormState.getUsernameError())); 110 | } 111 | if (loginFormState.getPasswordError() != null) { 112 | passwordEditText.setError(getString(loginFormState.getPasswordError())); 113 | } 114 | }); 115 | 116 | loginViewModel.getLoginResult().observe(this, loginResult -> { 117 | if (loginResult == null) { 118 | return; 119 | } 120 | loadingProgressBar.setVisibility(View.GONE); 121 | if (loginResult.getError() != null) { 122 | showLoginFailed(loginResult.getError()); 123 | } 124 | if (loginResult.getSuccess() != null) { 125 | updateUiWithUser(loginResult.getSuccess()); 126 | } 127 | setResult(Activity.RESULT_OK); 128 | 129 | //Complete and destroy login activity once successful 130 | finish(); 131 | }); 132 | 133 | TextWatcher afterTextChangedListener = new TextWatcher() { 134 | @Override 135 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 136 | // ignore 137 | } 138 | 139 | @Override 140 | public void onTextChanged(CharSequence s, int start, int before, int count) { 141 | // ignore 142 | } 143 | 144 | @Override 145 | public void afterTextChanged(Editable s) { 146 | loginViewModel.loginDataChanged(usernameEditText.getText().toString(), 147 | passwordEditText.getText().toString()); 148 | } 149 | }; 150 | usernameEditText.addTextChangedListener(afterTextChangedListener); 151 | passwordEditText.addTextChangedListener(afterTextChangedListener); 152 | passwordEditText.setOnEditorActionListener((v, actionId, event) -> { 153 | if (actionId == EditorInfo.IME_ACTION_DONE) { 154 | App app = new App(appConfig); 155 | resultArea.setText("操作结果:"); 156 | loadingProgressBar.setVisibility(View.VISIBLE); 157 | 158 | AppConfig appConfig = new AppConfig(); 159 | appConfig.setPhone(usernameEditText.getText().toString()); 160 | appConfig.setPassword(passwordEditText.getText().toString()); 161 | appConfig.setAppVersion(appVersionEditText.getText().toString()); 162 | 163 | appConfig.setBrand(SystemUtil.getDeviceBrand()); 164 | appConfig.setMobileType(SystemUtil.getSystemModel()); 165 | appConfig.setSysVersion(SystemUtil.getSystemVersion()); 166 | 167 | String distance = inputDistance.getText().toString(); 168 | if(distance.length()>0) 169 | appConfig.setDistance(Long.parseLong(distance)); 170 | String time = inputTimeEditText.getText().toString(); 171 | if(time.length()>0) 172 | appConfig.setRunTime(Integer.parseInt(inputTimeEditText.getText().toString())); 173 | 174 | System.out.println(appConfig); 175 | app.setResultArea(resultArea); 176 | app.setLoadingProgressBar(loadingProgressBar); 177 | app.start(); 178 | } 179 | return false; 180 | }); 181 | 182 | String mapPath = settings.getString(HOSTS_URI, null); 183 | if(mapPath != null) { 184 | Log.i(TAG, "map-path: " + mapPath); 185 | String[] split = mapPath.split("%2F"); 186 | if(split.length > 0) { 187 | mapFileArea.setText(MAP_PREFIX + split[split.length - 1]); 188 | } 189 | } 190 | // loginButton.setEnabled(true); 191 | // 配置系统信息 192 | appConfig.setBrand(SystemUtil.getDeviceBrand()); 193 | appConfig.setMobileType(SystemUtil.getSystemModel()); 194 | appConfig.setSysVersion(SystemUtil.getSystemVersion()); 195 | loginButton.setOnClickListener(v -> { 196 | App app = new App(appConfig); 197 | resultArea.setText("操作结果:"); 198 | loadingProgressBar.setVisibility(View.VISIBLE); 199 | 200 | // 配置填写的信息 201 | appConfig.setPhone(usernameEditText.getText().toString()); 202 | appConfig.setPassword(passwordEditText.getText().toString()); 203 | appConfig.setAppVersion(appVersionEditText.getText().toString()); 204 | 205 | resultArea.append("存储账户信息到本地..."); 206 | editor.putString("phone", appConfig.getPhone()); 207 | editor.putString("password", appConfig.getPassword()); 208 | editor.apply(); 209 | 210 | String distance = inputDistance.getText().toString(); 211 | if(distance.length() > 0) 212 | appConfig.setDistance(Long.parseLong(distance)); 213 | String time = inputTimeEditText.getText().toString(); 214 | if(time.length() > 0) 215 | appConfig.setRunTime(Integer.parseInt(inputTimeEditText.getText().toString())); 216 | 217 | // SharedPreferences settings = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); 218 | InputStream inputStream; 219 | 220 | try { 221 | inputStream = getContentResolver().openInputStream(Uri.parse(settings.getString(HOSTS_URI, null))); 222 | } catch (Exception e) { 223 | Log.e(TAG, "HOSTS FILE NOT FOUND", e); 224 | inputStream = null; 225 | } 226 | 227 | Log.i(TAG, appConfig.toString()); 228 | app.setResultArea(resultArea); 229 | app.setLoadingProgressBar(loadingProgressBar); 230 | app.setMapInput(inputStream); 231 | app.setType("run"); 232 | app.start(); 233 | // loginViewModel.login(usernameEditText.getText().toString(), 234 | // passwordEditText.getText().toString()); 235 | }); 236 | 237 | loadMapButton.setOnClickListener(view -> selectFile()); 238 | signInButton.setOnClickListener(v -> { 239 | App app = new App(appConfig); 240 | resultArea.setText("操作结果:"); 241 | loadingProgressBar.setVisibility(View.VISIBLE); 242 | 243 | // 配置填写的信息 244 | appConfig.setPhone(usernameEditText.getText().toString()); 245 | appConfig.setPassword(passwordEditText.getText().toString()); 246 | appConfig.setAppVersion(appVersionEditText.getText().toString()); 247 | 248 | // SharedPreferences settings = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); 249 | 250 | Log.i(TAG, appConfig.toString()); 251 | app.setResultArea(resultArea); 252 | app.setLoadingProgressBar(loadingProgressBar); 253 | app.setType("signInOrBack"); 254 | app.start(); 255 | }); 256 | String id = getAndroidId(this); 257 | // String uuid = SystemUtil.getUUID(this); 258 | CheckAllow checkAllow = new CheckAllow(); 259 | checkAllow.setApkVersion(getVersionName(this)); 260 | checkAllow.setResultArea(resultArea); 261 | checkAllow.setConsumer(e -> { 262 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 263 | String message = (String)e.get("message"); 264 | boolean cancelable = (boolean)e.get("cancelable"); 265 | builder = builder 266 | .setTitle("提示") 267 | .setCancelable(false) 268 | .setMessage(message); 269 | if (cancelable) { 270 | builder = builder.setNegativeButton("确定", (dialogInterface, i) -> { 271 | 272 | }); 273 | } 274 | AlertDialog dialog = builder.create(); 275 | dialog.show(); 276 | }); 277 | checkAllow.setAndroidId(id); 278 | // checkAllow.setUuid(uuid); 279 | checkAllow.start(); 280 | 281 | } 282 | /** 283 | * 获取当前apk的版本名 284 | * 285 | * @param context 上下文 286 | * @return 287 | */ 288 | public static String getVersionName(Context context) { 289 | String versionName = ""; 290 | try { 291 | //获取软件版本号,对应AndroidManifest.xml下android:versionName 292 | versionName = context.getPackageManager(). 293 | getPackageInfo(context.getPackageName(), 0).versionName; 294 | } catch (PackageManager.NameNotFoundException e) { 295 | e.printStackTrace(); 296 | } 297 | return versionName; 298 | } 299 | 300 | public static String getAndroidId (Context context) { 301 | return Settings.System.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); 302 | } 303 | 304 | 305 | private void updateUiWithUser(LoggedInUserView model) { 306 | String welcome = getString(R.string.welcome) + model.getDisplayName(); 307 | // TODO : initiate successful logged in experience 308 | Toast.makeText(getApplicationContext(), welcome, Toast.LENGTH_LONG).show(); 309 | } 310 | 311 | private void showLoginFailed(@StringRes Integer errorString) { 312 | Toast.makeText(getApplicationContext(), errorString, Toast.LENGTH_SHORT).show(); 313 | } 314 | 315 | private void selectFile() { 316 | Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); 317 | intent.setType("*/*"); 318 | try { 319 | String SHOW_ADVANCED; 320 | try { 321 | Field f = android.provider.DocumentsContract.class.getField("EXTRA_PROMPT"); 322 | SHOW_ADVANCED = Objects.requireNonNull(f.get(f.getName())).toString(); 323 | }catch (NoSuchFieldException e){ 324 | Log.e(TAG,e.getMessage(),e); 325 | SHOW_ADVANCED = "android.content.extra.SHOW_ADVANCED"; 326 | } 327 | intent.putExtra(SHOW_ADVANCED, true); 328 | } catch (Throwable e) { 329 | Log.e(TAG, "SET EXTRA_SHOW_ADVANCED", e); 330 | } 331 | 332 | try { 333 | intent.addCategory(Intent.CATEGORY_OPENABLE); 334 | // startActivityForResult(intent, SELECT_FILE_CODE); 335 | selectFileLauncher.launch(intent); 336 | } catch (Exception e) { 337 | Toast.makeText(this, R.string.file_select_error, Toast.LENGTH_LONG).show(); 338 | Log.e(TAG, "START SELECT_FILE_ACTIVE FAIL",e); 339 | SharedPreferences settings = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); 340 | SharedPreferences.Editor editor = settings.edit(); 341 | editor.putBoolean(IS_LOCAL, false); 342 | editor.apply(); 343 | startActivity(new Intent(getApplicationContext(), LoginActivity.class)); 344 | } 345 | 346 | } 347 | 348 | @SuppressLint("SetTextI18n") 349 | private void setUriByPREFS(Intent intent) { 350 | SharedPreferences settings = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); 351 | SharedPreferences.Editor editor = settings.edit(); 352 | Uri uri = intent.getData(); 353 | int takeFlags = (Intent.FLAG_GRANT_READ_URI_PERMISSION 354 | | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 355 | try { 356 | getContentResolver().takePersistableUriPermission(uri, takeFlags); 357 | editor.putString(HOSTS_URI, uri.toString()); 358 | editor.apply(); 359 | if (checkHostUri() == 1) { 360 | String[] split = uri.toString().split("%2F"); 361 | final EditText mapFileArea = binding.mapFile; 362 | mapFileArea.setText(MAP_PREFIX + split[split.length - 1]); 363 | Toast.makeText(this, R.string.file_select_ok, Toast.LENGTH_LONG).show(); 364 | } else { 365 | Toast.makeText(this, R.string.permission_error, Toast.LENGTH_LONG).show(); 366 | } 367 | 368 | } catch (Exception e) { 369 | Log.e(TAG, "permission error", e); 370 | } 371 | 372 | } 373 | private int checkHostUri() { 374 | SharedPreferences settings = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); 375 | if (settings.getBoolean(LoginActivity.IS_LOCAL, true)) { 376 | try { 377 | getContentResolver().openInputStream(Uri.parse(settings.getString(HOSTS_URI, null))).close(); 378 | return 1; 379 | } catch (Exception e) { 380 | Log.e(TAG, "HOSTS FILE NOT FOUND", e); 381 | return -1; 382 | } 383 | } else { 384 | try { 385 | openFileInput(LoginActivity.NET_HOST_FILE).close(); 386 | return 2; 387 | } catch (Exception e) { 388 | Log.e(TAG, "NET HOSTS FILE NOT FOUND", e); 389 | return -2; 390 | } 391 | } 392 | } 393 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/autorun/ui/login/LoginFormState.java: -------------------------------------------------------------------------------- 1 | package com.example.autorun.ui.login; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | /** 6 | * Data validation state of the login form. 7 | */ 8 | class LoginFormState { 9 | @Nullable 10 | private final Integer usernameError; 11 | @Nullable 12 | private final Integer passwordError; 13 | private final boolean isDataValid; 14 | 15 | LoginFormState(@Nullable Integer usernameError, @Nullable Integer passwordError) { 16 | this.usernameError = usernameError; 17 | this.passwordError = passwordError; 18 | this.isDataValid = false; 19 | } 20 | 21 | LoginFormState(boolean isDataValid) { 22 | this.usernameError = null; 23 | this.passwordError = null; 24 | this.isDataValid = isDataValid; 25 | } 26 | 27 | @Nullable 28 | Integer getUsernameError() { 29 | return usernameError; 30 | } 31 | 32 | @Nullable 33 | Integer getPasswordError() { 34 | return passwordError; 35 | } 36 | 37 | boolean isDataValid() { 38 | return isDataValid; 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/autorun/ui/login/LoginResult.java: -------------------------------------------------------------------------------- 1 | package com.example.autorun.ui.login; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | /** 6 | * Authentication result : success (user details) or error message. 7 | */ 8 | class LoginResult { 9 | @Nullable 10 | private LoggedInUserView success; 11 | @Nullable 12 | private Integer error; 13 | 14 | LoginResult(@Nullable Integer error) { 15 | this.error = error; 16 | } 17 | 18 | LoginResult(@Nullable LoggedInUserView success) { 19 | this.success = success; 20 | } 21 | 22 | @Nullable 23 | LoggedInUserView getSuccess() { 24 | return success; 25 | } 26 | 27 | @Nullable 28 | Integer getError() { 29 | return error; 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/autorun/ui/login/LoginViewModel.java: -------------------------------------------------------------------------------- 1 | package com.example.autorun.ui.login; 2 | 3 | import androidx.lifecycle.LiveData; 4 | import androidx.lifecycle.MutableLiveData; 5 | import androidx.lifecycle.ViewModel; 6 | 7 | import android.util.Patterns; 8 | 9 | import com.example.autorun.data.LoginRepository; 10 | import com.example.autorun.data.Result; 11 | import com.example.autorun.data.model.LoggedInUser; 12 | import com.example.autorun.R; 13 | 14 | public class LoginViewModel extends ViewModel { 15 | 16 | private MutableLiveData loginFormState = new MutableLiveData<>(); 17 | private MutableLiveData loginResult = new MutableLiveData<>(); 18 | private LoginRepository loginRepository; 19 | 20 | LoginViewModel(LoginRepository loginRepository) { 21 | this.loginRepository = loginRepository; 22 | } 23 | 24 | LiveData getLoginFormState() { 25 | return loginFormState; 26 | } 27 | 28 | LiveData getLoginResult() { 29 | return loginResult; 30 | } 31 | 32 | public void login(String username, String password) { 33 | // can be launched in a separate asynchronous job 34 | Result result = loginRepository.login(username, password); 35 | 36 | if (result instanceof Result.Success) { 37 | LoggedInUser data = ((Result.Success) result).getData(); 38 | loginResult.setValue(new LoginResult(new LoggedInUserView(data.getDisplayName()))); 39 | } else { 40 | loginResult.setValue(new LoginResult(R.string.login_failed)); 41 | } 42 | } 43 | 44 | public void loginDataChanged(String username, String password) { 45 | if (!isUserNameValid(username)) { 46 | loginFormState.setValue(new LoginFormState(R.string.invalid_username, null)); 47 | } else if (!isPasswordValid(password)) { 48 | loginFormState.setValue(new LoginFormState(null, R.string.invalid_password)); 49 | } else { 50 | loginFormState.setValue(new LoginFormState(true)); 51 | } 52 | } 53 | 54 | // A placeholder username validation check 55 | private boolean isUserNameValid(String username) { 56 | if (username == null) { 57 | return false; 58 | } 59 | if (username.length()==11) { 60 | return Patterns.PHONE.matcher(username).matches(); 61 | } else { 62 | return username.trim().isEmpty(); 63 | } 64 | } 65 | 66 | // A placeholder password validation check 67 | private boolean isPasswordValid(String password) { 68 | return password != null && password.trim().length() > 3; 69 | } 70 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/autorun/ui/login/LoginViewModelFactory.java: -------------------------------------------------------------------------------- 1 | package com.example.autorun.ui.login; 2 | 3 | import androidx.lifecycle.ViewModel; 4 | import androidx.lifecycle.ViewModelProvider; 5 | import androidx.annotation.NonNull; 6 | 7 | import com.example.autorun.data.LoginDataSource; 8 | import com.example.autorun.data.LoginRepository; 9 | 10 | /** 11 | * ViewModel provider factory to instantiate LoginViewModel. 12 | * Required given LoginViewModel has a non-empty constructor 13 | */ 14 | public class LoginViewModelFactory implements ViewModelProvider.Factory { 15 | 16 | @NonNull 17 | @Override 18 | @SuppressWarnings("unchecked") 19 | public T create(@NonNull Class modelClass) { 20 | if (modelClass.isAssignableFrom(LoginViewModel.class)) { 21 | return (T) new LoginViewModel(LoginRepository.getInstance(new LoginDataSource())); 22 | } else { 23 | throw new IllegalArgumentException("Unknown ViewModel class"); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/App.java: -------------------------------------------------------------------------------- 1 | package org.runrun; 2 | 3 | import com.fasterxml.jackson.core.type.TypeReference; 4 | import org.runrun.entity.AppConfig; 5 | import org.runrun.entity.Location; 6 | import org.runrun.entity.NewRecordBody; 7 | import org.runrun.entity.Response; 8 | import org.runrun.entity.ResponseType.NewRecordResult; 9 | import org.runrun.entity.ResponseType.RunStandard; 10 | import org.runrun.entity.ResponseType.SchoolBound; 11 | import org.runrun.entity.ResponseType.UserInfo; 12 | import org.runrun.run.Request; 13 | import org.runrun.utils.FileUtil; 14 | import org.runrun.utils.JsonUtils; 15 | import org.runrun.utils.TrackUtils; 16 | 17 | import java.io.IOException; 18 | import java.io.InputStream; 19 | import java.text.SimpleDateFormat; 20 | import java.util.Date; 21 | import java.util.Scanner; 22 | 23 | /** 24 | * 运行主体 25 | * 说明: 26 | * 本程序仅供学习交流使用,请在下载后24小时内及时删除。 27 | * 本程序不提供后续更新服务。 28 | * 若由使用本程序造成包括但不限于校方警告、课程分数计0、封号、勒令退学等不良后果,一切责任由使用者承当。 29 | * 使用本程序即代表使用者同意以上条款。 30 | * 31 | */ 32 | public class App { 33 | public static void main(String[] args) throws IOException { 34 | // ==========配置 START============== 35 | boolean input = true; 36 | String phone = ""; 37 | String password = ""; 38 | String token = ""; 39 | int schoolSite = 0; // 0航空港,1龙泉 40 | long runDistance = 0; // 路程米 41 | int runTime = 0; // 时间分钟 42 | // 型号仓库: https://github.com/KHwang9883/MobileModels 43 | AppConfig config = new AppConfig() {{ 44 | setAppVersion("1.8.0"); // APP版本,一般不做修改 45 | setBrand(""); // 手机品牌 46 | setMobileType(""); // 型号 47 | setSysVersion("10"); // 系统版本 48 | }}; 49 | // ==========配置 END============== 50 | 51 | if (config.getBrand().length() == 0) { 52 | System.out.println("请配置手机型号信息"); 53 | System.exit(-1); 54 | } 55 | if (input) { 56 | Scanner scanner = new Scanner(System.in); 57 | System.out.print("账号(手机):"); 58 | phone = scanner.next(); 59 | System.out.print("密码:"); 60 | password = scanner.next(); 61 | System.out.print("跑步路程(米):"); 62 | runDistance = scanner.nextLong(); // 路程米 63 | System.out.print("跑步时间(分钟):"); 64 | runTime = scanner.nextInt(); // 时间分钟 65 | } 66 | // 计算平均配速,防止跑太快 67 | double average = 1.0 * runTime / runDistance * 1000; 68 | if (average < 6) { 69 | String[] notice = { 70 | "我认为这种事情是不可能的", 71 | "太快了", 72 | "要死了", 73 | "你正在自毁", 74 | "你正在自残", 75 | "你得锻炼正造成身体上的损伤", 76 | "六分是养身", 77 | "七分是自娱", 78 | "八分是治愈" 79 | }; 80 | System.out.println("八分是治愈,七分是自娱,六分是养身,五分是自伤,四分是自残,三分是自毁。"); 81 | System.out.printf("你的配速是:%.2f 分钟/公里, %s", average, notice[(int) average]); 82 | System.exit(-1); 83 | } 84 | System.out.printf("平均配速:%.2f\n", average); 85 | 86 | Request request = new Request(token, config); 87 | Response userInfoResponse = request.login(phone, password); 88 | UserInfo userInfo = userInfoResponse.getResponse(); 89 | long userId = userInfo.getUserId(); 90 | if (userId != -1) { 91 | RunStandard runStandard = request.getRunStandard(userInfo.getSchoolId()); 92 | SchoolBound[] schoolBounds = request.getSchoolBound(userInfo.getSchoolId()); 93 | // 新增跑步数据 94 | NewRecordBody recordBody = new NewRecordBody(); 95 | recordBody.setUserId(userId); 96 | recordBody.setAppVersions(config.getAppVersion()); 97 | recordBody.setBrand(config.getBrand()); 98 | recordBody.setMobileType(config.getMobileType()); 99 | recordBody.setSysVersions(config.getSysVersion()); 100 | recordBody.setRunDistance(runDistance); 101 | recordBody.setRunTime(runTime); 102 | recordBody.setYearSemester(runStandard.getSemesterYear()); 103 | recordBody.setRealityTrackPoints(schoolBounds[schoolSite].getSiteBound() + "--"); 104 | 105 | // 今天日期 年-月-日 106 | SimpleDateFormat sdf = new SimpleDateFormat(); 107 | sdf.applyPattern("yyyy-MM-dd"); 108 | Date date = new Date(); 109 | String formatTime = sdf.format(date); 110 | recordBody.setRecordDate(formatTime); 111 | 112 | // 生成跑步数据 113 | String tack = genTack(runDistance); 114 | recordBody.setTrackPoints(tack); 115 | 116 | //发送数据 117 | String result = request.recordNew(recordBody); 118 | Response response = JsonUtils.string2Obj(result, new TypeReference>() { 119 | }); 120 | System.out.println(result); 121 | } else { 122 | System.out.println("用户Id获取失败"); 123 | } 124 | } 125 | 126 | public static String genTack(long distance) { 127 | InputStream resourceAsStream = App.class.getResourceAsStream("/map.json"); 128 | String json = FileUtil.ReadFile(resourceAsStream); 129 | if (json.length() == 0) { 130 | System.out.println("配置读取失败"); 131 | return null; 132 | } 133 | Location[] locations = JsonUtils.string2Obj(json, Location[].class); 134 | return TrackUtils.gen(distance, locations); 135 | } 136 | 137 | } -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/UniRunMain.java: -------------------------------------------------------------------------------- 1 | package org.runrun; 2 | 3 | import org.runrun.entity.AppConfig; 4 | import org.runrun.entity.Response; 5 | import org.runrun.entity.ResponseType.ClubInfo; 6 | import org.runrun.entity.ResponseType.SignInTf; 7 | import org.runrun.entity.ResponseType.UserInfo; 8 | import org.runrun.entity.SignInOrSignBackBody; 9 | import org.runrun.run.Request; 10 | 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | import java.io.IOException; 14 | import java.text.SimpleDateFormat; 15 | import java.util.ArrayList; 16 | import java.util.Date; 17 | import java.util.List; 18 | import java.util.stream.Collectors; 19 | 20 | /** 21 | * 运行主体 22 | * 说明: 23 | * 本程序仅供学习交流使用,请在下载后24小时内及时删除。 24 | * 本程序不提供后续更新服务。 25 | * 若由使用本程序造成包括但不限于校方警告、课程分数计0、封号、勒令退学等不良后果,一切责任由使用者承当。 26 | * 使用本程序即代表使用者同意以上条款。 27 | */ 28 | @Slf4j 29 | public class UniRunMain { 30 | private static final AppConfig config = new AppConfig() {{ 31 | // setAppVersion(SpringUtil.getValue("unirun.version")); // APP版本,一般不做修改 32 | setBrand("realme"); // 手机品牌 33 | setMobileType("RMX2117"); // 型号 34 | setSysVersion("10"); // 系统版本 35 | }}; 36 | 37 | public static void main(String[] args) { 38 | // Response response = UniRunMain.signInOrSignBack(new StringBuffer(), "", ""); 39 | // log.info("{}", response); 40 | // String token = "1243489ade4c457702e7c9c7fe2698a0"; 41 | // AppConfig config = new AppConfig() {{ 42 | // setAppVersion("1.8.3"); // APP版本,一般不做修改 43 | // setBrand("realme"); // 手机品牌 44 | // setMobileType("RMX2117"); // 型号 45 | // setSysVersion("10"); // 系统版本 46 | // }}; 47 | // Request request = new Request(token, config); 48 | // //UserInfo userInfo = request.login("", "1222"); 49 | // UserInfo userInfo = request.getUserInfo().getResponse(); 50 | // 51 | // // 今天日期 年-月-日 52 | // SimpleDateFormat sdf = new SimpleDateFormat(); 53 | // sdf.applyPattern("yyyy-MM-dd"); 54 | // Date date = new Date(); 55 | // String today = sdf.format(date); 56 | // List activityList = request.getActivityList(String.valueOf(userInfo.getStudentId()), today); 57 | // List mySportsClassClocking = request.getMySportsClassClocking(); 58 | // log.info("{}", mySportsClassClocking); 59 | } 60 | 61 | public static Response checkAccount(String phone, String password) throws IOException { 62 | 63 | Request request = new Request("", config); 64 | return request.login(phone, password); 65 | } 66 | public static List getAvailableActivityList(StringBuffer token, String phone, String password) throws IOException { 67 | 68 | Request request = new Request(token.toString(), config); 69 | Response userInfoResponse = request.getUserInfo(); 70 | //更新token 71 | if(userInfoResponse.getCode() != 10000) { 72 | log.info("token无效,更新"); 73 | userInfoResponse = request.login(phone, password); 74 | token.delete(0, token.length()); 75 | token.append(request.getToken()); 76 | } 77 | UserInfo userInfo = userInfoResponse.getResponse(); 78 | List list = new ArrayList<>(); 79 | if (userInfo != null) { 80 | long studentId = userInfo.getStudentId(); 81 | SimpleDateFormat sdf = new SimpleDateFormat(); 82 | sdf.applyPattern("yyyy-MM-dd"); 83 | Date date = new Date(new Date().getTime() + 1000 * 6 * 24 * 60 * 60); 84 | String today = sdf.format(date); 85 | List activityList = request.getActivityList(String.valueOf(studentId), today); 86 | 87 | for (ClubInfo clubInfo : activityList) { 88 | if (clubInfo.getSignInStudent() < clubInfo.getMaxStudent()) { 89 | list.add(clubInfo); 90 | } 91 | } 92 | } else { 93 | log.error("用户Id获取失败"); 94 | } 95 | return list; 96 | } 97 | 98 | public static Response autoJoinClub(StringBuffer token, String phone, String password, String location, String keyword) throws IOException { 99 | 100 | Request request = new Request(token.toString(), config); 101 | Response userInfoResponse = request.getUserInfo(); 102 | //更新token 103 | if(userInfoResponse.getCode() != 10000) { 104 | log.info("token无效,更新"); 105 | userInfoResponse = request.login(phone, password); 106 | token.delete(0, token.length()); 107 | token.append(request.getToken()); 108 | } 109 | UserInfo userInfo = userInfoResponse.getResponse(); 110 | if (userInfo == null) { 111 | log.info("用户信息获取失败:{}", userInfoResponse); 112 | return userInfoResponse; 113 | } 114 | SignInTf signInTf = request.getSignInTf(String.valueOf(userInfo.getStudentId())); 115 | log.info("signInTf:{}", signInTf); 116 | if (signInTf != null && signInTf.getActivityId() != null) { 117 | log.info("有将要进行的俱乐部活动:{}", signInTf); 118 | return null; 119 | } 120 | 121 | // 获取俱乐部列表 122 | List availableActivityList = new ArrayList<>(); 123 | long studentId = userInfo.getStudentId(); 124 | SimpleDateFormat sdf = new SimpleDateFormat(); 125 | sdf.applyPattern("yyyy-MM-dd"); 126 | Date date = new Date(new Date().getTime() + 1000 * 6 * 24 * 60 * 60); 127 | String today = sdf.format(date); 128 | List activityList = request.getActivityList(String.valueOf(studentId), today); 129 | for (ClubInfo clubInfo : activityList) { 130 | if (clubInfo.getSignInStudent() < clubInfo.getMaxStudent()) { 131 | availableActivityList.add(clubInfo); 132 | } 133 | } 134 | //没有可以参加的俱乐部 135 | if(availableActivityList.size() == 0)return null; 136 | 137 | // 筛选关键词俱乐部 138 | List keyActList = availableActivityList.stream().filter(activity -> { 139 | boolean result = activity.getActivityName().contains(location); 140 | if (keyword != null) 141 | result = result && activity.getActivityName().contains(keyword); 142 | return result; 143 | }).collect(Collectors.toList()); 144 | // 空 145 | if (keyActList.size() == 0) { 146 | return new Response() {{ 147 | setMsg(String.format("没有找到可加入的俱乐部\n你的校区:%s\n你的关键词:%s", location, keyword)); 148 | }}; 149 | } 150 | 151 | log.info("尝试加入:{}", keyActList.get(0)); 152 | // 取第一个 153 | Long activityId = keyActList.get(0).getClubActivityId(); 154 | // 加入 155 | return request.joinClub(String.valueOf(studentId), String.valueOf(activityId)); 156 | } 157 | 158 | /** 159 | * UniRun 签到签退 160 | * 161 | * @param token token 162 | * @param phone 手机号 163 | * @param password 密码 164 | * @return null-非可签到签退状态 | Response 165 | */ 166 | public static Response signInOrSignBack(StringBuffer token, String phone, String password) throws IOException { 167 | 168 | Request request = new Request(token.toString(), config); 169 | Response userInfoResponse = request.getUserInfo(); 170 | //更新token 171 | if(userInfoResponse.getCode() != 10000) { 172 | log.info("token无效,更新"); 173 | userInfoResponse = request.login(phone, password); 174 | token.delete(0, token.length()); 175 | token.append(request.getToken()); 176 | } 177 | UserInfo userInfo = userInfoResponse.getResponse(); 178 | 179 | if (userInfo != null) { 180 | Long studentId = userInfo.getStudentId(); 181 | SignInTf signInTf = request.getSignInTf(String.valueOf(studentId)); 182 | log.info("待签到俱乐部:{}", signInTf); 183 | String signStatus = signInTf.getSignStatus(); 184 | String signInStatus = signInTf.getSignInStatus(); 185 | String signBackStatus = signInTf.getSignBackStatus(); 186 | 187 | if ("1".equals(signInStatus) && "1".equals(signBackStatus)) return null; 188 | 189 | String signType; 190 | if ("1".equals(signStatus)) { 191 | // 可签到 192 | signType = "1"; 193 | } else if ("1".equals(signInStatus) && "2".equals(signStatus)) { 194 | // 可签退 195 | signType = "2"; 196 | } else { 197 | log.info("非可签到签退状态,或没有可签到项目"); 198 | return null; 199 | } 200 | 201 | SignInOrSignBackBody signInOrSignBackBody = new SignInOrSignBackBody( 202 | signInTf.getActivityId(), 203 | signInTf.getLatitude(), 204 | signInTf.getLongitude(), 205 | signType, 206 | studentId); 207 | 208 | return request.signInOrSignBack(signInOrSignBackBody); 209 | } else { 210 | return userInfoResponse; 211 | } 212 | } 213 | } -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/entity/AppConfig.java: -------------------------------------------------------------------------------- 1 | package org.runrun.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | /** 8 | * @Author jiyec 9 | * @Date 2021/10/17 13:39 10 | * @Version 1.0 11 | **/ 12 | @Data 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | public class AppConfig { 16 | private String phone; 17 | String password; 18 | private StringBuffer token = new StringBuffer(); 19 | 20 | long distance; 21 | int runTime; 22 | String appVersion; 23 | String brand; 24 | String deviceToken = ""; 25 | String deviceType = "1"; 26 | String mobileType; 27 | String sysVersion; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/entity/Location.java: -------------------------------------------------------------------------------- 1 | package org.runrun.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | /** 8 | * 路径的单个位置点 9 | * 10 | * @Author jiyec 11 | * @Date 2021/10/16 15:16 12 | * @Version 1.0 13 | **/ 14 | @Data 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | public class Location { 18 | private int id; 19 | private String location; 20 | private int[] edge; 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/entity/NewRecordBody.java: -------------------------------------------------------------------------------- 1 | package org.runrun.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | /** 8 | * @Author jiyec 9 | * @Date 2021/10/17 10:44 10 | * @Version 1.0 11 | **/ 12 | @Data 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | public class NewRecordBody { 16 | String againRunStatus = "0"; 17 | int againRunTime = 0; 18 | 19 | /** 20 | * APP版本 21 | */ 22 | String appVersions = "1.8.0"; 23 | /** 24 | * 手机品牌 25 | */ 26 | String brand; 27 | /** 28 | * 手机型号 29 | */ 30 | String mobileType; 31 | /** 32 | * 系统版本号 33 | */ 34 | String sysVersions; 35 | /** 36 | * 跑步路线 37 | */ 38 | String trackPoints; 39 | String distanceTimeStatus = "1"; 40 | String innerSchool = "1"; 41 | /** 42 | * 跑步距离 43 | */ 44 | long runDistance; 45 | /** 46 | * 跑步时间 47 | */ 48 | int runTime; 49 | /** 50 | * 用户ID 51 | */ 52 | long userId; 53 | /** 54 | * 声纹验证 55 | */ 56 | String vocalStatus = "1"; 57 | /** 58 | * 学期 59 | */ 60 | String yearSemester; 61 | /** 62 | * 记录日期 63 | */ 64 | String recordDate; 65 | /** 66 | * 学校经纬度区间 67 | */ 68 | String realityTrackPoints; 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/entity/Response.java: -------------------------------------------------------------------------------- 1 | package org.runrun.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | /** 8 | * 响应体 9 | * 10 | * @Author jiyec 11 | * @Date 2021/10/17 12:03 12 | * @Version 1.0 13 | **/ 14 | @Data 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | public class Response { 18 | private int code; 19 | private String msg; 20 | private T response; 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/entity/ResponseType/ClubInfo.java: -------------------------------------------------------------------------------- 1 | package org.runrun.entity.ResponseType; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | /** 8 | * @Author jiyec 9 | * @Date 2021/11/14 22:07 10 | * @Version 1.0 11 | **/ 12 | @Data 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | public class ClubInfo { 16 | private Long clubActivityId; 17 | private String activityName; 18 | private String addressDetail; 19 | private String clubIntroduction; 20 | private Long signInStudent; 21 | private Long maxStudent; 22 | private String teacherName; 23 | private String startTime; 24 | private String endTime; 25 | private String optionStatus; 26 | private String fullActivity; 27 | private Integer cancelSign; 28 | private Long yearSemester; 29 | private Long activityItemId; 30 | private Long signStatus; 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/entity/ResponseType/JoinClubResult.java: -------------------------------------------------------------------------------- 1 | package org.runrun.entity.ResponseType; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | /** 8 | * @Author jiyec 9 | * @Date 2021/11/15 0:33 10 | * @Version 1.0 11 | **/ 12 | @Data 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | public class JoinClubResult { 16 | private String message; 17 | private String status; 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/entity/ResponseType/MyActivityItem.java: -------------------------------------------------------------------------------- 1 | package org.runrun.entity.ResponseType; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | 6 | /** 7 | * @Author jiyec 8 | * @Date 2021/11/24 13:34 9 | * @Version 1.0 10 | **/ 11 | @Data 12 | @NoArgsConstructor 13 | public class MyActivityItem { 14 | private Long clubActivityId; 15 | private String activityName; 16 | private String activityStatus; 17 | private String addressDetail; 18 | private String clubIntroduction; 19 | private Long configurationTimeId; 20 | private Integer signInStudent; 21 | private Integer maxStudent; 22 | private String teacherName; 23 | private String startTime; 24 | private String endTime; 25 | private String mmdd; 26 | private Long nextClubActivityId; 27 | private String nextStartTime; 28 | private String nextEndTime; 29 | private String nextMmdd; 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/entity/ResponseType/NewRecordResult.java: -------------------------------------------------------------------------------- 1 | package org.runrun.entity.ResponseType; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | /** 9 | * @Author jiyec 10 | * @Date 2021/10/17 13:24 11 | * @Version 1.0 12 | **/ 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @JsonIgnoreProperties(ignoreUnknown = true) 17 | public class NewRecordResult { 18 | String resultStatus; 19 | String resultDesc; 20 | String overSpeedWarn; 21 | String warnContent; 22 | long recordId; 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/entity/ResponseType/RunStandard.java: -------------------------------------------------------------------------------- 1 | package org.runrun.entity.ResponseType; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | /** 9 | * @Author jiyec 10 | * @Date 2021/10/17 12:04 11 | * @Version 1.0 12 | **/ 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @JsonIgnoreProperties(ignoreUnknown = true) 17 | public class RunStandard { 18 | private long standardId; 19 | long schoolId; 20 | int boyOnceTimeMin; 21 | int boyOnceTimeMax; 22 | String semesterYear; 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/entity/ResponseType/SchoolBound.java: -------------------------------------------------------------------------------- 1 | package org.runrun.entity.ResponseType; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | /** 9 | * @Author jiyec 10 | * @Date 2021/10/17 12:29 11 | * @Version 1.0 12 | **/ 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @JsonIgnoreProperties(ignoreUnknown = true) 17 | public class SchoolBound { 18 | String siteName; 19 | String siteBound; 20 | String boundCenter; 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/entity/ResponseType/SignInTf.java: -------------------------------------------------------------------------------- 1 | package org.runrun.entity.ResponseType; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | /** 8 | * @Author jiyec 9 | * @Date 2021/11/17 19:27 10 | * @Version 1.0 11 | **/ 12 | @Data 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | public class SignInTf { 16 | private Long activityId; 17 | private String activityName; 18 | private String activityType; 19 | private String address; 20 | private Integer continueTime; 21 | /** 22 | * 俱乐部开始时间 23 | */ 24 | private String startTime; 25 | /** 26 | * 俱乐部结束时间 27 | */ 28 | private String endTime; 29 | /** 30 | * 经度 31 | */ 32 | private String longitude; 33 | /** 34 | * 纬度 35 | */ 36 | private String latitude; 37 | private Integer signBackLimitTime; 38 | private String signBackStatus; 39 | private String signInStatus; 40 | private String signInTime; 41 | private String signStatus; 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/entity/ResponseType/SportsClassStudentLearnClockingV0.java: -------------------------------------------------------------------------------- 1 | package org.runrun.entity.ResponseType; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | /** 8 | * @Author jiyec 9 | * @Date 2021/11/15 18:41 10 | * @Version 1.0 11 | **/ 12 | @Data 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | public class SportsClassStudentLearnClockingV0 { 16 | private Long classLearnId; 17 | private String clockingRange; 18 | private String startTime; 19 | private String endTime; 20 | private String latitude; 21 | private String longitude; 22 | private String planLearn; 23 | private String signBackStatus; 24 | private String signInStatus; 25 | private String signStatus; 26 | private String sportsClassId; 27 | private String sportsClassName; 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/entity/ResponseType/UserInfo.java: -------------------------------------------------------------------------------- 1 | package org.runrun.entity.ResponseType; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | /** 9 | * @Author jiyec 10 | * @Date 2021/10/17 12:44 11 | * @Version 1.0 12 | **/ 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @JsonIgnoreProperties(ignoreUnknown = true) 17 | public class UserInfo { 18 | long userId; 19 | long studentId; 20 | String registerCode; 21 | String studentName; 22 | String gender; 23 | long schoolId; 24 | String schoolName; 25 | long classId; 26 | int studentClass; 27 | String className; 28 | int startSchool; 29 | String collegeCode; 30 | String collegeName; 31 | String majorCode; 32 | String majorName; 33 | String nationCode; 34 | String birthday; 35 | String idCardNo; 36 | String addrDetail; 37 | String studentSource; 38 | String userVerifyStatus; 39 | OAuth oauthToken; 40 | @Data 41 | public static class OAuth{ 42 | String refreshToken; 43 | String token; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/entity/SignInOrSignBackBody.java: -------------------------------------------------------------------------------- 1 | package org.runrun.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | /** 8 | * API: v1/clubactivity/signInOrSignBack 9 | * 10 | * @Author jiyec 11 | * @Date 2021/11/15 18:19 12 | * @Version 1.0 13 | **/ 14 | 15 | @Data 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | public class SignInOrSignBackBody { 19 | private Long activityId; 20 | /** 21 | * 纬度 22 | */ 23 | private String latitude; 24 | /** 25 | * 经度 26 | */ 27 | private String longitude; 28 | /** 29 | * 签到或签退 30 | * 1:签到 31 | * 2:签退 32 | */ 33 | private String signType; 34 | private Long studentId; 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/run/Request.java: -------------------------------------------------------------------------------- 1 | package org.runrun.run; 2 | 3 | import com.fasterxml.jackson.core.type.TypeReference; 4 | 5 | import org.apache.hc.core5.http.ParseException; 6 | import org.runrun.entity.AppConfig; 7 | import org.runrun.entity.NewRecordBody; 8 | import org.runrun.entity.Response; 9 | import org.runrun.entity.ResponseType.ClubInfo; 10 | import org.runrun.entity.ResponseType.JoinClubResult; 11 | import org.runrun.entity.ResponseType.RunStandard; 12 | import org.runrun.entity.ResponseType.SchoolBound; 13 | import org.runrun.entity.ResponseType.SignInTf; 14 | import org.runrun.entity.ResponseType.SportsClassStudentLearnClockingV0; 15 | import org.runrun.entity.ResponseType.UserInfo; 16 | import org.runrun.entity.SignInOrSignBackBody; 17 | import org.runrun.utils.HTTP.HttpUtil2; 18 | import org.runrun.utils.JsonUtils; 19 | import org.runrun.utils.MD5Utils; 20 | import org.runrun.utils.SignUtils; 21 | 22 | import java.io.IOException; 23 | import java.util.HashMap; 24 | import java.util.List; 25 | import java.util.Map; 26 | 27 | import lombok.Getter; 28 | 29 | /** 30 | * @Author jiyec 31 | * @Date 2021/10/17 10:49 32 | * @Version 1.0 33 | **/ 34 | public class Request { 35 | private final static HttpUtil2 http = new HttpUtil2(); 36 | private final String appKey = "389885588s0648fa"; 37 | private final String HOST = "https://run-lb.tanmasports.com/"; 38 | @Getter 39 | private String token; 40 | private AppConfig config; 41 | 42 | public Request(String token, AppConfig config){ 43 | this.token = token; 44 | this.config = config; 45 | } 46 | 47 | public Response login(String phone, String password) throws IOException { 48 | String pass = MD5Utils.stringToMD5(password); 49 | String API = "https://run-lb.tanmasports.com/v1/auth/login/password"; 50 | try { 51 | Map body = new HashMap<>(); 52 | body.put("appVersion", config.getAppVersion()); 53 | body.put("brand", config.getBrand()); 54 | body.put("deviceToken", config.getDeviceToken()); 55 | body.put("deviceType", config.getDeviceType()); 56 | body.put("mobileType", config.getMobileType()); 57 | body.put("password", pass); 58 | body.put("sysVersion", config.getSysVersion()); 59 | body.put("userPhone", phone); 60 | 61 | Map headers = new HashMap<>(); 62 | String bodyStr = JsonUtils.obj2String(body); 63 | String sign = SignUtils.get(null, bodyStr); 64 | headers.put("sign", sign); 65 | headers.put("token", token); 66 | headers.put("appkey", appKey); 67 | headers.put("Content-Type", "application/json; charset=UTF-8"); 68 | byte[] bytes = http.doPostJson2Byte(API, headers, bodyStr); 69 | Response userInfoResponse = JsonUtils.string2Obj(new String(bytes), new TypeReference>() {}); 70 | int code = userInfoResponse.getCode(); 71 | if(code == 10000){ 72 | UserInfo userInfo = userInfoResponse.getResponse(); 73 | this.token = userInfo.getOauthToken().getToken(); 74 | return userInfoResponse; 75 | }else{ 76 | throw new RuntimeException(userInfoResponse.getMsg()); 77 | } 78 | } catch (IOException e) { 79 | e.printStackTrace(); 80 | throw e; 81 | } 82 | } 83 | public long getUserId(){ 84 | String API = "https://run-lb.tanmasports.com/v1/auth/query/token"; 85 | try { 86 | Map headers = new HashMap<>(); 87 | String sign = SignUtils.get(null, null); 88 | headers.put("sign", sign); 89 | headers.put("token", token); 90 | headers.put("appkey", appKey); 91 | headers.put("Content-Type", "application/json; charset=UTF-8"); 92 | String tokenInfo = http.doGet2(API, headers); 93 | Response userInfoResponse = JsonUtils.string2Obj(tokenInfo, new TypeReference>() {}); 94 | int code = userInfoResponse.getCode(); 95 | if(code == 10000){ 96 | return userInfoResponse.getResponse().getUserId(); 97 | }else{ 98 | throw new RuntimeException(userInfoResponse.getMsg()); 99 | } 100 | } catch (IOException e) { 101 | e.printStackTrace(); 102 | } catch (ParseException e) { 103 | e.printStackTrace(); 104 | } 105 | return -1; 106 | } 107 | 108 | public Response getUserInfo(){ 109 | String API = HOST + "v1/auth/query/token"; 110 | try { 111 | Map headers = new HashMap<>(); 112 | String sign = SignUtils.get(null, null); 113 | headers.put("sign", sign); 114 | headers.put("token", token); 115 | headers.put("appkey", appKey); 116 | headers.put("Content-Type", "application/json; charset=UTF-8"); 117 | headers.put("User-Agent", "okhttp/3.12.0"); 118 | String tokenInfo = http.doGet2(API, headers); 119 | return JsonUtils.string2Obj(tokenInfo, new TypeReference>() {}); 120 | } catch (IOException e) { 121 | e.printStackTrace(); 122 | } catch (ParseException e) { 123 | e.printStackTrace(); 124 | } 125 | return null; 126 | } 127 | 128 | public List getActivityList(String studentId, String date){ 129 | String schoolId = "3680"; 130 | 131 | String API = String.format(HOST + "v1/clubactivity/queryActivityList?queryTime=%s&studentId=%s&schoolId=%s&pageNo=1&pageSize=30", date, studentId, schoolId); 132 | try { 133 | Map headers = new HashMap<>(); 134 | Map params = new HashMap<>(); 135 | params.put("queryTime", date); 136 | params.put("studentId", studentId); 137 | params.put("schoolId", "3680"); 138 | params.put("pageNo", "1"); 139 | params.put("pageSize", "30"); 140 | 141 | String sign = SignUtils.get(params, null); 142 | 143 | headers.put("sign", sign); 144 | headers.put("token", token); 145 | headers.put("appkey", appKey); 146 | headers.put("Content-Type", "application/json; charset=UTF-8"); 147 | headers.put("User-Agent", "okhttp/3.12.0"); 148 | 149 | String tokenInfo = http.doGet2(API, headers); 150 | 151 | Response> standardResponse = JsonUtils.string2Obj(tokenInfo, new TypeReference>>() {}); 152 | return standardResponse.getResponse(); 153 | } catch (IOException e) { 154 | e.printStackTrace(); 155 | } catch (ParseException e) { 156 | e.printStackTrace(); 157 | } 158 | return null; 159 | } 160 | 161 | public Response joinClub(String studentId, String activityId){ 162 | 163 | String API = String.format(HOST + "v1/clubactivity/joinClubActivity?studentId=%s&activityId=%s", studentId, activityId); 164 | try { 165 | Map headers = new HashMap<>(); 166 | Map params = new HashMap<>(); 167 | params.put("studentId", studentId); 168 | params.put("activityId", activityId); 169 | 170 | String sign = SignUtils.get(params, null); 171 | 172 | headers.put("sign", sign); 173 | headers.put("token", token); 174 | headers.put("appkey", appKey); 175 | headers.put("Content-Type", "application/json; charset=UTF-8"); 176 | headers.put("User-Agent", "okhttp/3.12.0"); 177 | 178 | String joinResult = http.doGet2(API, headers); 179 | 180 | return JsonUtils.string2Obj(joinResult, new TypeReference>() {}); 181 | } catch (IOException e) { 182 | e.printStackTrace(); 183 | } catch (ParseException e) { 184 | e.printStackTrace(); 185 | } 186 | return null; 187 | } 188 | 189 | public SignInTf getSignInTf(String studentId){ 190 | String API = String.format(HOST + "v1/clubactivity/getSignInTf?studentId=%s", studentId); 191 | try { 192 | Map headers = new HashMap<>(); 193 | Map params = new HashMap<>(); 194 | params.put("studentId", studentId); 195 | 196 | String sign = SignUtils.get(params, null); 197 | 198 | headers.put("sign", sign); 199 | headers.put("token", token); 200 | headers.put("appkey", appKey); 201 | headers.put("Content-Type", "application/json; charset=UTF-8"); 202 | headers.put("User-Agent", "okhttp/3.12.0"); 203 | 204 | String signInTf = http.doGet2(API, headers); 205 | 206 | Response signInTfResponse = JsonUtils.string2Obj(signInTf, new TypeReference>() {}); 207 | return signInTfResponse.getResponse(); 208 | } catch (IOException e) { 209 | e.printStackTrace(); 210 | } catch (ParseException e) { 211 | e.printStackTrace(); 212 | } 213 | return null; 214 | } 215 | 216 | public Response signInOrSignBack(SignInOrSignBackBody signInOrSignBackBody){ 217 | String API = HOST + "v1/clubactivity/signInOrSignBack"; 218 | try { 219 | Map headers = new HashMap<>(); 220 | Map params = new HashMap<>(); 221 | 222 | String body = JsonUtils.obj2String(signInOrSignBackBody); 223 | 224 | String sign = SignUtils.get(params, body); 225 | 226 | headers.put("sign", sign); 227 | headers.put("token", token); 228 | headers.put("appkey", appKey); 229 | headers.put("Content-Type", "application/json; charset=UTF-8"); 230 | headers.put("User-Agent", "okhttp/3.12.0"); 231 | byte[] bytes = http.doPostJson2Byte(API, headers, body); 232 | 233 | String signInTf = new String(bytes); 234 | 235 | Response signResponse = JsonUtils.string2Obj(signInTf, new TypeReference() {}); 236 | return signResponse; 237 | } catch (IOException e) { 238 | e.printStackTrace(); 239 | } 240 | return null; 241 | } 242 | 243 | public List getMySportsClassClocking(){ 244 | 245 | String API = HOST + "v1/sports/class/getMySportsClassClocking"; 246 | try { 247 | Map headers = new HashMap<>(); 248 | Map params = new HashMap<>(); 249 | 250 | String sign = SignUtils.get(params, null); 251 | 252 | headers.put("sign", sign); 253 | headers.put("token", token); 254 | headers.put("appkey", appKey); 255 | headers.put("Content-Type", "application/json; charset=UTF-8"); 256 | headers.put("User-Agent", "okhttp/3.12.0"); 257 | 258 | String joinResult = http.doGet2(API, headers); 259 | 260 | Response> sportsClassStudentLearnClockingV0Response = JsonUtils.string2Obj(joinResult, new TypeReference>>() {}); 261 | return sportsClassStudentLearnClockingV0Response.getResponse(); 262 | } catch (IOException e) { 263 | e.printStackTrace(); 264 | } catch (ParseException e) { 265 | e.printStackTrace(); 266 | } 267 | return null; 268 | } 269 | public SchoolBound[] getSchoolBound(long schoolId){ 270 | 271 | String API = "https://run-lb.tanmasports.com/v1/unirun/querySchoolBound?schoolId=" + schoolId; 272 | try { 273 | Map headers = new HashMap<>(); 274 | Map params = new HashMap<>(); 275 | params.put("schoolId", String.valueOf(schoolId)); 276 | String sign = SignUtils.get(params, null); 277 | headers.put("sign", sign); 278 | headers.put("token", token); 279 | headers.put("appkey", appKey); 280 | headers.put("Content-Type", "application/json; charset=UTF-8"); 281 | String tokenInfo = http.doGet2(API, headers); 282 | Response schoolBoundResponse = JsonUtils.string2Obj(tokenInfo, new TypeReference>() {}); 283 | if(schoolBoundResponse.getCode() != 10000){ 284 | throw new RuntimeException(schoolBoundResponse.getMsg()); 285 | } 286 | return schoolBoundResponse.getResponse(); 287 | } catch (IOException e) { 288 | e.printStackTrace(); 289 | } catch (ParseException e) { 290 | e.printStackTrace(); 291 | } 292 | return null; 293 | } 294 | 295 | public RunStandard getRunStandard(long schoolId){ 296 | 297 | String API = "https://run-lb.tanmasports.com/v1/unirun/query/runStandard?schoolId=" + schoolId; 298 | try { 299 | Map headers = new HashMap<>(); 300 | Map params = new HashMap<>(); 301 | params.put("schoolId", String.valueOf(schoolId)); 302 | String sign = SignUtils.get(params, null); 303 | headers.put("sign", sign); 304 | headers.put("token", token); 305 | headers.put("appkey", appKey); 306 | headers.put("Content-Type", "application/json; charset=UTF-8"); 307 | String tokenInfo = http.doGet2(API, headers); 308 | Response standardResponse = JsonUtils.string2Obj(tokenInfo, new TypeReference>() {}); 309 | if(standardResponse.getCode() != 10000){ 310 | throw new RuntimeException(standardResponse.getMsg()); 311 | } 312 | return standardResponse.getResponse(); 313 | } catch (IOException e) { 314 | e.printStackTrace(); 315 | } catch (ParseException e) { 316 | e.printStackTrace(); 317 | } 318 | return null; 319 | } 320 | public String recordNew(NewRecordBody body){ 321 | String API = "https://run-lb.tanmasports.com/v1/unirun/save/run/record/new"; 322 | try { 323 | Map headers = new HashMap<>(); 324 | String bodyStr = JsonUtils.obj2String(body); 325 | String sign = SignUtils.get(null, bodyStr); 326 | headers.put("sign", sign); 327 | headers.put("token", token); 328 | headers.put("appkey", appKey); 329 | headers.put("Content-Type", "application/json; charset=UTF-8"); 330 | byte[] bytes = http.doPostJson2Byte(API, headers, bodyStr); 331 | return new String(bytes); 332 | } catch (IOException e) { 333 | e.printStackTrace(); 334 | } 335 | return null; 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/utils/FileUtil.java: -------------------------------------------------------------------------------- 1 | package org.runrun.utils; 2 | 3 | import java.io.*; 4 | import java.nio.charset.StandardCharsets; 5 | 6 | public class FileUtil { 7 | 8 | /** 9 | * 读取文件 10 | * @param Path 文件路径 11 | * @return String 文件内容 12 | */ 13 | public static String ReadFile(String Path) { 14 | BufferedReader reader = null; 15 | StringBuilder laststr = new StringBuilder(); 16 | try { 17 | FileInputStream fileInputStream = new FileInputStream(Path); 18 | InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_8); 19 | reader = new BufferedReader(inputStreamReader); 20 | String tempString; 21 | while ((tempString = reader.readLine()) != null) { 22 | laststr.append(tempString); 23 | } 24 | reader.close(); 25 | } catch (IOException e) { 26 | e.printStackTrace(); 27 | } finally { 28 | if (reader != null) { 29 | try { 30 | reader.close(); 31 | } catch (IOException e) { 32 | e.printStackTrace(); 33 | } 34 | } 35 | } 36 | return laststr.toString(); 37 | } 38 | /** 39 | * 读取文件 40 | * @return String 文件内容 41 | */ 42 | public static String ReadFile(InputStream is) { 43 | BufferedReader reader = null; 44 | StringBuilder laststr = new StringBuilder(); 45 | try { 46 | InputStreamReader inputStreamReader = new InputStreamReader(is, StandardCharsets.UTF_8); 47 | reader = new BufferedReader(inputStreamReader); 48 | String tempString; 49 | while ((tempString = reader.readLine()) != null) { 50 | laststr.append(tempString); 51 | } 52 | reader.close(); 53 | } catch (IOException e) { 54 | e.printStackTrace(); 55 | } finally { 56 | if (reader != null) { 57 | try { 58 | reader.close(); 59 | } catch (IOException e) { 60 | e.printStackTrace(); 61 | } 62 | } 63 | } 64 | return laststr.toString(); 65 | } 66 | public static boolean WriteFile(String file, String data){ 67 | try { 68 | new File(new File(file).getParent()).mkdirs(); 69 | OutputStreamWriter fileWriter = new OutputStreamWriter(new FileOutputStream(file), "UTF-8"); 70 | fileWriter.write(data); 71 | fileWriter.close(); 72 | } catch (IOException e) { 73 | 74 | e.printStackTrace(); 75 | return false; 76 | } 77 | return true; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/utils/HTTP/CustomCookieStore.java: -------------------------------------------------------------------------------- 1 | package org.runrun.utils.HTTP; 2 | 3 | 4 | import org.apache.hc.client5.http.cookie.Cookie; 5 | import org.apache.hc.client5.http.cookie.CookieIdentityComparator; 6 | import org.apache.hc.client5.http.cookie.CookieStore; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Date; 10 | import java.util.List; 11 | import java.util.TreeSet; 12 | import java.util.concurrent.locks.ReadWriteLock; 13 | import java.util.concurrent.locks.ReentrantReadWriteLock; 14 | 15 | /** 16 | * 自定义Cookie存储类 17 | * 让httpClient无法读取cookie,但开发者可以[达到禁用Cookie的目的] 18 | * 19 | */ 20 | public class CustomCookieStore implements CookieStore { 21 | private List emptyCookieStore = new ArrayList(); 22 | private final TreeSet cookies = new TreeSet(new CookieIdentityComparator()); 23 | private transient ReadWriteLock lock = new ReentrantReadWriteLock(); 24 | 25 | @Override 26 | public void addCookie(Cookie cookie) { 27 | if (cookie != null) { 28 | this.lock.writeLock().lock(); 29 | 30 | try { 31 | this.cookies.remove(cookie); 32 | if (!cookie.isExpired(new Date())) { 33 | this.cookies.add(cookie); 34 | } 35 | } finally { 36 | this.lock.writeLock().unlock(); 37 | } 38 | } 39 | } 40 | 41 | public void addCookies(Cookie[] cookies) { 42 | if (cookies != null) { 43 | Cookie[] arr$ = cookies; 44 | int len$ = cookies.length; 45 | 46 | for(int i$ = 0; i$ < len$; ++i$) { 47 | Cookie cookie = arr$[i$]; 48 | this.addCookie(cookie); 49 | } 50 | } 51 | 52 | } 53 | 54 | @Override 55 | public List getCookies() { 56 | return emptyCookieStore; 57 | } 58 | 59 | 60 | /** 61 | * 自定义Cookie读取策略,读完清空 o(* ̄▽ ̄*)ブ 62 | * 63 | * @return 64 | */ 65 | public List getCookiesCustom() { 66 | this.lock.readLock().lock(); 67 | 68 | ArrayList var1; 69 | try { 70 | var1 = new ArrayList(this.cookies); 71 | } finally { 72 | this.cookies.clear(); 73 | this.lock.readLock().unlock(); 74 | } 75 | 76 | return var1; 77 | } 78 | 79 | @Override 80 | public boolean clearExpired(Date date) { 81 | return true; 82 | } 83 | 84 | @Override 85 | public void clear() { 86 | 87 | } 88 | } -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/utils/HTTP/HttpUtil.java: -------------------------------------------------------------------------------- 1 | package org.runrun.utils.HTTP; 2 | 3 | import org.apache.hc.client5.http.config.RequestConfig; 4 | import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; 5 | import org.apache.hc.client5.http.protocol.HttpClientContext; 6 | import org.apache.hc.core5.http.ParseException; 7 | import org.apache.hc.core5.util.Timeout; 8 | 9 | import java.io.IOException; 10 | import java.util.Map; 11 | 12 | /** 13 | * 网络请求工具类[公共] 14 | * 15 | * @author jiyec 16 | */ 17 | public class HttpUtil { 18 | 19 | private static final CustomCookieStore httpCookieStore; 20 | private static final HttpClientContext defaultContext; 21 | private static final RequestConfig.Builder unBuildConfig; 22 | public static final HttpUtil2 HTTP = new HttpUtil2(); 23 | 24 | // 采用静态代码块,初始化超时时间配置,再根据配置生成默认httpClient对象 25 | static { 26 | 27 | // Cookie存储 28 | httpCookieStore = new CustomCookieStore(); 29 | 30 | unBuildConfig = RequestConfig.custom(); 31 | 32 | // 配置 33 | final RequestConfig config = unBuildConfig 34 | .setConnectTimeout(Timeout.ofSeconds(5)) 35 | .setRedirectsEnabled(false) 36 | // .setProxy(new HttpHost("127.0.0.1", 8866)) // 开发环境设置代理 37 | .setCircularRedirectsAllowed(true) 38 | .build(); 39 | 40 | // 动态配置 41 | defaultContext = HttpClientContext.create(); 42 | defaultContext.setCookieStore(httpCookieStore); 43 | defaultContext.setRequestConfig(config); 44 | 45 | } 46 | 47 | /** 48 | * _____/\\\\\\\\\\\\__/\\\\\\\\\\\\\\\__/\\\\\\\\\\\\\\\_ 49 | * ___/\\\//////////__\/\\\///////////__\///////\\\/////__ 50 | * __/\\\_____________\/\\\___________________\/\\\_______ 51 | * _\/\\\____/\\\\\\\_\/\\\\\\\\\\\___________\/\\\_______ 52 | * _\/\\\___\/////\\\_\/\\\///////____________\/\\\_______ 53 | * _\/\\\_______\/\\\_\/\\\___________________\/\\\_______ 54 | * _\/\\\_______\/\\\_\/\\\___________________\/\\\_______ 55 | * _\//\\\\\\\\\\\\/__\/\\\\\\\\\\\\\\\_______\/\\\_______ 56 | * __\////////////____\///////////////________\///________ 57 | * FROM:http://patorjk.com/software/taag 58 | */ 59 | public static String doGet(String url) throws IOException, ParseException { 60 | return HTTP.doGet(url); 61 | } 62 | 63 | public static String doGet(String url, Map params) throws IOException, ParseException { 64 | return HTTP.doGet(url, params); 65 | } 66 | 67 | /** 68 | * HTTP Get 获取内容 69 | * 70 | * @param url 请求的url地址 ?之前的地址 71 | * @param params 请求的参数 72 | * @param charset 编码格式 73 | * @return 页面内容 74 | */ 75 | public static String doGet(String url, Map params, String charset) throws IOException, ParseException { 76 | return HTTP.doGet(url, params, charset); 77 | } 78 | 79 | public static String doGet(String url, String charset, Map headers) throws IOException, ParseException { 80 | return HTTP.doGet(url, charset, headers); 81 | } 82 | 83 | 84 | public static CloseableHttpResponse doGet( 85 | String url, 86 | Map params, 87 | Map headers, 88 | String charset 89 | ) { 90 | return HTTP.doGet(url, params, headers, charset); 91 | } 92 | 93 | /** 94 | * HTTP Get 获取内容 [主方法] 95 | * 96 | * @param url 请求的url地址 ?之前的地址 97 | * @param params 请求的参数 98 | * @param headers 请求头信息 99 | * @param charset 编码格式 100 | * @return CloseableHttpResponse 101 | */ 102 | public static CloseableHttpResponse doGet( 103 | String url, 104 | Map params, 105 | Map headers, 106 | String charset, 107 | HttpClientContext context 108 | ) { 109 | return HttpUtil2.doGet(url, params, headers, charset, context); 110 | } 111 | 112 | /*** 113 | * __/\\\\\\\\\\\\\_________/\\\\\__________/\\\\\\\\\\\____/\\\\\\\\\\\\\\\_ 114 | * _\/\\\/////////\\\_____/\\\///\\\______/\\\/////////\\\_\///////\\\/////__ 115 | * _\/\\\_______\/\\\___/\\\/__\///\\\___\//\\\______\///________\/\\\_______ 116 | * _\/\\\\\\\\\\\\\/___/\\\______\//\\\___\////\\\_______________\/\\\_______ 117 | * _\/\\\/////////____\/\\\_______\/\\\______\////\\\____________\/\\\_______ 118 | * _\/\\\_____________\//\\\______/\\\__________\////\\\_________\/\\\_______ 119 | * _\/\\\______________\///\\\__/\\\_____/\\\______\//\\\________\/\\\_______ 120 | * _\/\\\________________\///\\\\\/_____\///\\\\\\\\\\\/_________\/\\\_______ 121 | * _\///___________________\/////_________\///////////___________\///________ 122 | * FROM:http://patorjk.com/software/taag 123 | */ 124 | 125 | public static String doFilePost(String url, byte[] data) throws IOException { 126 | return HTTP.doFilePost(url, data); 127 | } 128 | 129 | public static String doPost(String url, Map params, Map header) 130 | throws IOException, ParseException { 131 | return HTTP.doPost(url, params, header, null); 132 | } 133 | 134 | } 135 | 136 | -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/utils/HTTP/HttpUtilEntity.java: -------------------------------------------------------------------------------- 1 | package org.runrun.utils.HTTP; 2 | 3 | import java.util.Map; 4 | 5 | public class HttpUtilEntity { 6 | private String body; 7 | private Map headers; 8 | private int statusCode; 9 | private Map cookies; 10 | 11 | public Map getCookies() { 12 | return cookies; 13 | } 14 | 15 | public void setCookies(Map cookies) { 16 | this.cookies = cookies; 17 | } 18 | 19 | public int getStatusCode() { 20 | return statusCode; 21 | } 22 | 23 | public void setStatusCode(int statusCode) { 24 | this.statusCode = statusCode; 25 | } 26 | 27 | public String getBody() { 28 | return body; 29 | } 30 | 31 | public void setBody(String body) { 32 | this.body = body; 33 | } 34 | 35 | public Map getHeaders() { 36 | return headers; 37 | } 38 | 39 | public void setHeaders(Map headers) { 40 | this.headers = headers; 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | return "HttpUtilEntity{" + 46 | "body='" + body + '\'' + 47 | ", headers=" + headers + 48 | ", statusCode=" + statusCode + 49 | '}'; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/utils/JsonUtils.java: -------------------------------------------------------------------------------- 1 | package org.runrun.utils; 2 | 3 | import com.fasterxml.jackson.core.type.TypeReference; 4 | import com.fasterxml.jackson.databind.JavaType; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | 7 | /** 8 | * 9 | */ 10 | public class JsonUtils { 11 | private static final ObjectMapper objectMapper = new ObjectMapper(); 12 | 13 | /** 将对象转为string */ 14 | public static String obj2String(T obj){ 15 | 16 | if(obj == null){ 17 | return null; 18 | } 19 | 20 | try { 21 | return obj instanceof String ? (String)obj : objectMapper.writeValueAsString(obj); 22 | } catch (Exception e) { 23 | return null; 24 | } 25 | } 26 | 27 | public static String obj2StringPretty(T obj){ 28 | if(obj == null){ 29 | return null; 30 | } 31 | try { 32 | return obj instanceof String ? (String)obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj); 33 | } catch (Exception e) { 34 | return null; 35 | } 36 | } 37 | 38 | public static T string2Obj(String str, Class clazz){ 39 | if(str == null || clazz == null){ 40 | return null; 41 | } 42 | 43 | try { 44 | return clazz.equals(String.class)? (T)str : objectMapper.readValue(str, clazz); 45 | } catch (Exception e) { 46 | return null; 47 | } 48 | } 49 | 50 | public static T string2Obj(String str, TypeReference typeReference){ 51 | if(str == null || typeReference == null){ 52 | return null; 53 | } 54 | try { 55 | return (T)(typeReference.getType().equals(String.class)? str : objectMapper.readValue(str,typeReference)); 56 | } catch (Exception e) { 57 | return null; 58 | } 59 | } 60 | 61 | public static T string2Obj(String str, Class collectionClass, Class... elementClasses){ 62 | JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass,elementClasses); 63 | try { 64 | return objectMapper.readValue(str,javaType); 65 | } catch (Exception e) { 66 | return null; 67 | } 68 | } 69 | 70 | 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/utils/MD5Utils.java: -------------------------------------------------------------------------------- 1 | package org.runrun.utils; 2 | 3 | import java.math.BigInteger; 4 | import java.security.MessageDigest; 5 | import java.security.NoSuchAlgorithmException; 6 | 7 | /** 8 | * @Author jiyec 9 | * @Date 2021/10/15 20:56 10 | * @Version 1.0 11 | **/ 12 | public class MD5Utils { 13 | public static String stringToMD5(String plainText) { 14 | byte[] secretBytes = null; 15 | try { 16 | secretBytes = MessageDigest.getInstance("md5").digest( 17 | plainText.getBytes()); 18 | } catch (NoSuchAlgorithmException e) { 19 | throw new RuntimeException("没有这个md5算法!"); 20 | } 21 | String md5code = new BigInteger(1, secretBytes).toString(16); 22 | for (int i = 0; i < 32 - md5code.length(); i++) { 23 | md5code = "0" + md5code; 24 | } 25 | return md5code; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/utils/SignUtils.java: -------------------------------------------------------------------------------- 1 | package org.runrun.utils; 2 | 3 | 4 | import java.io.UnsupportedEncodingException; 5 | import java.net.URLEncoder; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.TreeSet; 9 | 10 | /** 11 | * @Author jiyec 12 | * @Date 2021/10/15 20:47 13 | * @Version 1.0 14 | **/ 15 | public class SignUtils { 16 | private static final String APPKEY = "389885588s0648fa"; 17 | private static final String APPSECRET = "56E39A1658455588885690425C0FD16055A21676"; 18 | 19 | public static String get(Map query, String body) throws UnsupportedEncodingException { 20 | String str = null; 21 | // 注意,TreeSet是有序集合,且默认为正序排列,即{a, b, c, d [,...]} 22 | if(query == null)query = new HashMap<>(); 23 | // 待签名字符串 24 | StringBuilder sb = new StringBuilder(); 25 | TreeSet treeSet = new TreeSet<>(query.keySet()); 26 | // 开始迭代所有请求参数 27 | for (String key : treeSet) { 28 | // key 为参数名 29 | // 获取参数名对应的值 30 | String value = query.get(key); 31 | if (value != null) { 32 | // key 参数名, str9 参数值 33 | // str9非空,追加str8 str9在sb之后 34 | sb.append(key); 35 | sb.append(value); 36 | } 37 | } 38 | // 追加APPKEY 39 | sb.append(APPKEY); 40 | // 追加APPSECRET 41 | sb.append(APPSECRET); 42 | if (body != null) { 43 | sb.append(body); 44 | } 45 | // 同sb 46 | String sb2 = sb.toString(), str3; 47 | CharSequence charSequence = sb2; 48 | boolean z2 = false; 49 | if (!(charSequence.length() == 0)) { 50 | // 本级语句块替换一些字符为空字符,即删除部分字符,这将导致参与MD5运算的字符串有所差异 51 | // z2 为是否发生过替换操作 52 | // 删除空格 53 | if (sb2.contains(" ")) { 54 | sb2 = sb2.replaceAll(" ", ""); 55 | z2 = true; 56 | } else { 57 | str3 = sb2; 58 | } 59 | // 删除~ 60 | if (sb2.contains("~")) { 61 | sb2 = sb2.replaceAll("~", ""); 62 | z2 = true; 63 | } 64 | // 删除! 65 | if (sb2.contains("!")) { 66 | sb2 = sb2.replaceAll("!", ""); 67 | z2 = true; 68 | } 69 | // 删除( 70 | if (sb2.contains("(")) { 71 | sb2 = sb2.replaceAll("\\(", ""); 72 | z2 = true; 73 | } 74 | // 删除) 75 | if (sb2.contains(")")) { 76 | sb2 = sb2.replaceAll("\\)", ""); 77 | z2 = true; 78 | } 79 | // 删除' 80 | if (sb2.contains("'")) { 81 | sb2 = sb2.replaceAll("'", ""); 82 | z2 = true; 83 | } 84 | if (z2) { 85 | sb2 = URLEncoder.encode(sb2, "utf-8"); 86 | } 87 | } 88 | if (z2) { 89 | StringBuilder sb3 = new StringBuilder(); 90 | // 使用替换结果 sb2 计算MD5 91 | String encodeByMD5 = MD5Utils.stringToMD5(sb2); 92 | String str2 = null; 93 | if (encodeByMD5 != null) { 94 | // 转换为大写 95 | str2 = encodeByMD5.toUpperCase(); 96 | } 97 | sb3.append(str2); 98 | // MD5追加编码 99 | sb3.append("encodeutf8"); 100 | str = sb3.toString(); 101 | } else { 102 | // 没有发生替换,使用sb 103 | String sb4 = sb.toString(); 104 | // 使用sb计算MD5 105 | String encodeByMD52 = MD5Utils.stringToMD5(sb4); 106 | if (encodeByMD52 != null) { 107 | // 转换为大写 108 | str = encodeByMD52.toUpperCase(); 109 | } 110 | } 111 | return str; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /app/src/main/java/org/runrun/utils/TrackUtils.java: -------------------------------------------------------------------------------- 1 | package org.runrun.utils; 2 | 3 | import org.runrun.entity.Location; 4 | import org.gavaghan.geodesy.Ellipsoid; 5 | import org.gavaghan.geodesy.GeodeticCalculator; 6 | import org.gavaghan.geodesy.GlobalCoordinates; 7 | 8 | import java.util.Date; 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | 12 | 13 | /** 14 | * 路径生成工具 15 | * 使用无向图进行自动寻路 16 | * 17 | * @Author jiyec 18 | * @Date 2021/10/16 15:21 19 | * @Version 1.0 20 | **/ 21 | public class TrackUtils { 22 | /** 23 | * 路径生成算法 24 | * 25 | * @param distance 26 | * @param locations 27 | * @return 28 | */ 29 | public static String gen(long distance, Location[] locations){ 30 | int currentDistance = 0; 31 | // 随机起始点 32 | int startIndex = (int)(locations.length * Math.random()); 33 | Location startLocation = locations[startIndex]; 34 | Location currentLocation = startLocation; 35 | // 路径集合 36 | List result = new LinkedList<>(); 37 | 38 | // 时间推前30分钟 39 | long startTime = new Date().getTime() - 30 * 60 * 1000; 40 | int lastIndex = -1; 41 | 42 | String[] current = currentLocation.getLocation().split(","); 43 | result.add(String.format("%s-%s-%s-%.1f", current[0], current[1], startTime, randAccuracy())); 44 | 45 | while (currentDistance < distance){ 46 | current = currentLocation.getLocation().split(","); 47 | int[] edge = currentLocation.getEdge(); 48 | 49 | // 随机选择下一个结点 50 | if(edge.length == 0){ 51 | System.out.println("edge为空"); 52 | } 53 | int randInt = randInt(0, edge.length); 54 | int edgeIndex = edge[randInt]; 55 | // 尽量不往回走 56 | if(edgeIndex == lastIndex){ 57 | edgeIndex = edge[(randInt + 1)%edge.length]; 58 | } 59 | // 下一个位置 60 | Location next = locations[edgeIndex]; 61 | 62 | String[] start = current; 63 | String[] end = next.getLocation().split(","); 64 | double[] startData = new double[]{Double.parseDouble(start[0]), Double.parseDouble(start[1])}; 65 | double[] endData = new double[]{Double.parseDouble(end[0]), Double.parseDouble(end[1])}; 66 | 67 | double goDistance = CalculateDistance(startData, endData); 68 | currentDistance += goDistance; 69 | 70 | double[] lastRandPos = startData; 71 | for (int i = 0; i < 10; i++) { 72 | double[] newRandPos = randPos(lastRandPos, endData); 73 | double distance1 = CalculateDistance(lastRandPos, newRandPos); 74 | lastRandPos = newRandPos; 75 | // 距离/速度 = 时间 (毫秒) | 随机速度 1-5m/s 76 | startTime += distance1 / randInt(1, 5) * 1000; 77 | result.add(String.format("%s-%s-%s-%.1f", lastRandPos[0], lastRandPos[1], startTime, randAccuracy())); 78 | } 79 | double distance1 = CalculateDistance(lastRandPos, endData); 80 | // 距离/速度 = 时间 (毫秒) | 随机速度 1-5m/s 81 | startTime += distance1 / randInt(1, 5) * 1000; 82 | result.add(String.format("%s-%s-%s-%.1f", end[0], end[1], startTime, randAccuracy())); 83 | 84 | lastIndex = currentLocation.getId(); 85 | currentLocation = next; 86 | } 87 | startTime += randInt(5, 10) * 1000L; 88 | String replace = currentLocation.getLocation().replace(',', '-'); 89 | result.add(String.format("%s-%s-%.1f", replace, startTime, randAccuracy())); 90 | return JsonUtils.obj2String(result); 91 | } 92 | 93 | public static int randInt(int end) { 94 | return randInt(0, end); 95 | } 96 | // [start, end) 97 | public static int randInt(int start, int end){ 98 | if(start == end) System.out.println("范围空!"); 99 | int len = end - start; 100 | return (int)(start + len * Math.random()); 101 | } 102 | 103 | /** 104 | * 105 | * @param start "经度,维度" 106 | * @param end "经度,维度" 107 | * @return 距离,单位:米 108 | */ 109 | public static double CalculateDistance(double[] start, double[] end){ 110 | GlobalCoordinates source = new GlobalCoordinates(start[1], start[0]); 111 | GlobalCoordinates target = new GlobalCoordinates(end[1], end[0]); 112 | return new GeodeticCalculator().calculateGeodeticCurve(Ellipsoid.Sphere, source, target).getEllipsoidalDistance(); 113 | } 114 | 115 | /** 116 | * 随机取一个经过点 117 | * @param start {经度x, 维度y} 118 | * @return 119 | */ 120 | public static double[] randPos(double[] start, double[] end){ 121 | double random = Math.random(); 122 | // y = ax + b 123 | double dy = end[1] - start[1]; 124 | double dx = end[0] - start[0]; 125 | return new double[]{ 126 | start[0] + dx * random, 127 | start[1] + dy * random 128 | }; 129 | } 130 | public static double randAccuracy(){ 131 | return 10 * Math.random(); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_login.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 29 | 30 | 46 | 47 |