All files / modules/auth auth.service.ts

0% Statements 0/81
0% Branches 0/1
0% Functions 0/1
0% Lines 0/81

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115                                                                                                                                                                                                                                     
import jwt from 'jsonwebtoken';
import { v4 as uuidv4 } from 'uuid';
import { config } from '../../config';
import { prisma } from '../../models';
import { JwtPayload } from '../../types';
 
// 验证码存储(生产环境应使用 Redis)
const smsCodes = new Map<string, { code: string; expireAt: number }>();
 
// 生成验证码
export function generateSmsCode(phone: string): string {
  const code = Math.random().toString().slice(2, 6);
  smsCodes.set(phone, {
    code,
    expireAt: Date.now() + 5 * 60 * 1000, // 5分钟有效
  });
  console.log(`📱 验证码已生成: ${phone} -> ${code}`);
  return code;
}
 
// 验证验证码
export function verifySmsCode(phone: string, code: string): boolean {
  const stored = smsCodes.get(phone);
  if (!stored) return false;
  if (Date.now() > stored.expireAt) {
    smsCodes.delete(phone);
    return false;
  }
  if (stored.code !== code) return false;
  smsCodes.delete(phone);
  return true;
}
 
// 生成 JWT Token
export function generateToken(userId: string, phone?: string): string {
  const secret = config.jwt.secret;
  const payload: Omit<JwtPayload, 'iat' | 'exp'> = { userId, phone };
  return jwt.sign(payload, secret, {
    expiresIn: '7d',
  });
}
 
// 手机号登录/注册
export async function loginWithPhone(phone: string, code?: string): Promise<{
  token: string;
  user: {
    id: string;
    phone: string;
    nickname: string;
    avatar: string;
    memberLevel: number;
    isNewUser: boolean;
  };
}> {
  // 免密登录:code 为空或为"123456"时直接登录(线上线下都关闭验证)
  const skipVerify = !code || code === '123456';
  
  if (!skipVerify) {
    // 验证验证码
    const isValid = verifySmsCode(phone, code || '');
    if (!isValid) {
      throw new Error('验证码错误或已过期');
    }
  }
 
  // 查找或创建用户
  let user = await prisma.user.findFirst({ where: { phone } });
  let isNewUser = false;
 
  if (!user) {
    user = await prisma.user.create({
      data: {
        phone,
        nickname: `用户${phone.slice(-4)}`,
        avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=${phone}`,
        memberLevel: 0,
        dailyUsage: 0,
        lastUsageDate: '',
      },
    });
    isNewUser = true;
  }
 
  const token = generateToken(user.id.toString(), phone);
 
  return {
    token,
    user: {
      id: user.id.toString(),
      phone: user.phone!,
      nickname: user.nickname,
      avatar: user.avatar,
      memberLevel: user.memberLevel,
      isNewUser,
    },
  };
}
 
// 获取用户信息
export async function getUserInfo(userId: string) {
  const user = await prisma.user.findUnique({ where: { id: parseInt(userId) } });
  if (!user) {
    throw new Error('用户不存在');
  }
 
  return {
    id: user.id.toString(),
    phone: user.phone,
    nickname: user.nickname,
    avatar: user.avatar,
    memberLevel: user.memberLevel,
    memberExpireAt: user.memberExpireAt,
    dailyUsage: user.dailyUsage,
  };
}