Phase 1 complete. build Ok, apk OK

Signed-off-by: chaoq <chaoq@gxtech.ltd>
This commit is contained in:
2025-09-17 22:50:29 +08:00
parent 8875a5def1
commit 3bdc40df94
15 changed files with 2671 additions and 18 deletions

View File

@@ -1,6 +1,8 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
android {
@@ -32,6 +34,10 @@ android {
}
kotlinOptions {
jvmTarget = '17'
freeCompilerArgs += [
"-opt-in=kotlin.RequiresOptIn",
"-Xjvm-default=all"
]
}
buildFeatures {
compose true
@@ -47,6 +53,7 @@ android {
}
dependencies {
// Core
implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.4'
implementation 'androidx.activity:activity-compose:1.9.1'
@@ -57,7 +64,33 @@ dependencies {
implementation 'androidx.compose.material3:material3'
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.4'
implementation 'androidx.navigation:navigation-compose:2.7.7'
// Network
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0'
// DI
implementation 'com.google.dagger:hilt-android:2.48'
kapt 'com.google.dagger:hilt-compiler:2.48'
implementation 'androidx.hilt:hilt-navigation-compose:1.1.0'
// Database
implementation 'androidx.room:room-runtime:2.6.1'
implementation 'androidx.room:room-ktx:2.6.1'
kapt 'androidx.room:room-compiler:2.6.1'
// LiveKit (TODO: 需要确认GXRTC服务器集成方式)
// implementation 'io.livekit:livekit-android:2.20.2'
// ARCore
implementation 'com.google.ar:core:1.50.0'
// MQTT (TODO: 需要确认具体MQTT客户端库)
// implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5'
// implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'

View File

@@ -1,20 +1,22 @@
package com.xsynergy.android
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
import com.xsynergy.android.utils.PerformanceOptimizer
/**
* XSynergy Application class for app-wide initialization
* Optimizes app startup and performance monitoring
*/
@HiltAndroidApp
class XSynergyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize performance monitoring
PerformanceOptimizer.initialize(this)
// Optimize network settings for low latency
PerformanceOptimizer.optimizeNetworkLatency()
}

View File

@@ -0,0 +1,13 @@
package com.xsynergy.android.data.local
import androidx.room.Database
import androidx.room.RoomDatabase
@Database(
entities = [UserEntity::class],
version = 1,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}

View File

@@ -0,0 +1,21 @@
package com.xsynergy.android.data.local
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
@Dao
interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: UserEntity)
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getUserById(userId: String): UserEntity?
@Query("SELECT * FROM users ORDER BY lastLoginTime DESC LIMIT 1")
suspend fun getLastLoggedInUser(): UserEntity?
@Query("DELETE FROM users")
suspend fun clearAllUsers()
}

View File

@@ -0,0 +1,33 @@
package com.xsynergy.android.data.local
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.xsynergy.android.data.model.User
@Entity(tableName = "users")
data class UserEntity(
@PrimaryKey val id: String,
val phone: String,
val name: String,
val token: String,
val avatarUrl: String?,
val lastLoginTime: Long = System.currentTimeMillis()
)
fun User.toEntity(): UserEntity = UserEntity(
id = id,
phone = phone,
name = name,
token = token,
avatarUrl = avatarUrl,
lastLoginTime = lastLoginTime
)
fun UserEntity.toDomain(): User = User(
id = id,
phone = phone,
name = name,
token = token,
avatarUrl = avatarUrl,
lastLoginTime = lastLoginTime
)

View File

@@ -0,0 +1,6 @@
package com.xsynergy.android.data.model
data class LoginRequest(
val phone: String,
val code: String
)

View File

@@ -0,0 +1,36 @@
package com.xsynergy.android.data.model
data class LoginResponse(
val success: Boolean,
val user: User?,
val token: String?,
val message: String?
)
data class GxrtcResponse<T>(
val meta: Meta,
val data: T
)
data class Meta(
val code: Int,
val message: String? = null
)
data class AccessTokenResponse(
val accessToken: String
)
data class RoomResponse(
val sid: String,
val name: String,
val emptyTimeout: Int,
val departureTimeout: Int,
val creationTime: Long,
val turnPassword: String,
val enabledCodecs: List<CodecInfo>
)
data class CodecInfo(
val mime: String
)

View File

@@ -0,0 +1,10 @@
package com.xsynergy.android.data.model
data class User(
val id: String,
val phone: String,
val name: String,
val token: String,
val avatarUrl: String? = null,
val lastLoginTime: Long = System.currentTimeMillis()
)

View File

@@ -0,0 +1,26 @@
package com.xsynergy.android.data.remote
import com.xsynergy.android.data.model.AccessTokenResponse
import com.xsynergy.android.data.model.GxrtcResponse
import com.xsynergy.android.data.model.RoomResponse
import retrofit2.Response
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET
import retrofit2.http.POST
interface GxrtcApi {
@POST("room/token")
@FormUrlEncoded
suspend fun getAccessToken(
@Field("uid") userId: String,
@Field("room") roomId: String
): Response<GxrtcResponse<AccessTokenResponse>>
@POST("room/")
@FormUrlEncoded
suspend fun createRoom(@Field("room") roomName: String): Response<GxrtcResponse<RoomResponse>>
@GET("room/")
suspend fun getRooms(): Response<GxrtcResponse<List<RoomResponse>>>
}

View File

@@ -0,0 +1,48 @@
package com.xsynergy.android.data.remote
import com.google.gson.GsonBuilder
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
import javax.inject.Singleton
object RetrofitClient {
private const val BASE_URL = "https://meeting.cnsdt.com/api/v1/"
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.addInterceptor { chain ->
val request = chain.request().newBuilder()
.addHeader("Content-Type", "application/json")
.addHeader("Accept", "application/json")
.build()
chain.proceed(request)
}
.addInterceptor(
HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
)
.build()
}
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
val gson = GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
.create()
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
}
fun provideGxrtcApi(retrofit: Retrofit): GxrtcApi = retrofit.create(GxrtcApi::class.java)
}

