diff --git a/packages/ui/certd-client/src/plugin/fast-crud/index.tsx b/packages/ui/certd-client/src/plugin/fast-crud/index.tsx index 0ffd43247..e285391f3 100644 --- a/packages/ui/certd-client/src/plugin/fast-crud/index.tsx +++ b/packages/ui/certd-client/src/plugin/fast-crud/index.tsx @@ -107,6 +107,9 @@ function install(app: App, options: any = {}) { scroll: { x: 960, }, + rowSelection: { + fixed: "left", + }, size: "small", pagination: false, onResizeColumn: (w: number, col: any) => { diff --git a/packages/ui/certd-client/src/router/source/modules/certd.ts b/packages/ui/certd-client/src/router/source/modules/certd.ts index 41f700f2e..9fe104818 100644 --- a/packages/ui/certd-client/src/router/source/modules/certd.ts +++ b/packages/ui/certd-client/src/router/source/modules/certd.ts @@ -389,7 +389,7 @@ export const certdResources = [ meta: { show: () => { const settingStore = useSettingStore(); - return settingStore.isComm; + return settingStore.isInviteCommissionEnabled; }, icon: "ion:gift-outline", auth: true, diff --git a/packages/ui/certd-client/src/store/user/index.ts b/packages/ui/certd-client/src/store/user/index.ts index 0c8a37d8e..dfe3ecf41 100644 --- a/packages/ui/certd-client/src/store/user/index.ts +++ b/packages/ui/certd-client/src/store/user/index.ts @@ -136,12 +136,12 @@ export const useUserStore = defineStore({ } catch (e) { console.error("注销登录请求失败:", e); } + // 第三方登录注销 + this.oauthLogout(); } this.resetState(); resetAllStores(); - // 第三方登录注销 - await this.oauthLogout(); goLogin && router.push("/login"); mitter.emit("app.logout"); }, diff --git a/packages/ui/certd-client/src/views/certd/invite/index.vue b/packages/ui/certd-client/src/views/certd/invite/index.vue index b9967e07c..e1ccb8fd1 100644 --- a/packages/ui/certd-client/src/views/certd/invite/index.vue +++ b/packages/ui/certd-client/src/views/certd/invite/index.vue @@ -300,7 +300,7 @@ async function handleTabChange() { } async function refreshInvitePage(autoOpenAgreement = true) { - await settingStore.initOnce(); + await settingStore.init(); enabled.value = settingStore.isInviteCommissionEnabled; loaded.value = true; if (!enabled.value) { diff --git a/packages/ui/certd-client/src/views/framework/oauth/api.ts b/packages/ui/certd-client/src/views/framework/oauth/api.ts index 2149b767a..4bf0c4af4 100644 --- a/packages/ui/certd-client/src/views/framework/oauth/api.ts +++ b/packages/ui/certd-client/src/views/framework/oauth/api.ts @@ -26,13 +26,14 @@ export async function OauthToken(type: string, validationCode: string) { }); } -export async function AutoRegister(type: string, code: string) { +export async function AutoRegister(type: string, code: string, inviteCode?: string) { return await request({ url: apiPrefix + `/autoRegister`, method: "post", data: { validationCode: code, type, + inviteCode, }, }); } diff --git a/packages/ui/certd-client/src/views/framework/oauth/oauth-callback.vue b/packages/ui/certd-client/src/views/framework/oauth/oauth-callback.vue index 9b05a6c8e..3500b85a9 100644 --- a/packages/ui/certd-client/src/views/framework/oauth/oauth-callback.vue +++ b/packages/ui/certd-client/src/views/framework/oauth/oauth-callback.vue @@ -29,6 +29,7 @@ import { useRoute, useRouter } from "vue-router"; import { useUserStore } from "/@/store/user"; import { notification } from "ant-design-vue"; import { useSettingStore } from "/@/store/settings"; +import { inviteUtils } from "/@/utils/util.invite"; const route = useRoute(); const router = useRouter(); @@ -99,7 +100,7 @@ async function goBindUser() { async function autoRegister() { //自动注册账号 - const res = await api.AutoRegister(oauthType, bindCode.value); + const res = await api.AutoRegister(oauthType, bindCode.value, inviteUtils.get()); //登录成功 userStore.onLoginSuccess(res); //跳转到首页 diff --git a/packages/ui/certd-client/src/views/framework/register/index.vue b/packages/ui/certd-client/src/views/framework/register/index.vue index a6ff2e04b..bb6190ea1 100644 --- a/packages/ui/certd-client/src/views/framework/register/index.vue +++ b/packages/ui/certd-client/src/views/framework/register/index.vue @@ -215,6 +215,8 @@ export default defineComponent({ const handleFinish = async (values: any) => { try { + //先注销登录 + userStore.resetState(); await userStore.register( toRaw({ type: registerType.value, diff --git a/packages/ui/certd-server/src/controller/basic/login/oauth-controller.ts b/packages/ui/certd-server/src/controller/basic/login/oauth-controller.ts index 0882fedec..8528e0ec6 100644 --- a/packages/ui/certd-server/src/controller/basic/login/oauth-controller.ts +++ b/packages/ui/certd-server/src/controller/basic/login/oauth-controller.ts @@ -8,7 +8,6 @@ import { LoginService } from "../../../modules/login/service/login-service.js"; import { OauthBoundService } from "../../../modules/login/service/oauth-bound-service.js"; import { AddonGetterService } from "../../../modules/pipeline/service/addon-getter-service.js"; import { UserEntity } from "../../../modules/sys/authority/entity/user.js"; -import { UserService } from "../../../modules/sys/authority/service/user-service.js"; import { IOauthProvider } from "../../../plugins/plugin-oauth/api.js"; type OauthProviderSetting = { @@ -42,8 +41,6 @@ export class ConnectController extends BaseController { loginService: LoginService; @Inject() codeService: CodeService; - @Inject() - userService: UserService; @Inject() oauthBoundService: OauthBoundService; @@ -199,7 +196,7 @@ export class ConnectController extends BaseController { } @Post("/autoRegister", { description: Constants.per.guest }) - public async autoRegister(@Body(ALL) body: { validationCode: string; type: string }) { + public async autoRegister(@Body(ALL) body: { validationCode: string; type: string; inviteCode?: string }) { const validationValue = this.codeService.getValidationValue(body.validationCode); if (!validationValue) { throw new Error("第三方认证授权已过期"); @@ -212,7 +209,7 @@ export class ConnectController extends BaseController { newUser.nickName = userInfo.nickName || simpleNanoId(6); newUser.email = userInfo.email || ""; - newUser = await this.userService.register("username", newUser, async txManager => { + newUser = await this.loginService.register("username", newUser, body.inviteCode, async txManager => { const oauthBound: OauthBoundEntity = new OauthBoundEntity(); oauthBound.userId = newUser.id; oauthBound.type = oauthType; diff --git a/packages/ui/certd-server/src/controller/basic/login/register-controller.ts b/packages/ui/certd-server/src/controller/basic/login/register-controller.ts index 586f9c308..30faa3113 100644 --- a/packages/ui/certd-server/src/controller/basic/login/register-controller.ts +++ b/packages/ui/certd-server/src/controller/basic/login/register-controller.ts @@ -1,9 +1,9 @@ import { ALL, Body, Controller, Inject, Post, Provide, RequestIP } from "@midwayjs/core"; import { BaseController, Constants, SysSettingsService } from "@certd/lib-server"; -import { RegisterType, UserService } from "../../../modules/sys/authority/service/user-service.js"; +import { RegisterType } from "../../../modules/sys/authority/service/user-service.js"; import { CodeService } from "../../../modules/basic/service/code-service.js"; import { checkComm, checkPlus } from "@certd/plus-core"; -import { InviteService } from "@certd/commercial-core"; +import { LoginService } from "../../../modules/login/service/login-service.js"; export type RegisterReq = { type: RegisterType; @@ -24,16 +24,13 @@ export type RegisterReq = { @Controller("/api/") export class RegisterController extends BaseController { @Inject() - userService: UserService; + loginService: LoginService; @Inject() codeService: CodeService; @Inject() sysSettingsService: SysSettingsService; - @Inject() - inviteService: InviteService; - @Post("/register", { description: Constants.per.guest }) public async register( @Body(ALL) @@ -62,9 +59,7 @@ export class RegisterController extends BaseController { username: body.username, password: body.password, } as any; - const newUser = await this.userService.register(body.type, registerUser, async txManager => { - await this.inviteService.bindInvitee({ manager: txManager }, { inviteeUserId: registerUser.id, inviteCode: body.inviteCode }); - }); + const newUser = await this.loginService.register(body.type, registerUser, body.inviteCode); return this.ok(newUser); } else if (body.type === "mobile") { if (sysPublicSettings.mobileRegisterEnabled === false) { @@ -84,9 +79,7 @@ export class RegisterController extends BaseController { mobile: body.mobile, password: body.password, } as any; - const newUser = await this.userService.register(body.type, registerUser, async txManager => { - await this.inviteService.bindInvitee({ manager: txManager }, { inviteeUserId: registerUser.id, inviteCode: body.inviteCode }); - }); + const newUser = await this.loginService.register(body.type, registerUser, body.inviteCode); return this.ok(newUser); } else if (body.type === "email") { if (sysPublicSettings.emailRegisterEnabled === false) { @@ -103,9 +96,7 @@ export class RegisterController extends BaseController { email: body.email, password: body.password, } as any; - const newUser = await this.userService.register(body.type, registerUser, async txManager => { - await this.inviteService.bindInvitee({ manager: txManager }, { inviteeUserId: registerUser.id, inviteCode: body.inviteCode }); - }); + const newUser = await this.loginService.register(body.type, registerUser, body.inviteCode); return this.ok(newUser); } } diff --git a/packages/ui/certd-server/src/modules/login/service/login-service.test.ts b/packages/ui/certd-server/src/modules/login/service/login-service.test.ts new file mode 100644 index 000000000..5142353fd --- /dev/null +++ b/packages/ui/certd-server/src/modules/login/service/login-service.test.ts @@ -0,0 +1,78 @@ +import assert from "node:assert/strict"; +import { LoginService } from "./login-service.js"; + +function createLoginService() { + const service = new LoginService(); + const calls: any = { + register: [], + bindInvitee: [], + withTx: [], + }; + const txManager = { tx: true }; + const newUser = { id: 1001, username: "new_user" }; + service.userService = { + async register(type: string, user: any, withTx?: any) { + calls.register.push({ type, user, withTx }); + user.id = newUser.id; + if (withTx) { + await withTx(txManager); + } + return newUser; + }, + } as any; + service.inviteService = { + async bindInvitee(ctx: any, req: any) { + calls.bindInvitee.push({ ctx, req }); + return { id: 1 }; + }, + } as any; + return { service, calls, newUser, txManager }; +} + +describe("LoginService.register", () => { + it("registers user and binds invite code after user creation", async () => { + const { service, calls, newUser } = createLoginService(); + const registerUser = { username: "alice" } as any; + + const result = await service.register("username", registerUser, "ABC123"); + + assert.equal(result, newUser); + assert.equal(calls.register.length, 1); + assert.equal(calls.register[0].type, "username"); + assert.equal(calls.register[0].user, registerUser); + assert.equal(calls.bindInvitee.length, 1); + assert.deepEqual(calls.bindInvitee[0].ctx, {}); + assert.deepEqual(calls.bindInvitee[0].req, { + inviteeUserId: newUser.id, + inviteCode: "ABC123", + }); + }); + + it("keeps registration successful when invite binding fails", async () => { + const { service, calls, newUser } = createLoginService(); + service.inviteService = { + async bindInvitee(ctx: any, req: any) { + calls.bindInvitee.push({ ctx, req }); + throw new Error("invalid invite code"); + }, + } as any; + + const result = await service.register("username", { username: "alice" } as any, "BADCODE"); + + assert.equal(result, newUser); + assert.equal(calls.register.length, 1); + assert.equal(calls.bindInvitee.length, 1); + }); + + it("keeps original registration transaction callback", async () => { + const { service, calls, txManager } = createLoginService(); + + await service.register("username", { username: "alice" } as any, "", async tx => { + calls.withTx.push(tx); + }); + + assert.equal(calls.withTx.length, 1); + assert.equal(calls.withTx[0], txManager); + assert.equal(calls.bindInvitee.length, 0); + }); +}); diff --git a/packages/ui/certd-server/src/modules/login/service/login-service.ts b/packages/ui/certd-server/src/modules/login/service/login-service.ts index d44b1eba7..542a29c50 100644 --- a/packages/ui/certd-server/src/modules/login/service/login-service.ts +++ b/packages/ui/certd-server/src/modules/login/service/login-service.ts @@ -4,7 +4,7 @@ import jwt from "jsonwebtoken"; import { AuthException, CommonException, Need2FAException, SysPrivateSettings, SysSettingsService } from "@certd/lib-server"; import { RoleService } from "../../sys/authority/service/role-service.js"; import { UserEntity } from "../../sys/authority/entity/user.js"; -import { cache, utils } from "@certd/basic"; +import { cache, logger, utils } from "@certd/basic"; import { LoginErrorException } from "@certd/lib-server"; import { CodeService } from "../../basic/service/code-service.js"; import { TwoFactorService } from "../../mine/service/two-factor-service.js"; @@ -14,6 +14,7 @@ import { AddonService } from "@certd/lib-server"; import { OauthBoundService } from "./oauth-bound-service.js"; import { PasskeyService } from "./passkey-service.js"; import { InviteService } from "@certd/commercial-core"; +import { EntityManager } from "typeorm"; /** */ @@ -108,6 +109,19 @@ export class LoginService { throw new LoginErrorException(errorMessage, leftTimes); } + async register(type: string, user: UserEntity, inviteCode?: string, withTx?: (tx: EntityManager) => Promise) { + const newUser = await this.userService.register(type, user, withTx); + if (!inviteCode) { + return newUser; + } + try { + await this.inviteService.bindInvitee({}, { inviteeUserId: newUser.id, inviteCode }); + } catch (e) { + logger.error("绑定邀请关系失败,不影响用户注册", e); + } + return newUser; + } + async loginBySmsCode(req: { mobile: string; phoneCode: string; smsCode: string; randomStr: string; inviteCode?: string }) { this.checkIsBlocked(req.mobile); @@ -130,9 +144,7 @@ export class LoginService { mobile, password: "", } as any; - info = await this.userService.register("mobile", registerUser, async txManager => { - await this.inviteService.bindInvitee({ manager: txManager }, { inviteeUserId: registerUser.id, inviteCode: req.inviteCode }); - }); + info = await this.register("mobile", registerUser, req.inviteCode); } this.clearCacheOnSuccess(mobile); return this.onLoginSuccess(info);