fix: add wx-login check

This commit is contained in:
abearxiong 2025-04-09 01:23:08 +08:00
parent 197d6415d3
commit e5111227fd
9 changed files with 188 additions and 18 deletions

View File

@ -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": [

View File

@ -24,10 +24,10 @@ export type WxToken = {
* @param code
* @returns
*/
export const fetchToken = async (code: string): Promise<WxToken> => {
export const fetchToken = async (code: string, type: 'open' | 'mp' = 'open'): Promise<WxToken> => {
let appId = config.wxOpen.appId;
let appSecret = config.wxOpen.appSecret;
if (!appId && !appSecret) {
if (type === 'mp') {
appId = config.wx.appId;
appSecret = config.wx.appSecret;
}

View File

@ -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',

View File

@ -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 '';
}
}
}

3
mini-web/README.md Normal file
View File

@ -0,0 +1,3 @@
# 登陆逻辑
微信访问 `login.html` 的界面, 然后 `login.html` 去获取 `/authorize` 获取 openid

View File

@ -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',
};

View File

@ -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;
};

View File

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0, maximum-scale=1, user-scalable=no">
<title>登录</title>
<style>
* {
box-sizing: border-box;
}
</style>
<style>
.loading {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.8);
z-index: 9999;
display: flex;
justify-content: center;
align-items: center;
font-size: 1.5em;
/* Adjust font size for mobile */
}
@media (max-width: 600px) {
.loading {
font-size: 1em;
/* Smaller font size for smaller screens */
}
}
</style>
</head>
<body>
<div class="loading"></div>
<div class="openid"></div>
<script type="module">
import { initGetOpenidEvent, isWechat } from './utils.js';
const loading = document.querySelector('.loading');
loading.textContent = !isWechat() ? '微信打开当前页面获取openid' : '';
initGetOpenidEvent();
</script>
</body>
</html>

View File

@ -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": [],