diff --git a/app/src/main/java/com/nova/brain/glass/repository/Service.kt b/app/src/main/java/com/nova/brain/glass/repository/Service.kt index 9d22593..f88440e 100644 --- a/app/src/main/java/com/nova/brain/glass/repository/Service.kt +++ b/app/src/main/java/com/nova/brain/glass/repository/Service.kt @@ -1,8 +1,24 @@ package com.nova.brain.glass.repository +import io.reactivex.Observable +import okhttp3.RequestBody +import okhttp3.ResponseBody +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Path +import retrofit2.http.Streaming interface Service { -// @GET("drug/stock/standard?storehouseCode=2&type=&form=&purpose=&restrict=&danger=&antibiotic=&keyword=&manufacturerCode=&supplierCode=&expirationDateMin=&expirationDateMax=&sort=id&asc=false&papeIndexOnView=2&pageSize=20&tenantId=101") -// fun standard(@Query("pageIndex") pageIndex: Int): Observable + + @GET("get") + fun demoGet(): Observable + + @POST("post") + fun demoPost(@Body body: RequestBody): Observable + + @Streaming + @GET("stream/{n}") + fun demoStream(@Path("n") n: Int): Observable } \ No newline at end of file diff --git a/app/src/main/java/com/nova/brain/glass/ui/WelcomeActivity.kt b/app/src/main/java/com/nova/brain/glass/ui/WelcomeActivity.kt index 6b58cfa..aaa1712 100644 --- a/app/src/main/java/com/nova/brain/glass/ui/WelcomeActivity.kt +++ b/app/src/main/java/com/nova/brain/glass/ui/WelcomeActivity.kt @@ -2,27 +2,38 @@ package com.nova.brain.glass.ui import android.content.Intent import android.os.Bundle +import androidx.lifecycle.ViewModelProvider import com.nova.brain.glass.R import com.nova.brain.glass.databinding.ActivityWelcomeBinding import com.nova.brain.glass.helper.OfflineCmdListener import com.nova.brain.glass.helper.OfflineCmdServiceHelper -import com.xuqm.base.common.LogHelper +import com.nova.brain.glass.viewmodel.WelcomeVM import com.xuqm.base.ui.BaseActivity -import java.util.Timer -import kotlin.concurrent.schedule class WelcomeActivity : BaseActivity() { override fun getLayoutId(): Int = R.layout.activity_welcome override fun fullscreen(): Boolean = true + private lateinit var vm: WelcomeVM + override fun initView(savedInstanceState: Bundle?) { super.initView(savedInstanceState) + vm = ViewModelProvider(this).get(WelcomeVM::class.java) + window.addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) binding.tv.setOnClickListener { startActivity(Intent(this@WelcomeActivity, TaskListActivity::class.java)) } - window.addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + binding.btnGet.setOnClickListener { vm.demoGet() } + binding.btnPost.setOnClickListener { vm.demoPost() } + binding.btnSse.setOnClickListener { vm.demoPostSse() } + } + override fun initData() { + super.initData() + vm.result.observe(this) { text -> + binding.tvResult.text = text + } } private val offlineCmdListener = object : OfflineCmdListener { @@ -51,4 +62,4 @@ class WelcomeActivity : BaseActivity() { super.onDestroy() window.clearFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/nova/brain/glass/viewmodel/WelcomeVM.kt b/app/src/main/java/com/nova/brain/glass/viewmodel/WelcomeVM.kt index 5a8096d..c537552 100644 --- a/app/src/main/java/com/nova/brain/glass/viewmodel/WelcomeVM.kt +++ b/app/src/main/java/com/nova/brain/glass/viewmodel/WelcomeVM.kt @@ -1,6 +1,75 @@ package com.nova.brain.glass.viewmodel +import androidx.lifecycle.MutableLiveData +import com.nova.brain.glass.repository.Service +import com.xuqm.base.di.manager.HttpManager import com.xuqm.sdhbwfu.core.viewModel.BaseViewModel +import io.reactivex.schedulers.Schedulers +import okhttp3.MediaType +import okhttp3.RequestBody class WelcomeVM : BaseViewModel() { + + val result = MutableLiveData() + + companion object { + // 修改为运行 server/app.py 的机器在局域网中的 IP + const val DEMO_SERVER_URL = "http://192.168.27.248:8080/" + } + + private val demoComponent by lazy { + HttpManager.getAppComponent(DEMO_SERVER_URL) + } + + private val service by lazy { + HttpManager.getApi(demoComponent, Service::class.java) + } + + fun demoGet() { + result.value = "GET 请求中..." + service.demoGet() + .subscribeOn(Schedulers.io()) + .subscribe({ body -> + result.postValue("GET 响应:\n${body.string()}") + }, { e -> + result.postValue("GET 失败: ${e.message}") + }).adds() + } + + fun demoPost() { + result.value = "POST 请求中..." + val json = """{"demo":"post","from":"glass"}""" + val body = RequestBody.create(MediaType.parse("application/json"), json) + service.demoPost(body) + .subscribeOn(Schedulers.io()) + .subscribe({ resp -> + result.postValue("POST 响应:\n${resp.string()}") + }, { e -> + result.postValue("POST 失败: ${e.message}") + }).adds() + } + + fun demoPostSse() { + result.postValue("SSE 连接中...") + service.demoStream(5) + .subscribeOn(Schedulers.io()) + .subscribe({ body -> + val sb = StringBuilder("SSE 流式响应:\n") + try { + val reader = body.charStream().buffered() + var line: String? + while (reader.readLine().also { line = it } != null) { + val l = line!! + if (l.isNotEmpty()) { + sb.appendLine(l) + result.postValue(sb.toString()) + } + } + } catch (e: Exception) { + result.postValue("SSE 读取异常: ${e.message}") + } + }, { e -> + result.postValue("SSE 失败: ${e.message}") + }).adds() + } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_welcome.xml b/app/src/main/res/layout/activity_welcome.xml index 49e9707..de3e36e 100644 --- a/app/src/main/res/layout/activity_welcome.xml +++ b/app/src/main/res/layout/activity_welcome.xml @@ -4,6 +4,7 @@ android:layout_width="match_parent" android:background="@color/app_color_black" android:layout_height="match_parent"> + + + + + + + + + + + + + app:layout_constraintEnd_toEndOf="parent"> + + + \ No newline at end of file diff --git a/server/app.py b/server/app.py new file mode 100644 index 0000000..612f53e --- /dev/null +++ b/server/app.py @@ -0,0 +1,119 @@ +""" +演示 HTTP 服务 +- GET /get 普通 GET 请求演示 +- POST /post 普通 POST 请求演示 +- GET /stream/ 流式响应演示(逐行返回 n 条 JSON) + +运行方式: + pip install flask + python app.py + +默认监听 0.0.0.0:8080,局域网内 Android 设备通过 http://<本机IP>:8080 访问 +""" + +import json +import time +from flask import Flask, request, Response, jsonify + +app = Flask(__name__) + +PORT = 8080 + + +def cors(response): + response.headers["Access-Control-Allow-Origin"] = "*" + response.headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization" + response.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS" + return response + + +@app.after_request +def after_request(response): + return cors(response) + + +# ── GET /get ────────────────────────────────────────────────────────────────── + +@app.route("/get", methods=["GET", "OPTIONS"]) +def demo_get(): + data = { + "code": 0, + "message": "GET 请求成功", + "data": { + "method": "GET", + "url": request.url, + "args": dict(request.args), + "headers": dict(request.headers), + "timestamp": int(time.time()), + }, + } + return jsonify(data) + + +# ── POST /post ──────────────────────────────────────────────────────────────── + +@app.route("/post", methods=["POST", "OPTIONS"]) +def demo_post(): + body = {} + raw = "" + try: + body = request.get_json(force=True) or {} + raw = json.dumps(body, ensure_ascii=False) + except Exception: + raw = request.data.decode("utf-8", errors="replace") + + data = { + "code": 0, + "message": "POST 请求成功", + "data": { + "method": "POST", + "url": request.url, + "body": body, + "raw": raw, + "headers": dict(request.headers), + "timestamp": int(time.time()), + }, + } + return jsonify(data) + + +# ── GET /stream/ ─────────────────────────────────────────────────────────── + +@app.route("/stream/", methods=["GET"]) +def demo_stream(n): + """ + 逐行输出 n 条 JSON,每条之间延迟 500ms,模拟流式响应。 + 客户端用 @Streaming + ResponseBody 逐行读取即可。 + """ + n = min(max(n, 1), 20) # 限制 1~20 条 + + def generate(): + for i in range(n): + line = json.dumps( + { + "index": i + 1, + "total": n, + "message": f"流式数据第 {i + 1} 条", + "timestamp": int(time.time()), + }, + ensure_ascii=False, + ) + yield line + "\n" + time.sleep(0.5) + + return Response( + generate(), + status=200, + mimetype="application/octet-stream", + headers={"X-Accel-Buffering": "no"}, + ) + + +# ── 入口 ────────────────────────────────────────────────────────────────────── + +if __name__ == "__main__": + print(f"Demo server running on http://0.0.0.0:{PORT}") + print(" GET http://<本机IP>:8080/get") + print(" POST http://<本机IP>:8080/post") + print(" GET http://<本机IP>:8080/stream/5") + app.run(host="0.0.0.0", port=PORT, debug=False, threaded=True) \ No newline at end of file diff --git a/server/requirements.txt b/server/requirements.txt new file mode 100644 index 0000000..67a0de0 --- /dev/null +++ b/server/requirements.txt @@ -0,0 +1 @@ +flask>=2.3.0 \ No newline at end of file