瀏覽代碼

Revert "修改line登录"

This reverts commit cbb5a7e05274439a5f6f78b3c2e43138e91f5442.
qmj 1 月之前
父節點
當前提交
a58956f7fb

文件差異過大導致無法顯示
+ 0 - 0
.claude/homunculus/observations.jsonl


+ 6 - 31
ruoyi-admin/src/main/java/com/ruoyi/app/user/LineCallbackController.java

@@ -44,7 +44,7 @@ import java.util.concurrent.TimeUnit;
  * <p><b>流程</b>:LINE → {@code GET /auth/line/callback?code=...&state=...}
  * → {@link OAuthVerifyService#verify verify("line", code)}(code 换 token 取 userId)
  * → 查 info_user_oauth:已绑定→签 JWT token;未绑定→生成 tempKey(needPhone)
- * → 返回 HTML 引导拉起 {@code oauth.line.app-redirect}(如 com.twanmsdyh.app://oauthLogin)带结果:
+ * → 302 跳到 {@code oauth.line.app-redirect}(如 com.twanmsdyh.app://oauthLogin)带结果:
  * <ul>
  *   <li>已绑定:{@code <app-redirect>?token=xxx}</li>
  *   <li>未绑定:{@code <app-redirect>?needPhone=1&tempKey=xxx}(App 弹手机号+短信码 UI 后调 /infouser/user/oauthBindPhone)</li>
@@ -81,7 +81,7 @@ public class LineCallbackController extends BaseController {
     private String appRedirect;
 
     /**
-     * LINE 服务端回调:接 code → 换 token → 登录 → 返回 HTML 拉起 App。
+     * LINE 服务端回调:接 code → 换 token → 登录 → 302 回 App。
      * 用 @Anonymous 放行(LINE 以浏览器/App 身份回调,无 token)。
      */
     @Anonymous
@@ -114,7 +114,7 @@ public class LineCallbackController extends BaseController {
                 }
                 String token = buildOauthToken(u, "line");
                 log.info("[OAuth][LINE] callback 已绑定登录成功 userId={}", u.getUserId());
-                respondAppRedirect(response, appRedirect + "?token=" + enc(token));
+                response.sendRedirect(appRedirect + "?token=" + enc(token));
                 return;
             }
 
@@ -122,7 +122,7 @@ public class LineCallbackController extends BaseController {
             String tempKey = UUID.randomUUID().toString().replace("-", "");
             redisCache.setCacheObject(OAUTH_TEMP_PREFIX + tempKey, "line@" + providerUid, 5, TimeUnit.MINUTES);
             log.info("[OAuth][LINE] callback 未绑定,返回 needPhone providerUid={}", providerUid);
-            respondAppRedirect(response, appRedirect + "?needPhone=1&tempKey=" + enc(tempKey));
+            response.sendRedirect(appRedirect + "?needPhone=1&tempKey=" + enc(tempKey));
         } catch (Exception e) {
             log.error("[OAuth][LINE] callback 异常", e);
             redirectToApp(response, "error", e.getMessage() == null ? "fail" : e.getMessage());
@@ -154,34 +154,9 @@ public class LineCallbackController extends BaseController {
         userDto.setLoginTime(System.currentTimeMillis());
     }
 
-    /** 回 App:app-redirect + ?<key>=<value>(value 做 URL 编码)。 */
+    /** 302 跳回 App:app-redirect + ?<key>=<value>(value 做 URL 编码)。 */
     private void redirectToApp(HttpServletResponse response, String key, String value) throws IOException {
-        respondAppRedirect(response, appRedirect + "?" + key + "=" + enc(value));
-    }
-
-    /**
-     * 返回 HTML 引导拉起 App(替代裸 302)。
-     * <p>原因:浏览器 / LINE 内置 webview 常拦截「自动 302 跳自定义 scheme」(防劫持),
-     * 且 App 热启动时收不到 scheme 参数。改为返回 HTML:meta refresh + JS 自动尝试 + 可见的
-     * 「点击打开 App」按钮(用户手势触发 scheme 导航最可靠,冷/热启动都能把参数送达 App)。
-     * <p>schemeUrl 内 value 均已 URL 编码,不含引号/尖括号,可安全嵌入单引号属性与 JS 串。
-     */
-    private void respondAppRedirect(HttpServletResponse response, String schemeUrl) throws IOException {
-        String html = "<!DOCTYPE html><html><head><meta charset='UTF-8'>"
-                + "<meta name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'>"
-                + "<meta http-equiv='refresh' content='0;url=" + schemeUrl + "'>"
-                + "<title>打開 App</title></head>"
-                + "<body style='margin:0;font-family:-apple-system,sans-serif;text-align:center'>"
-                + "<div style='padding:56px 20px'>"
-                + "<p style='font-size:18px;color:#333;margin:0 0 28px'>正在開啟 App…</p>"
-                + "<a href='" + schemeUrl + "' style='display:inline-block;padding:14px 36px;"
-                + "background:#06C755;color:#fff;border-radius:28px;text-decoration:none;font-size:16px'>點擊開啟 App</a>"
-                + "<p style='margin:28px 0 0;color:#999;font-size:13px'>若未自動跳轉,請點上方按鈕</p>"
-                + "</div>"
-                + "<script>try{location.href='" + schemeUrl + "';}catch(e){}</script>"
-                + "</body></html>";
-        response.setContentType("text/html;charset=UTF-8");
-        response.getWriter().write(html);
+        response.sendRedirect(appRedirect + "?" + key + "=" + enc(value));
     }
 
     private static String enc(String s) {

+ 6 - 6
specs/017-oauth-login/line-callback-frontend.md

@@ -1,7 +1,7 @@
 # LINE 登录回调 — 前端(uniapp)接入说明
 
 > 配套后端:`LineCallbackController` → `GET /auth/line/callback`(2026-08-05 增量,017-oauth-login FR-009)。
-> 适用场景:**「唤起 LINE App / 系统浏览器」** 授权 —— 这种方式前端拿不到 code,由后端接住 LINE 的重定向、完成登录后,再返回 HTML 拉起 App。
+> 适用场景:**「唤起 LINE App / 系统浏览器」** 授权 —— 这种方式前端拿不到 code,由后端接住 LINE 的重定向、完成登录后,再 302 跳回 App。
 >
 > 若你的场景是「H5 / 自家 webview / LINE SDK」(前端能自己拿到 code),**不走本回调**,直接调 `POST /infouser/user/oauthLogin {provider:"line", credential:code}`。两条流程并存。
 
@@ -14,12 +14,12 @@
 ② 用户在 LINE App 里一键同意
 ③ LINE 跳转:GET https://api.awayqtw.com/auth/line/callback?code=xxx&state=xxx
 ④ 后端:code → 换 token → 取 userId → 查绑定 → 签 JWT 或 生成 tempKey
-⑤ 后端返回 HTML(meta refresh + JS 自动尝试 + 「點擊開啟 App」按钮)拉起 App scheme:
+⑤ 后端 302 跳回 App scheme:
      com.twanmsdyh.app://oauthLogin?<参数>
 ⑥ uniapp 被 scheme 拉起,读参数,按下面三种情况处理
 ```
 
-**关键:** 前端不碰 code,只接收后端回跳的 scheme URL 并按参数分流。
+**关键:** 前端不碰 code,只接收后端 302 回来的 scheme URL 并按参数分流。
 
 ---
 
@@ -62,7 +62,7 @@ https://access.line.me/oauth2/v2.1/authorize
 
 ---
 
-## 4. 接收回跳的 scheme URL
+## 4. 接收 302 回跳的 scheme URL
 
 uniapp 在 `App.vue` 里读取拉起 App 的 URL:
 
@@ -104,7 +104,7 @@ export default {
 
 ## 5. 三种返回值处理(核心)
 
-后端回的 URL 形如 `com.twanmsdyh.app://oauthLogin?<参数>`,参数只有下列三种组合之一:
+后端 302 回的 URL 形如 `com.twanmsdyh.app://oauthLogin?<参数>`,参数只有下列三种组合之一:
 
 | 情况 | 参数 | 含义 | 前端动作 |
 |------|------|------|----------|
@@ -200,7 +200,7 @@ if (params.error) {
 
 1. **redirect_uri 三方一致铁律**:前端 authorize 里的 `redirect_uri`、后端 `application.yml` 的 `oauth.line.redirect-uri`、LINE Console 回调白名单,三者必须**完全相同**(当前 = `https://api.awayqtw.com/auth/line/callback`),否则 LINE 回 `400 redirect_uri_mismatch`。
 2. **tempKey 有效期 5 分钟**:超时后 `/oauthBindPhone` 回 `tempkey.expired`,需重新点 LINE 登录拿新的。
-3. **后端已返回 HTML(不是裸 302)**:裸 302 到自定义 scheme 常被浏览器/LINE 内置 webview 拦截(防劫持),且 App 热启动收不到 scheme 参数(只有冷启动「重新打开 App」才补到)。故后端 `LineCallbackController` 改为返回一段 HTML:`meta refresh` + JS 自动尝试 + 可见「點擊開啟 App」按钮。**用户手势点按钮触发 scheme 导航最可靠**,冷/热启动都能把参数送达 App。前端照常按第 4、5 节处理 scheme 即可
+3. **302 到自定义 scheme 的兼容性**:标准系统浏览器会跟随 302 到 `com.twanmsdyh.app://...` 拉起 App。若联调发现 **LINE 内置浏览器**不跟随 302(App 没被拉起),后端可改为返回 HTML(`meta refresh` + 手动「打开 App」链接)兜底 —— 这是待联调确认项,目前是 302
 4. **设备信息**:后端回调链路没有 App 上下文,`cid` / `deviceToken` / `voIPToken` 在回调时更新不到;绑手机走 `/oauthBindPhone` 时会带上,已绑定的老用户建议 5.1 拿到 token 后补报一次(或复用既有设备上报逻辑)。
 5. **state 暂未校验**:后端目前不验证 `state`,前端仍建议生成并传,后续后端会加 CSRF 校验。
 6. **与 `/oauthLogin` 并存**:如果某端(如 H5)前端能自己拿到 code,直接 `POST /infouser/user/oauthLogin {provider:"line", credential:code, ...}`,不必走本回调;返回值结构与本回调的 `token` / `needPhone+tempKey` 对应一致。

部分文件因文件數量過多而無法顯示