feat(android): add device activation flow and update dependencies

重构项目架构,引入启动页(SplashActivity)检查激活状态,未激活设备引导至激活页(ActivationActivity)完成provision+token流程。集成Retrofit+RxJava3网络层、Room数据库、Lifecycle组件、MMKV等依赖,升级OkHttp/Gson版本,并将构建配置从旧项目全面迁移至新项目结构。
This commit is contained in:
TongTongStudio
2026-08-03 00:25:51 +08:00
parent 61398ff4ff
commit ded6fe5ab7
105 changed files with 5363 additions and 1503 deletions

View File

@@ -1,4 +1,4 @@
import request from './request';
import request, { publicRequest } from './request';
export type DeviceType = 'CONTROLLER' | 'CONTROLLED' | null;
@@ -43,6 +43,54 @@ export interface ServerConfig {
serverPort: number;
}
// ==================== 管理员(独立体系) ====================
export interface AdminAccountView {
adminId: string;
username: string;
displayName: string | null;
status: 'ACTIVE' | 'DISABLED';
createdAt: number;
lastLoginAt: number | null;
}
export interface AdminLoginResult {
token: string;
tokenType: string;
expiresIn: number;
adminId: string;
username: string;
displayName: string | null;
}
export function adminLogin(username: string, password: string): Promise<AdminLoginResult> {
return publicRequest.post('/api/admin/login', { username, password }) as unknown as Promise<AdminLoginResult>;
}
export function getAdmins(): Promise<AdminAccountView[]> {
return request.get('/api/admin/admins') as unknown as Promise<AdminAccountView[]>;
}
export function createAdmin(username: string, password: string, displayName?: string): Promise<{ success: boolean; admin: AdminAccountView }> {
return request.post('/api/admin/admins', { username, password, displayName }) as unknown as Promise<{ success: boolean; admin: AdminAccountView }>;
}
export function disableAdmin(adminId: string): Promise<{ success: boolean }> {
return request.post(`/api/admin/admins/${adminId}/disable`) as unknown as Promise<{ success: boolean }>;
}
export function enableAdmin(adminId: string): Promise<{ success: boolean }> {
return request.post(`/api/admin/admins/${adminId}/enable`) as unknown as Promise<{ success: boolean }>;
}
export function deleteAdmin(adminId: string): Promise<{ success: boolean }> {
return request.delete(`/api/admin/admins/${adminId}`) as unknown as Promise<{ success: boolean }>;
}
export function resetAdminPassword(adminId: string, password: string): Promise<{ success: boolean }> {
return request.post(`/api/admin/admins/${adminId}/reset-password`, { password }) as unknown as Promise<{ success: boolean }>;
}
export function getDashboard(): Promise<DashboardData> {
return request.get('/api/admin/dashboard') as unknown as Promise<DashboardData>;
}
@@ -65,7 +113,188 @@ export function getHealth(): Promise<{ status: string }> {
return request.get('/api/admin/health') as unknown as Promise<{ status: string }>;
}
/** 校验令牌有效性(用于登录时验证)。 */
/** 校验管理员令牌有效性(调用 /me 校验)。 */
export function verifyToken(token: string): Promise<unknown> {
return request.get('/api/admin/health', { headers: { 'X-Admin-Token': token } });
return request.get('/api/admin/me', {
headers: { Authorization: `Bearer ${token}` },
}) as Promise<unknown>;
}
// ==================== 用户账号管理 ====================
export type AccountStatus = 'ACTIVE' | 'SUSPENDED' | 'BANNED';
export interface UserAccountView {
userId: string;
username: string;
status: AccountStatus;
statusReason: string | null;
statusUntil: number | null;
admin: boolean;
totpEnabled: boolean;
createdAt: number;
lastLoginAt: number | null;
activeSessions: number;
}
export interface UserSessionView {
sessionId: string;
createdAt: number;
lastSeenAt: number | null;
expiresAt: number | null;
ip: string | null;
userAgent: string | null;
}
export interface UserQuery {
keyword?: string;
status?: string;
}
export function getUsers(query: UserQuery = {}): Promise<UserAccountView[]> {
return request.get('/api/admin/users', { params: query }) as unknown as Promise<UserAccountView[]>;
}
export function createUser(payload: {
username: string;
password: string;
admin?: boolean;
}): Promise<{ success: boolean; message?: string; user?: UserAccountView }> {
return request.post('/api/admin/users', payload) as unknown as Promise<{
success: boolean;
message?: string;
user?: UserAccountView;
}>;
}
export function deleteUser(userId: string): Promise<{ success: boolean }> {
return request.delete(`/api/admin/users/${encodeURIComponent(userId)}`) as unknown as Promise<{
success: boolean;
}>;
}
export function resetUserPassword(
userId: string,
password: string,
): Promise<{ success: boolean; message?: string }> {
return request.post(`/api/admin/users/${encodeURIComponent(userId)}/reset-password`, {
password,
}) as unknown as Promise<{ success: boolean; message?: string }>;
}
export function setUserAdmin(userId: string, admin: boolean): Promise<{ success: boolean }> {
return request.post(`/api/admin/users/${encodeURIComponent(userId)}/admin`, {
admin,
}) as unknown as Promise<{ success: boolean }>;
}
/** 封禁账号durationSeconds 省略或 <=0 表示永久。 */
export function banUser(
userId: string,
reason: string,
durationSeconds?: number,
): Promise<{ success: boolean }> {
return request.post(`/api/admin/users/${encodeURIComponent(userId)}/ban`, {
reason,
durationSeconds,
}) as unknown as Promise<{ success: boolean }>;
}
export function unbanUser(userId: string): Promise<{ success: boolean }> {
return request.post(
`/api/admin/users/${encodeURIComponent(userId)}/unban`,
) as unknown as Promise<{ success: boolean }>;
}
export function kickUser(userId: string, reason?: string): Promise<{ success: boolean }> {
return request.post(`/api/admin/users/${encodeURIComponent(userId)}/kick`, {
reason,
}) as unknown as Promise<{ success: boolean }>;
}
export function getUserSessions(userId: string): Promise<UserSessionView[]> {
return request.get(
`/api/admin/users/${encodeURIComponent(userId)}/sessions`,
) as unknown as Promise<UserSessionView[]>;
}
export function kickSession(sessionId: string): Promise<{ success: boolean }> {
return request.post(
`/api/admin/sessions/${encodeURIComponent(sessionId)}/kick`,
) as unknown as Promise<{ success: boolean }>;
}
// ==================== 设备账号管理 ====================
export interface DeviceAccountView {
deviceUid: string;
sn: string;
model: string | null;
status: AccountStatus;
statusReason: string | null;
statusUntil: number | null;
provisionedAt: number;
lastOnlineAt: number | null;
online: boolean;
}
export interface DeviceQuery {
keyword?: string;
status?: string;
online?: boolean;
}
export function getDeviceAccounts(query: DeviceQuery = {}): Promise<DeviceAccountView[]> {
return request.get('/api/admin/device-accounts', { params: query }) as unknown as Promise<
DeviceAccountView[]
>;
}
export function disableDevice(deviceUid: string, reason: string): Promise<{ success: boolean }> {
return request.post(`/api/admin/device-accounts/${encodeURIComponent(deviceUid)}/disable`, {
reason,
}) as unknown as Promise<{ success: boolean }>;
}
export function enableDevice(deviceUid: string): Promise<{ success: boolean }> {
return request.post(
`/api/admin/device-accounts/${encodeURIComponent(deviceUid)}/enable`,
) as unknown as Promise<{ success: boolean }>;
}
export function kickDevice(deviceUid: string, reason?: string): Promise<{ success: boolean }> {
return request.post(`/api/admin/device-accounts/${encodeURIComponent(deviceUid)}/kick`, {
reason,
}) as unknown as Promise<{ success: boolean }>;
}
export function deleteDevice(deviceUid: string): Promise<{ success: boolean }> {
return request.delete(
`/api/admin/device-accounts/${encodeURIComponent(deviceUid)}`,
) as unknown as Promise<{ success: boolean }>;
}
// ==================== SN 白名单 ====================
export function getAllowlist(): Promise<{ total: number; sns: string[] }> {
return request.get('/api/admin/device-allowlist') as unknown as Promise<{
total: number;
sns: string[];
}>;
}
export function importAllowlist(
sns: string[],
): Promise<{ success: boolean; added: number; total: number }> {
return request.post('/api/admin/device-allowlist', { sns }) as unknown as Promise<{
success: boolean;
added: number;
total: number;
}>;
}
export function removeAllowlist(sn: string): Promise<{ success: boolean; total: number }> {
return request.delete(
`/api/admin/device-allowlist/${encodeURIComponent(sn)}`,
) as unknown as Promise<{ success: boolean; total: number }>;
}

View File

@@ -10,11 +10,37 @@ const request = axios.create({
request.interceptors.request.use((config) => {
const token = localStorage.getItem('admin_token');
if (token) {
config.headers['X-Admin-Token'] = token;
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
});
/**
* 不携带任何鉴权头的请求实例,专用于登录等公开接口,
* 避免把本地可能过期/失效的令牌误带上去造成 401 / 鉴权混乱。
*/
export const publicRequest = axios.create({
baseURL: import.meta.env.VITE_API_BASE || 'http://localhost:8080',
timeout: 15000,
});
publicRequest.interceptors.response.use(
(resp) => resp.data,
(error) => {
const status = error.response?.status;
if (status === 401) {
localStorage.removeItem('admin_token');
ElMessage.error('登录失败,请检查账号或密码');
} else {
ElMessage.error(error.response?.data?.message || error.message || '请求失败');
}
return Promise.reject(error);
},
);
export default request;
request.interceptors.response.use(
(resp) => resp.data,
(error) => {
@@ -30,4 +56,3 @@ request.interceptors.response.use(
},
);
export default request;