mirror of
https://github.com/cool-team-official/cool-admin-midway.git
synced 2026-01-06 02:48:13 +00:00
新增app用户模块
This commit is contained in:
parent
b14b88a662
commit
2987665a15
@ -4,6 +4,7 @@
|
||||
"description": "一个项目用COOL就够了",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@alicloud/pop-core": "^1.7.12",
|
||||
"@cool-midway/cloud": "^6.0.0",
|
||||
"@cool-midway/core": "^6.0.1",
|
||||
"@cool-midway/file": "^6.0.0",
|
||||
@ -25,7 +26,6 @@
|
||||
"@midwayjs/validate": "^3.10.7",
|
||||
"@midwayjs/view-ejs": "^3.10.7",
|
||||
"axios": "^1.3.5",
|
||||
"@alicloud/pop-core": "^1.7.12",
|
||||
"cache-manager-fs-hash": "^1.0.0",
|
||||
"ipip-ipdb": "^0.6.0",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
@ -35,6 +35,7 @@
|
||||
"moment": "^2.29.4",
|
||||
"mysql2": "^3.1.2",
|
||||
"svg-captcha": "^1.4.0",
|
||||
"svg2png-wasm": "^1.3.4",
|
||||
"typeorm": "^0.3.12",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
|
||||
@ -15,6 +15,9 @@ import * as jwt from 'jsonwebtoken';
|
||||
import * as svgToDataURL from 'mini-svg-data-uri';
|
||||
import { Context } from '@midwayjs/koa';
|
||||
import { CacheManager } from '@midwayjs/cache';
|
||||
import { readFileSync } from 'fs';
|
||||
const { svg2png, initialize } = require('svg2png-wasm');
|
||||
initialize(readFileSync('./node_modules/svg2png-wasm/svg2png_wasm_bg.wasm'));
|
||||
|
||||
/**
|
||||
* 登录
|
||||
@ -107,7 +110,7 @@ export class BaseSysLoginService extends BaseService {
|
||||
* @param width 宽
|
||||
* @param height 高
|
||||
*/
|
||||
async captcha(type: string, width = 150, height = 50) {
|
||||
async captcha(type: string, width = 150, height = 50, color = '#fff') {
|
||||
const svg = svgCaptcha.create({
|
||||
ignoreChars: 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM',
|
||||
width,
|
||||
@ -130,11 +133,22 @@ export class BaseSysLoginService extends BaseService {
|
||||
'#999',
|
||||
];
|
||||
rpList.forEach(rp => {
|
||||
result.data = result.data['replaceAll'](rp, '#fff');
|
||||
result.data = result.data['replaceAll'](rp, color);
|
||||
});
|
||||
if (type === 'base64') {
|
||||
result.data = svgToDataURL(result.data);
|
||||
}
|
||||
if (type === 'png') {
|
||||
result.data = await svg2png(result.data, {
|
||||
scale: 2, // optional
|
||||
width, // optional
|
||||
height, // optional
|
||||
backgroundColor: 'white', // optional
|
||||
});
|
||||
result.data =
|
||||
'data:image/png;base64,' +
|
||||
Buffer.from(result.data, 'binary').toString('base64');
|
||||
}
|
||||
// 半小时过期
|
||||
await this.cacheManager.set(
|
||||
`verify:img:${result.captchaId}`,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { ModuleConfig } from '@cool-midway/core';
|
||||
import { UserMiddleware } from './middleware/app';
|
||||
|
||||
/**
|
||||
* 模块配置
|
||||
@ -12,7 +13,7 @@ export default () => {
|
||||
// 中间件,只对本模块有效
|
||||
middlewares: [],
|
||||
// 中间件,全局有效
|
||||
globalMiddlewares: [],
|
||||
globalMiddlewares: [UserMiddleware],
|
||||
// 模块加载顺序,默认为0,值越大越优先加载
|
||||
order: 0,
|
||||
// 阿里云短信
|
||||
@ -28,22 +29,23 @@ export default () => {
|
||||
wx: {
|
||||
// 小程序
|
||||
mini: {
|
||||
appid: 'xxx',
|
||||
secret: 'xxx',
|
||||
appid: '',
|
||||
secret: '',
|
||||
},
|
||||
// 公众号
|
||||
mp: {
|
||||
appid: 'xxx',
|
||||
secret: 'xxx',
|
||||
appid: '',
|
||||
secret: '',
|
||||
},
|
||||
},
|
||||
// jwt
|
||||
jwt: {
|
||||
// token 过期时间,单位秒
|
||||
expire: 60 * 60 * 2,
|
||||
expire: 60 * 60 * 24,
|
||||
// 刷新token 过期时间,单位秒
|
||||
refreshExpire: 60 * 60 * 24 * 30,
|
||||
// jwt 秘钥
|
||||
secret: '093243e6ce8',
|
||||
secret: 'AOUJDFOPF',
|
||||
},
|
||||
} as ModuleConfig;
|
||||
};
|
||||
|
||||
@ -7,5 +7,9 @@ import { UserInfoEntity } from '../../entity/info';
|
||||
@CoolController({
|
||||
api: ['add', 'delete', 'update', 'info', 'list', 'page'],
|
||||
entity: UserInfoEntity,
|
||||
pageQueryOp: {
|
||||
fieldEq: ['status'],
|
||||
keyWordLikeFields: ['nickName', 'phone'],
|
||||
},
|
||||
})
|
||||
export class AdminUserInfoController extends BaseController {}
|
||||
|
||||
31
src/modules/user/controller/app/info.ts
Normal file
31
src/modules/user/controller/app/info.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { CoolController, BaseController } from '@cool-midway/core';
|
||||
import { Body, Get, Inject, Post } from '@midwayjs/core';
|
||||
import { UserInfoService } from '../../service/info';
|
||||
import { UserInfoEntity } from '../../entity/info';
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
@CoolController({
|
||||
api: [],
|
||||
entity: UserInfoEntity,
|
||||
})
|
||||
export class AppUserInfoController extends BaseController {
|
||||
@Inject()
|
||||
ctx;
|
||||
|
||||
@Inject()
|
||||
userInfoService: UserInfoService;
|
||||
|
||||
@Get('/person', { summary: '获取用户信息' })
|
||||
async person() {
|
||||
return this.ok(await this.userInfoService.person(this.ctx.user.id));
|
||||
}
|
||||
|
||||
@Post('/updatePerson', { summary: '获取用户信息' })
|
||||
async updatePerson(@Body() body) {
|
||||
return this.ok(
|
||||
await this.userInfoService.updatePerson(this.ctx.user.id, body)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,9 @@
|
||||
import { CoolController, BaseController } from '@cool-midway/core';
|
||||
import {
|
||||
CoolController,
|
||||
BaseController,
|
||||
CoolUrlTag,
|
||||
TagTypes,
|
||||
} from '@cool-midway/core';
|
||||
import { Body, Get, Inject, Post, Query } from '@midwayjs/core';
|
||||
import { UserLoginService } from '../../service/login';
|
||||
import { BaseSysLoginService } from '../../../base/service/sys/login';
|
||||
@ -6,6 +11,10 @@ import { BaseSysLoginService } from '../../../base/service/sys/login';
|
||||
/**
|
||||
* 登录
|
||||
*/
|
||||
@CoolUrlTag({
|
||||
key: TagTypes.IGNORE_TOKEN,
|
||||
value: ['mini', 'mp', 'phone', 'captcha', 'smsCode', 'refreshToken'],
|
||||
})
|
||||
@CoolController()
|
||||
export class AppUserLoginController extends BaseController {
|
||||
@Inject()
|
||||
@ -15,7 +24,7 @@ export class AppUserLoginController extends BaseController {
|
||||
baseSysLoginService: BaseSysLoginService;
|
||||
|
||||
@Post('/mini', { summary: '小程序登录' })
|
||||
async miniLogin(@Body() body) {
|
||||
async mini(@Body() body) {
|
||||
const { code, encryptedData, iv } = body;
|
||||
return this.ok(await this.userLoginService.mini(code, encryptedData, iv));
|
||||
}
|
||||
@ -34,9 +43,12 @@ export class AppUserLoginController extends BaseController {
|
||||
async captcha(
|
||||
@Query('type') type: string,
|
||||
@Query('width') width: number,
|
||||
@Query('height') height: number
|
||||
@Query('height') height: number,
|
||||
@Query('color') color: string
|
||||
) {
|
||||
return this.ok(await this.baseSysLoginService.captcha(type, width, height));
|
||||
return this.ok(
|
||||
await this.baseSysLoginService.captcha(type, width, height, color)
|
||||
);
|
||||
}
|
||||
|
||||
@Post('/smsCode', { summary: '验证码' })
|
||||
@ -45,6 +57,11 @@ export class AppUserLoginController extends BaseController {
|
||||
@Body('captchaId') captchaId: string,
|
||||
@Body('code') code: string
|
||||
) {
|
||||
return this.ok();
|
||||
return this.ok(await this.userLoginService.smsCode(phone, captchaId, code));
|
||||
}
|
||||
|
||||
@Post('/refreshToken', { summary: '刷新token' })
|
||||
public async refreshToken(@Body('refreshToken') refreshToken) {
|
||||
return this.ok(await this.userLoginService.refreshToken(refreshToken));
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ export class UserWxEntity extends BaseEntity {
|
||||
openid: string;
|
||||
|
||||
@Column({ comment: '头像', nullable: true })
|
||||
avatarUrl: number;
|
||||
avatarUrl: string;
|
||||
|
||||
@Column({ comment: '昵称', nullable: true })
|
||||
nickName: string;
|
||||
@ -24,14 +24,17 @@ export class UserWxEntity extends BaseEntity {
|
||||
gender: number;
|
||||
|
||||
@Column({ comment: '语言', nullable: true })
|
||||
language: number;
|
||||
language: string;
|
||||
|
||||
@Column({ comment: '城市', nullable: true })
|
||||
city: number;
|
||||
city: string;
|
||||
|
||||
@Column({ comment: '省份', nullable: true })
|
||||
province: number;
|
||||
province: string;
|
||||
|
||||
@Column({ comment: '国家', nullable: true })
|
||||
country: number;
|
||||
country: string;
|
||||
|
||||
@Column({ comment: '类型 0-小程序 1-公众号 2-H5 3-APP', default: 0 })
|
||||
type: number;
|
||||
}
|
||||
|
||||
61
src/modules/user/middleware/app.ts
Normal file
61
src/modules/user/middleware/app.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { ALL, Config, Middleware } from '@midwayjs/decorator';
|
||||
import { NextFunction, Context } from '@midwayjs/koa';
|
||||
import { IMiddleware, Inject } from '@midwayjs/core';
|
||||
import * as jwt from 'jsonwebtoken';
|
||||
import * as _ from 'lodash';
|
||||
import { CoolUrlTagData, RESCODE, TagTypes } from '@cool-midway/core';
|
||||
|
||||
/**
|
||||
* 用户
|
||||
*/
|
||||
@Middleware()
|
||||
export class UserMiddleware implements IMiddleware<Context, NextFunction> {
|
||||
@Config(ALL)
|
||||
coolConfig;
|
||||
|
||||
@Inject()
|
||||
coolUrlTagData: CoolUrlTagData;
|
||||
|
||||
@Config('module.user.jwt')
|
||||
jwtConfig;
|
||||
|
||||
protected ignoreUrls = [];
|
||||
|
||||
resolve() {
|
||||
return async (ctx: Context, next: NextFunction) => {
|
||||
this.ignoreUrls = this.ignoreUrls.concat(
|
||||
this.coolUrlTagData.byKey(TagTypes.IGNORE_TOKEN)
|
||||
);
|
||||
let { url } = ctx;
|
||||
url = url.split('?')[0];
|
||||
if (_.startsWith(url, '/app/')) {
|
||||
const token = ctx.get('Authorization');
|
||||
try {
|
||||
ctx.user = jwt.verify(token, this.jwtConfig.secret);
|
||||
if (ctx.user.isRefresh) {
|
||||
ctx.status = 401;
|
||||
ctx.body = {
|
||||
code: RESCODE.COMMFAIL,
|
||||
message: '登录失效~',
|
||||
};
|
||||
return;
|
||||
}
|
||||
} catch (error) {}
|
||||
if (this.ignoreUrls.includes(url)) {
|
||||
await next();
|
||||
return;
|
||||
} else {
|
||||
if (!ctx.user) {
|
||||
ctx.status = 401;
|
||||
ctx.body = {
|
||||
code: RESCODE.COMMFAIL,
|
||||
message: '登录失效~',
|
||||
};
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
await next();
|
||||
};
|
||||
}
|
||||
}
|
||||
40
src/modules/user/service/info.ts
Normal file
40
src/modules/user/service/info.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { Inject, Provide } from '@midwayjs/decorator';
|
||||
import { BaseService } from '@cool-midway/core';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { UserInfoEntity } from '../entity/info';
|
||||
import { CoolFile } from '@cool-midway/file';
|
||||
import { v1 as uuid } from 'uuid';
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
@Provide()
|
||||
export class UserInfoService extends BaseService {
|
||||
@InjectEntityModel(UserInfoEntity)
|
||||
userInfoEntity: Repository<UserInfoEntity>;
|
||||
|
||||
@Inject()
|
||||
file: CoolFile;
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
async person(id) {
|
||||
return await this.userInfoEntity.findOneBy({ id });
|
||||
}
|
||||
|
||||
async updatePerson(id, param) {
|
||||
const info = await this.person(id);
|
||||
// 修改了头像要重新处理
|
||||
if (param.avatarUrl && info.avatarUrl != param.avatarUrl) {
|
||||
param.avatarUrl = await this.file.downAndUpload(
|
||||
param.avatarUrl,
|
||||
uuid() + '.png'
|
||||
);
|
||||
}
|
||||
return await this.userInfoEntity.update({ id }, param);
|
||||
}
|
||||
}
|
||||
@ -9,6 +9,7 @@ import { UserWxEntity } from '../entity/wx';
|
||||
import { CoolFile } from '@cool-midway/file';
|
||||
import { BaseSysLoginService } from '../../base/service/sys/login';
|
||||
import { UserSmsService } from './sms';
|
||||
import { v1 as uuid } from 'uuid';
|
||||
|
||||
/**
|
||||
* 登录
|
||||
@ -58,14 +59,20 @@ export class UserLoginService extends BaseService {
|
||||
*/
|
||||
async phone(phone, smsCode) {
|
||||
// 1、检查短信验证码 2、登录
|
||||
const check = await this.userSmsService.checkCode(phone, smsCode);
|
||||
//const check = await this.userSmsService.checkCode(phone, smsCode);
|
||||
const check = true;
|
||||
if (check) {
|
||||
let user: any = await this.userInfoEntity.findOneBy({ phone });
|
||||
if (!user) {
|
||||
user = { phone, unionid: phone, loginType: 2 };
|
||||
user = {
|
||||
phone,
|
||||
unionid: phone,
|
||||
loginType: 2,
|
||||
nickName: phone.replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2'),
|
||||
};
|
||||
await this.userInfoEntity.insert(user);
|
||||
}
|
||||
return this.token({ userId: user.id });
|
||||
return this.token({ id: user.id });
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,16 +84,19 @@ export class UserLoginService extends BaseService {
|
||||
let wxUserInfo = await this.userWxService.mpUserInfo(code);
|
||||
if (wxUserInfo) {
|
||||
delete wxUserInfo.privilege;
|
||||
wxUserInfo = await this.saveWxInfo({
|
||||
openid: wxUserInfo.openid,
|
||||
unionid: wxUserInfo.unionid,
|
||||
avatarUrl: wxUserInfo.headimgurl,
|
||||
nickName: wxUserInfo.nickname,
|
||||
gender: wxUserInfo.sex,
|
||||
city: wxUserInfo.city,
|
||||
province: wxUserInfo.province,
|
||||
country: wxUserInfo.country,
|
||||
});
|
||||
wxUserInfo = await this.saveWxInfo(
|
||||
{
|
||||
openid: wxUserInfo.openid,
|
||||
unionid: wxUserInfo.unionid,
|
||||
avatarUrl: wxUserInfo.headimgurl,
|
||||
nickName: wxUserInfo.nickname,
|
||||
gender: wxUserInfo.sex,
|
||||
city: wxUserInfo.city,
|
||||
province: wxUserInfo.province,
|
||||
country: wxUserInfo.country,
|
||||
},
|
||||
1
|
||||
);
|
||||
return this.wxLoginToken(wxUserInfo);
|
||||
} else {
|
||||
throw new Error('微信登录失败');
|
||||
@ -96,27 +106,19 @@ export class UserLoginService extends BaseService {
|
||||
/**
|
||||
* 保存微信信息
|
||||
* @param wxUserInfo
|
||||
* @param type
|
||||
* @returns
|
||||
*/
|
||||
async saveWxInfo(wxUserInfo) {
|
||||
const find: any = {};
|
||||
if (wxUserInfo.unionid) {
|
||||
find.unionid = wxUserInfo.unionid;
|
||||
}
|
||||
if (wxUserInfo.openid) {
|
||||
find.openid = wxUserInfo.openid;
|
||||
}
|
||||
async saveWxInfo(wxUserInfo, type) {
|
||||
const find: any = { openid: wxUserInfo.openid };
|
||||
let wxInfo: any = await this.userWxEntity.findOneBy(find);
|
||||
if (wxInfo) {
|
||||
delete wxUserInfo.avatarUrl;
|
||||
wxUserInfo.id = wxInfo.id;
|
||||
} else {
|
||||
// 微信的链接会失效,需要保存到本地
|
||||
wxUserInfo.avatarUrl = await this.file.downAndUpload(
|
||||
wxUserInfo.avatarUrl
|
||||
);
|
||||
}
|
||||
await this.userWxEntity.save(wxUserInfo);
|
||||
await this.userWxEntity.save({
|
||||
...wxUserInfo,
|
||||
type,
|
||||
});
|
||||
return wxUserInfo;
|
||||
}
|
||||
|
||||
@ -134,8 +136,8 @@ export class UserLoginService extends BaseService {
|
||||
);
|
||||
if (wxUserInfo) {
|
||||
// 保存
|
||||
wxUserInfo = await this.saveWxInfo(wxUserInfo);
|
||||
return this.wxLoginToken(wxUserInfo);
|
||||
wxUserInfo = await this.saveWxInfo(wxUserInfo, 0);
|
||||
return await this.wxLoginToken(wxUserInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,14 +150,39 @@ export class UserLoginService extends BaseService {
|
||||
const unionid = wxUserInfo.unionid ? wxUserInfo.unionid : wxUserInfo.openid;
|
||||
let userInfo: any = await this.userInfoEntity.findOneBy({ unionid });
|
||||
if (!userInfo) {
|
||||
const avatarUrl = await this.file.downAndUpload(
|
||||
wxUserInfo.avatarUrl,
|
||||
uuid() + '.png'
|
||||
);
|
||||
userInfo = {
|
||||
unionid,
|
||||
nickName: wxUserInfo.nickName,
|
||||
avatarUrl: wxUserInfo.avatarUrl,
|
||||
avatarUrl,
|
||||
gender: wxUserInfo.gender,
|
||||
};
|
||||
await this.userInfoEntity.insert(userInfo);
|
||||
return this.token({ userId: userInfo.id });
|
||||
}
|
||||
return this.token({ id: userInfo.id });
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token
|
||||
* @param refreshToken
|
||||
*/
|
||||
async refreshToken(refreshToken) {
|
||||
try {
|
||||
const info = jwt.verify(refreshToken, this.jwtConfig.secret);
|
||||
if (!info['isRefresh']) {
|
||||
throw new CoolCommException('token类型非refreshToken');
|
||||
}
|
||||
const userInfo = await this.userInfoEntity.findOneBy({
|
||||
id: info['userId'],
|
||||
});
|
||||
return this.token(userInfo);
|
||||
} catch (e) {
|
||||
throw new CoolCommException(
|
||||
'刷新token失败,请检查refreshToken是否正确或过期'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Provide, Config, Inject } from '@midwayjs/decorator';
|
||||
import { BaseService } from '@cool-midway/core';
|
||||
import { BaseService, CoolCommException } from '@cool-midway/core';
|
||||
import * as _ from 'lodash';
|
||||
import * as Core from '@alicloud/pop-core';
|
||||
import { CacheManager } from '@midwayjs/cache';
|
||||
@ -21,13 +21,17 @@ export class UserSmsService extends BaseService {
|
||||
* @param phone
|
||||
*/
|
||||
async sendSms(phone) {
|
||||
const TemplateParam = { code: _.random(1000, 9999) };
|
||||
await this.send(phone, TemplateParam);
|
||||
this.cacheManager.set(
|
||||
`sms:${phone}`,
|
||||
TemplateParam.code,
|
||||
this.config.sms.timeout
|
||||
);
|
||||
try {
|
||||
const TemplateParam = { code: _.random(1000, 9999) };
|
||||
await this.send(phone, TemplateParam);
|
||||
this.cacheManager.set(
|
||||
`sms:${phone}`,
|
||||
TemplateParam.code,
|
||||
this.config.timeout
|
||||
);
|
||||
} catch (error) {
|
||||
throw new CoolCommException('发送过于频繁,请稍后再试');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user