View File

@@ -0,0 +1,79 @@
package com.xsynergy.android.data.repository
import android.content.Context
import android.content.SharedPreferences
import com.xsynergy.android.data.local.UserDao
import com.xsynergy.android.data.local.UserEntity
import com.xsynergy.android.data.remote.GxrtcApi
import com.xsynergy.android.data.model.LoginRequest
import com.xsynergy.android.data.model.User
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AuthRepository @Inject constructor(
private val gxrtcApi: GxrtcApi,
private val userDao: UserDao,
private val prefs: SharedPreferences
) {
suspend fun getAccessToken(userId: String, roomId: String): Result<String> {
return try {
val response = gxrtcApi.getAccessToken(userId, roomId)
if (response.isSuccessful && response.body()?.meta?.code == 200) {
Result.success(response.body()!!.data.accessToken)
} else {
Result.failure(Exception("获取access token失败: ${response.body()?.meta?.message}"))
}
} catch (e: Exception) {
Result.failure(Exception("网络请求失败: ${e.message}"))
}
}
suspend fun createRoom(roomName: String): Result<String> {
return try {
val response = gxrtcApi.createRoom(roomName)
if (response.isSuccessful && response.body()?.meta?.code == 200) {
Result.success(response.body()!!.data.sid)
} else {
Result.failure(Exception("创建房间失败: ${response.body()?.meta?.message}"))
}
} catch (e: Exception) {
Result.failure(Exception("网络请求失败: ${e.message}"))
}
}
suspend fun getCurrentUser(): User? {
val userEntity = userDao.getLastLoggedInUser()
return userEntity?.let {
User(
id = it.id,
phone = it.phone,
name = it.name,
token = it.token,
avatarUrl = it.avatarUrl,
lastLoginTime = it.lastLoginTime
)
}
}
suspend fun saveUser(user: User) {
val userEntity = UserEntity(
id = user.id,
phone = user.phone,
name = user.name,
token = user.token,
avatarUrl = user.avatarUrl,
lastLoginTime = user.lastLoginTime
)
userDao.insertUser(userEntity)
prefs.edit().putString("auth_token", user.token).apply()
}
suspend fun logout() {
prefs.edit().remove("auth_token").apply()
userDao.clearAllUsers()
}
fun getAuthToken(): String? = prefs.getString("auth_token", null)
}

