This commit is contained in:
eoao
2025-04-26 17:24:39 +08:00
commit 24ba4772e6
107 changed files with 13612 additions and 0 deletions

View File

@@ -0,0 +1,127 @@
import BizError from '../error/biz-error';
import verifyUtils from '../utils/verify-utils';
import emailUtils from '../utils/email-utils';
import userService from './user-service';
import emailService from './email-service';
import orm from '../entity/orm';
import account from '../entity/account';
import { and, asc, eq, gt } from 'drizzle-orm';
import { isDel } from '../const/entity-const';
import settingService from './setting-service';
import turnstileService from './turnstile-service';
const accountService = {
async add(c, params, userId) {
if (!await settingService.isAddEmail(c)) {
throw new BizError('添加邮箱功能已关闭');
}
let { email, token } = params;
if (!email) {
throw new BizError('邮箱不能为空');
}
if (!verifyUtils.isEmail(email)) {
throw new BizError('非法邮箱');
}
if (!c.env.domain.includes(emailUtils.getDomain(email))) {
throw new BizError('未配置改邮箱域名');
}
const accountRow = await this.selectByEmailIncludeDel(c, email);
if (accountRow && accountRow.isDel === isDel.DELETE) {
throw new BizError('该邮箱已被注销');
}
if (accountRow) {
throw new BizError('该邮箱已被注册');
}
if (await settingService.isAddEmailVerify(c)) {
await turnstileService.verify(c, token);
}
return orm(c).insert(account).values({ email: email, userId: userId }).returning().get();
},
selectByEmailIncludeDel(c, email) {
return orm(c).select().from(account).where(eq(account.email, email)).get();
},
selectByEmail(c, email) {
return orm(c).select().from(account).where(
and(
eq(account.email, email),
eq(account.isDel, isDel.NORMAL)))
.get();
},
list(c, params, userId) {
let { accountId, size } = params;
accountId = Number(accountId);
size = Number(size);
if (size > 30) {
size = 30;
}
if (!accountId) {
accountId = 0;
}
return orm(c).select().from(account).where(
and(
eq(account.userId, userId),
eq(account.isDel, isDel.NORMAL),
gt(account.accountId, accountId)))
.orderBy(asc(account.accountId))
.limit(size)
.all();
},
async delete(c, params, userId) {
let { accountId } = params;
const user = await userService.selectById(c, userId);
const accountRow = await this.selectById(c, accountId);
if (accountRow.email === user.email) {
throw new BizError('不可以删除自己的邮箱');
}
if (accountRow.userId !== user.userId) {
throw new BizError('该邮箱不属于当前用户');
}
await orm(c).update(account).set({ isDel: isDel.DELETE }).where(
and(eq(account.userId, userId),
eq(account.accountId, accountId)))
.run();
await emailService.removeByAccountId(c, accountId);
},
selectById(c, accountId) {
return orm(c).select().from(account).where(
and(eq(account.accountId, accountId),
eq(account.isDel, isDel.NORMAL)))
.get();
},
async insert(c, params) {
await orm(c).insert(account).values({ ...params }).returning();
},
async removeByUserId(c, userId) {
await orm(c).update(account).set({ isDel: isDel.DELETE }).where(eq(account.userId, userId)).run();
}
};
export default accountService;

View File

@@ -0,0 +1,22 @@
import orm from '../entity/orm';
import { att } from '../entity/att';
import { and, eq } from 'drizzle-orm';
const attService = {
async addAtt(c, params) {
await orm(c).insert(att).values(params).run();
},
async list(c, params, userId) {
const { emailId } = params;
const list = await orm(c).select().from(att).where(
and(
eq(att.emailId, emailId),
eq(att.userId, userId)))
.all();
return list;
}
};
export default attService;

View File

@@ -0,0 +1,102 @@
import orm from '../entity/orm';
import email from '../entity/email';
import { isDel } from '../const/entity-const';
import { and, desc, eq, gt, inArray, lt, sql } from 'drizzle-orm';
import { star } from '../entity/star';
const emailService = {
async list(c, params, userId) {
let { emailId, accountId, size } = params;
size = Number(size);
emailId = Number(emailId);
if (size > 30) {
size = 30;
}
if (!emailId) {
emailId = 9999999999;
}
const list = await orm(c)
.select({
...email,
starId: star.starId
})
.from(email)
.leftJoin(
star,
and(
eq(star.emailId, email.emailId),
eq(star.userId, userId)
)
)
.where(
and(
lt(email.emailId, emailId),
eq(email.accountId, accountId),
eq(email.userId, userId),
eq(email.isDel, isDel.NORMAL)
)
)
.orderBy(desc(email.emailId))
.limit(size)
.all();
const resultList = list.map(item => ({
...item,
isStar: item.starId != null ? 1 : 0
}));
return { list: resultList };
},
async delete(c, params, userId) {
const { emailIds } = params;
const emailIdList = emailIds.split(',').map(Number);
await orm(c).update(email).set({ isDel: isDel.DELETE }).where(
and(
eq(email.userId, userId),
inArray(email.emailId, emailIdList)))
.run();
},
receive(c, params) {
return orm(c).insert(email).values({ ...params }).returning().get();
},
async removeByAccountId(c, accountId) {
await orm(c).update(email)
.set({ isDel: isDel.DELETE })
.where(eq(email.accountId, accountId))
.run();
},
async removeByUserId(c, userId) {
await orm(c).update(email).set({ isDel: isDel.DELETE }).where(eq(email.userId, userId)).run();
},
selectById(c, emailId) {
return orm(c).select().from(email).where(
and(eq(email.emailId, emailId),
eq(email.isDel, isDel.NORMAL)))
.get();
},
latest(c, params, userId) {
let { emailId,accountId } = params;
return orm(c).select().from(email).where(
and(
eq(email.userId, userId),
eq(email.isDel, isDel.NORMAL),
eq(email.accountId, accountId),
gt(email.emailId, emailId)
))
.orderBy(desc(email.emailId))
.limit(20);
}
};
export default emailService;

