auth.tsx 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. import { createContext, useContext, PropsWithChildren, useState } from 'react'
  2. import Taro, { useLaunch } from '@tarojs/taro'
  3. import { authClient } from '../api'
  4. import { InferResponseType, InferRequestType } from 'hono'
  5. import { useMutation } from '@tanstack/react-query'
  6. import { isWeapp, isH5 } from './platform'
  7. // 用户类型定义
  8. export type User = InferResponseType<typeof authClient.me.$get, 200>
  9. export type MiniLoginUser = InferResponseType<typeof authClient['mini-login']['$post'], 200>['user']
  10. type LoginRequest = InferRequestType<typeof authClient.login.$post>['json']
  11. type RegisterRequest = InferRequestType<typeof authClient.register.$post>['json']
  12. interface AuthContextType {
  13. user: User | null
  14. login: (data: LoginRequest) => Promise<User>
  15. logout: () => Promise<void>
  16. register: (data: RegisterRequest) => Promise<User>
  17. updateUser: (userData: Partial<User>) => void
  18. isLoading: boolean
  19. isLoggedIn: boolean
  20. }
  21. const AuthContext = createContext<AuthContextType | undefined>(undefined)
  22. export const AuthProvider: React.FC<PropsWithChildren> = ({ children }) => {
  23. // 用户状态
  24. const [user, setUser] = useState<User | null>(() => {
  25. // 从本地存储初始化用户信息
  26. const userInfoStr = Taro.getStorageSync('userInfo')
  27. if (userInfoStr) {
  28. try {
  29. return JSON.parse(userInfoStr)
  30. } catch {
  31. return null
  32. }
  33. }
  34. return null
  35. })
  36. // 获取用户信息的通用函数
  37. const fetchUserInfo = async (): Promise<User | null> => {
  38. // const token = Taro.getStorageSync('mini_token')
  39. // if (!token) {
  40. // return null
  41. // }
  42. try {
  43. const response = await authClient.me.$get({})
  44. if (response.status !== 200) {
  45. throw new Error('获取用户信息失败')
  46. }
  47. const user = await response.json()
  48. Taro.setStorageSync('userInfo', JSON.stringify(user))
  49. return user
  50. } catch (error) {
  51. // Token失效时的处理逻辑
  52. console.debug('Token失效,开始处理重新登录逻辑')
  53. if (isWeapp()) {
  54. // 小程序环境:使用静默登录重新获取token
  55. console.debug('小程序环境,尝试静默登录')
  56. try {
  57. const loginRes = await Taro.login()
  58. if (loginRes.code) {
  59. const response = await authClient['mini-login'].$post({
  60. json: {
  61. code: loginRes.code,
  62. tenantId: Number(process.env.TARO_APP_TENANT_ID) || 1
  63. }
  64. })
  65. if (response.status === 200) {
  66. const { token: newToken } = await response.json()
  67. Taro.setStorageSync('mini_token', newToken)
  68. // 重新获取完整的用户信息
  69. const meResponse = await authClient.me.$get({})
  70. if (meResponse.status === 200) {
  71. const user = await meResponse.json()
  72. Taro.setStorageSync('userInfo', JSON.stringify(user))
  73. console.debug('静默登录成功,返回新用户信息')
  74. return user
  75. }
  76. }
  77. }
  78. } catch (silentLoginError) {
  79. console.debug('静默登录失败:', silentLoginError)
  80. }
  81. } else if (isH5()) {
  82. // H5环境:跳转到登录页
  83. console.debug('H5环境,跳转到登录页')
  84. Taro.redirectTo({ url: '/pages/login/index' })
  85. }
  86. // 如果重新登录失败或不是小程序环境,清除token并返回null
  87. Taro.removeStorageSync('mini_token')
  88. Taro.removeStorageSync('userInfo')
  89. return null
  90. }
  91. }
  92. // 静默登录mutation - 应用启动时自动尝试登录
  93. const silentLoginMutation = useMutation<User | null, Error, void>({
  94. mutationFn: async () => {
  95. // 使用统一的用户信息获取函数
  96. return await fetchUserInfo()
  97. },
  98. onSuccess: (user) => {
  99. if (user) {
  100. setUser(user)
  101. }
  102. }
  103. })
  104. // 在小程序启动时执行静默登录,刷新token和sessionKey
  105. useLaunch(() => {
  106. silentLoginMutation.mutate()
  107. })
  108. const loginMutation = useMutation<User, Error, LoginRequest>({
  109. mutationFn: async (data) => {
  110. const response = await authClient.login.$post({ json: data })
  111. if (response.status !== 200) {
  112. throw new Error('登录失败')
  113. }
  114. const { token, user } = await response.json()
  115. Taro.setStorageSync('mini_token', token)
  116. Taro.setStorageSync('userInfo', JSON.stringify(user))
  117. return user
  118. },
  119. onSuccess: (newUser) => {
  120. setUser(newUser)
  121. },
  122. onError: (error) => {
  123. Taro.showToast({
  124. title: error.message || '登录失败,请检查用户名和密码',
  125. icon: 'none',
  126. duration: 2000,
  127. })
  128. },
  129. })
  130. const registerMutation = useMutation<User, Error, RegisterRequest>({
  131. mutationFn: async (data) => {
  132. const response = await authClient.register.$post({ json: data })
  133. if (response.status !== 201) {
  134. throw new Error('注册失败')
  135. }
  136. const { token, user } = await response.json()
  137. Taro.setStorageSync('mini_token', token)
  138. Taro.setStorageSync('userInfo', JSON.stringify(user))
  139. return user
  140. },
  141. onSuccess: (newUser) => {
  142. setUser(newUser)
  143. },
  144. onError: (error) => {
  145. Taro.showToast({
  146. title: error.message || '注册失败,请重试',
  147. icon: 'none',
  148. duration: 2000,
  149. })
  150. },
  151. })
  152. const logoutMutation = useMutation<void, Error>({
  153. mutationFn: async () => {
  154. try {
  155. const response = await authClient.logout.$post({})
  156. if (response.status !== 200) {
  157. throw new Error('登出失败')
  158. }
  159. } catch (error) {
  160. console.error('Logout error:', error)
  161. } finally {
  162. Taro.removeStorageSync('mini_token')
  163. Taro.removeStorageSync('userInfo')
  164. }
  165. },
  166. onSuccess: () => {
  167. setUser(null)
  168. Taro.redirectTo({ url: '/pages/login/index' })
  169. },
  170. onError: (error) => {
  171. Taro.showToast({
  172. title: error.message || '登出失败',
  173. icon: 'none',
  174. duration: 2000,
  175. })
  176. },
  177. })
  178. const updateUserMutation = useMutation<User, Error, Partial<User>>({
  179. mutationFn: async (userData) => {
  180. const response = await authClient.me.$put({ json: userData })
  181. if (response.status !== 200) {
  182. throw new Error('更新用户信息失败')
  183. }
  184. const updatedUser = await response.json()
  185. Taro.setStorageSync('userInfo', JSON.stringify(updatedUser))
  186. return updatedUser
  187. },
  188. onSuccess: (updatedUser) => {
  189. setUser(updatedUser)
  190. Taro.showToast({
  191. title: '更新成功',
  192. icon: 'success',
  193. duration: 2000,
  194. })
  195. },
  196. onError: (error) => {
  197. Taro.showToast({
  198. title: error.message || '更新失败,请重试',
  199. icon: 'none',
  200. duration: 2000,
  201. })
  202. },
  203. })
  204. const updateUser = updateUserMutation.mutateAsync
  205. const value = {
  206. user: user || null,
  207. login: loginMutation.mutateAsync,
  208. logout: logoutMutation.mutateAsync,
  209. register: registerMutation.mutateAsync,
  210. updateUser,
  211. isLoading: loginMutation.isPending || registerMutation.isPending || logoutMutation.isPending || silentLoginMutation.isPending,
  212. isLoggedIn: !!user,
  213. }
  214. return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
  215. }
  216. export const useAuth = () => {
  217. const context = useContext(AuthContext)
  218. if (context === undefined) {
  219. throw new Error('useAuth must be used within an AuthProvider')
  220. }
  221. return context
  222. }