View File

@@ -0,0 +1,68 @@
package com.xsynergy.android.di
import android.content.Context
import android.content.SharedPreferences
import androidx.room.Room
import com.xsynergy.android.data.local.AppDatabase
import com.xsynergy.android.data.remote.GxrtcApi
import com.xsynergy.android.data.remote.RetrofitClient
import com.xsynergy.android.data.repository.AuthRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(): okhttp3.OkHttpClient {
return RetrofitClient.provideOkHttpClient()
}
@Provides
@Singleton
fun provideRetrofit(okHttpClient: okhttp3.OkHttpClient): retrofit2.Retrofit {
return RetrofitClient.provideRetrofit(okHttpClient)
}
@Provides
@Singleton
fun provideGxrtcApi(retrofit: retrofit2.Retrofit): GxrtcApi {
return RetrofitClient.provideGxrtcApi(retrofit)
}
@Provides
@Singleton
fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"xsynergy_database"
).build()
}
@Provides
@Singleton
fun provideUserDao(database: AppDatabase) = database.userDao()
@Provides
@Singleton
fun provideSharedPreferences(@ApplicationContext context: Context): SharedPreferences {
return context.getSharedPreferences("xsynergy_prefs", Context.MODE_PRIVATE)
}
@Provides
@Singleton
fun provideAuthRepository(
gxrtcApi: GxrtcApi,
userDao: com.xsynergy.android.data.local.UserDao,
prefs: SharedPreferences
): AuthRepository {
return AuthRepository(gxrtcApi, userDao, prefs)
}
}

View File

@@ -1,6 +1,7 @@
buildscript {
ext.kotlin_version = '1.9.24'
ext.compose_version = '1.6.8'
ext.hilt_version = '2.48'
repositories {
google()
mavenCentral()
@@ -8,6 +9,7 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:8.13.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
}
}

View File