View File

@@ -0,0 +1,113 @@
import BizError from '../error/biz-error';
import userService from './user-service';
import emailUtils from '../utils/email-utils';
import { isDel, userConst } from '../const/entity-const';
import JwtUtils from '../utils/jwt-utils';
import { v4 as uuidv4 } from 'uuid';
import KvConst from '../const/kv-const';
import constant from '../const/constant';
import userContext from '../security/user-context';
import verifyUtils from '../utils/verify-utils';
import accountService from './account-service';
import settingService from './setting-service';
import saltHashUtils from '../utils/crypto-utils';
import cryptoUtils from '../utils/crypto-utils';
import turnstileService from './turnstile-service';
const loginService = {
async register(c, params) {
const { email, password, token } = params;
if (!await settingService.isRegister(c)) {
throw new BizError('注册功能已关闭');
}
if (!verifyUtils.isEmail(email)) {
throw new BizError('非法邮箱');
}
if (password.length < 6) {
throw new BizError('密码必须大于6位');
}
if (!c.env.domain.includes(emailUtils.getDomain(email))) {
throw new BizError('非法邮箱域名');
}
const userRow = await userService.selectByEmailIncludeDel(c, email);
if (userRow && userRow.isDel === isDel.DELETE) {
throw new BizError('该邮箱已被注销');
}
if (userRow) {
throw new BizError('该邮箱已被注册');
}
if (await settingService.isRegisterVerify(c)) {
await turnstileService.verify(c,token)
}
const { salt, hash } = await saltHashUtils.hashPassword(password);
const userId = await userService.insert(c, { email, password: hash, salt, type: userConst.type.COMMON });
await accountService.insert(c, { userId: userId, email });
},
async login(c, params) {
const { email, password } = params;
if (!email || !password) {
throw new BizError('邮箱和密码不能为空');
}
const userRow = await userService.selectByEmail(c, email);
if (!userRow) {
throw new BizError('该邮箱不存在');
}
if (!await cryptoUtils.verifyPassword(password, userRow.salt, userRow.password)) {
throw new BizError('密码输入错误');
}
const uuid = uuidv4();
const jwt = await JwtUtils.generateToken(c,{ userId: userRow.userId, token: uuid });
let authInfo = await c.env.kv.get(KvConst.AUTH_INFO + userRow.userId, { type: 'json' });
if (authInfo) {
authInfo.tokens.push(uuid);
} else {
authInfo = {
tokens: [],
user: userRow,
refreshTime: new Date().toISOString()
};
authInfo.tokens.push(uuid);
}
await c.env.kv.put(KvConst.AUTH_INFO + userRow.userId, JSON.stringify(authInfo), { expirationTtl: constant.TOKEN_EXPIRE });
return jwt;
},
async logout(c, userId) {
const token = await userContext.getToken(c);
const authInfo = await c.env.kv.get(KvConst.AUTH_INFO + userId, { type: 'json' });
const index = authInfo.tokens.findIndex(item => item === token);
authInfo.tokens.splice(index, 1);
await c.env.kv.put(KvConst.AUTH_INFO + userId, JSON.stringify(authInfo));
}
};
export default loginService;

View File

@@ -0,0 +1,11 @@
const r2Service = {
async putObj(c, key, content, metadata) {
const body = typeof content === 'string'
? new TextEncoder().encode(content)
: content;
await c.env.r2.put(key, body, {
httpMetadata: {...metadata}
});
}
};
export default r2Service;

View File

