随机取游戏角色名称

75 阅读5分钟

前言

同学们好久不见了,最近业余时间我完成了一个微信小程序的开发,今天想和大家分享一下整个项目的技术栈和开发心得。

项目技术架构

后端服务

  • 框架: NestJS (一个渐进式Node.js框架,完美支持TypeScript)
  • 数据库: MySQL (关系型数据库存储核心数据)
  • 缓存: Redis (用于高频访问数据缓存和会话管理)
  • ORM: TypeORM (强大的TypeScript ORM工具)

后台管理系统

  • 前端框架: Vue.js (选择了熟悉的Vue作为管理后台基础)
  • UI库: Ant Design Vue (企业级UI组件库,开发效率高)
  • 状态管理: Pinia (全局状态管理方案)

小程序端

  • 开发方式: 原生微信小程序开发
  • API: 直接使用微信官方API文档
  • 备选方案: 考虑过使用Uni-app (最终选择了原生开发)

为什么选择这些技术?

后端选择NestJS的原因

  1. 模块化架构清晰,适合个人/团队协作
  2. TypeScript支持完善,类型检查减少运行时错误
  3. 依赖注入机制让代码更易维护
  4. 丰富的生态系统和装饰器语法

数据库选择

  • MySQL作为主数据库存储核心业务数据

  • Redis用于:

    • 会话管理(session storage)
    • 高频访问数据缓存
    • 分布式锁实现

为什么没用Uni-app?

虽然Uni-app可以跨平台,但考虑到:

  1. 当前项目只需要微信小程序端 2 希望更直接使用微信原生API
  2. 避免跨平台框架可能带来的性能损耗

开发中的经验分享

后端开发Tips

  1. 使用TypeORM的Repository模式让数据操作更清晰
  2. NestJS的Interceptor非常适合统一处理响应格式
  3. 合理使用装饰器进行参数验证和权限控制

小程序开发经验

  1. 善用微信开发者工具的云开发能力
  2. 小程序分包加载显著提升首屏速度
  3. 自定义组件开发提高代码复用率

管理后台

  1. Ant Design Pro Vue模板大幅加速开发
  2. 基于角色的权限控制(RBAC)实现

遇到的挑战与解决方案

  1. 高并发场景:使用Redis缓存+MySQL优化查询
  2. 小程序性能优化:图片懒加载、减少setData次数
  3. TypeORM复杂查询:使用QueryBuilder处理多表关联
  4. 多端JWT: 不同的端对应不同的jwt

微信小程序静默授权

通过静默授权获取用户的code 小程序的代码如下

onLaunch() {
    wx.login({
      success: (res) => {
        const { code } = res;
        loginApi({ code: code }).then(response => {
          if (response.code === 200) {
            const token = response.data.token;
            this.globalData.token = token;
            this.globalData.username = response.data.nickName
            wx.setStorageSync('token', token)
          }
        }).finally( () => {
          
        })
      }
    })
  },

通过小程序的code拿到用户的openid,其中openid是唯一值 nestjs代码如下

@Injectable()
export class WxAuthService {
    constructor(
        private readonly httpService: HttpService,
        private configService: ConfigService<ConfigKeyPaths>,
        private readonly wxTokenService: WxTokenService,
        
        private readonly wxUserService: WxUserService,
    ) { }

    async login(wxCode: string) {
        const { appid, secret, grant_type } = this.configService.get<IWxConfig>('wx', { infer: true });
        const response = await firstValueFrom(
            this.httpService.get('https://api.weixin.qq.com/sns/jscode2session', {
                params: {
                    appid,secret,grant_type,js_code:wxCode
                }
            })
        )
        // 如果请求错误 则返回错误信息
        if (response.data.errcode) 
        throw new BadRequestException(response.data.errmsg,{
            cause: new Error(),
            description: response.data.errcode
        })

        const {openid, nickName}  =  await this.wxUserService.wxRegister({openid: response.data.openid});  
        const token = await this.wxTokenService.generateAccessTokenByWechat(openid);
        return {openid,token,nickName};
    }

}

通过该接口https://api.weixin.qq.com/sns/jscode2session获取到openid, 同学们我这边是通过openid生成对应的token其实和后台管理系统生成的token类似,区别在于后台一般是账号、密码、验证码生成一个token小程序这边我直接使用的是openid唯一标识

细心的同学一定发现了上面代码的一个问题,老哥你的27行代码的nickName咋来的,忽悠我的吧。各位同学别急等我娓娓道来!

静默授权的问题

使用的是微信静默授权其实是拿不到用户的相关详细信息、性别图像手机号等,这些属于用户的隐私信息。单纯的去看openid你是不知道是谁在操作,是不是可以给这个用户取一个别名呢。以下就是取别名的js逻辑

 /**
 * 随机生成名字
 */

async function generateRandomNames(count = 1) {
  // 常用姓氏(单姓和复姓)
  const commonSurnames = [
    '李', '王', '张', '刘', '陈', '杨', '赵', '黄', '周', '吴',
    '欧阳', '诸葛', '司马', '上官', '东方', '南宫', '西门', '慕容',
    '令狐', '长孙', '端木', '轩辕', '皇甫'
  ];
  // 辅助函数:从数组中随机获取一个元素
  const getRandomElement = arr => arr[Math.floor(Math.random() * arr.length)];
  // 辅助函数:生成随机汉字(常用字范围)
  const getRandomHanzi = () => {
    // 常用汉字Unicode范围: 0x4E00-0x9FA5 (20902个字符)
    // 这里取较常用的前6000字
    const start = 0x4E00;
    const end = start + 6000;
    return String.fromCharCode(Math.floor(Math.random() * (end - start + 1)) + start);
  };
  // 辅助函数:随机生成1-3个字的名称部分
  const generateNamePart = () => {
    const length = Math.floor(Math.random() * 3) + 1; // 1-3个字
    let part = '';
    for (let i = 0; i < length; i++) {
      part += getRandomHanzi();
    }
    return part;
  };
  const results = [];
  for (let i = 0; i < count; i++) {
    // 随机选择姓氏(30%概率用复姓)
    const surname = Math.random() < 0.3 
      ? getRandomElement(commonSurnames.filter(s => s.length > 1))
      : getRandomElement(commonSurnames.filter(s => s.length === 1));

    // 随机生成名字部分(1-3个字)
    const namePart = generateNamePart();

    // 10%概率添加特殊连接符
    const connector = Math.random() < 0.1 
      ? getRandomElement(['·', '之', '-', '丶']) 
      : '';
    // 组合成完整名字(2-5个字)
    const fullName = surname + connector + namePart;
    results.push(fullName);
  }

  return results;
}

其中会在nestjs中调用该方法

@Injectable()
export class WxUserService {
    constructor(
        @InjectRepository(WxUserEntity)
        private readonly wxUserRepository: Repository<WxUserEntity>,
        // 使用事务 对数据进行写入操作
        @InjectEntityManager() private entityManager: EntityManager,
    ) { }

    /**
     * 微信用户注册
     */
    async wxRegister({ openid }: RegisterWxUserDto): Promise<any> {
        const exists = await this.wxUserRepository.findOneBy({ openid })
        if (!isEmpty(exists)) {
            // 直接返回用户的相关信息
            return exists;
        }
        const [nickName] = await generateRandomNames();
        const wxUserInfo = this.wxUserRepository.create({
            openid,
            nickName
        });
        const wxUser = await this.wxUserRepository.save(wxUserInfo);
        return wxUser;
    }
}

业余时间做的一个小程序,可以探讨全栈(nestjsflutter小程序)相关问题

百科.jpg