@@ -25,6 +25,14 @@ object NetworkModule {
@Provides
@Singleton
fun provideGxrtcApi(retrofit: Retrofit): GxrtcApi = retrofit.create(GxrtcApi::class.java)
@Provides
@Singleton
fun provideArAnnotationApi(retrofit: Retrofit): ArAnnotationApi = retrofit.create(ArAnnotationApi::class.java)
@Provides
@Singleton
fun provideFeaturePointApi(retrofit: Retrofit): FeaturePointApi = retrofit.create(FeaturePointApi::class.java)
}
// data/remote/GxrtcApi.kt - 新增
@@ -44,6 +52,128 @@ interface GxrtcApi {
suspend fun getRooms(): Response<GxrtcResponse<List<RoomResponse>>>
}
// data/remote/ArAnnotationApi.kt - 新增
interface ArAnnotationApi {
@POST("ar/anchor/save")
suspend fun saveAnchor(@Body request: SaveAnchorRequest): Response<GxrtcResponse<Unit>>
@GET("ar/room/{roomId}/anchors")
suspend fun getRoomAnchors(@Path("roomId") roomId: String): Response<GxrtcResponse<AnchorListResponse>>
@POST("ar/annotation/save")
suspend fun saveAnnotation(@Body annotation: ArAnnotation): Response<GxrtcResponse<Unit>>
@GET("ar/room/{roomId}/annotations")
suspend fun getRoomAnnotations(@Path("roomId") roomId: String): Response<GxrtcResponse<AnnotationListResponse>>
@DELETE("ar/annotation/{annotationId}")
suspend fun deleteAnnotation(@Path("annotationId") annotationId: String): Response<GxrtcResponse<Unit>>
@POST("ar/spatial/validate")
suspend fun validateSpatialConsistency(@Body request: SpatialValidationRequest): Response<GxrtcResponse<SpatialValidationResult>>
@POST("ar/annotations/version")
suspend fun createAnnotationVersion(@Body request: CreateVersionRequest): Response<GxrtcResponse<VersionResponse>>
@GET("ar/annotations/{roomId}/versions")
suspend fun getAnnotationVersions(@Path("roomId") roomId: String): Response<GxrtcResponse<List<VersionResponse>>>
}
// data/remote/FeaturePointApi.kt - 新增
interface FeaturePointApi {
@POST("ar/feature-points/save")
suspend fun saveFeaturePoints(@Body request: SaveFeaturePointsRequest): Response<GxrtcResponse<Unit>>
@GET("ar/feature-points/{anchorId}")
suspend fun getAnchorFeatures(@Path("anchorId") anchorId: String): Response<GxrtcResponse<FeaturePointListResponse>>
@POST("ar/feature-points/match")
suspend fun matchFeaturePoints(@Body request: FeatureMatchRequest): Response<GxrtcResponse<FeatureMatchResponse>>
@POST("ar/feature-points/quality-assessment")
suspend fun assessFeatureQuality(@Body request: FeatureQualityRequest): Response<GxrtcResponse<FeatureQualityResponse>>
}
// 请求和响应数据模型
data class SaveAnchorRequest(
val roomId: String,
val anchorId: String,
val position: List<Float>,
val rotation: List<Float>,
val createdBy: String
)
data class AnchorListResponse(val anchors: List<AnchorData>)
data class AnnotationListResponse(val annotations: List<ArAnnotation>)
data class SaveFeaturePointsRequest(
val anchorId: String,
val roomId: String,
val featurePoints: List<FeaturePoint>,
val deviceInfo: DeviceInfo
)
data class FeaturePointListResponse(val featurePoints: List<FeaturePoint>)
data class SpatialValidationRequest(
val anchorId: String,
val currentPosition: List<Float>,
val currentRotation: List<Float>,
val deviceInfo: DeviceInfo
)
data class SpatialValidationResult(
val isValid: Boolean,
val confidence: Float,
val suggestedCorrection: SpatialTransform?,
val errorMessage: String?
)
data class FeatureMatchRequest(
val anchorId: String,
val queryFeatures: List<FeaturePoint>,
val maxResults: Int = 10
)
data class FeatureMatchResponse(
val matches: List<FeatureMatch>,
val confidence: Float
)
data class FeatureMatch(
val featurePoint: FeaturePoint,
val similarity: Float,
val spatialDistance: Float
)
data class FeatureQualityRequest(
val anchorId: String,
val featurePoints: List<FeaturePoint>
)
data class FeatureQualityResponse(
val overallQuality: Float,
val individualScores: Map<String, Float>,
val recommendations: List<String>
)
data class CreateVersionRequest(
val roomId: String,
val versionName: String,
val description: String,
val createdBy: String
)
data class VersionResponse(
val versionId: String,
val versionName: String,
val description: String,
val createdAt: Long,
val createdBy: String,
val annotationCount: Int
)
// 响应数据结构
data class GxrtcResponse<T>(
@SerializedName("meta") val meta: Meta,
@@ -423,35 +553,53 @@ data class AnchorData(
)
```
#### 4.2 AR标注持久化服务
#### 4.2 AR标注持久化服务(增强版)
```kotlin
// domain/service/ArAnnotationService.kt - 新增
class ArAnnotationService @Inject constructor(
private val arAnnotationApi: ArAnnotationApi
private val arAnnotationApi: ArAnnotationApi,
private val featurePointApi: FeaturePointApi,
private val userRepository: UserRepository
) {
suspend fun saveAnchor(
suspend fun saveAnchorWithFeatures(
roomId: String,
anchorId: String,
position: Pose
position: Pose,
featurePoints: List<FeaturePoint>,
deviceInfo: DeviceInfo
): Result<Unit> {
return try {
val request = SaveAnchorRequest(
// 保存锚点基本信息
val anchorRequest = SaveAnchorRequest(
roomId = roomId,
anchorId = anchorId,
position = listOf(position.tx(), position.ty(), position.tz()),
rotation = listOf(position.qx(), position.qy(), position.qz(), position.qw()),
createdBy = currentUserId
createdBy = userRepository.getCurrentUser()?.id ?: ""
)
arAnnotationApi.saveAnchor(anchorRequest)
// 保存特征点数据
val featureRequest = SaveFeaturePointsRequest(
anchorId = anchorId,
roomId = roomId,
featurePoints = featurePoints,
deviceInfo = deviceInfo
)
featurePointApi.saveFeaturePoints(featureRequest)
arAnnotationApi.saveAnchor(request)
Result.success(Unit)
} catch (e: Exception) {
Result.failure(e)
}
}
suspend fun loadRoomAnchors(roomId: String): List<AnchorData> {
return arAnnotationApi.getRoomAnchors(roomId).anchors
suspend fun loadRoomAnchorsWithFeatures(roomId: String): List<AnchorWithFeatures> {
val anchors = arAnnotationApi.getRoomAnchors(roomId).anchors
return anchors.map { anchor ->
val features = featurePointApi.getAnchorFeatures(anchor.anchorId).featurePoints
AnchorWithFeatures(anchor, features)
}
}
suspend fun saveAnnotation(annotation: ArAnnotation): Result<Unit> {
@@ -466,9 +614,59 @@ class ArAnnotationService @Inject constructor(
suspend fun loadRoomAnnotations(roomId: String): List<ArAnnotation> {
return arAnnotationApi.getRoomAnnotations(roomId).annotations
}
suspend fun validateSpatialConsistency(
anchorId: String,
currentPose: Pose,
deviceInfo: DeviceInfo
): Result<SpatialValidationResult> {
return try {
val request = SpatialValidationRequest(
anchorId = anchorId,
currentPosition = listOf(currentPose.tx(), currentPose.ty(), currentPose.tz()),
currentRotation = listOf(currentPose.qx(), currentPose.qy(), currentPose.qz(), currentPose.qw()),
deviceInfo = deviceInfo
)
val result = arAnnotationApi.validateSpatialConsistency(request)
Result.success(result)
} catch (e: Exception) {
Result.failure(e)
}
}
}
// data/model/ArAnnotation.kt - 新增
// 数据模型增强
data class AnchorWithFeatures(
val anchor: AnchorData,
val featurePoints: List<FeaturePoint>
)
data class FeaturePoint(
val position: List<Float>,
val descriptor: String, // base64编码的特征描述符
val qualityScore: Float,
val timestamp: Long = System.currentTimeMillis()
)
data class DeviceInfo(
val model: String,
val arCoreVersion: String,
val cameraCalibration: CameraCalibration?,
val imuCalibration: ImuCalibration?
)
data class CameraCalibration(
val focalLength: List<Float>,
val principalPoint: List<Float>,
val distortionCoefficients: List<Float>?
)
data class ImuCalibration(
val gyroBias: List<Float>?,
val accelBias: List<Float>?,
val magnetometerBias: List<Float>?
)
data class ArAnnotation(
val id: String,
val anchorId: String,
@@ -478,12 +676,21 @@ data class ArAnnotation(
val size: Float,
val roomId: String,
val createdBy: String,
val spatialTransform: SpatialTransform? = null,
val createdAt: Long = System.currentTimeMillis(),
val updatedAt: Long = System.currentTimeMillis()
val updatedAt: Long = System.currentTimeMillis(),
val version: Int = 1
)
data class SpatialTransform(
val position: List<Float>,
val rotation: List<Float>,
val scale: List<Float>,
val confidence: Float
)
enum class AnnotationType {
ARROW, CIRCLE, RECTANGLE, TEXT, HIGHLIGHT
ARROW, CIRCLE, RECTANGLE, TEXT, HIGHLIGHT, MEASUREMENT, NOTE
}
```
@@ -1025,6 +1232,139 @@ GET /ar/analytics/accuracy?room_id={roomId}
GET /ar/analytics/compatibility
```
### 7. 特征点管理API关键补充
```kotlin
// 保存ARCore特征点数据
POST /ar/feature-points/save
Content-Type: application/json
{
"anchor_id": "string",
"room_id": "string",
"feature_points": [
{
"position": [0.0, 0.0, 0.0],
"descriptor": "base64_encoded_descriptor",
"quality_score": 0.95
}
],
"device_info": {
"model": "Pixel 6",
"ar_core_version": "1.50.0",
"camera_calibration": {
"focal_length": [1000.0, 1000.0],
"principal_point": [500.0, 500.0]
}
}
}
// 获取特征点数据用于重定位
GET /ar/feature-points/{anchorId}
// 批量特征点匹配(跨设备空间对齐)
POST /ar/feature-points/match
{
"anchor_id": "string",
"query_features": [
{
"position": [0.0, 0.0, 0.0],
"descriptor": "base64_encoded_descriptor"
}
],
"max_results": 10
}
// 特征点质量评估
POST /ar/feature-points/quality-assessment
{
"anchor_id": "string",
"feature_points": [
{
"position": [0.0, 0.0, 0.0],
"descriptor": "base64_encoded_descriptor"
}
]
}
```
### 8. 空间一致性验证API
```kotlin
// 空间坐标转换验证
POST /ar/spatial/validate-transform
{
"source_anchor": "anchor_123",
"target_anchor": "anchor_456",
"transform_matrix": [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[1.5, 2.0, 0.5, 1.0]
],
"confidence": 0.92
}
// 多设备空间对齐状态
GET /ar/spatial/alignment-status?room_id={roomId}
// 强制空间重新对齐
POST /ar/spatial/realign
{
"room_id": "string",
"anchor_ids": ["anchor1", "anchor2", "anchor3"]
}
```
### 9. 标注版本管理API
```kotlin
// 创建标注版本
POST /ar/annotations/version
{
"room_id": "string",
"version_name": "v1.0",
"description": "初始标注版本",
"created_by": "user123"
}
// 获取标注版本历史
GET /ar/annotations/{roomId}/versions
// 恢复到特定版本
POST /ar/annotations/{roomId}/restore/{versionId}
// 标注差异比较
GET /ar/annotations/{roomId}/diff?version1={v1}&version2={v2}
```
### 10. 设备性能优化API
```kotlin
// 设备性能基准上报
POST /ar/device/performance
{
"device_model": "Pixel 6",
"ar_core_version": "1.50.0",
"performance_metrics": {
"frame_rate": 60.0,
"tracking_quality": 0.95,
"memory_usage": 256,
"battery_drain": 15.0
},
"recommended_settings": {
"max_annotations": 50,
"feature_point_density": 0.8,
"texture_quality": "medium"
}
}
// 获取设备优化配置
GET /ar/device/{deviceModel}/optimization
// 性能问题诊断
POST /ar/device/diagnose
{
"device_info": {...},
"performance_issues": ["low_framerate", "high_memory"]
}
```
---
## 📋 阶段8优化和兼容性增强版

File diff suppressed because it is too large Load Diff