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:
@@ -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 }>;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user