@@ -0,0 +1,54 @@
import KvConst from '../const/kv-const';
import setting from '../entity/setting';
import orm from '../entity/orm';
import { settingConst } from '../const/entity-const';
const settingService = {
async refresh(c) {
const settingRow = await orm(c).select().from(setting).get();
await c.env.kv.put(KvConst.SETTING, JSON.stringify(settingRow));
},
async query(c) {
const setting = await c.env.kv.get(KvConst.SETTING, { type: 'json' });
let domainList = c.env.domain;
domainList = domainList.map(item => '@' + item);
setting.domainList = domainList;
setting.siteKey = c.env.site_key;
setting.r2Domain = c.env.r2_domain;
return setting;
},
async set(c, params) {
await orm(c).update(setting).set({ ...params }).returning().get();
await this.refresh(c);
},
async isRegister(c) {
const { register } = await this.query(c);
return register === settingConst.register.OPEN;
},
async isReceive(c) {
const { receive } = await this.query(c);
return receive === settingConst.receive.OPEN;
},
async isAddEmail(c) {
const { addEmail, manyEmail } = await this.query(c);
return addEmail === settingConst.addEmail.OPEN && manyEmail === settingConst.manyEmail.OPEN;
},
async isRegisterVerify(c) {
const { registerVerify } = await this.query(c);
return registerVerify === settingConst.registerVerify.OPEN;
},
async isAddEmailVerify(c) {
const { addEmailVerify } = await this.query(c);
return addEmailVerify === settingConst.addEmailVerify.OPEN;
}
};
export default settingService;

View File

@@ -0,0 +1,69 @@
import orm from '../entity/orm';
import { star } from '../entity/star';
import emailService from './email-service';
import BizError from '../error/biz-error';
import { and, desc, eq, lt, sql } from 'drizzle-orm';
import email from '../entity/email';
import { isDel } from '../const/entity-const';
const startService = {
async add(c, params, userId) {
const { emailId } = params;
const email = await emailService.selectById(c, emailId);
if (!email) {
throw new BizError('星标的邮件不存在');
}
if (!email.userId === userId) {
throw new BizError('星标的邮件非当前用户所有');
}
const exist = await orm(c).select().from(star).where(
and(
eq(star.userId, userId),
eq(star.emailId, emailId)))
.get()
if (exist) {
return
}
await orm(c).insert(star).values({ userId, emailId }).run();
},
async cancel(c, params, userId) {
const { emailId } = params;
await orm(c).delete(star).where(
and(
eq(star.userId, userId),
eq(star.emailId, emailId)))
.run();
},
async list(c, params, userId) {
let { emailId, size } = params;
emailId = Number(emailId);
size = Number(size);
if (!emailId) {
emailId = 9999999999;
}
const list = await orm(c).select({
isStar: sql`1`.as('isStar'),
starId: star.starId
, ...email
}).from(star)
.leftJoin(email, eq(email.emailId, star.emailId))
.where(
and(
eq(star.userId, userId),
eq(email.isDel, isDel.NORMAL),
lt(star.emailId, emailId)))
.orderBy(desc(star.emailId))
.limit(size)
.all();
return { list };
}
};
export default startService;

View File

@@ -0,0 +1,30 @@
import BizError from '../error/biz-error';
const turnstileService = {
async verify(c, token) {
if (!token) {
throw new BizError('验证token不能为空');
}
const res = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
secret: c.env.secret_key,
response: token,
remoteip: c.req.header('cf-connecting-ip')
})
});
const result = await res.json();
console.log(result)
if (!result.success) {
throw new BizError('人机验证失败,请重试',400)
}
}
};
export default turnstileService;

View File

@@ -0,0 +1,67 @@
import BizError from '../error/biz-error';
import accountService from './account-service';
import orm from '../entity/orm';
import user from '../entity/user';
import { eq, and } from 'drizzle-orm';
import { isDel } from '../const/entity-const';
import kvConst from '../const/kv-const';
import cryptoUtils from '../utils/crypto-utils';
const userService = {
async loginUserInfo(c, userId) {
let user = await userService.selectById(c, userId);
let account = await accountService.selectByEmailIncludeDel(c, user.email);
delete user.password;
delete user.salt;
user.accountId = account.accountId;
user.type = c.env.admin === user.email ? 0 : 1;
return user;
},
async resetPassword(c, params, userId) {
const { password } = params;
if (password < 6) {
throw new BizError('密码不能小于6位');
}
const { salt, hash } = await cryptoUtils.hashPassword(password);
await orm(c).update(user).set({ password: hash, salt: salt }).where(eq(user.userId, userId)).run();
},
selectByEmail(c, email) {
return orm(c).select().from(user).where(
and(
eq(user.email, email),
eq(user.isDel, isDel.NORMAL)))
.get();
},
async insert(c, params) {
const { userId } = await orm(c).insert(user).values({ ...params }).returning().get();
return userId;
},
selectByEmailIncludeDel(c, email) {
return orm(c).select().from(user).where(eq(user.email, email)).get();
},
selectById(c, userId) {
return orm(c).select().from(user).where(
and(
eq(user.userId, userId),
eq(user.isDel, isDel.NORMAL)))
.get();
},
async delete(c, userId) {
await orm(c).update(user).set({ isDel: isDel.DELETE }).where(eq(user.userId, userId)).run();
await Promise.all([
c.env.kv.delete(kvConst.AUTH_INFO + userId),
accountService.removeByUserId(c, userId)
]);
}
};
export default userService;