优化门禁、访客码、业主二维码加密方式&添加智能门禁物联网模块
This commit is contained in:
parent
f5bedbe367
commit
9708e1f673
@ -1,3 +1,20 @@
|
||||
## 「e家宜业」物联网服务端
|
||||
|
||||
> 代码整理中,敬请期待
|
||||
|
||||
### 智能门禁
|
||||
|
||||
> 目前仅支持卡帕斯品牌
|
||||
|
||||
#### 接口地址
|
||||
|
||||
- 心跳检测 `域名/iot/entrance?id={{接口形参第一位}}&community_id={{接口形参第二位}}&method=status`
|
||||
- 开门刷卡 `域名/iot/entrance?id={{接口形参第一位}}&community_id={{接口形参第二位}}&method=access`
|
||||
|
||||
#### 门禁配置
|
||||
|
||||
* 选择`启用AES128加密`,秘钥配置中台对应的秘钥
|
||||
* 心跳间隔设置为`60`
|
||||
* `本机信息-标识`配置为中台对应的标识名称
|
||||
* `通讯协议`选择`启用Http通讯`
|
||||
* `验证模式`选择`脱机验证优先`(主要针对老旧小区用的RFID卡)
|
||||
|
@ -72,4 +72,4 @@
|
||||
"list": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -97,7 +97,7 @@ CwPage({
|
||||
},
|
||||
chooseContact() {
|
||||
wx.chooseContact({
|
||||
success: (res) => {
|
||||
success: res => {
|
||||
this.setData({
|
||||
vistor_name: res.displayName,
|
||||
vistor_phone: res.phoneNumber
|
||||
|
@ -10,7 +10,7 @@
|
||||
is-detail
|
||||
url="/pages/community/index"
|
||||
/>
|
||||
<cw-cell is-link clickable title="在通讯录中选择访客" bind:click="chooseContact"/>
|
||||
<cw-cell is-link clickable title="在通讯录中选择访客" bind:click="chooseContact" />
|
||||
<cw-field
|
||||
label="访客称呼"
|
||||
maxlength="{{ 8 }}"
|
||||
|
@ -32,13 +32,6 @@ CREATE TABLE `ejyy_ask_for_leave_flow` (
|
||||
`finished_at` bigint(13) DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE `ejyy_building_access` (
|
||||
`id` bigint(20) NOT NULL,
|
||||
`building_id` bigint(20) NOT NULL,
|
||||
`uid` varchar(128) DEFAULT NULL,
|
||||
`created_at` bigint(13) NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE `ejyy_building_info` (
|
||||
`id` int(11) NOT NULL,
|
||||
`community_id` bigint(20) NOT NULL,
|
||||
@ -1092,7 +1085,6 @@ CREATE TABLE `ejyy_vistor` (
|
||||
`vistor_phone` varchar(11) NOT NULL,
|
||||
`car_number` varchar(8) DEFAULT NULL,
|
||||
`have_vistor_info` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`uid` varchar(128) DEFAULT NULL,
|
||||
`expire` bigint(13) NOT NULL,
|
||||
`used_at` bigint(13) DEFAULT NULL,
|
||||
`scan_by` bigint(20) DEFAULT NULL,
|
||||
|
@ -33,6 +33,7 @@ import ModelMiddleware from '~/middleware/model';
|
||||
import IpMiddleware from '~/middleware/ip';
|
||||
import HeaderMiddleware from '~/middleware/header';
|
||||
import WatcherMiddleware from '~/middleware/watcher';
|
||||
import * as iot from '~/iot';
|
||||
|
||||
if (cluster.isMaster) {
|
||||
cwlog.success(`main process ${process.pid}`);
|
||||
@ -57,6 +58,15 @@ if (cluster.isMaster) {
|
||||
NotifyModule(router);
|
||||
OaModule(router);
|
||||
|
||||
// WebSocket
|
||||
wss.init(server);
|
||||
|
||||
// for socket
|
||||
redisService.subscribe();
|
||||
|
||||
// 物联网
|
||||
iot.init(router);
|
||||
|
||||
app.use(KoaBodyMiddleware({ multipart: true }))
|
||||
.use(
|
||||
KoaLogMiddleware({
|
||||
@ -80,12 +90,6 @@ if (cluster.isMaster) {
|
||||
.use(router.routes())
|
||||
.use(WatcherMiddleware());
|
||||
|
||||
// WebSocket
|
||||
wss.init(server);
|
||||
|
||||
// for socket
|
||||
redisService.subscribe();
|
||||
|
||||
const port = process.env.port ? parseInt(process.env.port, 10) : config.server.port;
|
||||
|
||||
server.listen(port, '0.0.0.0', () => {
|
||||
|
@ -148,3 +148,19 @@ export const PARK_NAME_EXIST = -171;
|
||||
export const PARK_BLACKLIST_EXIST = -172;
|
||||
// 预警中控名称存在
|
||||
export const WARNING_NAME_EXIST = -173;
|
||||
// 门禁非法
|
||||
export const IOT_ENTRANCE_ILLEGAL = -174;
|
||||
// 梯控非法
|
||||
export const IOT_ELEVATOR_ILLEGAL = -175;
|
||||
// 灯控非法
|
||||
export const IOT_LAMP_ILLEGAL = -176;
|
||||
// 中继器非法
|
||||
export const IOT_REPEATER_ILLEGAL = -177;
|
||||
// 仪表非法
|
||||
export const IOT_METER_ILLEGAL = -178;
|
||||
// 停车差非法
|
||||
export const IOT_PARK_ILLEGAL = -179;
|
||||
// 预警中控非法
|
||||
export const IOT_WARNING_ILLEGAL = -180;
|
||||
// 物联网设备秘钥错误
|
||||
export const IOT_SECRET_ERROR = -181;
|
||||
|
124
server/src/iot/entrance/access.ts
Normal file
124
server/src/iot/entrance/access.ts
Normal file
@ -0,0 +1,124 @@
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | 「e家宜业」 —— 助力物业服务升级,用心服务万千业主
|
||||
* +----------------------------------------------------------------------
|
||||
* | Copyright (c) 2020~2021 https://www.chowa.com All rights reserved.
|
||||
* +----------------------------------------------------------------------
|
||||
* | Licensed 未经许可不能去掉「e家宜业」和「卓瓦科技」相关版权
|
||||
* +----------------------------------------------------------------------
|
||||
* | Author: jixuecong@chowa.cn
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
import Knex from 'Knex';
|
||||
import { EjyyIotEntrance } from '~/types/model';
|
||||
import { SELF_ACCESS_QRCODE, VISTOR_ACCESS_QRCODE } from '~/constant/open_access';
|
||||
import utils from '~/utils';
|
||||
import { IOT_METHOD_QRCODE, IOT_METHOD_NFC, IOT_METHOD_ICCARD } from '~/constant/iot';
|
||||
|
||||
const TYPE_CARD = '0';
|
||||
const TYPE_SERIAL = '1';
|
||||
const TYPE_PASSWORD = '2';
|
||||
const TYPE_BUTTON = '3';
|
||||
const TYPE_ID_CARD = '6';
|
||||
const TYPE_QRCODE = '9';
|
||||
const TYPE_FINGERPRINT = '10';
|
||||
const TYPE_PULSE = '11';
|
||||
const TYPE_RFID = '12';
|
||||
const TYPE_FACE = '13';
|
||||
|
||||
const OPEN = '1';
|
||||
const CLOSE = '3';
|
||||
|
||||
const ENTER = '0';
|
||||
const LEAVE = '1';
|
||||
|
||||
interface AccessParams {
|
||||
type:
|
||||
| typeof TYPE_CARD
|
||||
| typeof TYPE_SERIAL
|
||||
| typeof TYPE_PASSWORD
|
||||
| typeof TYPE_BUTTON
|
||||
| typeof TYPE_ID_CARD
|
||||
| typeof TYPE_QRCODE
|
||||
| typeof TYPE_FINGERPRINT
|
||||
| typeof TYPE_PULSE
|
||||
| typeof TYPE_RFID
|
||||
| typeof TYPE_FACE;
|
||||
Reader: string;
|
||||
DataLen: string;
|
||||
Index: string;
|
||||
Serial: string;
|
||||
Status: string;
|
||||
Input: string;
|
||||
Ver: string;
|
||||
ID: string;
|
||||
IP: string;
|
||||
MAC: string;
|
||||
Card: string;
|
||||
}
|
||||
|
||||
interface AccessResponse {
|
||||
AcsRes: typeof OPEN | typeof CLOSE;
|
||||
ActIndex?: typeof ENTER | typeof LEAVE;
|
||||
Time?: string;
|
||||
}
|
||||
|
||||
async function accessMethod(params: AccessParams, entranceInfo: EjyyIotEntrance, model: Knex): Promise<AccessResponse> {
|
||||
const mom = moment();
|
||||
await utils.redis.set(`entrance${entranceInfo.id}`, mom.valueOf().toString());
|
||||
|
||||
// 卡 二维码
|
||||
if (
|
||||
params.type === TYPE_CARD ||
|
||||
params.type === TYPE_RFID ||
|
||||
params.type === TYPE_SERIAL ||
|
||||
params.type === TYPE_QRCODE
|
||||
) {
|
||||
const reuslt =
|
||||
params.type === TYPE_SERIAL || params.type === TYPE_QRCODE
|
||||
? utils.access.decrypt(Buffer.from(params.Card, 'base64').toString())
|
||||
: utils.access.decrypt(params.Card);
|
||||
|
||||
if (!reuslt.success) {
|
||||
return { AcsRes: CLOSE };
|
||||
}
|
||||
|
||||
const existBuilding = await model
|
||||
.from('ejyy_building_info')
|
||||
.where('id', reuslt.building_id)
|
||||
.andWhere('community_id', entranceInfo.community_id)
|
||||
.first();
|
||||
|
||||
if (!existBuilding) {
|
||||
return { AcsRes: CLOSE };
|
||||
}
|
||||
|
||||
let method: typeof IOT_METHOD_NFC | typeof IOT_METHOD_ICCARD | typeof IOT_METHOD_QRCODE = IOT_METHOD_QRCODE;
|
||||
|
||||
if (params.type === TYPE_CARD) {
|
||||
method = IOT_METHOD_NFC;
|
||||
} else if (params.type === TYPE_RFID) {
|
||||
method = IOT_METHOD_ICCARD;
|
||||
}
|
||||
|
||||
await model.from('ejyy_iot_entrance_log').insert({
|
||||
wechat_mp_user_id: reuslt.type === VISTOR_ACCESS_QRCODE ? null : reuslt.id,
|
||||
vistor_id: reuslt.type === SELF_ACCESS_QRCODE ? null : reuslt.id,
|
||||
entrance_id: entranceInfo.id,
|
||||
method,
|
||||
created_at: mom.valueOf()
|
||||
});
|
||||
|
||||
return { AcsRes: OPEN, ActIndex: ENTER, Time: '5' };
|
||||
}
|
||||
// 按钮
|
||||
else if (params.type === TYPE_BUTTON) {
|
||||
return { AcsRes: OPEN, ActIndex: LEAVE };
|
||||
} else {
|
||||
return { AcsRes: CLOSE };
|
||||
}
|
||||
}
|
||||
|
||||
export default accessMethod;
|
197
server/src/iot/entrance/index.ts
Normal file
197
server/src/iot/entrance/index.ts
Normal file
@ -0,0 +1,197 @@
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | 「e家宜业」 —— 助力物业服务升级,用心服务万千业主
|
||||
* +----------------------------------------------------------------------
|
||||
* | Copyright (c) 2020~2021 https://www.chowa.com All rights reserved.
|
||||
* +----------------------------------------------------------------------
|
||||
* | Licensed 未经许可不能去掉「e家宜业」和「卓瓦科技」相关版权
|
||||
* +----------------------------------------------------------------------
|
||||
* | Author: jixuecong@chowa.cn
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import crypto from 'crypto';
|
||||
import RawBody from 'raw-body';
|
||||
import KoaRouter from 'koa-router';
|
||||
import { Context } from 'koa';
|
||||
import { Action } from '~/types/action';
|
||||
import { IOT_ENTRANCE_ILLEGAL, PARAMS_ERROR, IOT_SECRET_ERROR } from '~/constant/code';
|
||||
import validatorService from '~/service/validator';
|
||||
import statusMethod from './status';
|
||||
import accessMethod from './access';
|
||||
import config from '~/config';
|
||||
import cwlog from 'chowa-log';
|
||||
|
||||
function zeroPad(str: string, length = 8): string {
|
||||
str = Buffer.from(str, 'utf8').toString('hex');
|
||||
const bitLength = str.length * length;
|
||||
if (bitLength < 256) {
|
||||
for (let i = bitLength; i < 256; i += length) {
|
||||
str += 0;
|
||||
}
|
||||
} else if (bitLength > 256) {
|
||||
while ((str.length * length) % 256 != 0) {
|
||||
str += 0;
|
||||
}
|
||||
}
|
||||
return Buffer.from(str, 'hex').toString('utf8');
|
||||
}
|
||||
|
||||
function encrypt(str: string, secret: string): string {
|
||||
const cipher = crypto.createCipheriv('aes-128-ecb', secret, '');
|
||||
|
||||
cipher.setAutoPadding(false);
|
||||
|
||||
let crypted = cipher.update(zeroPad(str), 'utf8', 'hex');
|
||||
|
||||
crypted += cipher.final('hex');
|
||||
|
||||
return crypted;
|
||||
}
|
||||
|
||||
function decrypt(str: string, secret: string): string {
|
||||
const cipher = crypto.createDecipheriv('aes-128-ecb', secret, '');
|
||||
const isBase64 = Buffer.from(str, 'base64').toString('base64') === str;
|
||||
|
||||
cipher.setAutoPadding(false);
|
||||
|
||||
let decrypted = cipher.update(zeroPad(str), isBase64 ? 'base64' : 'hex', 'utf8');
|
||||
decrypted = decrypted + cipher.final('utf8');
|
||||
|
||||
return decrypted.replace(/[\u0000-\u0019]+/g, '');
|
||||
}
|
||||
|
||||
const IotEntranceAction = <Action>{
|
||||
router: {
|
||||
path: '/entrance',
|
||||
method: 'post',
|
||||
authRequired: false
|
||||
},
|
||||
validator: {
|
||||
query: [
|
||||
{
|
||||
name: 'community_id',
|
||||
required: true,
|
||||
regex: /^\d+$/
|
||||
},
|
||||
{
|
||||
name: 'id',
|
||||
required: true,
|
||||
regex: /^\d+$/
|
||||
},
|
||||
{
|
||||
name: 'method',
|
||||
required: true,
|
||||
regex: /^status|access$/
|
||||
}
|
||||
]
|
||||
},
|
||||
response: async ctx => {
|
||||
const { community_id, id, method } = ctx.request.query;
|
||||
|
||||
const entranceInfo = await ctx.model
|
||||
.from('ejyy_iot_entrance')
|
||||
.where('id', id)
|
||||
.andWhere('community_id', community_id)
|
||||
.first();
|
||||
|
||||
if (!entranceInfo) {
|
||||
return (ctx.body = {
|
||||
code: IOT_ENTRANCE_ILLEGAL,
|
||||
message: '非法的物联网门禁'
|
||||
});
|
||||
}
|
||||
|
||||
const raw = await RawBody(ctx.req, {
|
||||
length: ctx.request.length,
|
||||
limit: '1mb',
|
||||
encoding: ctx.request.charset || 'utf-8'
|
||||
});
|
||||
|
||||
const datas = raw.replace('DATAS=', '');
|
||||
|
||||
if (!datas) {
|
||||
return (ctx.body = {
|
||||
code: PARAMS_ERROR,
|
||||
message: '物联网门禁受控参数错误'
|
||||
});
|
||||
}
|
||||
|
||||
let decrypted = null;
|
||||
|
||||
try {
|
||||
decrypted = decrypt(datas, entranceInfo.secret);
|
||||
} catch (e) {
|
||||
return (ctx.bod = {
|
||||
code: IOT_SECRET_ERROR,
|
||||
message: '物联网门禁秘钥错误'
|
||||
});
|
||||
}
|
||||
|
||||
let params = null;
|
||||
|
||||
try {
|
||||
params = JSON.parse(decrypted);
|
||||
} catch (e) {
|
||||
return (ctx.body = {
|
||||
code: IOT_SECRET_ERROR,
|
||||
message: '物联网门禁秘钥错误'
|
||||
});
|
||||
}
|
||||
|
||||
if (!('Serial' in params) || !('ID' in params)) {
|
||||
return (ctx.body = {
|
||||
code: PARAMS_ERROR,
|
||||
message: '物联网门禁控制参数错误'
|
||||
});
|
||||
}
|
||||
|
||||
if (params.ID !== entranceInfo.sign) {
|
||||
return (ctx.body = {
|
||||
code: PARAMS_ERROR,
|
||||
message: '物联网门禁自定义标识符配置错误'
|
||||
});
|
||||
}
|
||||
|
||||
let res = {};
|
||||
console.log(params);
|
||||
switch (method) {
|
||||
// 心跳
|
||||
case 'status':
|
||||
res = await statusMethod(params, entranceInfo);
|
||||
break;
|
||||
|
||||
// 开门
|
||||
case 'access':
|
||||
res = await accessMethod(params, entranceInfo, ctx.model);
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
|
||||
ctx.res.setHeader('Content-Type', 'text/html');
|
||||
ctx.body = `DATAS={${encrypt(JSON.stringify(res), entranceInfo.secret)}}`;
|
||||
}
|
||||
};
|
||||
|
||||
export default (appRouter: KoaRouter) => {
|
||||
const { router, validator, response } = IotEntranceAction;
|
||||
|
||||
appRouter[router.method](path.posix.join('/iot', router.path), async (ctx: Context, next) => {
|
||||
const vs = validatorService(ctx, validator);
|
||||
|
||||
if (!vs.success) {
|
||||
return (ctx.body = {
|
||||
code: PARAMS_ERROR,
|
||||
message: vs.message
|
||||
});
|
||||
}
|
||||
|
||||
await response.apply(this, [ctx, next]);
|
||||
});
|
||||
|
||||
if (config.debug) {
|
||||
cwlog.info(`IotEntrance mounted and request from ${path.posix.join('/iot', router.path)} by ${router.method}`);
|
||||
}
|
||||
};
|
51
server/src/iot/entrance/status.ts
Normal file
51
server/src/iot/entrance/status.ts
Normal file
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | 「e家宜业」 —— 助力物业服务升级,用心服务万千业主
|
||||
* +----------------------------------------------------------------------
|
||||
* | Copyright (c) 2020~2021 https://www.chowa.com All rights reserved.
|
||||
* +----------------------------------------------------------------------
|
||||
* | Licensed 未经许可不能去掉「e家宜业」和「卓瓦科技」相关版权
|
||||
* +----------------------------------------------------------------------
|
||||
* | Author: jixuecong@chowa.cn
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
import { EjyyIotEntrance } from '~/types/model';
|
||||
import utils from '~/utils';
|
||||
|
||||
interface StatusParams {
|
||||
Serial: string;
|
||||
Status: string;
|
||||
Input: string;
|
||||
Ver: string;
|
||||
ID: string;
|
||||
IP: string;
|
||||
MAC: string;
|
||||
Now: string;
|
||||
Crc: string;
|
||||
T1: string;
|
||||
H1: string;
|
||||
T2: string;
|
||||
H2: string;
|
||||
NextNum: string;
|
||||
Key: string;
|
||||
}
|
||||
|
||||
interface StatusResponse {
|
||||
Key: string;
|
||||
Now: string;
|
||||
}
|
||||
|
||||
async function statusMethod(params: StatusParams, entranceInfo: EjyyIotEntrance): Promise<StatusResponse> {
|
||||
const mom = moment();
|
||||
|
||||
await utils.redis.set(`entrance${entranceInfo.id}`, mom.valueOf().toString());
|
||||
|
||||
return {
|
||||
Key: params.Key,
|
||||
Now: `${mom.format('YYYYMMDDHHmmss')}${mom.day()}`
|
||||
};
|
||||
}
|
||||
|
||||
export default statusMethod;
|
18
server/src/iot/index.ts
Normal file
18
server/src/iot/index.ts
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | 「e家宜业」 —— 助力物业服务升级,用心服务万千业主
|
||||
* +----------------------------------------------------------------------
|
||||
* | Copyright (c) 2020~2021 https://www.chowa.com All rights reserved.
|
||||
* +----------------------------------------------------------------------
|
||||
* | Licensed 未经许可不能去掉「e家宜业」和「卓瓦科技」相关版权
|
||||
* +----------------------------------------------------------------------
|
||||
* | Author: jixuecong@chowa.cn
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import KoaRouter from 'koa-router';
|
||||
import IotEntrance from './entrance';
|
||||
|
||||
export function init(appRouter: KoaRouter) {
|
||||
IotEntrance(appRouter);
|
||||
}
|
@ -12,6 +12,8 @@
|
||||
|
||||
import { Action } from '~/types/action';
|
||||
import { SUCCESS } from '~/constant/code';
|
||||
import utils from '~/utils';
|
||||
import { SELF_ACCESS_QRCODE } from '~/constant/open_access';
|
||||
|
||||
interface RequestBody {
|
||||
building_ids: number[];
|
||||
@ -43,11 +45,22 @@ const MpAccessListAction = <Action>{
|
||||
response: async ctx => {
|
||||
const { building_ids, community_id } = <RequestBody>ctx.request.body;
|
||||
|
||||
const cardList = await ctx.model
|
||||
.from('ejyy_building_access')
|
||||
.whereIn('building_id', building_ids)
|
||||
.select('building_id', 'uid')
|
||||
.orderBy('id', 'desc');
|
||||
const buildings = await ctx.model
|
||||
.from('ejyy_user_building')
|
||||
.leftJoin('ejyy_building_info', 'ejyy_building_info.id', 'ejyy_user_building.building_id')
|
||||
.where('ejyy_building_info.community_id', community_id)
|
||||
.whereIn('ejyy_user_building.building_id', building_ids)
|
||||
.select('ejyy_user_building.building_id')
|
||||
.orderBy('ejyy_user_building.id', 'desc');
|
||||
|
||||
const cardList = [];
|
||||
|
||||
buildings.forEach(({ building_id }) => {
|
||||
cardList.push({
|
||||
building_id,
|
||||
uid: utils.access.encrypt(ctx.mpUserInfo.id, building_id, SELF_ACCESS_QRCODE)
|
||||
});
|
||||
});
|
||||
|
||||
const entranceList = await ctx.model
|
||||
.from('ejyy_iot_entrance')
|
||||
|
@ -41,7 +41,7 @@ const MpCommunityBindingByFamliyAction = <Action>{
|
||||
response: async ctx => {
|
||||
const { qrcontent } = <RequestBody>ctx.request.body;
|
||||
|
||||
const qrInfo = utils.community.decode(qrcontent);
|
||||
const qrInfo = utils.community.decrypt(qrcontent);
|
||||
|
||||
if (
|
||||
!qrInfo.success ||
|
||||
|
@ -39,7 +39,7 @@ const MpCommunityBindingByPropertyAction = <Action>{
|
||||
},
|
||||
response: async ctx => {
|
||||
const { qrcontent } = <RequestBody>ctx.request.body;
|
||||
const qrInfo = utils.community.decode(qrcontent);
|
||||
const qrInfo = utils.community.decrypt(qrcontent);
|
||||
|
||||
if (!qrInfo.success || qrInfo.authenticated_type !== AUTHENTICTED_BY_PROPERTY_COMPANY) {
|
||||
return (ctx.body = {
|
||||
|
@ -41,7 +41,7 @@ const MpCommunityFamliyCodeAction = <Action>{
|
||||
response: async ctx => {
|
||||
const { building_ids } = <RequestBody>ctx.request.body;
|
||||
|
||||
const content = utils.community.encode(building_ids, AUTHENTICTED_BY_FAMILY, ctx.mpUserInfo.id);
|
||||
const content = utils.community.encrypt(building_ids, AUTHENTICTED_BY_FAMILY, ctx.mpUserInfo.id);
|
||||
|
||||
ctx.body = {
|
||||
code: SUCCESS,
|
||||
|
@ -12,8 +12,7 @@
|
||||
|
||||
import { Action } from '~/types/action';
|
||||
import { SUCCESS } from '~/constant/code';
|
||||
import config from '~/config';
|
||||
import crypto from 'crypto';
|
||||
import utils from '~/utils';
|
||||
|
||||
const MpUserCardAction = <Action>{
|
||||
router: {
|
||||
@ -24,15 +23,10 @@ const MpUserCardAction = <Action>{
|
||||
},
|
||||
|
||||
response: async ctx => {
|
||||
const cipher = crypto.createCipheriv('aes-256-cbc', config.crypto.key, config.crypto.iv);
|
||||
let crypted = cipher.update(`${ctx.mpUserInfo.id}-${Date.now()}`, 'utf8', 'hex');
|
||||
|
||||
crypted += cipher.final('hex');
|
||||
|
||||
ctx.body = {
|
||||
code: SUCCESS,
|
||||
data: {
|
||||
uid: crypted
|
||||
uid: utils.crypto.encrypt(`${ctx.mpUserInfo.id}-${Date.now()}`)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -13,7 +13,6 @@
|
||||
import { Action } from '~/types/action';
|
||||
import { SUCCESS, STATUS_ERROR } from '~/constant/code';
|
||||
import { BINDING_BUILDING, TRUE, FALSE } from '~/constant/status';
|
||||
import { VISTOR_ACCESS_QRCODE } from '~/constant/open_access';
|
||||
import utils from '~/utils';
|
||||
import * as vistorService from '~/service/vistor';
|
||||
import moment from 'moment';
|
||||
@ -117,13 +116,6 @@ const MpVistorCreateAction = <Action>{
|
||||
created_at: Date.now()
|
||||
});
|
||||
|
||||
const uid = utils.openAccess.encode(id, building_id, VISTOR_ACCESS_QRCODE);
|
||||
|
||||
await ctx.model
|
||||
.from('ejyy_vistor')
|
||||
.update({ uid })
|
||||
.where('id', id);
|
||||
|
||||
if (vistorInfo) {
|
||||
vistorService.pushAccessToVistor(ctx.model, vistorInfo, id, expire);
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
import { Action } from '~/types/action';
|
||||
import { SUCCESS } from '~/constant/code';
|
||||
import { VISTOR_ACCESS_QRCODE } from '~/constant/open_access';
|
||||
import utils from '~/utils';
|
||||
|
||||
interface RequestParams {
|
||||
@ -51,6 +52,7 @@ const MpVistorDetailAction = <Action>{
|
||||
'ejyy_vistor.expire',
|
||||
'ejyy_vistor.used_at',
|
||||
'ejyy_vistor.created_at',
|
||||
'ejyy_vistor.building_id',
|
||||
'ejyy_building_info.type',
|
||||
'ejyy_building_info.area',
|
||||
'ejyy_building_info.building',
|
||||
@ -65,10 +67,13 @@ const MpVistorDetailAction = <Action>{
|
||||
|
||||
detail.vistor_phone = utils.phone.hide(detail.vistor_phone);
|
||||
|
||||
const uid = utils.access.encrypt(detail.id, detail.building_id, VISTOR_ACCESS_QRCODE);
|
||||
|
||||
ctx.body = {
|
||||
code: SUCCESS,
|
||||
data: {
|
||||
...detail
|
||||
...detail,
|
||||
uid
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -12,6 +12,8 @@
|
||||
|
||||
import { Action } from '~/types/action';
|
||||
import { SUCCESS } from '~/constant/code';
|
||||
import { VISTOR_ACCESS_QRCODE } from '~/constant/open_access';
|
||||
import utils from '~/utils';
|
||||
|
||||
interface RequestParams {
|
||||
id: number;
|
||||
@ -47,6 +49,7 @@ const MpVistorUseAction = <Action>{
|
||||
'ejyy_vistor.expire',
|
||||
'ejyy_vistor.used_at',
|
||||
'ejyy_vistor.created_at',
|
||||
'ejyy_vistor.building_id',
|
||||
'ejyy_building_info.type',
|
||||
'ejyy_building_info.area',
|
||||
'ejyy_building_info.building',
|
||||
@ -59,10 +62,13 @@ const MpVistorUseAction = <Action>{
|
||||
.where('ejyy_vistor.id', id)
|
||||
.first();
|
||||
|
||||
const uid = utils.access.encrypt(detail.id, detail.building_id, VISTOR_ACCESS_QRCODE);
|
||||
|
||||
ctx.body = {
|
||||
code: SUCCESS,
|
||||
data: {
|
||||
...detail
|
||||
...detail,
|
||||
uid
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ import { SUCCESS } from '~/constant/code';
|
||||
import * as ROLE from '~/constant/role_access';
|
||||
import utils from '~/utils';
|
||||
import { HOUSE, CARPORT, WAREHOUSE, MERCHANT, GARAGE } from '~/constant/building';
|
||||
import { SELF_ACCESS_QRCODE } from '~/constant/open_access';
|
||||
|
||||
interface Building {
|
||||
type: typeof HOUSE | typeof CARPORT | typeof WAREHOUSE;
|
||||
@ -127,18 +126,6 @@ const PcBuildingImportAction = <Action>{
|
||||
});
|
||||
}
|
||||
|
||||
if (type === HOUSE) {
|
||||
const [accessId] = await ctx.model.from('ejyy_building_access').insert({
|
||||
building_id: insertId,
|
||||
created_at
|
||||
});
|
||||
const uid = utils.openAccess.encode(accessId, insertId, SELF_ACCESS_QRCODE);
|
||||
await ctx.model
|
||||
.from('ejyy_building_access')
|
||||
.update({ uid })
|
||||
.where('id', accessId);
|
||||
}
|
||||
|
||||
ids.push(insertId);
|
||||
}
|
||||
|
||||
|
@ -17,8 +17,6 @@ import { NORMAL_STATUS, TRUE, BINDING_BUILDING } from '~/constant/status';
|
||||
import { HOUSE, CARPORT, WAREHOUSE, MERCHANT, GARAGE } from '~/constant/building';
|
||||
import * as ROLE from '~/constant/role_access';
|
||||
import utils from '~/utils';
|
||||
import config from '~/config';
|
||||
import crypto from 'crypto';
|
||||
|
||||
interface RequestBody {
|
||||
uid: string;
|
||||
@ -57,21 +55,17 @@ const PcOptionCardAction = <Action>{
|
||||
},
|
||||
response: async ctx => {
|
||||
const { uid, community_id } = <RequestBody>ctx.request.body;
|
||||
let userId = null;
|
||||
let stamp = null;
|
||||
const origin = utils.crypto.decrypt(uid);
|
||||
|
||||
try {
|
||||
const cipher = crypto.createDecipheriv('aes-256-cbc', config.crypto.key, config.crypto.iv);
|
||||
const decrypted = cipher.update(uid, 'hex', 'utf8');
|
||||
const origin = decrypted + cipher.final('utf8');
|
||||
[userId, stamp] = origin.split('-');
|
||||
} catch (e) {
|
||||
if (!origin) {
|
||||
return (ctx.body = {
|
||||
code: QUERY_ILLEFAL,
|
||||
message: '非法业主名片'
|
||||
});
|
||||
}
|
||||
|
||||
const [userId, stamp] = origin.split('-');
|
||||
|
||||
if (!/^\d+$/.test(userId) || !/^\d{13}$/.test(stamp)) {
|
||||
return (ctx.body = {
|
||||
code: QUERY_ILLEFAL,
|
||||
|
@ -61,7 +61,7 @@ const PcOwerApproveAction = <Action>{
|
||||
});
|
||||
}
|
||||
|
||||
const res = utils.community.encode(building_ids, AUTHENTICTED_BY_PROPERTY_COMPANY, ctx.pcUserInfo.id);
|
||||
const res = utils.community.encrypt(building_ids, AUTHENTICTED_BY_PROPERTY_COMPANY, ctx.pcUserInfo.id);
|
||||
|
||||
ctx.body = {
|
||||
code: SUCCESS,
|
||||
|
@ -110,17 +110,12 @@ const PcVistorCreateAction = <Action>{
|
||||
created_at: Date.now()
|
||||
});
|
||||
|
||||
const uid = utils.openAccess.encode(id, building_id, VISTOR_ACCESS_QRCODE);
|
||||
|
||||
await ctx.model
|
||||
.from('ejyy_vistor')
|
||||
.update({ uid })
|
||||
.where('id', id);
|
||||
|
||||
if (vistorInfo) {
|
||||
vistorService.pushAccessToVistor(ctx.model, vistorInfo, id, expire);
|
||||
}
|
||||
|
||||
const uid = utils.access.encrypt(id, building_id, VISTOR_ACCESS_QRCODE);
|
||||
|
||||
ctx.body = {
|
||||
code: SUCCESS,
|
||||
data: {
|
||||
|
@ -13,6 +13,8 @@
|
||||
import { Action } from '~/types/action';
|
||||
import { SUCCESS, QUERY_ILLEFAL } from '~/constant/code';
|
||||
import * as ROLE from '~/constant/role_access';
|
||||
import { VISTOR_ACCESS_QRCODE } from '~/constant/open_access';
|
||||
import utils from '~/utils';
|
||||
|
||||
interface RequestBody {
|
||||
id: number;
|
||||
@ -94,10 +96,13 @@ const PcVistorDetailAction = <Action>{
|
||||
|
||||
delete info.property_company_user_id;
|
||||
|
||||
const uid = utils.access.encrypt(info.id, info.building_id, VISTOR_ACCESS_QRCODE);
|
||||
|
||||
ctx.body = {
|
||||
code: SUCCESS,
|
||||
data: {
|
||||
info,
|
||||
uid,
|
||||
registrant
|
||||
}
|
||||
};
|
||||
|
@ -45,7 +45,7 @@ const PcVistorScanAction = <Action>{
|
||||
},
|
||||
response: async ctx => {
|
||||
const { uid, community_id } = <RequestBody>ctx.request.body;
|
||||
const { id, building_id, type, success } = utils.openAccess.decode(uid);
|
||||
const { id, building_id, type, success } = utils.access.decrypt(uid);
|
||||
|
||||
if (!success || type !== VISTOR_ACCESS_QRCODE) {
|
||||
return (ctx.body = {
|
||||
|
9
server/src/types/model.d.ts
vendored
9
server/src/types/model.d.ts
vendored
@ -415,13 +415,6 @@ declare namespace EjyyModel {
|
||||
replyed_at?: number;
|
||||
}
|
||||
|
||||
interface EjyyBuildingAccess {
|
||||
id?: number;
|
||||
building_id: number;
|
||||
uid?: string;
|
||||
created_at: number;
|
||||
}
|
||||
|
||||
interface EjyyRepair {
|
||||
id?: number;
|
||||
wechat_mp_user_id?: number;
|
||||
@ -656,7 +649,6 @@ declare namespace EjyyModel {
|
||||
vistor_phone: string;
|
||||
car_number?: string;
|
||||
have_vistor_info: typeof TRUE | typeof FALSE;
|
||||
uid?: string;
|
||||
expire: number;
|
||||
used_at?: number;
|
||||
scan_by?: number;
|
||||
@ -1355,7 +1347,6 @@ declare module 'knex/types/tables' {
|
||||
ejyy_notice_to_user_readed: EjyyModel.EjyyNoticeToUserReaded;
|
||||
ejyy_notice_tpl: EjyyModel.EjyyNoticeTpl;
|
||||
ejyy_feedback: EjyyModel.EjyyFeedback;
|
||||
ejyy_building_access: EjyyModel.EjyyBuildingAccess;
|
||||
ejyy_repair: EjyyModel.EjyyRepair;
|
||||
ejyy_repair_urge: EjyyModel.EjyyRepairUrge;
|
||||
ejyy_complain: EjyyModel.EjyyComplain;
|
||||
|
@ -10,39 +10,32 @@
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import crypto from 'crypto';
|
||||
import config from '~/config';
|
||||
import * as crypto from './crypto';
|
||||
|
||||
import { SELF_ACCESS_QRCODE, VISTOR_ACCESS_QRCODE } from '~/constant/open_access';
|
||||
|
||||
interface DecodeResult {
|
||||
interface DecryptResult {
|
||||
id?: number;
|
||||
building_id?: number;
|
||||
type?: typeof SELF_ACCESS_QRCODE | typeof VISTOR_ACCESS_QRCODE;
|
||||
stamp?: number;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
// 区分访客二维码和自己的二维码
|
||||
export function encode(
|
||||
// 访客二维码 id 为 vistor_id 自己的二维码为user_id
|
||||
export function encrypt(
|
||||
id: number,
|
||||
building_id: number,
|
||||
type: typeof SELF_ACCESS_QRCODE | typeof VISTOR_ACCESS_QRCODE
|
||||
): string {
|
||||
const cipher = crypto.createCipheriv('aes-256-cbc', config.crypto.key, config.crypto.iv);
|
||||
let crypted = cipher.update(`${id}#${building_id}#${type}`, 'utf8', 'hex');
|
||||
|
||||
crypted += cipher.final('hex');
|
||||
|
||||
return crypted;
|
||||
return crypto.encrypt(`${id}#${building_id}#${type}${Date.now()}`);
|
||||
}
|
||||
|
||||
export function decode(uid: string): DecodeResult {
|
||||
let origin = null;
|
||||
export function decrypt(uid: string): DecryptResult {
|
||||
const origin = crypto.decrypt(uid);
|
||||
|
||||
try {
|
||||
const cipher = crypto.createDecipheriv('aes-256-cbc', config.crypto.key, config.crypto.iv);
|
||||
const decrypted = cipher.update(uid, 'hex', 'utf8');
|
||||
origin = decrypted + cipher.final('utf8');
|
||||
} catch (e) {
|
||||
if (!origin) {
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
@ -50,9 +43,15 @@ export function decode(uid: string): DecodeResult {
|
||||
const id = parseInt(arr[0], 10);
|
||||
const building_id = parseInt(arr[1], 10);
|
||||
const type = <typeof SELF_ACCESS_QRCODE | typeof VISTOR_ACCESS_QRCODE>parseInt(arr[2], 10);
|
||||
const stamp = parseInt(arr[3], 10);
|
||||
let success = true;
|
||||
|
||||
if ((type !== SELF_ACCESS_QRCODE && type !== VISTOR_ACCESS_QRCODE) || building_id === NaN || id === NaN) {
|
||||
if (
|
||||
(type !== SELF_ACCESS_QRCODE && type !== VISTOR_ACCESS_QRCODE) ||
|
||||
building_id === NaN ||
|
||||
id === NaN ||
|
||||
/^\d{13}$/.test(stamp.toString())
|
||||
) {
|
||||
success = false;
|
||||
}
|
||||
|
||||
@ -60,6 +59,7 @@ export function decode(uid: string): DecodeResult {
|
||||
id,
|
||||
building_id,
|
||||
type,
|
||||
stamp,
|
||||
success
|
||||
};
|
||||
}
|
@ -10,8 +10,7 @@
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import crypto from 'crypto';
|
||||
import config from '~/config';
|
||||
import * as crypto from './crypto';
|
||||
import { AUTHENTICTED_BY_PROPERTY_COMPANY, AUTHENTICTED_BY_FAMILY } from '~/constant/authenticated_type';
|
||||
|
||||
interface DecodeResult {
|
||||
@ -106,7 +105,7 @@ function fake(len: number): string {
|
||||
return ret.join('');
|
||||
}
|
||||
|
||||
export function encode(
|
||||
export function encrypt(
|
||||
building_ids: number[],
|
||||
authenticated_type: typeof AUTHENTICTED_BY_PROPERTY_COMPANY | typeof AUTHENTICTED_BY_FAMILY,
|
||||
user_id: number
|
||||
@ -120,25 +119,16 @@ export function encode(
|
||||
|
||||
ret.push(fake(8));
|
||||
|
||||
const cipher = crypto.createCipheriv('aes-256-cbc', config.crypto.key, config.crypto.iv);
|
||||
let crypted = cipher.update(ret.join(''), 'utf8', 'base64');
|
||||
|
||||
crypted += cipher.final('base64');
|
||||
|
||||
return {
|
||||
text: crypted,
|
||||
text: crypto.encrypt(ret.join('')),
|
||||
stamp
|
||||
};
|
||||
}
|
||||
|
||||
export function decode(text: string): DecodeResult {
|
||||
let str = null;
|
||||
export function decrypt(text: string): DecodeResult {
|
||||
let str = crypto.decrypt(text);
|
||||
|
||||
try {
|
||||
const cipher = crypto.createDecipheriv('aes-256-cbc', config.crypto.key, config.crypto.iv);
|
||||
let decrypted = cipher.update(text, 'base64', 'utf8');
|
||||
str = decrypted + cipher.final('utf8');
|
||||
} catch (e) {
|
||||
if (!str) {
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
*/
|
||||
|
||||
import crypto from 'crypto';
|
||||
import config from '~/config';
|
||||
|
||||
export function md5(str: string, upperCase = false): string {
|
||||
const hash = crypto.createHash('md5');
|
||||
@ -21,3 +22,26 @@ export function md5(str: string, upperCase = false): string {
|
||||
|
||||
return upperCase ? ret.toUpperCase() : ret;
|
||||
}
|
||||
|
||||
export function encrypt(str: string): string {
|
||||
const cipher = crypto.createCipheriv('aes-256-cbc', config.crypto.key, config.crypto.iv);
|
||||
let crypted = cipher.update(str, 'utf8', 'hex');
|
||||
|
||||
crypted += cipher.final('hex');
|
||||
|
||||
return crypted;
|
||||
}
|
||||
|
||||
export function decrypt(str: string): string {
|
||||
let result = null;
|
||||
|
||||
try {
|
||||
const cipher = crypto.createDecipheriv('aes-256-cbc', config.crypto.key, config.crypto.iv);
|
||||
const decrypted = cipher.update(str, 'hex', 'utf8');
|
||||
result = decrypted + cipher.final('utf8');
|
||||
} catch (e) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
*/
|
||||
|
||||
import * as crypto from './crypto';
|
||||
import * as openAccess from './open_access';
|
||||
import * as access from './access';
|
||||
import * as phone from './phone';
|
||||
import * as community from './community';
|
||||
import * as building from './building';
|
||||
@ -20,10 +20,11 @@ import * as order from './order';
|
||||
import * as text from './text';
|
||||
import * as idcard from './idcard';
|
||||
import * as mail from './mail';
|
||||
import * as redis from './redis';
|
||||
|
||||
const utils = {
|
||||
crypto,
|
||||
openAccess,
|
||||
access,
|
||||
phone,
|
||||
community,
|
||||
building,
|
||||
@ -31,7 +32,8 @@ const utils = {
|
||||
order,
|
||||
text,
|
||||
idcard,
|
||||
mail
|
||||
mail,
|
||||
redis
|
||||
};
|
||||
|
||||
export default utils;
|
||||
|
58
server/src/utils/redis.ts
Normal file
58
server/src/utils/redis.ts
Normal file
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* +----------------------------------------------------------------------
|
||||
* | 「e家宜业」 —— 助力物业服务升级,用心服务万千业主
|
||||
* +----------------------------------------------------------------------
|
||||
* | Copyright (c) 2020~2021 https://www.chowa.com All rights reserved.
|
||||
* +----------------------------------------------------------------------
|
||||
* | Licensed 未经许可不能去掉「e家宜业」和「卓瓦科技」相关版权
|
||||
* +----------------------------------------------------------------------
|
||||
* | Author: jixuecong@chowa.cn
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import redis from 'redis';
|
||||
import config from '~/config';
|
||||
|
||||
const client = config.debug ? null : redis.createClient(config.redis);
|
||||
|
||||
const ERROR = 'REDIS_ERROR';
|
||||
const SUCCESS = 'OK';
|
||||
|
||||
export type ERROR_TYPE = typeof ERROR;
|
||||
export type SUCCESS_TYPE = typeof SUCCESS;
|
||||
|
||||
const storage = {};
|
||||
|
||||
export async function set(key: string, value: string): Promise<ERROR_TYPE | SUCCESS_TYPE> {
|
||||
if (config.debug) {
|
||||
storage[key] = value;
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
client.set(key, value, (err, reply) => {
|
||||
if (err) {
|
||||
resolve(ERROR);
|
||||
} else {
|
||||
resolve(reply);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function get(key: string): Promise<ERROR_TYPE | string> {
|
||||
if (config.debug) {
|
||||
return storage[key];
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
client.get(key, (err, reply) => {
|
||||
if (err) {
|
||||
resolve(ERROR);
|
||||
} else {
|
||||
resolve(reply);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
@ -151,7 +151,8 @@ export default {
|
||||
fetching: true,
|
||||
detail: {
|
||||
info: {},
|
||||
registrant: {}
|
||||
registrant: {},
|
||||
uid: ''
|
||||
},
|
||||
now: Date.now()
|
||||
};
|
||||
@ -175,7 +176,7 @@ export default {
|
||||
this.detail = res.data;
|
||||
|
||||
this.$nextTick(() => {
|
||||
qrcode.toCanvas(this.$refs.canvas, res.data.info.uid, {
|
||||
qrcode.toCanvas(this.$refs.canvas, res.data.uid, {
|
||||
width: 220,
|
||||
height: 220,
|
||||
margin: 2
|
||||
|
Loading…
x
Reference in New Issue
Block a user