Skip to content

Commit fd64f67

Browse files
committed
✨ feat: 支持绑定账号
1 parent 7e5778c commit fd64f67

File tree

7 files changed

+155
-2
lines changed

7 files changed

+155
-2
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Entity, Column } from "typeorm";
2+
3+
@Entity({ comment: '第三方账号绑定表', name: 'user-bind' })
4+
export class UserBind {
5+
@Column({ comment: "用户ID", primary: true, unique: true })
6+
id: string;
7+
8+
@Column({ comment: "用户名" })
9+
username: string = '';
10+
11+
@Column({ comment: "绑定来源" })
12+
from: string = '';
13+
14+
@Column({ comment: "第三方账号ID" })
15+
thirdPartyId: string = '';
16+
17+
@Column({ comment: "第三方账号昵称" })
18+
thirdPartyNickname: string = '';
19+
20+
@Column({ comment: "第三方用户名", default: '' })
21+
thirdPartyUsername: string = '';
22+
}

game/backend/src/entities/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Record } from "./Record";
88
import { PlayerStats } from "./PlayerStats";
99
import utils from '@/utils'
1010
import { Manage } from './Manage';
11+
import { UserBind } from './UserBind';
1112

1213
@EventSubscriber()
1314
export class EntitySubscriber implements EntitySubscriberInterface {
@@ -64,6 +65,7 @@ export { initDataSource };
6465

6566
export {
6667
User,
68+
UserBind,
6769
Room,
6870
Manage,
6971
RoomSQL,
@@ -77,6 +79,7 @@ export * from './mongo';
7779
export * from './redis';
7880

7981
export const UserRepo = () => utils.config ? AppDataSource.getRepository(User) : {} as Repository<User>;
82+
export const UserBindRepo = () => utils.config ? AppDataSource.getRepository(UserBind) : {} as Repository<UserBind>;
8083
export const LogRepo = () => utils.config ? AppDataSource.getRepository(Log) : {} as Repository<Log>;
8184
export const RecordRepo = () => utils.config ? AppDataSource.getRepository(Record) : {} as Repository<Record>;
8285
export const ManageRepo = () => utils.config ? AppDataSource.getRepository(Manage) : {} as Repository<Manage>;

game/backend/src/login/github.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,56 @@
11
import { Request, Response } from "express";
22
import utils from '../utils';
33
import { saveUser } from './index';
4+
import { UserBindRepo, UserRepo } from "@/entities";
45
const clientId = utils.config?.login.githubClientId || '';
56

7+
export async function bind(req: Request, res: Response) {
8+
if (!req.session.player) {
9+
req.session.error = "请先登录后再进行绑定操作";
10+
return res.redirect("/#/login");
11+
}
12+
const domain = new URL(req.headers.referer || `${req.protocol}://${req.headers.host}`).host;
13+
if (!clientId) return res.end('GitHub OAuth 未配置,请联系管理员');
14+
if (req.query['code']) {
15+
const accessToken = await verify(req);
16+
if (accessToken) {
17+
const userInfo = await getUserInfo(accessToken);
18+
// 绑定逻辑
19+
const user = req.session.player;
20+
if (user) {
21+
const bindUser = UserBindRepo().create({
22+
id: user.id,
23+
username: user.username,
24+
from: 'github',
25+
thirdPartyId: userInfo.id,
26+
thirdPartyNickname: userInfo.name || userInfo.login,
27+
thirdPartyUsername: userInfo.login,
28+
})
29+
await UserBindRepo().save(bindUser);
30+
}
31+
}
32+
req.session.isBinding = false;
33+
return res.redirect("/u/" + (req.session.player ? req.session.player.username : ''));
34+
}
35+
req.session.isBinding = true;
36+
res.redirect(`https://github.com/login/oauth/authorize?client_id=${clientId}&redirect_uri=https%3A%2F%2F${domain}%2Fapi%2Flogin%2Fgithub&scope=user:email`);
37+
}
38+
639
export async function login(req: Request, res: Response) {
740
const domain = new URL(req.headers.referer || `${req.protocol}://${req.headers.host}`).host;
841
if (!clientId) return res.end('GitHub OAuth 未配置,请联系管理员');
942
if (req.query['code']) {
1043
const accessToken = await verify(req);
1144
if (accessToken) {
1245
const userInfo = await getUserInfo(accessToken);
46+
const bind = await UserBindRepo().findOneBy({ from: 'github', thirdPartyId: String(userInfo.id) });
47+
if (bind) {
48+
const user = await UserRepo().findOneBy({ id: bind.id });
49+
if (user) {
50+
req.session.player = user;
51+
return res.redirect("/");
52+
}
53+
}
1354
req.session.player = await saveUser({
1455
name: 'github-' + userInfo.login,
1556
nickname: userInfo.name || userInfo.login,

game/backend/src/login/steam.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
import { Request, Response } from "express";
22
import { saveUser } from './index';
33
import utils from "../utils";
4+
import { UserBindRepo, UserRepo } from "@/entities";
45

56
export async function login(req: Request, res: Response) {
67
const domain = new URL(req.headers.referer || `${req.protocol}://${req.headers.host}`).host;
78
if (req.query['openid.mode'] === 'id_res') {
89
const userId = await verify(req);
910
if (userId) {
11+
const bind = await UserBindRepo().findOneBy({ from: 'steam', thirdPartyId: userId });
12+
if (bind) {
13+
const user = await UserRepo().findOneBy({ id: bind.id });
14+
if (user) {
15+
req.session.player = user;
16+
return res.redirect("/");
17+
}
18+
}
1019
const [ userInfo ] = await getUserInfo(userId);
1120
req.session.player = await saveUser({
1221
name: 'steam-' + userInfo.profileurl.trim().split('/').slice(0, -1).pop() || userId,
@@ -52,4 +61,36 @@ function getUserInfo(steamid: string) {
5261
.then(data => {
5362
return data.response.players;
5463
});
64+
}
65+
66+
export async function bind(req: Request, res: Response) {
67+
if (!req.session.player) {
68+
req.session.error = "请先登录后再进行绑定操作";
69+
return res.redirect("/#/login");
70+
}
71+
const domain = new URL(req.headers.referer || `${req.protocol}://${req.headers.host}`).host;
72+
if (!utils.config?.login.steamApiKey) return res.end('Steam API 未配置,请联系管理员');
73+
if (req.query['openid.mode'] === 'id_res') {
74+
const userId = await verify(req);
75+
if (userId) {
76+
const [ userInfo ] = await getUserInfo(userId);
77+
// 绑定逻辑
78+
const user = req.session.player;
79+
if (user) {
80+
const bindUser = UserBindRepo().create({
81+
id: user.id,
82+
username: user.username,
83+
from: 'steam',
84+
thirdPartyId: userId,
85+
thirdPartyNickname: userInfo.personaname,
86+
thirdPartyUsername: userInfo.profileurl.trim().split('/').slice(0, -1).pop() || userId,
87+
})
88+
await UserBindRepo().save(bindUser);
89+
}
90+
}
91+
req.session.isBinding = false;
92+
return res.redirect("/u/" + (req.session.player ? req.session.player.username : ''));
93+
}
94+
req.session.isBinding = true;
95+
res.redirect(`https://steamcommunity.com/openid/login?openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.mode=checkid_setup&openid.return_to=https%3A%2F%2F${domain}%2Fapi%2Fbind%2Fsteam&openid.realm=https%3A%2F%2F${domain}&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select`);
5596
}

game/backend/src/login/wechat.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Request, Response } from "express";
22
import utils from '../utils';
33
import { saveUser } from './index';
4+
import { UserBindRepo, UserRepo } from "@/entities";
45

56
const appId = utils.config?.login.wechatAppId || '';
67

@@ -11,6 +12,14 @@ export async function login(req: Request, res: Response) {
1112
const tokenData = await verify(req);
1213
if (tokenData.access_token) {
1314
const userInfo = await getUserInfo(tokenData.access_token, tokenData.openid);
15+
const bind = await UserBindRepo().findOneBy({ from: 'wechat', thirdPartyId: userInfo.openid });
16+
if (bind) {
17+
const user = await UserRepo().findOneBy({ id: bind.id });
18+
if (user) {
19+
req.session.player = user;
20+
return res.redirect("/");
21+
}
22+
}
1423
req.session.player = await saveUser({
1524
name: 'wechat-' + userInfo.openid,
1625
nickname: userInfo.nickname,
@@ -49,4 +58,36 @@ async function getUserInfo(access_token: string, openid: string) {
4958
});
5059
const data = await response.json();
5160
return data;
61+
}
62+
63+
export async function bind(req: Request, res: Response) {
64+
if (!req.session.player) {
65+
req.session.error = "请先登录后再进行绑定操作";
66+
return res.redirect("/#/login");
67+
}
68+
const domain = new URL(req.headers.referer || `${req.protocol}://${req.headers.host}`).host;
69+
if (!appId) return res.end('微信OAuth 未配置,请联系管理员');
70+
if (req.query['code']) {
71+
const tokenData = await verify(req);
72+
if (tokenData.access_token) {
73+
const userInfo = await getUserInfo(tokenData.access_token, tokenData.openid);
74+
// 绑定逻辑
75+
const user = req.session.player;
76+
if (user) {
77+
const bindUser = UserBindRepo().create({
78+
id: user.id,
79+
username: user.username,
80+
from: 'wechat',
81+
thirdPartyId: userInfo.openid,
82+
thirdPartyNickname: userInfo.nickname,
83+
thirdPartyUsername: userInfo.openid,
84+
})
85+
await UserBindRepo().save(bindUser);
86+
}
87+
}
88+
req.session.isBinding = false;
89+
return res.redirect("/u/" + (req.session.player ? req.session.player.username : ''));
90+
}
91+
req.session.isBinding = true;
92+
res.redirect(`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=https%3A%2F%2F${domain}%2Fapi%2Fbind%2Fwechat&response_type=code&scope=snsapi_login&state=STATE#wechat_redirect`);
5293
}

game/backend/src/routes/api.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { Router, Request, Response } from "express";
22
import { Controller } from "../controller";
33
import { login as fishpiLogin, register as fishpiRegister, updateUserInfo } from "../login/fishpi";
44
import { login as steamLogin } from "../login/steam";
5-
import { login as githubLogin } from "../login/github";
6-
import { login as wechatLogin } from "../login/wechat";
5+
import { bind as steamBind } from "../login/steam";
6+
import { login as githubLogin, bind as githubBind } from "../login/github";
7+
import { login as wechatLogin, bind as wechatBind } from "../login/wechat";
78
import { Record, RecordRepo, User, UserRepo, AppDataSource, PlayerStats, ManageRepo } from "@/entities";
89
import { getPlayerStats, isConfigured } from "@/utils";
910
import { FindOptionsWhere, Like } from "typeorm";
@@ -169,6 +170,9 @@ const createRoutes = (game: GameContext, gameName: string) => {
169170
router.get("/login/steam", steamLogin);
170171
router.get("/login/github", githubLogin);
171172
router.get("/login/wechat", wechatLogin);
173+
router.get("/bind/github", githubBind);
174+
router.get("/bind/steam", steamBind);
175+
router.get("/bind/wechat", wechatBind);
172176

173177
router.post("/logout", (req: Request, res: Response) => {
174178
req.session.destroy((err) => {

game/backend/types/express-session/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ declare module 'express-session' {
55
interface SessionData {
66
error: string;
77
player: User;
8+
isBinding?: boolean;
89
}
910
}

0 commit comments

Comments
 (0)