前言
同学们好久不见了,最近业余时间我完成了一个微信小程序的开发,今天想和大家分享一下整个项目的技术栈和开发心得。
项目技术架构
后端服务
- 框架: NestJS (一个渐进式Node.js框架,完美支持TypeScript)
- 数据库: MySQL (关系型数据库存储核心数据)
- 缓存: Redis (用于高频访问数据缓存和会话管理)
- ORM: TypeORM (强大的TypeScript ORM工具)
后台管理系统
- 前端框架: Vue.js (选择了熟悉的Vue作为管理后台基础)
- UI库: Ant Design Vue (企业级UI组件库,开发效率高)
- 状态管理: Pinia (全局状态管理方案)
小程序端
- 开发方式: 原生微信小程序开发
- API: 直接使用微信官方API文档
- 备选方案: 考虑过使用Uni-app (最终选择了原生开发)
为什么选择这些技术?
后端选择NestJS的原因
- 模块化架构清晰,适合个人/团队协作
- TypeScript支持完善,类型检查减少运行时错误
- 依赖注入机制让代码更易维护
- 丰富的生态系统和装饰器语法
数据库选择
-
MySQL作为主数据库存储核心业务数据
-
Redis用于:
- 会话管理(session storage)
- 高频访问数据缓存
- 分布式锁实现
为什么没用Uni-app?
虽然Uni-app可以跨平台,但考虑到:
- 当前项目只需要微信小程序端 2 希望更直接使用微信原生API
- 避免跨平台框架可能带来的性能损耗
开发中的经验分享
后端开发Tips
- 使用TypeORM的Repository模式让数据操作更清晰
- NestJS的Interceptor非常适合统一处理响应格式
- 合理使用装饰器进行参数验证和权限控制
小程序开发经验
- 善用微信开发者工具的云开发能力
- 小程序分包加载显著提升首屏速度
- 自定义组件开发提高代码复用率
管理后台
- Ant Design Pro Vue模板大幅加速开发
- 基于角色的权限控制(RBAC)实现
遇到的挑战与解决方案
- 高并发场景:使用Redis缓存+MySQL优化查询
- 小程序性能优化:图片懒加载、减少setData次数
- TypeORM复杂查询:使用QueryBuilder处理多表关联
- 多端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;
}
}
业余时间做的一个小程序,可以探讨全栈(nestjs、flutter、小程序)相关问题