diff --git a/app/package.json b/app/package.json index f91a8f7..55eff8b 100644 --- a/app/package.json +++ b/app/package.json @@ -5,7 +5,7 @@ "main": "index.js", "basename": "/root/wx-app-services", "app": { - "key": "wx-app", + "key": "wx-app-services", "entry": "dist/app.mjs", "type": "system-app", "files": [ diff --git a/app/src/modules/wx.ts b/app/src/modules/wx.ts index 8678f73..ecd054a 100644 --- a/app/src/modules/wx.ts +++ b/app/src/modules/wx.ts @@ -24,10 +24,10 @@ export type WxToken = { * @param code * @returns */ -export const fetchToken = async (code: string): Promise => { +export const fetchToken = async (code: string, type: 'open' | 'mp' = 'open'): Promise => { let appId = config.wxOpen.appId; let appSecret = config.wxOpen.appSecret; - if (!appId && !appSecret) { + if (type === 'mp') { appId = config.wx.appId; appSecret = config.wx.appSecret; } diff --git a/app/src/routes/wx/login.ts b/app/src/routes/wx/login.ts index 324fc91..95f6dd1 100644 --- a/app/src/routes/wx/login.ts +++ b/app/src/routes/wx/login.ts @@ -68,7 +68,7 @@ app const code = ctx.query.code; try { const wx = new WxServices(); - const token = await wx.login(code); + const token = await wx.login(code, 'mp'); const redis = useContextKey('redis'); await redis.set(`wx:mp:login:${state}`, token, 'EX', 10000); // 30秒过期 ctx.body = { @@ -81,6 +81,24 @@ app }) .addTo(app); +app + .route({ + path: 'wx', + key: 'mp-get-openid', + isDebug: true, + }) + .define(async (ctx) => { + const code = ctx.query.code; + if (!code) { + ctx.throw(400, 'code is required'); + return; + } + const wx = new WxServices(); + const mpInfo = await wx.getOpenid(code, 'mp'); + ctx.body = mpInfo; + }) + .addTo(app); + app .route({ path: 'wx', diff --git a/app/src/routes/wx/services.ts b/app/src/routes/wx/services.ts index 020e85c..c2ba122 100644 --- a/app/src/routes/wx/services.ts +++ b/app/src/routes/wx/services.ts @@ -36,8 +36,22 @@ export class WxServices { } return random; } - async login(code: string) { - const token = await fetchToken(code); + /** + * 获取openid + * @param code + * @returns + */ + async getOpenid(code: string, type: 'mp' | 'open' = 'open') { + const token = await fetchToken(code, type); + console.log('login token', token); + return { + openid: token.openid, + scope: token.scope, + unionid: token.unionid, + }; + } + async login(code: string, type: 'mp' | 'open' = 'open') { + const token = await fetchToken(code, type); console.log('login token', token); if (!token.unionid) { throw new CustomError(400, 'code is invalid, wxdata can not be found'); @@ -52,32 +66,49 @@ export class WxServices { }, }); // @ts-ignore - if (user && user.data.wxOpenid !== token.openid) { + if (type === 'open' && user && user.data.wxOpenid !== token.openid) { user.data = { ...user.data, // @ts-ignore wxOpenid: token.openid, }; - await user.update({ data: user.data }); + user = await user.update({ data: user.data }); console.log('mp-user login openid update=============', token.openid, token.unionid); + // @ts-ignore + } else if (type === 'mp' && user && user.data.wxmpOpenid !== token.openid) { + user.data = { + ...user.data, + // @ts-ignore + wxmpOpenid: token.openid, + }; + user = await user.update({ data: user.data }); } if (!user) { const username = await this.randomUsername(); user = await User.createUser(username, nanoid(10)); - user.data = { + let data = { ...user.data, - // @ts-ignore - wxOpenid: token.openid, wxUnionId: unionid, }; - await user.save({ fields: ['data'] }); + user.data = data; + if ((type = 'mp')) { + // @ts-ignore + data.wxmpOpenid = token.openid; + } else { + // @ts-ignore + data.wxOpenid = token.openid; + } + this.user = await user.save({ fields: ['data'] }); + + this.getUserInfo(); this.isNew = true; } - + this.user = user; const tokenInfo = await user.createToken(null, 'plugin', { wx: { openid: token.openid, unionid: unionid, + type, }, }); this.webToken = tokenInfo.accessToken; @@ -107,7 +138,12 @@ export class WxServices { } catch (error) { console.error('Error downloading or converting image:', error); } - await this.user.save(); + const data = { + ...this.user.data, + wxUserInfo: userInfo, + }; + this.user.data = data; + await this.user.save({ fields: ['data', 'nickname', 'avatar'] }); } catch (error) { console.error('Error getting user info:', error); } @@ -121,7 +157,7 @@ export class WxServices { return await downloadImag(url); } catch (error) { console.error('Error downloading or converting image:', error); - throw error; + return ''; } } } diff --git a/mini-web/README.md b/mini-web/README.md new file mode 100644 index 0000000..7d28d2a --- /dev/null +++ b/mini-web/README.md @@ -0,0 +1,3 @@ +# 登陆逻辑 + +微信访问 `login.html` 的界面, 然后 `login.html` 去获取 `/authorize` 获取 openid diff --git a/mini-web/envision/config.js b/mini-web/envision/config.js index ba02c37..a2e6efb 100644 --- a/mini-web/envision/config.js +++ b/mini-web/envision/config.js @@ -3,9 +3,15 @@ export const config = { appid: 'wxff97d569b1db16b6', appid_open: 'wx9378885c8390e09b', // 公众开放平台, 逸文设计 // redirect_uri: 'https://kevisual.xiongxiao.me/root/mini-web/callback.html', - scope: 'snsapi_userinfo', + scope: 'snsapi_userinfo', // 授权作用域, 默认是snsapi_base }; export const loginUrl = `https://kevisual.xiongxiao.me/root/mini-web/login.html`; export const loginSuccessUrl = `/apps/wallnote/`; + +export const openidConfig = { + appid: config.appid, + redirect_uri: 'https://kevisual.xiongxiao.me/root/mini-web/wx-get-openid.html', + scope: 'snsapi_base', +}; diff --git a/mini-web/envision/utils.js b/mini-web/envision/utils.js new file mode 100644 index 0000000..13e1fcd --- /dev/null +++ b/mini-web/envision/utils.js @@ -0,0 +1,57 @@ +import { openidConfig } from './config.js'; + +export const isWechat = () => { + const ua = navigator.userAgent.toLowerCase(); + return /micromessenger/i.test(ua); +}; + +export const getOpenid = async () => { + const url = new URL(window.location.href); + const state = url.searchParams.get('state'); + const code = url.searchParams.get('code'); + const orgin = url.origin; + const res = await fetch(`${orgin}/api/router?path=wx&key=mp-get-openid&state=${state}&code=${code}`) + .then((res) => res.json()) + .catch((err) => { + console.error(err); + alert('登录失败,请稍后再试'); + document.body.append('登录失败,请稍后再试'); + // closePage(); + }); + if (res.code === 200) { + document.body.append(JSON.stringify(res.data, null, 2)); + } +}; + +export const checkHasCode = () => { + const url = new URL(window.location.href); + const code = url.searchParams.get('code'); + return code; +}; + +export const initGetOpenidEvent = () => { + if (!isWechat()) { + document.body.append('请在微信中打开'); + return; + } + const hasCode = checkHasCode(); + if (hasCode) { + getOpenid(); + } else { + // 没有code,则跳转获取code + getCodeRedirect(); + } +}; + +const randomString = () => { + return Math.random().toString(36).substring(2, 15); +}; +export const getCodeRedirect = () => { + const defaultURL = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect`; + const appid = openidConfig.appid; + const redirect_uri = encodeURIComponent(openidConfig.redirect_uri); + const scope = openidConfig.scope; + const state = randomString(); + const link = defaultURL.replace('APPID', appid).replace('REDIRECT_URI', redirect_uri).replace('SCOPE', scope).replace('STATE', state); + window.location.href = link; +}; diff --git a/mini-web/envision/wx-get-openid.html b/mini-web/envision/wx-get-openid.html new file mode 100644 index 0000000..ff59d1f --- /dev/null +++ b/mini-web/envision/wx-get-openid.html @@ -0,0 +1,50 @@ + + + + + + + + 登录 + + + + + +
+
+ + + + \ No newline at end of file diff --git a/mini-web/package.json b/mini-web/package.json index cc9df34..879df44 100644 --- a/mini-web/package.json +++ b/mini-web/package.json @@ -1,11 +1,11 @@ { "name": "mini-web", - "version": "0.0.4", + "version": "0.0.5", "description": "", "basename": "/root/mini-web", "main": "index.js", "scripts": { - "pub": "envision deploy ./envision -k mini-web -v 0.0.4 -y y", + "pub": "envision deploy ./envision -k mini-web -v 0.0.5 -u ", "ev": "npm run build && npm run deploy" }, "keywords": [],