Commit d58a9579 authored by chenyuling's avatar chenyuling

first

parents
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
# Default ignored files
/shelf/
/workspace.xml
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
<component name="SuppressKotlinCodeStyleNotification">
<option name="disableForAll" value="true" />
</component>
</project>
\ No newline at end of file
/build
\ No newline at end of file
plugins {
id 'com.android.application'
}
apply plugin:'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
signingConfigs {
release {
storeFile file('C:\\E\\dep_work\\kuangshi\\StatInfo\\srthinker.jks')
storePassword 'srthinker'
keyAlias 'srthinker'
keyPassword 'srthinker'
}
}
namespace 'com.srthinker.statinfo'
compileSdk 32
defaultConfig {
applicationId "com.srthinker.statinfo"
minSdk 24
targetSdk 32
versionCode 1
versionName "1.0.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
viewBinding{
enabled = true
}
applicationVariants.all { variant ->
variant.outputs.all { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
def fileName
if (variant.buildType.name == 'release') {
fileName = "${appName}-V${defaultConfig.versionName}-release-${releaseTime()}.apk"
} else if (variant.buildType.name == 'debug') {
fileName = "${appName}-V${defaultConfig.versionName}-debug.-${releaseTime()}apk"
} else {
fileName = "${appName}-V${defaultConfig.versionName}-other-${releaseTime()}.apk"
}
outputFileName = fileName
}
}
}
}
def releaseTime() {
return new Date().format("yyyyMMdd_HHmmss", TimeZone.getDefault())
}
ext.appName = "statinfo"
def dbflow_version = "4.2.4"
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.core:core-ktx:1.7.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation 'com.github.bumptech.glide:glide:4.12.0' //4.9.0
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
implementation 'com.github.JessYanCoding:AndroidAutoSize:v1.2.1'
implementation 'com.orhanobut:logger:2.2.0'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.alibaba:fastjson:1.2.75'
annotationProcessor "com.github.Raizlabs.DBFlow:dbflow-processor:$dbflow_version"
implementation "com.github.Raizlabs.DBFlow:dbflow-core:$dbflow_version"
implementation "com.github.Raizlabs.DBFlow:dbflow:$dbflow_version"
implementation 'com.squareup.okhttp3:okhttp:3.12.1'
implementation 'org.apache.commons:commons-lang3:3.9'
implementation 'org.slf4j:slf4j-android:1.7.30'
implementation 'org.slf4j:slf4j-api:1.7.30'
implementation 'org.java-websocket:Java-WebSocket:1.5.1'
implementation 'org.bouncycastle:bcprov-jdk15on:1.68'
}
\ No newline at end of file
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
\ No newline at end of file
package com.srthinker.statinfo;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.srthinker.statinfo", appContext.getPackageName());
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- 安装APK权限,需要在程序中动态申请,并且不同于外部存储读写权限申请 -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application
android:name=".uis.MyApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.StatInfo"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<meta-data
android:name="design_width_in_dp"
android:value="960" />
<meta-data
android:name="design_height_in_dp"
android:value="540" /> <!-- fileprovider名称在安装时传递给系统安装程序 -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/autoupdate" />
</provider>
<activity
android:name=".uis.MainActivity"
android:configChanges="keyboardHidden|orientation"
android:exported="true"
android:windowSoftInputMode="adjustPan">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<!--<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />-->
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
<activity
android:name=".uis.GroupActivity"
android:exported="false">
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
<activity
android:name=".uis.FlashActivity"
android:exported="false">
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
</application>
</manifest>
\ No newline at end of file
package com.srthinker.statinfo.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.srthinker.statinfo.database.entity.PersonEntity;
import com.srthinker.statinfo.databinding.ItemEnterBinding;
import java.util.List;
public class EnterAdapter extends RecyclerView.Adapter {
private final Context context;
private List<PersonEntity> peopleBeans;
public EnterAdapter(Context context, List<PersonEntity> peopleBeans){
this.context = context;
this.peopleBeans = peopleBeans;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemEnterBinding binding = ItemEnterBinding.inflate(LayoutInflater.from(context), parent, false);
return new ItemHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
ItemHolder itemHolder = (ItemHolder) holder;
PersonEntity peopleBean = peopleBeans.get(position);
if (peopleBean!=null){
itemHolder.mBinding.tvInfo.setText(peopleBean.getTimestamp()+" "+peopleBean.getPerson_name());
}
}
@Override
public int getItemCount() {
return peopleBeans!=null&&peopleBeans.size()>0?peopleBeans.size():0;
}
private class ItemHolder extends RecyclerView.ViewHolder {
private final com.srthinker.statinfo.databinding.ItemEnterBinding mBinding;
public ItemHolder(ItemEnterBinding binding) {
super(binding.getRoot());
mBinding = binding;
}
}
public void setDateList(List<PersonEntity> peopleBeans){
this.peopleBeans = peopleBeans;
notifyDataSetChanged();
}
}
package com.srthinker.statinfo.adapter;
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.srthinker.statinfo.bean.GroupBean;
import com.srthinker.statinfo.databinding.ItemGroupBinding;
import com.srthinker.statinfo.util.common.KeyBoardUtil;
import com.srthinker.statinfo.util.common.SharedUtil;
import com.srthinker.statinfo.util.common.StatusBarUtil;
import java.util.ArrayList;
public class GroupAdapter extends RecyclerView.Adapter {
private final Context context;
private ArrayList<GroupBean> groupBeans;
private final Activity activity;
public GroupAdapter(Context context, ArrayList<GroupBean> groupBeans){
this.context = context;
activity = (Activity) context;
this.groupBeans = groupBeans;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemGroupBinding binding = ItemGroupBinding.inflate(LayoutInflater.from(context), parent, false);
return new ItemHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
ItemHolder itemHolder = (ItemHolder) holder;
GroupBean groupBean = groupBeans.get(position);
if (groupBean!=null){
itemHolder.mBinding.llItem.setBackgroundColor(context.getResources().getColor(groupBean.getColor()));
itemHolder.mBinding.tvDesc.setText(groupBean.getDesc());
itemHolder.mBinding.etNumber.setText(groupBean.getNumber()+"");
KeyBoardUtil.SoftKeyBoardListener.setListener(activity, new KeyBoardUtil.SoftKeyBoardListener.OnSoftKeyBoardChangeListener() {
@Override
public void keyBoardShow(int height) {
}
@Override
public void keyBoardHide(int height) {
itemHolder.mBinding.etNumber.clearFocus();
StatusBarUtil.hideSystemUI(activity);
int groupNumber = Integer.parseInt(itemHolder.mBinding.etNumber.getText().toString());
SharedUtil.getInstance(context).writeShared(groupBean.getType(),groupNumber);
}
});
}
}
@Override
public int getItemCount() {
return groupBeans!=null&&groupBeans.size()>0?groupBeans.size():0;
}
private class ItemHolder extends RecyclerView.ViewHolder {
private final com.srthinker.statinfo.databinding.ItemGroupBinding mBinding;
public ItemHolder(ItemGroupBinding binding) {
super(binding.getRoot());
mBinding = binding;
}
}
public void setDataList(ArrayList<GroupBean> groupBeans){
this.groupBeans = groupBeans;
notifyDataSetChanged();
}
}
package com.srthinker.statinfo.adapter;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import com.srthinker.statinfo.uis.fragment.group.TabGroupFragment;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Objects;
public class GroupPagerAdapter extends FragmentPagerAdapter {
private final LinkedHashMap<String, Integer> groupMap;
private ArrayList<String> titleList;
//private ArrayList<TabGroupFragment> fragmentList = new ArrayList<>();
private final HashMap<Integer,TabGroupFragment> fragmentHashMap = new HashMap<>();
private String TAG = "GroupPagerAdapter";
public GroupPagerAdapter(@NonNull FragmentManager fm, LinkedHashMap<String,Integer> groupMap) {
super(fm);
this.groupMap = groupMap;
if (groupMap!=null){
titleList = new ArrayList<>(groupMap.keySet());
}
}
@NonNull
@Override
public Fragment getItem(int position) {
//Log.i(TAG, "getItem: position="+position);
if (titleList!=null && titleList.size()>0) {
if (fragmentHashMap.containsKey(position)) {
return Objects.requireNonNull(fragmentHashMap.get(position));
}else{
TabGroupFragment fragment = TabGroupFragment.getInstance(groupMap.get(titleList.get(position)));
fragmentHashMap.put(position,fragment);
return fragment;
}
}
return null;
}
@Override
public int getCount() {
return titleList!=null&&titleList.size()>0?titleList.size():0;
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return titleList!=null&&titleList.size()>0?titleList.get(position):null;
}
}
package com.srthinker.statinfo.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.srthinker.statinfo.database.entity.PersonEntity;
import com.srthinker.statinfo.databinding.ItemLeaveBinding;
import java.util.List;
public class LeaveAdapter extends RecyclerView.Adapter {
private final Context context;
private List<PersonEntity> peopleBeans;
public LeaveAdapter(Context context, List<PersonEntity> peopleBeans){
this.context = context;
this.peopleBeans = peopleBeans;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemLeaveBinding binding = ItemLeaveBinding.inflate(LayoutInflater.from(context), parent, false);
return new ItemHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
ItemHolder itemHolder = (ItemHolder) holder;
PersonEntity peopleBean = peopleBeans.get(position);
if (peopleBean != null) {
itemHolder.mBinding.tvInfo.setText(peopleBean.getTimestamp()+" "+peopleBean.getPerson_name());
}
}
@Override
public int getItemCount() {
return peopleBeans!=null&&peopleBeans.size()>0?peopleBeans.size():0;
}
private class ItemHolder extends RecyclerView.ViewHolder {
private final com.srthinker.statinfo.databinding.ItemLeaveBinding mBinding;
public ItemHolder(ItemLeaveBinding binding) {
super(binding.getRoot());
mBinding = binding;
}
}
public void setDateList(List<PersonEntity> peopleBeans){
this.peopleBeans = peopleBeans;
notifyDataSetChanged();
}
}
package com.srthinker.statinfo.adapter;
import android.content.Context;
import android.util.SparseBooleanArray;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.srthinker.statinfo.database.entity.PersonEntity;
import com.srthinker.statinfo.databinding.ItemSelectGroupBinding;
import java.util.ArrayList;
import java.util.List;
public class PersonGroupSAdapter extends RecyclerView.Adapter {
private final Context context;
private List<PersonEntity> personEntities = new ArrayList<>();
private SparseBooleanArray mSelectedPositions = new SparseBooleanArray();
private boolean mIsSelectable =false;
public PersonGroupSAdapter(Context context, List<PersonEntity> personEntities){
this.context = context;
this.personEntities = personEntities;
}
public void setDataList(List<PersonEntity> personEntities){
this.personEntities = personEntities;
mSelectedPositions = new SparseBooleanArray();
notifyDataSetChanged();
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemSelectGroupBinding binding = ItemSelectGroupBinding.inflate(LayoutInflater.from(context), parent, false);
return new ItemHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
ItemHolder itemHolder = (ItemHolder) holder;
PersonEntity person = personEntities.get(position);
if (person!=null){
itemHolder.mBinding.tvPersonName.setText(person.getPerson_name());
itemHolder.mBinding.tvPersonName.setSelected(isItemChecked(position));
itemHolder.mBinding.llItem.setOnClickListener(v->{
if (isItemChecked(position)){
itemHolder.mBinding.tvPersonName.setSelected(false);
setItemChecked(position,false);
}else{
itemHolder.mBinding.tvPersonName.setSelected(true);
setItemChecked(position,true);
}
});
}
}
@Override
public int getItemCount() {
return personEntities!=null&&personEntities.size()>=0?personEntities.size():0;
}
//设置给定位置条目的选择状态
private void setItemChecked(int position, boolean isChecked) {
mSelectedPositions.put(position, isChecked);
}
//根据位置判断条目是否选中
private boolean isItemChecked(int position) {
return mSelectedPositions.get(position);
}
//根据位置判断条目是否可选
private boolean isSelectable() {
return mIsSelectable;
}
//设置给定位置条目的可选与否的状态
private void setSelectable(boolean selectable) {
mIsSelectable = selectable;
}
//获取选中的结果
public List<PersonEntity> getSelectedItem(){
List<PersonEntity> selectList = new ArrayList<>();
if (personEntities!=null){
for (int i = 0; i < personEntities.size(); i++) {
if (isItemChecked(i)){
selectList.add(personEntities.get(i));
}
}
}
return selectList;
}
private class ItemHolder extends RecyclerView.ViewHolder {
private final com.srthinker.statinfo.databinding.ItemSelectGroupBinding mBinding;
public ItemHolder(ItemSelectGroupBinding binding) {
super(binding.getRoot());
mBinding = binding;
}
}
}
package com.srthinker.statinfo.api.kuangshi;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URI;
/**
* 描述:TODO
*
* @author: liuqingliang
* Date: 2021/3/10
*/
public class MyWebSocketClient extends WebSocketClient {
Logger logger = LoggerFactory.getLogger(MyWebSocketClient.class);
public MyWebSocketClient(String serverUrl) {
super(URI.create(serverUrl));
this.waitConnect();
}
public void waitConnect(){
try {
this.connectBlocking();
} catch (InterruptedException e) {
logger.error(e.getMessage(),e);
}
}
@Override
public void onOpen(ServerHandshake serverHandshake) {
logger.info("---------MyWebSocketClient onOpen---------------");
}
@Override
public void onMessage(String s) {
logger.info("---------MyWebSocketClient onMessage---------------");
System.out.println(s);
//收到的消息内容,可根据协议文档进行json数据解析
}
@Override
public void onClose(int i, String s, boolean b) {
logger.info("---------MyWebSocketClient onClose---------------");
}
@Override
public void onError(Exception e) {
logger.info("---------MyWebSocketClient onError---------------");
}
}
package com.srthinker.statinfo.api.kuangshi.api;
import java.util.List;
public class ErrorResp {
private List<ErrorsBean> errors;
public List<ErrorsBean> getErrors() {
return errors;
}
public void setErrors(List<ErrorsBean> errors) {
this.errors = errors;
}
public static class ErrorsBean {
private int status;
private SourceBean source;
private String title;
private String detail;
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public SourceBean getSource() {
return source;
}
public void setSource(SourceBean source) {
this.source = source;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDetail() {
return detail;
}
public void setDetail(String detail) {
this.detail = detail;
}
public static class SourceBean {
private String pointer;
public String getPointer() {
return pointer;
}
public void setPointer(String pointer) {
this.pointer = pointer;
}
}
}
}
package com.srthinker.statinfo.api.kuangshi.api.auth;
import com.srthinker.statinfo.api.kuangshi.api.auth.bean.LoginResp;
/**
* 描述:认证管理类接口
*
* @author: liuqingliang
* Date: 2021/3/9
*/
public interface AuthApi {
/**
* 登录接口
* @param username 登录用户名
* @param password 用户密码(明文)
* @return
*/
LoginResp login(String username, String password);
String auth(String username);
}
package com.srthinker.statinfo.api.kuangshi.api.auth.bean;
/**
* 描述:TODO
*
* @author:
* Date: 2021/3/8
*/
public class LoginLockInfo {
/**
* 是否已锁定
*/
private boolean locked;
/**
* 剩余重试次数
*/
private int retryTime;
/**
* 锁定后剩余解锁时间,单位为秒
*/
private int unlockTime;
public boolean isLocked() {
return locked;
}
public void setLocked(boolean locked) {
this.locked = locked;
}
public int getRetryTime() {
return retryTime;
}
public void setRetryTime(int retryTime) {
this.retryTime = retryTime;
}
public int getUnlockTime() {
return unlockTime;
}
public void setUnlockTime(int unlockTime) {
this.unlockTime = unlockTime;
}
}
package com.srthinker.statinfo.api.kuangshi.api.auth.bean;
import com.alibaba.fastjson.annotation.JSONField;
/**
* 描述:登录接口返回
*
* @author: liuqingliang
* Date: 2021/3/8
*/
public class LoginResp {
/**
* 校验结果,200成功,401失败
*/
private int status;
/**
* 保存作为后续请求的凭证
*/
@JSONField(name = "session_id")
private String sessionId;
/**
* 只有失败后才会返回该字段
*/
private LoginLockInfo loginLockInfo;
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getSessionId() {
return sessionId;
}
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
public LoginLockInfo getLoginLockInfo() {
return loginLockInfo;
}
public void setLoginLockInfo(LoginLockInfo loginLockInfo) {
this.loginLockInfo = loginLockInfo;
}
}
package com.srthinker.statinfo.api.kuangshi.api.auth.impl;
import com.alibaba.fastjson.JSONObject;
import com.srthinker.statinfo.api.kuangshi.api.auth.AuthApi;
import com.srthinker.statinfo.api.kuangshi.api.auth.bean.LoginResp;
import com.srthinker.statinfo.api.kuangshi.client.DefaultApiClient;
import com.srthinker.statinfo.api.kuangshi.utils.DigestUtils;
import java.util.HashMap;
import java.util.Map;
/**
* 描述:TODO
*
* @author: liuqingliang
* Date: 2021/3/9
*/
public class AuthApiImpl implements AuthApi {
private static final String CHALLENGE_URL = "/api/auth/login/challenge";
private static final String LOGIN_URL = "/api/auth/login";
private DefaultApiClient httpClient;
public AuthApiImpl(DefaultApiClient httpClient) {
this.httpClient = httpClient;
}
@Override
public LoginResp login(String username, String password) {
Map<String, String> queryParams = new HashMap<>();
queryParams.put("username", username);
String response = httpClient.doGet(CHALLENGE_URL, queryParams);
JSONObject challengeJsonResp = JSONObject.parseObject(response);
if (challengeJsonResp != null) {
JSONObject jsonBody = new JSONObject();
String sessionId = challengeJsonResp.getString("session_id");
jsonBody.put("session_id", sessionId);
jsonBody.put("username", username);
// set password
String pwd = DigestUtils.sha256(String.format("%s%s%s", password, challengeJsonResp.getString("salt"), challengeJsonResp.getString("challenge")));
jsonBody.put("password", pwd);
//set ciphertext
String ciphertext = DigestUtils.aesEncrypt(password, sessionId);
jsonBody.put("ciphertext", ciphertext);
String resp = httpClient.doPostJson(LOGIN_URL, jsonBody.toJSONString());
LoginResp loginResp = JSONObject.parseObject(resp, LoginResp.class);
if (loginResp != null) {
if(loginResp.getStatus() == 200) {
//登录成功,将sessionid写入cookie
if (loginResp != null) {
httpClient.setCoolie(String.format("sessionID=%s", loginResp.getSessionId()));
}
}
}
return loginResp;
}
return null;
}
@Override
public String auth(String username) {
Map<String, String> queryParams = new HashMap<>();
queryParams.put("username", username);
String response = httpClient.doGet(CHALLENGE_URL, queryParams);
JSONObject challengeJsonResp = JSONObject.parseObject(response);
return challengeJsonResp.getString("session_id");
}
}
package com.srthinker.statinfo.api.kuangshi.api.device;
import com.srthinker.statinfo.api.kuangshi.api.device.bean.DeviceStatusResp;
import com.srthinker.statinfo.api.kuangshi.api.device.bean.FirmwareBean;
import java.io.File;
/**
* 描述:设备管理类接口
*
* @author: liuqingliang
* Date: 2021/3/9
*/
public interface DeviceApi {
/**
* 获取设备状态信息
* @return
*/
DeviceStatusResp status();
/**
* 上传升级文件
* @param file
* @return
*/
FirmwareBean firmware(File file);
/**
* OTA升级接口
* @param otaUri
* @param md5
* @return
*/
FirmwareBean firmwareOta(String otaUri, String md5);
}
package com.srthinker.statinfo.api.kuangshi.api.device.bean;
import com.alibaba.fastjson.annotation.JSONField;
/**
* 描述:获取设备状态接口返回
*
* @author: liuqingliang
* Date: 2021/3/9
*/
public class DeviceStatusResp {
/**
* 设备型号
*/
@JSONField(name = "model_spec")
private String modelSpec;
/**
* 设备序列号
*/
@JSONField(name = "serial_no")
private String serialNo;
/**
* 设备硬件地址
*/
@JSONField(name = "mac_str")
private String macStr;
/**
* 固件版本
*/
@JSONField(name = "firmware_version")
private String firmwareVersion;
/**
* 固件发布日期
*/
@JSONField(name = "firmware_date")
private String firmwareDate;
/**
* Web api版本
*/
@JSONField(name = "api_version")
private String apiVersion;
/**
* 设备人脸库版本
*/
@JSONField(name = "face_version")
private String faceVersion;
public String getModelSpec() {
return modelSpec;
}
public void setModelSpec(String modelSpec) {
this.modelSpec = modelSpec;
}
public String getSerialNo() {
return serialNo;
}
public void setSerialNo(String serialNo) {
this.serialNo = serialNo;
}
public String getMacStr() {
return macStr;
}
public void setMacStr(String macStr) {
this.macStr = macStr;
}
public String getFirmwareVersion() {
return firmwareVersion;
}
public void setFirmwareVersion(String firmwareVersion) {
this.firmwareVersion = firmwareVersion;
}
public String getFirmwareDate() {
return firmwareDate;
}
public void setFirmwareDate(String firmwareDate) {
this.firmwareDate = firmwareDate;
}
public String getApiVersion() {
return apiVersion;
}
public void setApiVersion(String apiVersion) {
this.apiVersion = apiVersion;
}
public String getFaceVersion() {
return faceVersion;
}
public void setFaceVersion(String faceVersion) {
this.faceVersion = faceVersion;
}
}
package com.srthinker.statinfo.api.kuangshi.api.device.bean;
/**
* 描述:TODO
*
* @author: liuqingliang
* Date: 2021/3/10
*/
public class FirmwareBean {
/**
* 获取升级状态的uri
*/
private String href;
/**
* http 方法
*/
private String method;
public String getHref() {
return href;
}
public void setHref(String href) {
this.href = href;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
}
package com.srthinker.statinfo.api.kuangshi.api.device.impl;
import com.alibaba.fastjson.JSONObject;
import com.srthinker.statinfo.api.kuangshi.api.device.DeviceApi;
import com.srthinker.statinfo.api.kuangshi.api.device.bean.DeviceStatusResp;
import com.srthinker.statinfo.api.kuangshi.api.device.bean.FirmwareBean;
import com.srthinker.statinfo.api.kuangshi.client.DefaultApiClient;
import org.apache.commons.lang3.StringUtils;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
/**
* 描述:TODO
*
* @author: liuqingliang
* Date: 2021/3/9
*/
public class DeviceApiImpl implements DeviceApi {
private static final String STATUS_URL = "/api/devices/status";
private static final String FIRMWARE_URL = "/api/devices/firmware";
private static final String FIRMWARE_OTA_URL = "/api/devices/firmware/ota";
private DefaultApiClient httpClient;
public DeviceApiImpl(DefaultApiClient httpClient) {
this.httpClient = httpClient;
}
@Override
public DeviceStatusResp status() {
String resp = this.httpClient.doGet(STATUS_URL);
return JSONObject.parseObject(resp, DeviceStatusResp.class);
}
@Override
public FirmwareBean firmware(File file) {
BufferedInputStream bis = null;
try{
bis = new BufferedInputStream(new FileInputStream(file));
Map<String, InputStream> inputStreamMap = new HashMap<>();
inputStreamMap.put("file", bis);
String resp = this.httpClient.doPostFormMultipartByInputStream(FIRMWARE_URL, null, inputStreamMap);
return JSONObject.parseObject(resp, FirmwareBean.class);
} catch (Exception e) {
throw new RuntimeException("firmware error.");
} finally {
if(bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Override
public FirmwareBean firmwareOta(String otaUri, String md5) {
JSONObject paramJson = new JSONObject();
paramJson.put("ota_uri", otaUri);
/*if (!TextUtils.isEmpty(md5)){
paramJson.put("md5", md5);
}*/
if(StringUtils.isNotBlank(md5)) {
paramJson.put("md5", md5);
}
String resp = this.httpClient.doPostJson(FIRMWARE_OTA_URL, paramJson.toJSONString());
return JSONObject.parseObject(resp, FirmwareBean.class);
}
}
package com.srthinker.statinfo.api.kuangshi.api.group;
import com.srthinker.statinfo.api.kuangshi.api.group.bean.GroupBean;
import com.srthinker.statinfo.api.kuangshi.api.group.bean.GroupQueryIdResp;
/**
* 描述:人员组相关接口
*
* @author: liuqingliang
* Date: 2021/3/10
*/
public interface GroupApi {
/**
* 添加人员组
* @param group
* @return
*/
GroupBean add(GroupBean group);
GroupQueryIdResp query(String group_id);
}
package com.srthinker.statinfo.api.kuangshi.api.group.bean;
import com.alibaba.fastjson.annotation.JSONField;
/**
* 描述:人员组
*
* @author: liuqingliang
* Date: 2021/3/10
*/
public class GroupBean {
/**
* 客户端请求的id
*/
@JSONField(name = "request_id")
private String requestId;
/**
* 人员组的名称
*/
@JSONField(name = "group_name")
private String groupName;
/**
* 人员组绑定的门禁计划id
*/
@JSONField(name = "schedule_id")
private String scheduleId;
/**
* id
*/
@JSONField(name = "id")
private String id;
public String getRequestId() {
return requestId;
}
public void setRequestId(String requestId) {
this.requestId = requestId;
}
public String getGroupName() {
return groupName;
}
public void setGroupName(String groupName) {
this.groupName = groupName;
}
public String getScheduleId() {
return scheduleId;
}
public void setScheduleId(String scheduleId) {
this.scheduleId = scheduleId;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
package com.srthinker.statinfo.api.kuangshi.api.group.bean;
import java.util.List;
public class GroupQueryIdResp {
private String cmd;
private String id;
private String type;
private String group_name;
private int person_count;
private String create_timestamp;
private String schedule_id;
private List<LinksBean> links;
public String getCmd() {
return cmd;
}
public void setCmd(String cmd) {
this.cmd = cmd;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getGroup_name() {
return group_name;
}
public void setGroup_name(String group_name) {
this.group_name = group_name;
}
public int getPerson_count() {
return person_count;
}
public void setPerson_count(int person_count) {
this.person_count = person_count;
}
public String getCreate_timestamp() {
return create_timestamp;
}
public void setCreate_timestamp(String create_timestamp) {
this.create_timestamp = create_timestamp;
}
public String getSchedule_id() {
return schedule_id;
}
public void setSchedule_id(String schedule_id) {
this.schedule_id = schedule_id;
}
public List<LinksBean> getLinks() {
return links;
}
public void setLinks(List<LinksBean> links) {
this.links = links;
}
public static class LinksBean {
private String rel;
private String href;
public String getRel() {
return rel;
}
public void setRel(String rel) {
this.rel = rel;
}
public String getHref() {
return href;
}
public void setHref(String href) {
this.href = href;
}
}
}
package com.srthinker.statinfo.api.kuangshi.api.group.impl;
import com.alibaba.fastjson.JSONObject;
import com.srthinker.statinfo.api.kuangshi.api.group.GroupApi;
import com.srthinker.statinfo.api.kuangshi.api.group.bean.GroupBean;
import com.srthinker.statinfo.api.kuangshi.api.group.bean.GroupQueryIdResp;
import com.srthinker.statinfo.api.kuangshi.client.DefaultApiClient;
import org.apache.commons.lang3.StringUtils;
/**
* 描述:TODO
*
* @author: liuqingliang
* Date: 2021/3/10
*/
public class GroupApiImpl implements GroupApi {
private static final String POST_ITEM_URL = "/api/groups/item";
private static final String POST_QUERY_URL = "/api/groups/item";
private DefaultApiClient httpClient;
public GroupApiImpl(DefaultApiClient httpClient) {
this.httpClient = httpClient;
}
@Override
public GroupBean add(GroupBean group) {
String resp = this.httpClient.doPostJson(POST_ITEM_URL, JSONObject.toJSONString(group));
return JSONObject.parseObject(resp, GroupBean.class);
}
@Override
public GroupQueryIdResp query(String group_id) {
if (StringUtils.isEmpty(group_id)){
return null;
}else{
String resp = httpClient.doGet(POST_QUERY_URL + "/" + group_id);
//System.out.println(resp);
}
return null;
}
}
package com.srthinker.statinfo.api.kuangshi.api.pass;
import com.srthinker.statinfo.api.kuangshi.api.pass.bean.PassQueryBean;
public interface PassApi {
PassQueryBean query(int limit,int offset, String sort, String begin_time, String end_time);
//PassQueryBean query(String begin_time,String end_time);
}
package com.srthinker.statinfo.api.kuangshi.api.pass.bean;
public class Body {
private String sort;
private String begin_time;
private String end_time;
private int limit;
private int offset;
public String getSort() {
return sort;
}
public void setSort(String sort) {
this.sort = sort;
}
public String getBegin_time() {
return begin_time;
}
public void setBegin_time(String begin_time) {
this.begin_time = begin_time;
}
public String getEnd_time() {
return end_time;
}
public void setEnd_time(String end_time) {
this.end_time = end_time;
}
public int getLimit() {
return limit;
}
public void setLimit(int limit) {
this.limit = limit;
}
public int getOffset() {
return offset;
}
public void setOffset(int offset) {
this.offset = offset;
}
/*public Body(int limit) {
this.limit = limit;
}*/
public Body(String sort, String begin_time, String end_time, int limit, int offset) {
this.sort = sort;
this.begin_time = begin_time;
this.end_time = end_time;
this.limit = limit;
this.offset = offset;
}
}
package com.srthinker.statinfo.api.kuangshi.api.pass.bean;
public class Paging {
private int limit;
private int offset;
private int total;
public int getLimit() {
return limit;
}
public void setLimit(int limit) {
this.limit = limit;
}
public int getOffset() {
return offset;
}
public void setOffset(int offset) {
this.offset = offset;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
}
package com.srthinker.statinfo.api.kuangshi.api.pass.bean;
import java.util.List;
public class PassQueryBean {
private String query_id;
private PagingBean paging;
private List<DataBean> data;
@Override
public String toString() {
return "PassQueryBean{" +
"query_id='" + query_id + '\'' +
", paging=" + paging +
", data=" + data +
'}';
}
public String getQuery_id() {
return query_id;
}
public void setQuery_id(String query_id) {
this.query_id = query_id;
}
public PagingBean getPaging() {
return paging;
}
public void setPaging(PagingBean paging) {
this.paging = paging;
}
public List<DataBean> getData() {
return data;
}
public void setData(List<DataBean> data) {
this.data = data;
}
public static class PagingBean {
private int limit;
private int offset;
private int total;
public int getLimit() {
return limit;
}
public void setLimit(int limit) {
this.limit = limit;
}
public int getOffset() {
return offset;
}
public void setOffset(int offset) {
this.offset = offset;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
}
public static class DataBean {
private String person_id;
private String person_code;
private String pass_mode;
private String verification_mode;
private String recognition_type;
private double recognition_score;
private boolean liveness;
private double liveness_score;
private String person_name;
private String card_number;
private String id_number;
private String timestamp;
private boolean mask;
private boolean therm;
private int temperature;
private int health_code;
public String getPerson_id() {
return person_id;
}
public void setPerson_id(String person_id) {
this.person_id = person_id;
}
public String getPerson_code() {
return person_code;
}
public void setPerson_code(String person_code) {
this.person_code = person_code;
}
public String getPass_mode() {
return pass_mode;
}
public void setPass_mode(String pass_mode) {
this.pass_mode = pass_mode;
}
public String getVerification_mode() {
return verification_mode;
}
public void setVerification_mode(String verification_mode) {
this.verification_mode = verification_mode;
}
public String getRecognition_type() {
return recognition_type;
}
public void setRecognition_type(String recognition_type) {
this.recognition_type = recognition_type;
}
public double getRecognition_score() {
return recognition_score;
}
public void setRecognition_score(double recognition_score) {
this.recognition_score = recognition_score;
}
public boolean isLiveness() {
return liveness;
}
public void setLiveness(boolean liveness) {
this.liveness = liveness;
}
public double getLiveness_score() {
return liveness_score;
}
public void setLiveness_score(double liveness_score) {
this.liveness_score = liveness_score;
}
public String getPerson_name() {
return person_name;
}
public void setPerson_name(String person_name) {
this.person_name = person_name;
}
public String getCard_number() {
return card_number;
}
public void setCard_number(String card_number) {
this.card_number = card_number;
}
public String getId_number() {
return id_number;
}
public void setId_number(String id_number) {
this.id_number = id_number;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public boolean isMask() {
return mask;
}
public void setMask(boolean mask) {
this.mask = mask;
}
public boolean isTherm() {
return therm;
}
public void setTherm(boolean therm) {
this.therm = therm;
}
public int getTemperature() {
return temperature;
}
public void setTemperature(int temperature) {
this.temperature = temperature;
}
public int getHealth_code() {
return health_code;
}
public void setHealth_code(int health_code) {
this.health_code = health_code;
}
}
}
package com.srthinker.statinfo.api.kuangshi.api.pass.impl;
import com.alibaba.fastjson.JSONObject;
import com.srthinker.statinfo.api.kuangshi.api.pass.PassApi;
import com.srthinker.statinfo.api.kuangshi.api.pass.bean.Body;
import com.srthinker.statinfo.api.kuangshi.api.pass.bean.PassQueryBean;
import com.srthinker.statinfo.api.kuangshi.client.DefaultApiClient;
public class PassApiImpl implements PassApi {
private static final String PASS_QUERY_URL = "/api/passes/query";
private DefaultApiClient httpClient;
public PassApiImpl(DefaultApiClient httpClient){
this.httpClient = httpClient;
}
@Override
public PassQueryBean query(int limit,int offset,String sort, String begin_time, String end_time) {
String resp = httpClient.doPostJson(PASS_QUERY_URL, JSONObject.toJSONString(new Body(sort,begin_time,end_time,limit,offset)));
PassQueryBean passQueryBean = JSONObject.parseObject(resp, PassQueryBean.class);
return passQueryBean;
}
}
package com.srthinker.statinfo.api.kuangshi.api.person;
import com.srthinker.statinfo.api.kuangshi.api.person.bean.PersonBean;
import com.srthinker.statinfo.api.kuangshi.api.person.bean.PersonQueryResp;
/**
* 描述:人员类相关接口
*
* @author: liuqingliang
* Date: 2021/3/10
*/
public interface PersonApi {
/**
* 添加人员
* @param person
*/
void add(PersonBean person);
PersonQueryResp query(int limit,int offset,String sort);
}
package com.srthinker.statinfo.api.kuangshi.api.person.bean;
/**
* 描述:人员照片
*
* @author: liuqingliang
* Date: 2021/3/10
*/
public class FaceListItem {
/**
* 照片索引
*/
private int idx;
/**
* Data URI: Base64编码的照片数据
*/
private String data;
public FaceListItem() {}
public FaceListItem(int idx, String data) {
this.idx = idx;
this.data = data;
}
public int getIdx() {
return idx;
}
public void setIdx(int idx) {
this.idx = idx;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
package com.srthinker.statinfo.api.kuangshi.api.person.bean;
import com.alibaba.fastjson.annotation.JSONField;
import java.util.List;
/**
* 描述:人员信息
*
* @author: liuqingliang
* Date: 2021/3/10
*/
public class PersonBean {
/**
* 人员id,为空时使用默认编号
*/
private String id;
/**
* 人员类型,staff – 普通人员,visitor – 访客,blacklist – 黑名单
*/
@JSONField(name = "recognition_type")
private String recognitionType;
/**
* 是否启用管理员权限
*/
@JSONField(name = "is_admin")
private String isAdmin;
/**
* 人员名称
*/
@JSONField(name = "person_name")
private String personName;
/**
* 密码加密方式,plain-明文, aes-AES加密
*/
@JSONField(name = "password_encrypt_type")
private String passwordEncryptType;
/**
* 密码使用6位数字
*/
@JSONField(name = "password")
private String password;
/**
* 卡号,最大20位数字
*/
@JSONField(name = "card_number")
private String cardNumber;
/**
* 绑定人员组的列表
*/
@JSONField(name = "group_list")
private List<String> groupList;
/**
* 人员照片
*/
@JSONField(name = "face_list")
private List<FaceListItem> faceList;
/**
* 人员编号
*/
@JSONField(name = "person_code")
private String personCode;
/**
* 身份证号码,需要aes加密!
*/
@JSONField(name = "id_number")
private String idNumber;
/**
* 访客开始时间,ISO8601格式,仅访客模式有效
*/
@JSONField(name = "visit_begin_time")
private String visitBeginTime;
/**
* 访客结束时间,ISO8601格式,仅访客模式有效
*/
@JSONField(name = "visit_end_time")
private String visitEndTime;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getRecognitionType() {
return recognitionType;
}
public void setRecognitionType(String recognitionType) {
this.recognitionType = recognitionType;
}
public String getIsAdmin() {
return isAdmin;
}
public void setIsAdmin(String isAdmin) {
this.isAdmin = isAdmin;
}
public String getPersonName() {
return personName;
}
public void setPersonName(String personName) {
this.personName = personName;
}
public String getPasswordEncryptType() {
return passwordEncryptType;
}
public void setPasswordEncryptType(String passwordEncryptType) {
this.passwordEncryptType = passwordEncryptType;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getCardNumber() {
return cardNumber;
}
public void setCardNumber(String cardNumber) {
this.cardNumber = cardNumber;
}
public List<String> getGroupList() {
return groupList;
}
public void setGroupList(List<String> groupList) {
this.groupList = groupList;
}
public List<FaceListItem> getFaceList() {
return faceList;
}
public void setFaceList(List<FaceListItem> faceList) {
this.faceList = faceList;
}
public String getPersonCode() {
return personCode;
}
public void setPersonCode(String personCode) {
this.personCode = personCode;
}
public String getVisitBeginTime() {
return visitBeginTime;
}
public void setVisitBeginTime(String visitBeginTime) {
this.visitBeginTime = visitBeginTime;
}
public String getVisitEndTime() {
return visitEndTime;
}
public void setVisitEndTime(String visitEndTime) {
this.visitEndTime = visitEndTime;
}
public String getIdNumber() {
return idNumber;
}
public void setIdNumber(String idNumber) {
this.idNumber = idNumber;
}
}
package com.srthinker.statinfo.api.kuangshi.api.person.bean;
import java.util.List;
public class PersonQueryResp {
private String query_id;
private PagingBean paging;
private List<DataBean> data;
public String getQuery_id() {
return query_id;
}
public void setQuery_id(String query_id) {
this.query_id = query_id;
}
public PagingBean getPaging() {
return paging;
}
public void setPaging(PagingBean paging) {
this.paging = paging;
}
public List<DataBean> getData() {
return data;
}
public void setData(List<DataBean> data) {
this.data = data;
}
public static class PagingBean {
private int limit;
private int offset;
private int total;
public int getLimit() {
return limit;
}
public void setLimit(int limit) {
this.limit = limit;
}
public int getOffset() {
return offset;
}
public void setOffset(int offset) {
this.offset = offset;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
}
public static class DataBean {
private String recognition_type;
private String id;
private String type;
private boolean is_admin;
private String person_name;
private String card_number;
private String person_code;
private String id_number;
private List<String> group_list;
private List<LinksBean> links;
public String getRecognition_type() {
return recognition_type;
}
public void setRecognition_type(String recognition_type) {
this.recognition_type = recognition_type;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public boolean isIs_admin() {
return is_admin;
}
public void setIs_admin(boolean is_admin) {
this.is_admin = is_admin;
}
public String getPerson_name() {
return person_name;
}
public void setPerson_name(String person_name) {
this.person_name = person_name;
}
public String getCard_number() {
return card_number;
}
public void setCard_number(String card_number) {
this.card_number = card_number;
}
public String getPerson_code() {
return person_code;
}
public void setPerson_code(String person_code) {
this.person_code = person_code;
}
public String getId_number() {
return id_number;
}
public void setId_number(String id_number) {
this.id_number = id_number;
}
public List<String> getGroup_list() {
return group_list;
}
public void setGroup_list(List<String> group_list) {
this.group_list = group_list;
}
public List<LinksBean> getLinks() {
return links;
}
public void setLinks(List<LinksBean> links) {
this.links = links;
}
public static class LinksBean {
private String rel;
private String href;
public String getRel() {
return rel;
}
public void setRel(String rel) {
this.rel = rel;
}
public String getHref() {
return href;
}
public void setHref(String href) {
this.href = href;
}
}
}
}
package com.srthinker.statinfo.api.kuangshi.api.person.bean;
public class QueryBody {
private int limit;
private int offset;
private String sort;
public int getLimit() {
return limit;
}
public void setLimit(int limit) {
this.limit = limit;
}
public int getOffset() {
return offset;
}
public void setOffset(int offset) {
this.offset = offset;
}
public String getSort() {
return sort;
}
public void setSort(String sort) {
this.sort = sort;
}
public QueryBody(int limit, int offset, String sort) {
this.limit = limit;
this.offset = offset;
this.sort = sort;
}
}
package com.srthinker.statinfo.api.kuangshi.api.person.impl;
import com.alibaba.fastjson.JSONObject;
import com.srthinker.statinfo.api.kuangshi.api.person.PersonApi;
import com.srthinker.statinfo.api.kuangshi.api.person.bean.PersonBean;
import com.srthinker.statinfo.api.kuangshi.api.person.bean.PersonQueryResp;
import com.srthinker.statinfo.api.kuangshi.api.person.bean.QueryBody;
import com.srthinker.statinfo.api.kuangshi.client.DefaultApiClient;
/**
* 描述:TODO
*
* @author: liuqingliang
* Date: 2021/3/10
*/
public class PersonApiImpl implements PersonApi {
private static final String POST_ITEM_URL = "/api/persons/item";
private static final String POST_QUERY_PERSON_URL = "/api/persons/query";
private DefaultApiClient httpClient;
public PersonApiImpl(DefaultApiClient httpClient) {
this.httpClient = httpClient;
}
@Override
public void add(PersonBean person) {
String resp = httpClient.doPostJson(POST_ITEM_URL, JSONObject.toJSONString(person));
System.out.println(resp);
}
@Override
public PersonQueryResp query(int limit, int offset, String sort) {
QueryBody queryBody = new QueryBody(limit, offset, sort);
String resp = httpClient.doPostJson(POST_QUERY_PERSON_URL, JSONObject.toJSONString(queryBody));
//System.out.println(resp);
PersonQueryResp personQueryResp = JSONObject.parseObject(resp, PersonQueryResp.class);
return personQueryResp;
}
}
package com.srthinker.statinfo.api.kuangshi.api.schedule;
import com.srthinker.statinfo.api.kuangshi.api.schedule.bean.ScheduleCrondBean;
import com.srthinker.statinfo.api.kuangshi.api.schedule.bean.ScheduleJsonBean;
/**
* 描述:门禁计划类接口
*
* @author: liuqingliang
* Date: 2021/3/9
*/
public interface ScheduleApi {
/**
* 通过json数据格式添加
* @param item
* @return
*/
ScheduleJsonBean addScheduleItemByJson(ScheduleJsonBean item);
/**
* 通过crond数据格式添加
* @param item
* @return
*/
ScheduleCrondBean addScheduleItemByCrond(ScheduleCrondBean item);
}
package com.srthinker.statinfo.api.kuangshi.api.schedule.bean;
import com.alibaba.fastjson.annotation.JSONField;
/**
* 描述:TODO
*
* @author: liuqingliang
* Date: 2021/3/9
*/
public class ScheduleBean {
/**
* 客户端请求的uuid【非必须】
*/
@JSONField(name = "request_id")
private String requestId;
/**
* 门禁计划的名称【必须】
*/
@JSONField(name = "schedule_name")
private String scheduleName;
/**
* 门禁计划的名称【非必须】
*/
@JSONField(name = "id")
private String id;
public String getRequestId() {
return requestId;
}
public void setRequestId(String requestId) {
this.requestId = requestId;
}
public String getScheduleName() {
return scheduleName;
}
public void setScheduleName(String scheduleName) {
this.scheduleName = scheduleName;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
package com.srthinker.statinfo.api.kuangshi.api.schedule.bean;
import com.alibaba.fastjson.annotation.JSONField;
import java.util.List;
/**
* 描述:TODO
*
* @author: liuqingliang
* Date: 2021/3/9
*/
public class ScheduleCrondBean extends ScheduleBean {
/**
* 门禁计划规则类型,crond-Linux定时计划格式,json-json计划格式。【非必须】
*/
@JSONField(name = "schedule_list_type")
private String scheduleListType = "crond";
/**
* 门禁计划时间列表【必须】
*/
@JSONField(name = "schedule_list")
private List<ScheduleListRuleCrondBean> scheduleList;
public List<ScheduleListRuleCrondBean> getScheduleList() {
return scheduleList;
}
public void setScheduleList(List<ScheduleListRuleCrondBean> scheduleList) {
this.scheduleList = scheduleList;
}
public String getScheduleListType() {
return scheduleListType;
}
public void setScheduleListType(String scheduleListType) {
this.scheduleListType = scheduleListType;
}
}
package com.srthinker.statinfo.api.kuangshi.api.schedule.bean;
import com.alibaba.fastjson.annotation.JSONField;
import java.util.List;
/**
* 描述:TODO
*
* @author: liuqingliang
* Date: 2021/3/9
*/
public class ScheduleJsonBean extends ScheduleBean {
/**
* 门禁计划规则类型,crond-Linux定时计划格式,json-json计划格式。【非必须】
*/
@JSONField(name = "schedule_list_type")
private String scheduleListType = "json";
/**
* 门禁计划时间列表【必须】
*/
@JSONField(name = "schedule_list")
private List<ScheduleListRuleJsonBean> scheduleList;
public List<ScheduleListRuleJsonBean> getScheduleList() {
return scheduleList;
}
public void setScheduleList(List<ScheduleListRuleJsonBean> scheduleList) {
this.scheduleList = scheduleList;
}
public String getScheduleListType() {
return scheduleListType;
}
public void setScheduleListType(String scheduleListType) {
this.scheduleListType = scheduleListType;
}
}
package com.srthinker.statinfo.api.kuangshi.api.schedule.bean;
/**
* 描述:门禁计划列表规则
*
* @author: liuqingliang
* Date: 2021/3/9
*/
public class ScheduleListRuleBean {
/**
* 计划类型,workday 工作日,holiday 假日
*/
private String type;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
package com.srthinker.statinfo.api.kuangshi.api.schedule.bean;
/**
* 描述:TODO
*
* @author: liuqingliang
* Date: 2021/3/9
*/
public class ScheduleListRuleCrondBean extends ScheduleListRuleBean {
private String rule;
public String getRule() {
return rule;
}
public void setRule(String rule) {
this.rule = rule;
}
}
package com.srthinker.statinfo.api.kuangshi.api.schedule.bean;
import java.util.HashMap;
/**
* 描述:TODO
*
* @author: liuqingliang
* Date: 2021/3/9
*/
public class ScheduleListRuleJsonBean extends ScheduleListRuleBean {
private HashMap<String, String> rule = new HashMap<>();
public HashMap<String, String> getRule() {
return rule;
}
public void setRule(HashMap<String, String> rule) {
this.rule = rule;
}
}
package com.srthinker.statinfo.api.kuangshi.api.schedule.impl;
import com.alibaba.fastjson.JSONObject;
import com.srthinker.statinfo.api.kuangshi.api.schedule.ScheduleApi;
import com.srthinker.statinfo.api.kuangshi.api.schedule.bean.ScheduleCrondBean;
import com.srthinker.statinfo.api.kuangshi.api.schedule.bean.ScheduleJsonBean;
import com.srthinker.statinfo.api.kuangshi.client.DefaultApiClient;
/**
* 描述:门禁计划相关接口
*
* @author: liuqingliang
* Date: 2021/3/9
*/
public class ScheduleApiImpl implements ScheduleApi {
private static final String POST_ITEM_URL = "/api/schedules/item";
private DefaultApiClient httpClient;
public ScheduleApiImpl(DefaultApiClient httpClient) {
this.httpClient = httpClient;
}
@Override
public ScheduleJsonBean addScheduleItemByJson(ScheduleJsonBean item) {
String resp = this.httpClient.doPostJson(POST_ITEM_URL, JSONObject.toJSONString(item));
return JSONObject.parseObject(resp, ScheduleJsonBean.class);
}
@Override
public ScheduleCrondBean addScheduleItemByCrond(ScheduleCrondBean item) {
String resp = this.httpClient.doPostJson(POST_ITEM_URL, JSONObject.toJSONString(item));
return JSONObject.parseObject(resp, ScheduleCrondBean.class);
}
}
package com.srthinker.statinfo.api.kuangshi.api.subscribe;
/**
* 描述:实时数据推送相关接口
*
* @author: liuqingliang
* Date: 2021/3/10
*/
public interface SubscribeApi {
/**
* 设置第三方服务器推送配置
* @param serverUri 需要接收设备推送数据的uri
* @param enabled 是否启用
* @param method HTTP方法(serverUri对应的http请求对应的方法)
*/
void push(String serverUri, boolean enabled, String method);
}
package com.srthinker.statinfo.api.kuangshi.api.subscribe.impl;
import com.alibaba.fastjson.JSONObject;
import com.srthinker.statinfo.api.kuangshi.api.subscribe.SubscribeApi;
import com.srthinker.statinfo.api.kuangshi.client.DefaultApiClient;
/**
* 描述:TODO
*
* @author: liuqingliang
* Date: 2021/3/10
*/
public class SubscribeApiImpl implements SubscribeApi {
private static final String PUSH_URL = "/api/subscribe/push";
private DefaultApiClient httpClient;
public SubscribeApiImpl(DefaultApiClient httpClient) {
this.httpClient = httpClient;
}
@Override
public void push(String serverUri, boolean enabled, String method) {
JSONObject paramJson = new JSONObject();
paramJson.put("server_uri", serverUri);
paramJson.put("enabled", enabled);
paramJson.put("method", method);
this.httpClient.doPutJson(PUSH_URL, paramJson.toJSONString());
}
}
package com.srthinker.statinfo.api.kuangshi.client;
/**
* 描述:TODO
*
* @author: liuqingliang
* Date: 2021/3/10
*/
public class ApiClientException extends RuntimeException {
private int httpStatus;
private String message;
private String response;
public ApiClientException() {
}
public ApiClientException(int httpStatus, String message, String response) {
this.httpStatus = httpStatus;
this.message = message;
this.response = response;
}
public int getHttpStatus() {
return httpStatus;
}
public void setHttpStatus(int httpStatus) {
this.httpStatus = httpStatus;
}
public String getResponse() {
return response;
}
public void setResponse(String response) {
this.response = response;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
package com.srthinker.statinfo.api.kuangshi.utils;
import org.apache.commons.lang3.StringUtils;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
/**
* 描述:TODO
*
* @author: liuqingliang
* Date: 2021/3/1
*/
public class DigestUtils {
/**
* 加密算法
*/
private static final String KEY_ALGORITHM = "AES";
/**
* 设定秘钥长度
*/
private static final int KEY_LENGTH = 32;
//AES_256_cbc pkcs7
private static final String ALGORITHM = "AES/ECB/PKCS7Padding";
/**
* 用来补位
*/
static StringBuffer additionalStr = new StringBuffer("00000000000000000000000000000000");
/**
* ASE加密
* @param data
* @param secretKey
* @return
*/
public static String aesEncrypt(String data, String secretKey) {
try {
SecretKeySpec keySpec = getSecretKey(secretKey);
//Security.addProvider(new BouncyCastleProvider());
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
//return Base64.getEncoder().encodeToString(encData);
return android.util.Base64.encodeToString(encData,android.util.Base64.DEFAULT);
} catch (Exception e) {
throw new RuntimeException("aes encrypt error.");
}
}
public static String aesDecrypt(String base64Data, String secretKey) {
try {
SecretKeySpec keySpec = getSecretKey(secretKey);
//Security.addProvider(new BouncyCastleProvider());
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, keySpec);
//byte[] decbbdt = cipher.doFinal(Base64.getDecoder().decode(base64Data));
byte[] decbbdt = cipher.doFinal(android.util.Base64.decode(base64Data,android.util.Base64.DEFAULT));
return new String(decbbdt, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("aes decrypt error.");
}
}
public static String sha256(String value){
MessageDigest sha = null;
try {
sha = MessageDigest.getInstance("SHA-256");
byte[] byteArray = value.getBytes("UTF-8");
byte[] md5Bytes = sha.digest(byteArray);
StringBuffer hexValue = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++) {
int val = ((int) md5Bytes[i]) & 0xff;
if (val < 16) {
hexValue.append("0");
}
hexValue.append(Integer.toHexString(val));
}
return hexValue.toString();
} catch (Exception e) {
throw new RuntimeException("sha256 error");
}
}
/**
* 使用密码获取 AES 秘钥
*/
public static SecretKeySpec getSecretKey(String key) {
if (StringUtils.isEmpty(key)) {
return null;
}
try {
if (key.length() > KEY_LENGTH) {
key = key.substring(0, KEY_LENGTH);
} else if (key.length() < KEY_LENGTH) {
int count = KEY_LENGTH - key.length();
String addstr = additionalStr.substring(0, count);
key = key + addstr;
}
return new SecretKeySpec(key.getBytes(), KEY_ALGORITHM);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
package com.srthinker.statinfo.api.kuangshi.utils.http.okhttp;
/**
* 描述:进度回调接口
* Author: zhouxi
* Date: 2020/3/11
*/
public interface IProgress {
/**
* 进度条回调
* @param handledBytes 已处理完的字节数组
*/
void onProgress(long totalBytes, long handledBytes);
}
package com.srthinker.statinfo.bean;
import java.util.Objects;
public class ConfigBean {
private String username;
private String password;
private String serverIp;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getServerIp() {
return serverIp;
}
public void setServerIp(String serverIp) {
this.serverIp = serverIp;
}
public ConfigBean(String username, String password, String serverIp) {
this.username = username;
this.password = password;
this.serverIp = serverIp;
}
public ConfigBean() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ConfigBean that = (ConfigBean) o;
return Objects.equals(username, that.username) && Objects.equals(password, that.password) && Objects.equals(serverIp, that.serverIp);
}
@Override
public int hashCode() {
return Objects.hash(username, password, serverIp);
}
@Override
public String toString() {
return "ConfigBean{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", serverIp='" + serverIp + '\'' +
'}';
}
}
package com.srthinker.statinfo.bean;
public class GroupBean {
private String desc;
private int number;
private int color;
private String type;
public GroupBean(String desc, int number, int color, String type) {
this.desc = desc;
this.number = number;
this.color = color;
this.type = type;
}
public GroupBean() {
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
package com.srthinker.statinfo.bean;
public class GroupSizeBean {
//private int group;
private int interTotal;
private int leaveTotal;
/*public int getGroup() {
return group;
}
public void setGroup(int group) {
this.group = group;
}*/
public int getInterTotal() {
return interTotal;
}
public void setInterTotal(int interTotal) {
this.interTotal = interTotal;
}
public int getLeaveTotal() {
return leaveTotal;
}
public void setLeaveTotal(int leaveTotal) {
this.leaveTotal = leaveTotal;
}
public GroupSizeBean(int interTotal, int leaveTotal) {
this.interTotal = interTotal;
this.leaveTotal = leaveTotal;
}
}
package com.srthinker.statinfo.bean;
public class PeopleBean {
private String time;
private String name;
private int type; //进出情况:进:0,出:1
public PeopleBean() {
}
public PeopleBean(String time, String name) {
this.time = time;
this.name = name;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}
package com.srthinker.statinfo.bean;
public class PointBean {
private String time;
private int people_num;
private int type;
public PointBean() {
}
public PointBean(String time, int people_num, int type) {
this.time = time;
this.people_num = people_num;
this.type = type;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public int getPeople_num() {
return people_num;
}
public void setPeople_num(int people_num) {
this.people_num = people_num;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}
package com.srthinker.statinfo.bean;
public class TimeBean {
private String yMD;
private String hm;
private String week;
public TimeBean(String yMD, String hm, String week) {
this.yMD = yMD;
this.hm = hm;
this.week = week;
}
public TimeBean() {
}
public String getyMD() {
return yMD;
}
public void setyMD(String yMD) {
this.yMD = yMD;
}
public String getHm() {
return hm;
}
public void setHm(String hm) {
this.hm = hm;
}
public String getWeek() {
return week;
}
public void setWeek(String week) {
this.week = week;
}
}
package com.srthinker.statinfo.constant;
public class Const {
public static final String DEVICE_TYPE = "RC13";
}
package com.srthinker.statinfo.constant;
public class GroupConst {
public static final String WATERPROOF = "waterproof";
public static final String OUTRIGGER = "outrigger";
public static final String BUILD = "build";
public static final String SUPERVISOR = "supervisor";
public static final int WATERPROOF_ID = 1;
public static final int OUTRIGGER_ID = 2;
public static final int BUILD_ID = 3;
public static final int SUPERVISOR_ID = 4;
public static final int NO_GROUP = 100;
public static final int GROUPED = 200;
}
package com.srthinker.statinfo.constant;
public class InOutType {
public static final int ENTER_TYPE = 0x12138;
public static final int LEAVE_TYPE = 0x22138;
}
package com.srthinker.statinfo.database;
import com.raizlabs.android.dbflow.annotation.Database;
@Database(name = AppDB.DB_NAME,version = AppDB.DB_VERSION)
public class AppDB {
public static final String DB_NAME = "AppDB";
public static final int DB_VERSION = 1;
}
package com.srthinker.statinfo.database;
import android.text.TextUtils;
import android.util.Log;
import com.srthinker.statinfo.database.entity.PersonEntity;
import com.srthinker.statinfo.database.helper.PersonHelper;
import com.srthinker.statinfo.listener.PersonMgrCallback;
import com.srthinker.statinfo.util.common.ThreadPool;
import java.util.ArrayList;
import java.util.List;
public class PersonMgr {
private static final String TAG = "PersonMgr";
private static PersonMgr personMgr;
private final PersonHelper personHelper;
// private PersonMgrCallback callback;
private PersonMgr(){
personHelper = new PersonHelper();
}
public static PersonMgr getInstance(){
if (personMgr == null) {
synchronized (PersonMgr.class){
personMgr = new PersonMgr();
}
}
return personMgr;
}
/* public void setOnPersonMgrCallback(PersonMgrCallback callback){
this.callback = callback;
}*/
//从接口中拿去新数据
private void updatePerson(List<PersonEntity> personEntities){
List<PersonEntity> queryPersons = personHelper.queryAll();
Log.i(TAG, "updatePerson: queryPersons="+queryPersons.size());
//如果拉来的数据为空或者为null
if (personEntities==null || personEntities.size()<=0){
return;
}
//如果数据库为空或没有
if (queryPersons==null||queryPersons.size()<=0){
personHelper.add(personEntities);
return;
}
//如果都有
//给从接口获取新的数据之前有分过组的数据写分组的信息,已经被删除的人员也要删除掉
ArrayList<PersonEntity> needDelPersons = new ArrayList<>();
for (PersonEntity queryPerson : queryPersons) {
String queryId = queryPerson.getId();
boolean isFound = false;
for (PersonEntity newPersonEntity : personEntities) {
String newId = newPersonEntity.getId();
if (TextUtils.equals(queryId,newId)){
newPersonEntity.setGroup(queryPerson.getGroup());
isFound = true;
break;
}
}
if (!isFound){
needDelPersons.add(queryPerson);
}
}
if (personHelper != null) {
personHelper.save(personEntities);
Log.i(TAG, "updatePerson: needDelPersons.size()="+needDelPersons.size());
personHelper.delete(needDelPersons);
}
}
public void updatePersonThread(List<PersonEntity> personEntities,PersonMgrCallback callback){
ThreadPool.getInstance().getThreadPoolExecutor().execute(new Runnable() {
@Override
public void run() {
updatePerson(personEntities);
if (callback != null) {
callback.personUpdated();
}
}
});
}
/* public void getUpdatePerson(List<PersonEntity> personEntities){
Runnable updatePersonRunnable = () -> {
List<PersonEntity> updatePersonList = new ArrayList<>();
updatePerson(personEntities);
updatePersonList = personHelper.queryAll();
if (callback != null) {
callback.getUpdatePerson(updatePersonList);
}
};
ThreadPool.getInstance().getThreadPoolExecutor().execute(updatePersonRunnable);
}*/
//从组列表中获取有分组的列表(获取的列表不一定是全部的,可能是部分的
private void updateFromList(List<PersonEntity> personEntities){
// Log.i(TAG, "updateFromList: personEntities.size="+personEntities.size());
List<PersonEntity> queryPersons = personHelper.queryAll();
// Log.i(TAG, "updateFromList: queryPersons.size="+queryPersons.size());
if (personEntities==null || personEntities.size()<=0){
return ;
}
if (queryPersons==null || queryPersons.size()<=0){
return ;
}
List<PersonEntity> needUpdate = new ArrayList<>();
for (PersonEntity personEntity : personEntities) {
String personId = personEntity.getId();
for (PersonEntity queryPerson : queryPersons) {
String queryId = queryPerson.getId();
if (TextUtils.equals(personId,queryId)){
queryPerson.setGroup(personEntity.getGroup());
needUpdate.add(queryPerson);
break;
}
}
}
if (personHelper != null) {
personHelper.update(needUpdate);
}
}
public void updateFromListThread(List<PersonEntity> personEntities,PersonMgrCallback callback){
ThreadPool.getInstance().getThreadPoolExecutor().execute(new Runnable() {
@Override
public void run() {
updateFromList(personEntities);
if (callback != null) {
callback.personUpdated();
}
}
});
}
/*public void getUpdateFromList(List<PersonEntity> personEntities){
Runnable updateFromListRunnable = ()->{
List<PersonEntity> resultList = new ArrayList<>();
updateFromList(personEntities);
resultList = personHelper.queryAll();
if (callback != null) {
callback.getUpdatePerson(resultList);
}
};
ThreadPool.getInstance().getThreadPoolExecutor().execute(updateFromListRunnable);
}*/
}
package com.srthinker.statinfo.database.entity;
import com.raizlabs.android.dbflow.annotation.Column;
import com.raizlabs.android.dbflow.annotation.PrimaryKey;
import com.raizlabs.android.dbflow.annotation.Table;
import com.raizlabs.android.dbflow.structure.BaseModel;
import com.srthinker.statinfo.database.AppDB;
@Table(database = AppDB.class)
public class PersonEntity extends BaseModel {
@PrimaryKey
@Column
private String id;
@Column
private String type;
@Column
private String recognition_type;
@Column
private String person_name;
@Column
private String card_number;
@Column
private String person_code;
@Column
private String id_number;
@Column
private int group;
@Column
private String picture_data;
@Column
private String timestamp;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getRecognition_type() {
return recognition_type;
}
public void setRecognition_type(String recognition_type) {
this.recognition_type = recognition_type;
}
public String getPerson_name() {
return person_name;
}
public void setPerson_name(String person_name) {
this.person_name = person_name;
}
public String getCard_number() {
return card_number;
}
public void setCard_number(String card_number) {
this.card_number = card_number;
}
public String getPerson_code() {
return person_code;
}
public void setPerson_code(String person_code) {
this.person_code = person_code;
}
public String getId_number() {
return id_number;
}
public void setId_number(String id_number) {
this.id_number = id_number;
}
public int getGroup() {
return group;
}
public void setGroup(int group) {
this.group = group;
}
public String getPicture_data() {
return picture_data;
}
public void setPicture_data(String picture_data) {
this.picture_data = picture_data;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
}
package com.srthinker.statinfo.database.helper;
import com.raizlabs.android.dbflow.config.DatabaseDefinition;
import com.raizlabs.android.dbflow.config.FlowManager;
import com.raizlabs.android.dbflow.sql.language.SQLite;
import com.raizlabs.android.dbflow.structure.database.DatabaseWrapper;
import com.raizlabs.android.dbflow.structure.database.transaction.ITransaction;
import com.srthinker.statinfo.database.AppDB;
import com.srthinker.statinfo.database.entity.PersonEntity;
import com.srthinker.statinfo.database.entity.PersonEntity_Table;
import com.srthinker.statinfo.util.common.Mutex;
import java.util.List;
public class PersonHelper {
private static PersonHelper mHelper = null;
private Mutex locker = new Mutex();
public static PersonHelper getInstance(){
if (mHelper == null) {
synchronized (PersonHelper.class){
mHelper = new PersonHelper();
}
}
return mHelper;
}
public boolean save(PersonEntity entity){
boolean result = false;
locker.lock(0);
result = entity.save();
locker.unlock();
return result;
}
public long add(PersonEntity entity) {
long result = -1;
locker.lock(0);
result = entity.insert();
locker.unlock();
return result;
}
public void add(List<PersonEntity> entities){
/*FlowManager.getDatabase(AppDB.class).executeTransaction(new ITransaction() {
@Override
public void execute(DatabaseWrapper databaseWrapper) {
FastStoreModelTransaction<PersonEntity> fastStoreModelTransaction =
FastStoreModelTransaction.saveBuilder(FlowManager.getModelAdapter(PersonEntity.class))
.addAll(entities)
.build();
fastStoreModelTransaction.execute(databaseWrapper);
}
});*/
locker.lock(0);
DatabaseDefinition database = FlowManager.getDatabase(AppDB.class);
database.beginTransactionAsync(new ITransaction() {
@Override
public void execute(DatabaseWrapper databaseWrapper) {
for (PersonEntity entity : entities) {
entity.insert(databaseWrapper);
}
}
}).build().execute();
locker.unlock();
}
public void save(List<PersonEntity> entities){
locker.lock(0);
DatabaseDefinition database = FlowManager.getDatabase(AppDB.class);
database.beginTransactionAsync(new ITransaction() {
@Override
public void execute(DatabaseWrapper databaseWrapper) {
for (PersonEntity entity : entities) {
entity.save(databaseWrapper);
}
}
}).build().execute();
locker.unlock();
}
public boolean delete(PersonEntity entity) {
boolean result = false;
locker.lock(0);
result = entity.delete();
locker.unlock();
return result;
}
public void delete(List<PersonEntity> entities){
locker.lock(0);
DatabaseDefinition database = FlowManager.getDatabase(AppDB.class);
database.beginTransactionAsync(new ITransaction() {
@Override
public void execute(DatabaseWrapper databaseWrapper) {
for (PersonEntity entity : entities) {
entity.delete(databaseWrapper);
}
}
}).build().execute();
locker.unlock();
}
public void update(List<PersonEntity> entities){
locker.lock(0);
DatabaseDefinition database = FlowManager.getDatabase(AppDB.class);
database.beginTransactionAsync(new ITransaction() {
@Override
public void execute(DatabaseWrapper databaseWrapper) {
for (PersonEntity entity : entities) {
entity.update(databaseWrapper);
}
}
}).build().execute();
locker.unlock();
}
public boolean update(PersonEntity entity) {
boolean result = false;
locker.lock(0);
result = entity.update();
locker.unlock();
return result;
}
public void clear() {
locker.lock(0);
SQLite.delete(PersonEntity.class).execute();
locker.unlock();
}
public List<PersonEntity> queryAll(){
List<PersonEntity> personEntityList = null;
locker.lock(0);
personEntityList = SQLite.select().from(PersonEntity.class).queryList();
locker.unlock();
return personEntityList;
}
public PersonEntity queryById(String id){
PersonEntity entity = null;
locker.lock(0);
entity = SQLite.select().from(PersonEntity.class).where(PersonEntity_Table.id.eq(id)).querySingle();
locker.unlock();
return entity;
}
public List<PersonEntity> queryByGroup(int group){
List<PersonEntity> personEntities = null;
locker.lock(0);
personEntities = SQLite.select().from(PersonEntity.class).where(PersonEntity_Table.group.eq(group)).queryList();
locker.unlock();
return personEntities;
}
}
package com.srthinker.statinfo.download;
import android.os.Environment;
/**
* @author liwanlian
* @date 2023/3/15 10:14
*/
public abstract class BaseDownloadImpl {
private static final String TAG = "lwl-DownloadImpl";
protected DownloadCallback downloadCallback;
public void setDownloadCallback(DownloadCallback downloadCallback) {
this.downloadCallback = downloadCallback;
}
/**
* 数据初始化
*/
protected abstract void initData();
/**
* 启动下载
*
* @param downloadUrl
* @param filePath
*/
public void startDownload(String downloadUrl, String filePath) {
}
public void startDownload(String downloadUrl, String filePath, long startPoint, long totalSize) {
}
/**
* 文件上传
*
* @param filePath
* @param uploadUrl
*/
public void uploadFile(String filePath, String uploadUrl) {
}
/**
* 资源释放
*/
public void releaseRes() {
}
public String getFilename(String path) {
int start = path.lastIndexOf("/") + 1;
String substring = path.substring(start);
String fileName = Environment.getExternalStorageDirectory().getPath() + "/" + substring;
return fileName;
}
}
package com.srthinker.statinfo.download;
public interface DownloadCallback {
/**
* 开始下载
*/
void onStart(String downloadUrl,String filePath);
/**
* 下载中
*/
void onProgress(long currentSize,long totalSize,String downloadUrl,String filePath);
/**
* 完成
* @param
*/
void onFinish(String downloadUrl,String filePath);
/**
* 出错
* @param error
*/
void onError(String error,String downloadUrl, String filePath);
}
package com.srthinker.statinfo.download;
import android.text.TextUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class OkhttpInterruptDownImpl extends BaseDownloadImpl{
private static OkhttpInterruptDownImpl down;
private FileOutputStream fos;
private InputStream inputStream;
private Call call;
public static OkhttpInterruptDownImpl getInstance(){
if (down == null) {
synchronized (OkhttpInterruptDownImpl.class){
down = new OkhttpInterruptDownImpl();
}
}
return down;
}
@Override
protected void initData() {
}
@Override
public void startDownload(String downloadUrl, String filePath) {
super.startDownload(downloadUrl, filePath);
boolean isFinish = true;
long downloadedBytes = 0;
if (TextUtils.isEmpty(downloadUrl)){
if (downloadCallback!=null){
downloadCallback.onError("路径为空",downloadUrl,filePath);
return;
}
}
File downFile = new File(filePath);
if (downFile.exists()){
downloadedBytes = downFile.length();
}
try {
//OkHttpClient client = new OkHttpClient();
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS) // 设置连接超时时间为10秒
.readTimeout(60, TimeUnit.SECONDS) // 设置读取超时时间为10秒
.build();
Request request = new Request.Builder().url(downloadUrl)
.header("Range", "bytes=" + downloadedBytes + "-")
.build();
call = client.newCall(request);
Response response = call.execute();
if (response.isSuccessful()){
//从响应头获取要下载的大小
Long totalBytes = Long.parseLong(response.header("Content-Length"))+downloadedBytes; //Content-Length获取的不是总大小,而是看你从哪里读起
fos = new FileOutputStream(downFile, true);
//读取并写入
inputStream = response.body().byteStream();
//开始下载文件
byte[] bytes = new byte[2048];
int bytesRead;
long totalDownloadedBytes = downloadedBytes;
if (downloadCallback != null) {
downloadCallback.onStart(downloadUrl,filePath);
}
//Log.i(TAG, "saveRangeFile: totalDownloadedBytes="+ Formatter.formatFileSize(context,totalDownloadedBytes));
while ((bytesRead = inputStream.read(bytes))!=-1){
fos.write(bytes,0,bytesRead);
totalDownloadedBytes += bytesRead;
if (downloadCallback != null) {
downloadCallback.onProgress(totalDownloadedBytes,totalBytes,downloadUrl,filePath);
}
}
inputStream.close();
fos.close();
}else{
isFinish = false;
if (downloadCallback != null) {
downloadCallback.onError("响应码为:"+response.code(),downloadUrl,filePath);
}
}
} catch (Exception e) {
isFinish = false;
e.printStackTrace();
if (!TextUtils.equals(e.getMessage(),"Socket closed") && !TextUtils.equals(e.getMessage(),"Socket is closed")){
if(downloadCallback!=null){
//下载失败
downloadCallback.onError(e.toString(),downloadUrl,filePath);
}
}
}finally {
if (isFinish){
if (downloadCallback != null) {
downloadCallback.onFinish(downloadUrl,filePath);
}
}
}
}
@Override
public void startDownload(String downloadUrl, String filePath, long startPoint, long totalSize) {
super.startDownload(downloadUrl, filePath, startPoint, totalSize);
}
@Override
public void releaseRes() {
super.releaseRes();
try {
/*if (fos != null) {
fos.close();
}
if (inputStream != null) {
inputStream.close();
}*/
call.cancel();
} catch (Exception e) {
e.printStackTrace();
}
}
public void stopDownload(){
releaseRes();
}
}
package com.srthinker.statinfo.download.upper;
public class DownStatus {
public final static int DOWN_FINISH = 1;
public final static int DOWN_ERROR = 2;
public final static int DOWN_START = 3;
}
package com.srthinker.statinfo.download.upper;
import static com.srthinker.statinfo.download.upper.DownStatus.DOWN_ERROR;
import static com.srthinker.statinfo.download.upper.DownStatus.DOWN_FINISH;
import static com.srthinker.statinfo.download.upper.DownStatus.DOWN_START;
import android.app.Activity;
import androidx.lifecycle.ViewModel;
import com.srthinker.statinfo.download.DownloadCallback;
import com.srthinker.statinfo.download.OkhttpInterruptDownImpl;
import com.srthinker.statinfo.util.common.ThreadPool;
public class DownloadViewModel extends ViewModel implements DownloadCallback {
private final Activity activity;
private OkhttpInterruptDownImpl okhttpInterruptDown;
private UpperDownloadCallback upperDownloadCallback;
private float lastProgress=-1.0f;
public DownloadViewModel(Activity activity){
this.activity = activity;
}
public void initData(){
okhttpInterruptDown = new OkhttpInterruptDownImpl();
okhttpInterruptDown.setDownloadCallback(this);
}
public void setOnUpperDownloadCallback(UpperDownloadCallback upperDownloadCallback){
this.upperDownloadCallback = upperDownloadCallback;
}
public void startDownload(String downloadUrl,String filePath){
Runnable runnable = new Runnable() {
@Override
public void run() {
okhttpInterruptDown.startDownload(downloadUrl,filePath);
}
};
ThreadPool.getInstance().getThreadPoolExecutor().execute(runnable);
}
public void stopDownload(){
okhttpInterruptDown.stopDownload();
}
@Override
public void onProgress(long currentSize, long totalSize, String downloadUrl, String filePath) {
if (upperDownloadCallback != null) {
int progress = (int) ((currentSize * 1.0 / totalSize)*100);
if (progress!=lastProgress){
String progressString = progress + "%";
upperDownloadCallback.onProgress(progressString,filePath);
lastProgress = progress;
}
}
}
@Override
public void onStart(String downloadUrl, String filePath) {
if (upperDownloadCallback != null) {
upperDownloadCallback.onResult(DOWN_START,filePath);
}
}
@Override
public void onFinish(String downloadUrl, String filePath) {
if (upperDownloadCallback != null) {
upperDownloadCallback.onResult(DOWN_FINISH,filePath);
}
}
@Override
public void onError(String error,String downloadUrl, String filePath) {
if (upperDownloadCallback != null) {
upperDownloadCallback.onResult(DOWN_ERROR,filePath);
}
}
}
package com.srthinker.statinfo.download.upper;
public interface UpperDownloadCallback {
void onProgress(String progress,String filePath);
void onResult(int downStatus,String filePath);
}
package com.srthinker.statinfo.listener;
import com.srthinker.statinfo.bean.ConfigBean;
public interface ConfigCallback {
void onUpdateConfig(ConfigBean configBean,int type);
}
package com.srthinker.statinfo.listener;
import com.srthinker.statinfo.database.entity.PersonEntity;
import java.util.List;
public interface DeviceCallback {
void getPassInfo(List<PersonEntity> passEntities,int type);
void onConnectStatus(boolean isConnect,int type);
}
package com.srthinker.statinfo.listener;
public interface OnItemClickListener {
void OnItemClick(int position);
}
package com.srthinker.statinfo.listener;
import com.srthinker.statinfo.bean.TimeBean;
public interface OnTimeUpdateListener {
void OnTimeUpdate(TimeBean timeBean);
}
package com.srthinker.statinfo.listener;
public interface PersonMgrCallback {
void personUpdated();
}
package com.srthinker.statinfo.listener;
import com.srthinker.statinfo.database.entity.PersonEntity;
import java.util.List;
public interface QueryPersonCallback {
void onQueryStatus(String desc,boolean status);
void getTotalPerson(List<PersonEntity> personEntities, int type);
}
package com.srthinker.statinfo.presenter;
import static com.srthinker.statinfo.constant.InOutType.ENTER_TYPE;
import static com.srthinker.statinfo.constant.InOutType.LEAVE_TYPE;
import com.srthinker.statinfo.bean.ConfigBean;
import com.srthinker.statinfo.listener.DeviceCallback;
import com.srthinker.statinfo.listener.QueryPersonCallback;
public class ApiQuest {
private static ApiManager enterMgr;
private static ApiManager leaveMgr;
public static void startEnterApi(ConfigBean configBean,DeviceCallback callback){
if (configBean != null) {
//如果之前曾经有过的,则停掉之前的
if (enterMgr!=null){
stopEnterApi();
}
enterMgr = new ApiManager(configBean.getUsername(), configBean.getPassword(), configBean.getServerIp(), ENTER_TYPE);
if (enterMgr != null) {
enterMgr.start();
enterMgr.setOnDeviceCallback(callback);
}
}
}
public static void startLeaveApi(ConfigBean configBean,DeviceCallback callback){
if (configBean != null) {
//如果之前曾经有过的,则停掉之前的
if (leaveMgr!=null){
stopLeaveApi();
}
leaveMgr = new ApiManager(configBean.getUsername(), configBean.getPassword(), configBean.getServerIp(), LEAVE_TYPE);
if (leaveMgr != null) {
leaveMgr.start();
leaveMgr.setOnDeviceCallback(callback);
}
}
}
public static void stopApi(){
stopEnterApi();
stopLeaveApi();
}
public static void stopEnterApi(){
if (enterMgr != null) {
enterMgr.stop();
enterMgr.setOnDeviceCallback(null);
enterMgr.setOnQueryPersonsCallback(null);
enterMgr = null;
}
}
public static void stopLeaveApi(){
if (leaveMgr != null) {
leaveMgr.stop();
leaveMgr.setOnDeviceCallback(null);
leaveMgr.setOnQueryPersonsCallback(null);
leaveMgr = null;
}
}
public static void queryPersons(QueryPersonCallback callback){
if (enterMgr!=null){
enterMgr.queryPersons();
enterMgr.setOnQueryPersonsCallback(callback);
}
}
}
package com.srthinker.statinfo.presenter;
import android.os.Handler;
import android.text.TextUtils;
import com.srthinker.statinfo.bean.TimeBean;
import com.srthinker.statinfo.listener.OnTimeUpdateListener;
import com.srthinker.statinfo.util.common.DateUtil;
public class RealTimeClock {
private Handler handler;
private OnTimeUpdateListener onTimeUpdateListener;
private String lastTime;
public RealTimeClock(){
this.handler = new Handler();
}
public void setOnTimeUpdateListener(OnTimeUpdateListener onTimeUpdateListener){
this.onTimeUpdateListener = onTimeUpdateListener;
}
private Runnable runnable = new Runnable() {
@Override
public void run() {
String nowDateTime = DateUtil.getNowDateTime("yyyy/MM/dd HH:mm:ss");
String nowWeekString = DateUtil.getNowWeekString();
if (onTimeUpdateListener!=null && !TextUtils.equals(lastTime,nowDateTime)){
TimeBean timeBean = new TimeBean();
String[] s = nowDateTime.split(" ");
timeBean.setyMD(s[0]);
timeBean.setHm(s[1]);
timeBean.setWeek(nowWeekString);
onTimeUpdateListener.OnTimeUpdate(timeBean);
lastTime = nowDateTime;
}
handler.postDelayed(this,1000);
}
};
public void start(){
handler.post(runnable);
}
public void stop(){
handler.removeCallbacks(runnable);
}
}
package com.srthinker.statinfo.uis;
import android.content.Context;
import android.os.Bundle;
import android.os.IBinder;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.LayoutRes;
import androidx.appcompat.app.AppCompatActivity;
import com.srthinker.statinfo.R;
import com.srthinker.statinfo.databinding.ActivityBaseBinding;
import com.srthinker.statinfo.util.common.StatusBarUtil;
import com.srthinker.statinfo.util.common.Utils;
public abstract class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityBaseBinding inflate = ActivityBaseBinding.inflate(getLayoutInflater());
setContentView(inflate.getRoot());
setStatusBar();
Utils.autoSizeSwitch(this);
}
protected void setStatusBar() {
StatusBarUtil.hideSystemUI(this);
}
protected abstract void initData();
protected abstract void initView();
public void setTheView(@LayoutRes int layoutId){
LayoutInflater.from(this).inflate(layoutId,this.findViewById(R.id.baseContentView));
}
protected void releaseRes(){
}
@Override
protected void onDestroy() {
releaseRes();
super.onDestroy();
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
View v = getCurrentFocus();
if (isShouldHideKeyboard(v, ev)) {
hideKeyboard(v.getWindowToken());
}
}
return super.dispatchTouchEvent(ev);
}
/**
* 根据EditText所在坐标和用户点击的坐标相对比,来判断是否隐藏键盘,因为当用户点击EditText时则不能隐藏
*
* @param v
* @param event
* @return
*/
private boolean isShouldHideKeyboard(View v, MotionEvent event) {
if (v != null && (v instanceof EditText)) {
int[] l = {0, 0};
v.getLocationInWindow(l);
int left = l[0],
top = l[1],
bottom = top + v.getHeight(),
right = left + v.getWidth();
if (event.getX() > left && event.getX() < right
&& event.getY() > top && event.getY() < bottom) {
// 点击EditText的事件,忽略它。
return false;
} else {
return true;
}
}
// 如果焦点不是EditText则忽略,这个发生在视图刚绘制完,第一个焦点不在EditText上,和用户用轨迹球选择其他的焦点
return false;
}
/**
* 获取InputMethodManager,隐藏软键盘
*
* @param token
*/
private void hideKeyboard(IBinder token) {
if (token != null) {
InputMethodManager im = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
im.hideSoftInputFromWindow(token, InputMethodManager.HIDE_NOT_ALWAYS);
}
}
public void showTips(String tips){
Toast.makeText(this.getApplicationContext(), tips, Toast.LENGTH_SHORT).show();
}
}
\ No newline at end of file
package com.srthinker.statinfo.uis;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import com.srthinker.statinfo.R;
public class FlashActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_flash);
}
}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment