Files
happy-life-star/docs/superpowers/specs/2026-05-31-mini-program-wechat-auth-design.md
T

14 KiB

Mini Program WeChat Auth Design

Problem

The mini program currently supports phone number plus SMS code login only. The new requirement is to add full WeChat authorization login while keeping the existing phone login available. WeChat login must use the free openid-based login flow as the primary path, and phone number authorization should be integrated when available so the backend can identify and bind existing phone users automatically.

Goals

  • Add WeChat mini program login with wx.login/uni.login and backend code2Session exchange.
  • Keep the existing phone plus SMS code login flow unchanged as a supported fallback and alternate login method.
  • Use WeChat openid as the free, reliable identity for mini program login.
  • If WeChat phone authorization succeeds, use the returned phone number to identify an existing user and automatically bind the WeChat openid to that account.
  • Save nickname and avatar into existing t_user.nickname and t_user.avatar fields when the user provides them.
  • Reuse the current JWT response, token storage, session restore, onboarding routing, and profile flow.
  • Match the current mini program dark glass/cosmic visual style.

Non-Goals

  • Do not remove, hide permanently, or degrade phone SMS login.
  • Do not introduce paid third-party login or SMS providers.
  • Do not require phone number authorization before a user can log in with WeChat openid.
  • Do not redesign unrelated onboarding, main tabs, or profile features.
  • Do not store WeChat session_key on the client.

Current Findings

  • mini-program/src/pages/login/index.vue contains the existing phone/SMS login UI and routes to main or onboarding based on profile presence.
  • mini-program/src/stores/app.js already centralizes login, logout, token restore, and profile fetch behavior.
  • mini-program/src/services/auth.js stores access_token and refresh_token from AuthResponse.
  • backend-single/src/main/java/com/emotion/controller/AuthController.java exposes /auth/login, /auth/refreshToken, and token validation endpoints.
  • t_user already has phone, avatar, nickname, third_party_id, and third_party_type, which can support WeChat binding without a required schema change.
  • The current backend issues JWT plus Redis-backed access/refresh tokens through AuthServiceImpl.
  • The mini program manifest already has a WeChat appid: wxaf2eaba72d28f0e4.
  • application.yml security ignore URLs must include the new public WeChat login endpoint, while the phone binding endpoint must remain protected by JWT.
  • UserInfoResponse exposes phone, nickname, and avatar, but it does not expose whether the current account has a WeChat binding. The frontend needs a non-sensitive wechatBound boolean instead of receiving openid.
  • Binding an openid from a temporary WeChat user to an existing phone user must also transfer or preserve data created under the temporary user; otherwise early actions before phone binding can be stranded on the temporary account.

Use a two-track login page:

  1. Primary action: WeChat login. The mini program calls uni.login({ provider: 'weixin' }), sends the returned code to the backend, and receives the same AuthResponse shape used by existing login.
  2. Secondary action: phone SMS login. The current phone/code form remains available and continues to call /auth/login.

Phone authorization is a post-login enhancement:

  1. After WeChat login succeeds, show or surface a "bind phone" action using WeChat open-type="getPhoneNumber" when the platform supports it.
  2. The mini program sends the phone authorization code to the backend.
  3. The backend calls WeChat's phone-number API.
  4. If the phone already belongs to an existing user, bind the current openid to that existing user and return a fresh AuthResponse for that existing user.
  5. If the phone does not exist, write it to the current WeChat user and return updated user info.

This keeps WeChat openid login free and resilient while letting phone number authorization unify identities when available.

Backend Design

Configuration

Add configuration under emotion.wechat.mini-program:

  • app-id
  • app-secret
  • base-url, default https://api.weixin.qq.com

Secrets must come from environment/profile configuration in deploy environments. app-secret must not be sent to the client or logged.

Add a database migration for WeChat identity integrity:

  • Add a unique composite index on t_user(third_party_type, third_party_id).
  • MySQL allows multiple NULL values in a unique index, so ordinary phone users without WeChat binding are unaffected.
  • Before transferring an openid from a temporary WeChat user to an existing phone user, clear the temporary user's third-party fields so the unique index remains valid.

DTOs

Add request/response DTOs:

  • WechatLoginRequest

    • code: required login code from uni.login.
    • nickname: optional user-provided nickname.
    • avatar: optional user-provided avatar URL.
  • WechatPhoneBindRequest

    • code: required phone authorization code from getPhoneNumber.
  • The response can reuse AuthResponse for login and phone binding so token handling stays consistent.

  • Extend UserInfoResponse with wechatBound: boolean. It must be computed from the server-side third-party fields and must not expose openid or session key.

Service Boundaries

Add WechatMiniProgramService for WeChat API calls:

  • code2Session(code) returns openid/session metadata.
  • getStableAccessToken() obtains and caches the mini program interface token in Redis.
  • getPhoneNumber(phoneCode) returns the verified phone number.
  • Translate WeChat API error codes into clear BusinessException messages.

Add UserIdentityMergeService for account convergence:

  • mergeTemporaryWechatUserIntoPhoneUser(String temporaryWechatUserId, String phoneUserId) transfers user-owned rows from the temporary WeChat user to the canonical phone user.
  • The service updates the core mini-program tables that store user_id: t_user_profile, t_life_event, t_epic_script, t_life_path, t_life_path_step, t_social_content_item, t_social_profile_insight, t_user_consent_log, t_tts_task, and t_analytics_event.
  • It also updates user-facing shared data where appropriate: t_conversation, t_message, t_coze_api_call, t_emotion_analysis, t_emotion_record, t_diary_post, t_diary_comment, t_community_post, t_comment, t_user_stats, and t_ai_call_log.
  • If both users have a singleton row such as t_user_profile or t_user_stats, keep the existing phone user's row and only transfer rows that do not conflict. This avoids overwriting a mature phone account with sparse temporary data.

Extend AuthService with:

  • wechatLogin(WechatLoginRequest request)
  • bindWechatPhone(String currentUserId, WechatPhoneBindRequest request)

Extend UserService with focused lookup helpers:

  • getByThirdParty(String thirdPartyType, String thirdPartyId)
  • getByPhone(String phone) already exists and should be reused for phone identity matching.

The controller stays thin and delegates all identity decisions to the service layer.

Identity Matching

For WeChat login:

  1. Exchange login code for openid.
  2. Query user by third_party_type = "wechat-mp" and third_party_id = openid.
  3. If found and enabled, issue tokens for that user.
  4. If not found, create a user:
    • account = "wx_" + openid
    • username = generated username
    • nickname = request nickname or generated username
    • avatar = request avatar if present
    • third_party_type = "wechat-mp"
    • third_party_id = openid
    • member_level = "free"
    • status = 1

For phone binding:

  1. Require a valid current JWT.
  2. Resolve the current WeChat user's openid from third_party_id.
  3. Exchange the phone authorization code for phone number.
  4. Query existing user by phone.
  5. If no phone user exists, set current user's phone and mark verified.
  6. If the phone user is the current user, ensure openid and phone are both present.
  7. If another enabled user owns the phone, treat that phone user as canonical.
  8. If the phone user already has a different WeChat openid, reject with a clear "phone already bound to another WeChat account" message.
  9. If the phone user can accept the binding, transfer user-owned rows from the temporary WeChat user to the phone user, clear the temporary user's third-party fields, bind the openid to the phone user, and issue new tokens for the phone user.

When the phone belongs to an existing account, the existing account is the canonical identity. The response must return tokens for that account so future openid login lands on the same user as phone SMS login.

Frontend Design

Login Page

Update mini-program/src/pages/login/index.vue while preserving the current visual language:

  • Keep the dark background, glass card, purple accents, and safe-area handling.
  • Add a primary WeChat login button at the top of the card.
  • Keep the existing phone number and SMS code form as an alternate method below a subtle divider.
  • Use one loading state for the active login method and prevent duplicate taps.
  • On successful login, reuse current routing:
    • has profile -> /pages/main/index?tab=script
    • no profile -> /pages/onboarding/index

Frontend Services And Store

Add to mini-program/src/services/auth.js:

  • wechatLogin({ code, nickname, avatar })
  • bindWechatPhone({ code })

Both methods store returned tokens exactly like existing login() and refreshToken().

Add to mini-program/src/stores/app.js:

  • loginWithWechat(profilePayload)
  • bindWechatPhone(phoneCode)
  • fetchCurrentUserInfo(), called after login, token restore, and phone binding.
  • Keep login(phone, smsCode) unchanged.
  • After phone binding returns new tokens, refresh current profile/user state.

Phone Authorization UI

Add a small phone-bind entry in the logged-in experience, preferably in MineView.vue or onboarding completion area:

  • Show only when logged in, userInfo.phone is empty, and userInfo.wechatBound is true.
  • Use WeChat button open-type="getPhoneNumber" on mp-weixin.
  • On non-WeChat/H5 builds, show the existing phone SMS login/bind fallback.
  • If authorization succeeds and backend returns tokens for an existing phone user, overwrite local tokens and refresh profile.
  • If the user denies authorization, keep them logged in and show a non-blocking message.

Nickname And Avatar

Use WeChat's current nickname/avatar collection pattern in profile or onboarding:

  • Nickname input can populate existing registrationData.nickname.
  • Avatar selection can populate existing avatar fields.
  • Save through existing user/profile update endpoints where possible.
  • Do not block WeChat openid login on nickname or avatar.

Error Handling

  • Missing backend WeChat appid/secret: backend returns configuration error; frontend keeps phone login available.
  • uni.login failure: show a concise "WeChat login failed, please try again later" message and keep phone login visible.
  • code2Session failure or invalid code: show a concise login failure message.
  • Disabled user: reject login consistently with existing auth behavior.
  • Phone authorization denied: do not log out; show a non-blocking message that the user can bind a phone later in the profile center.
  • Phone API unavailable or permission not enabled: keep openid login active and route user normally.
  • Existing phone is bound to another openid: reject binding and ask user to use phone login or contact support.
  • Existing phone account has data while temporary WeChat account also has data: transfer non-conflicting temporary rows to the phone account before switching tokens.
  • Temporary WeChat account after successful merge: clear its third_party_type and third_party_id and disable it from future WeChat login matching.
  • Network failures: preserve current session restore behavior and avoid clearing tokens unless backend explicitly rejects auth.

Data Flow

WeChat Login

  1. User taps WeChat login.
  2. Mini program calls uni.login.
  3. Mini program posts { code } to /auth/wechat/login.
  4. Backend exchanges code for openid through WeChat.
  5. Backend finds or creates user by openid.
  6. Backend returns AuthResponse.
  7. Mini program stores tokens, fetches profile, and routes.

Phone Auto-Binding

  1. Logged-in user taps authorize phone.
  2. WeChat returns a phone authorization code.
  3. Mini program posts { code } to /auth/wechat/phone.
  4. Backend exchanges code for phone number.
  5. Backend finds existing user by phone.
  6. Backend binds openid to the phone user or writes phone to current user.
  7. If an existing phone user is canonical, backend transfers non-conflicting temporary user data and clears the temporary WeChat identity.
  8. Backend returns AuthResponse.
  9. Mini program replaces tokens and refreshes user/profile state.

Testing

Backend:

  • WeChat login creates a new user for a new openid.
  • WeChat login returns the same user for a known openid.
  • Phone binding writes phone to current WeChat user when no existing phone user exists.
  • Phone binding binds openid to an existing phone user and returns that user's tokens.
  • Phone binding transfers temporary WeChat user data to the existing phone user before returning phone-user tokens.
  • Temporary WeChat user no longer matches future openid login after a successful transfer.
  • Phone binding rejects a phone user already bound to a different openid.
  • Existing phone SMS login still works.
  • Token validation and refresh still work after WeChat login.
  • /api/auth/wechat/login is public, and /api/auth/wechat/phone rejects unauthenticated requests.
  • UserInfoResponse returns wechatBound without returning openid.

Mini program:

  • Build npm run build:mp-weixin.
  • Login page supports both WeChat and phone SMS login.
  • WeChat login success routes through the current profile/onboarding rules.
  • Phone login success routes through the current profile/onboarding rules.
  • Denying phone authorization does not break logged-in state.
  • Successful phone authorization refreshes tokens and profile.
  • Personal center reflects nickname/avatar/phone state where available.
  • Restored sessions fetch current user info so phone binding visibility is correct after app restart.

References