优化门禁、访客码、业主二维码加密方式&添加智能门禁物联网模块
This commit is contained in:
parent
f5bedbe367
commit
9708e1f673
@ -1,3 +1,20 @@
|
|||||||
## 「e家宜业」物联网服务端
|
## 「e家宜业」物联网服务端
|
||||||
|
|
||||||
> 代码整理中,敬请期待
|
> 代码整理中,敬请期待
|
||||||
|
|
||||||
|
### 智能门禁
|
||||||
|
|
||||||
|
> 目前仅支持卡帕斯品牌
|
||||||
|
|
||||||
|
#### 接口地址
|
||||||
|
|
||||||
|
- 心跳检测 `域名/iot/entrance?id={{接口形参第一位}}&community_id={{接口形参第二位}}&method=status`
|
||||||
|
- 开门刷卡 `域名/iot/entrance?id={{接口形参第一位}}&community_id={{接口形参第二位}}&method=access`
|
||||||
|
|
||||||
|
#### 门禁配置
|
||||||
|
|
||||||
|
* 选择`启用AES128加密`,秘钥配置中台对应的秘钥
|
||||||
|
* 心跳间隔设置为`60`
|
||||||
|
* `本机信息-标识`配置为中台对应的标识名称
|
||||||
|
* `通讯协议`选择`启用Http通讯`
|
||||||
|
* `验证模式`选择`脱机验证优先`(主要针对老旧小区用的RFID卡)
|
||||||
|
@ -97,7 +97,7 @@ CwPage({
|
|||||||
},
|
},
|
||||||
chooseContact() {
|
chooseContact() {
|
||||||
wx.chooseContact({
|
wx.chooseContact({
|
||||||
success: (res) => {
|
success: res => {
|
||||||
this.setData({
|
this.setData({
|
||||||
vistor_name: res.displayName,
|
vistor_name: res.displayName,
|
||||||
vistor_phone: res.phoneNumber
|
vistor_phone: res.phoneNumber
|
||||||
|
@ -32,13 +32,6 @@ CREATE TABLE `ejyy_ask_for_leave_flow` (
|
|||||||
`finished_at` bigint(13) DEFAULT NULL
|
`finished_at` bigint(13) DEFAULT NULL
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) 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` (
|
CREATE TABLE `ejyy_building_info` (
|
||||||
`id` int(11) NOT NULL,
|
`id` int(11) NOT NULL,
|
||||||
`community_id` bigint(20) NOT NULL,
|
`community_id` bigint(20) NOT NULL,
|
||||||
@ -1092,7 +1085,6 @@ CREATE TABLE `ejyy_vistor` (
|
|||||||
`vistor_phone` varchar(11) NOT NULL,
|
`vistor_phone` varchar(11) NOT NULL,
|
||||||
`car_number` varchar(8) DEFAULT NULL,
|
`car_number` varchar(8) DEFAULT NULL,
|
||||||
`have_vistor_info` tinyint(1) NOT NULL DEFAULT 0,
|
`have_vistor_info` tinyint(1) NOT NULL DEFAULT 0,
|
||||||
`uid` varchar(128) DEFAULT NULL,
|
|
||||||
`expire` bigint(13) NOT NULL,
|
`expire` bigint(13) NOT NULL,
|
||||||
`used_at` bigint(13) DEFAULT NULL,
|
`used_at` bigint(13) DEFAULT NULL,
|
||||||
`scan_by` bigint(20) DEFAULT NULL,
|
`scan_by` bigint(20) DEFAULT NULL,
|
||||||
|
@ -33,6 +33,7 @@ import ModelMiddleware from '~/middleware/model';
|
|||||||
import IpMiddleware from '~/middleware/ip';
|
import IpMiddleware from '~/middleware/ip';
|
||||||
import HeaderMiddleware from '~/middleware/header';
|
import HeaderMiddleware from '~/middleware/header';
|
||||||
import WatcherMiddleware from '~/middleware/watcher';
|
import WatcherMiddleware from '~/middleware/watcher';
|
||||||
|
import * as iot from '~/iot';
|
||||||
|
|
||||||
if (cluster.isMaster) {
|
if (cluster.isMaster) {
|
||||||
cwlog.success(`main process ${process.pid}`);
|
cwlog.success(`main process ${process.pid}`);
|
||||||
@ -57,6 +58,15 @@ if (cluster.isMaster) {
|
|||||||
NotifyModule(router);
|
NotifyModule(router);
|
||||||
OaModule(router);
|
OaModule(router);
|
||||||
|
|
||||||
|
// WebSocket
|
||||||
|
wss.init(server);
|
||||||
|
|
||||||
|
// for socket
|
||||||
|
redisService.subscribe();
|
||||||
|
|
||||||
|
// 物联网
|
||||||
|
iot.init(router);
|
||||||
|
|
||||||
app.use(KoaBodyMiddleware({ multipart: true }))
|
app.use(KoaBodyMiddleware({ multipart: true }))
|
||||||
.use(
|
.use(
|
||||||
KoaLogMiddleware({
|
KoaLogMiddleware({
|
||||||
@ -80,12 +90,6 @@ if (cluster.isMaster) {
|
|||||||
.use(router.routes())
|
.use(router.routes())
|
||||||
.use(WatcherMiddleware());
|
.use(WatcherMiddleware());
|
||||||
|
|
||||||
// WebSocket
|
|
||||||
wss.init(server);
|
|
||||||
|
|
||||||
// for socket
|
|
||||||
redisService.subscribe();
|
|
||||||
|
|
||||||
const port = process.env.port ? parseInt(process.env.port, 10) : config.server.port;
|
const port = process.env.port ? parseInt(process.env.port, 10) : config.server.port;
|
||||||
|
|
||||||
server.listen(port, '0.0.0.0', () => {
|
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 PARK_BLACKLIST_EXIST = -172;
|
||||||
// 预警中控名称存在
|
// 预警中控名称存在
|
||||||
export const WARNING_NAME_EXIST = -173;
|
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 { Action } from '~/types/action';
|
||||||
import { SUCCESS } from '~/constant/code';
|
import { SUCCESS } from '~/constant/code';
|
||||||
|
import utils from '~/utils';
|
||||||
|
import { SELF_ACCESS_QRCODE } from '~/constant/open_access';
|
||||||
|
|
||||||
interface RequestBody {
|
interface RequestBody {
|
||||||
building_ids: number[];
|
building_ids: number[];
|
||||||
@ -43,11 +45,22 @@ const MpAccessListAction = <Action>{
|
|||||||
response: async ctx => {
|
response: async ctx => {
|
||||||
const { building_ids, community_id } = <RequestBody>ctx.request.body;
|
const { building_ids, community_id } = <RequestBody>ctx.request.body;
|
||||||
|
|
||||||
const cardList = await ctx.model
|
const buildings = await ctx.model
|
||||||
.from('ejyy_building_access')
|
.from('ejyy_user_building')
|
||||||
.whereIn('building_id', building_ids)
|
.leftJoin('ejyy_building_info', 'ejyy_building_info.id', 'ejyy_user_building.building_id')
|
||||||
.select('building_id', 'uid')
|
.where('ejyy_building_info.community_id', community_id)
|
||||||
.orderBy('id', 'desc');
|
.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
|
const entranceList = await ctx.model
|
||||||
.from('ejyy_iot_entrance')
|
.from('ejyy_iot_entrance')
|
||||||
|
@ -41,7 +41,7 @@ const MpCommunityBindingByFamliyAction = <Action>{
|
|||||||
response: async ctx => {
|
response: async ctx => {
|
||||||
const { qrcontent } = <RequestBody>ctx.request.body;
|
const { qrcontent } = <RequestBody>ctx.request.body;
|
||||||
|
|
||||||
const qrInfo = utils.community.decode(qrcontent);
|
const qrInfo = utils.community.decrypt(qrcontent);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!qrInfo.success ||
|
!qrInfo.success ||
|
||||||
|
@ -39,7 +39,7 @@ const MpCommunityBindingByPropertyAction = <Action>{
|
|||||||
},
|
},
|
||||||
response: async ctx => {
|
response: async ctx => {
|
||||||
const { qrcontent } = <RequestBody>ctx.request.body;
|
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) {
|
if (!qrInfo.success || qrInfo.authenticated_type !== AUTHENTICTED_BY_PROPERTY_COMPANY) {
|
||||||
return (ctx.body = {
|
return (ctx.body = {
|
||||||
|
@ -41,7 +41,7 @@ const MpCommunityFamliyCodeAction = <Action>{
|
|||||||
response: async ctx => {
|
response: async ctx => {
|
||||||
const { building_ids } = <RequestBody>ctx.request.body;
|
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 = {
|
ctx.body = {
|
||||||
code: SUCCESS,
|
code: SUCCESS,
|
||||||
|
@ -12,8 +12,7 @@
|
|||||||
|
|
||||||
import { Action } from '~/types/action';
|
import { Action } from '~/types/action';
|
||||||
import { SUCCESS } from '~/constant/code';
|
import { SUCCESS } from '~/constant/code';
|
||||||
import config from '~/config';
|
import utils from '~/utils';
|
||||||
import crypto from 'crypto';
|
|
||||||
|
|
||||||
const MpUserCardAction = <Action>{
|
const MpUserCardAction = <Action>{
|
||||||
router: {
|
router: {
|
||||||
@ -24,15 +23,10 @@ const MpUserCardAction = <Action>{
|
|||||||
},
|
},
|
||||||
|
|
||||||
response: async ctx => {
|
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 = {
|
ctx.body = {
|
||||||
code: SUCCESS,
|
code: SUCCESS,
|
||||||
data: {
|
data: {
|
||||||
uid: crypted
|
uid: utils.crypto.encrypt(`${ctx.mpUserInfo.id}-${Date.now()}`)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
import { Action } from '~/types/action';
|
import { Action } from '~/types/action';
|
||||||
import { SUCCESS, STATUS_ERROR } from '~/constant/code';
|
import { SUCCESS, STATUS_ERROR } from '~/constant/code';
|
||||||
import { BINDING_BUILDING, TRUE, FALSE } from '~/constant/status';
|
import { BINDING_BUILDING, TRUE, FALSE } from '~/constant/status';
|
||||||
import { VISTOR_ACCESS_QRCODE } from '~/constant/open_access';
|
|
||||||
import utils from '~/utils';
|
import utils from '~/utils';
|
||||||
import * as vistorService from '~/service/vistor';
|
import * as vistorService from '~/service/vistor';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
@ -117,13 +116,6 @@ const MpVistorCreateAction = <Action>{
|
|||||||
created_at: Date.now()
|
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) {
|
if (vistorInfo) {
|
||||||
vistorService.pushAccessToVistor(ctx.model, vistorInfo, id, expire);
|
vistorService.pushAccessToVistor(ctx.model, vistorInfo, id, expire);
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
import { Action } from '~/types/action';
|
import { Action } from '~/types/action';
|
||||||
import { SUCCESS } from '~/constant/code';
|
import { SUCCESS } from '~/constant/code';
|
||||||
|
import { VISTOR_ACCESS_QRCODE } from '~/constant/open_access';
|
||||||
import utils from '~/utils';
|
import utils from '~/utils';
|
||||||
|
|
||||||
interface RequestParams {
|
interface RequestParams {
|
||||||
@ -51,6 +52,7 @@ const MpVistorDetailAction = <Action>{
|
|||||||
'ejyy_vistor.expire',
|
'ejyy_vistor.expire',
|
||||||
'ejyy_vistor.used_at',
|
'ejyy_vistor.used_at',
|
||||||
'ejyy_vistor.created_at',
|
'ejyy_vistor.created_at',
|
||||||
|
'ejyy_vistor.building_id',
|
||||||
'ejyy_building_info.type',
|
'ejyy_building_info.type',
|
||||||
'ejyy_building_info.area',
|
'ejyy_building_info.area',
|
||||||
'ejyy_building_info.building',
|
'ejyy_building_info.building',
|
||||||
@ -65,10 +67,13 @@ const MpVistorDetailAction = <Action>{
|
|||||||
|
|
||||||
detail.vistor_phone = utils.phone.hide(detail.vistor_phone);
|
detail.vistor_phone = utils.phone.hide(detail.vistor_phone);
|
||||||
|
|
||||||
|
const uid = utils.access.encrypt(detail.id, detail.building_id, VISTOR_ACCESS_QRCODE);
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
code: SUCCESS,
|
code: SUCCESS,
|
||||||
data: {
|
data: {
|
||||||
...detail
|
...detail,
|
||||||
|
uid
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
|
|
||||||
import { Action } from '~/types/action';
|
import { Action } from '~/types/action';
|
||||||
import { SUCCESS } from '~/constant/code';
|
import { SUCCESS } from '~/constant/code';
|
||||||
|
import { VISTOR_ACCESS_QRCODE } from '~/constant/open_access';
|
||||||
|
import utils from '~/utils';
|
||||||
|
|
||||||
interface RequestParams {
|
interface RequestParams {
|
||||||
id: number;
|
id: number;
|
||||||
@ -47,6 +49,7 @@ const MpVistorUseAction = <Action>{
|
|||||||
'ejyy_vistor.expire',
|
'ejyy_vistor.expire',
|
||||||
'ejyy_vistor.used_at',
|
'ejyy_vistor.used_at',
|
||||||
'ejyy_vistor.created_at',
|
'ejyy_vistor.created_at',
|
||||||
|
'ejyy_vistor.building_id',
|
||||||
'ejyy_building_info.type',
|
'ejyy_building_info.type',
|
||||||
'ejyy_building_info.area',
|
'ejyy_building_info.area',
|
||||||
'ejyy_building_info.building',
|
'ejyy_building_info.building',
|
||||||
@ -59,10 +62,13 @@ const MpVistorUseAction = <Action>{
|
|||||||
.where('ejyy_vistor.id', id)
|
.where('ejyy_vistor.id', id)
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
|
const uid = utils.access.encrypt(detail.id, detail.building_id, VISTOR_ACCESS_QRCODE);
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
code: SUCCESS,
|
code: SUCCESS,
|
||||||
data: {
|
data: {
|
||||||
...detail
|
...detail,
|
||||||
|
uid
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@ import { SUCCESS } from '~/constant/code';
|
|||||||
import * as ROLE from '~/constant/role_access';
|
import * as ROLE from '~/constant/role_access';
|
||||||
import utils from '~/utils';
|
import utils from '~/utils';
|
||||||
import { HOUSE, CARPORT, WAREHOUSE, MERCHANT, GARAGE } from '~/constant/building';
|
import { HOUSE, CARPORT, WAREHOUSE, MERCHANT, GARAGE } from '~/constant/building';
|
||||||
import { SELF_ACCESS_QRCODE } from '~/constant/open_access';
|
|
||||||
|
|
||||||
interface Building {
|
interface Building {
|
||||||
type: typeof HOUSE | typeof CARPORT | typeof WAREHOUSE;
|
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);
|
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 { HOUSE, CARPORT, WAREHOUSE, MERCHANT, GARAGE } from '~/constant/building';
|
||||||
import * as ROLE from '~/constant/role_access';
|
import * as ROLE from '~/constant/role_access';
|
||||||
import utils from '~/utils';
|
import utils from '~/utils';
|
||||||
import config from '~/config';
|
|
||||||
import crypto from 'crypto';
|
|
||||||
|
|
||||||
interface RequestBody {
|
interface RequestBody {
|
||||||
uid: string;
|
uid: string;
|
||||||
@ -57,21 +55,17 @@ const PcOptionCardAction = <Action>{
|
|||||||
},
|
},
|
||||||
response: async ctx => {
|
response: async ctx => {
|
||||||
const { uid, community_id } = <RequestBody>ctx.request.body;
|
const { uid, community_id } = <RequestBody>ctx.request.body;
|
||||||
let userId = null;
|
const origin = utils.crypto.decrypt(uid);
|
||||||
let stamp = null;
|
|
||||||
|
|
||||||
try {
|
if (!origin) {
|
||||||
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) {
|
|
||||||
return (ctx.body = {
|
return (ctx.body = {
|
||||||
code: QUERY_ILLEFAL,
|
code: QUERY_ILLEFAL,
|
||||||
message: '非法业主名片'
|
message: '非法业主名片'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [userId, stamp] = origin.split('-');
|
||||||
|
|
||||||
if (!/^\d+$/.test(userId) || !/^\d{13}$/.test(stamp)) {
|
if (!/^\d+$/.test(userId) || !/^\d{13}$/.test(stamp)) {
|
||||||
return (ctx.body = {
|
return (ctx.body = {
|
||||||
code: QUERY_ILLEFAL,
|
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 = {
|
ctx.body = {
|
||||||
code: SUCCESS,
|
code: SUCCESS,
|
||||||
|
@ -110,17 +110,12 @@ const PcVistorCreateAction = <Action>{
|
|||||||
created_at: Date.now()
|
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) {
|
if (vistorInfo) {
|
||||||
vistorService.pushAccessToVistor(ctx.model, vistorInfo, id, expire);
|
vistorService.pushAccessToVistor(ctx.model, vistorInfo, id, expire);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uid = utils.access.encrypt(id, building_id, VISTOR_ACCESS_QRCODE);
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
code: SUCCESS,
|
code: SUCCESS,
|
||||||
data: {
|
data: {
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
import { Action } from '~/types/action';
|
import { Action } from '~/types/action';
|
||||||
import { SUCCESS, QUERY_ILLEFAL } from '~/constant/code';
|
import { SUCCESS, QUERY_ILLEFAL } from '~/constant/code';
|
||||||
import * as ROLE from '~/constant/role_access';
|
import * as ROLE from '~/constant/role_access';
|
||||||
|
import { VISTOR_ACCESS_QRCODE } from '~/constant/open_access';
|
||||||
|
import utils from '~/utils';
|
||||||
|
|
||||||
interface RequestBody {
|
interface RequestBody {
|
||||||
id: number;
|
id: number;
|
||||||
@ -94,10 +96,13 @@ const PcVistorDetailAction = <Action>{
|
|||||||
|
|
||||||
delete info.property_company_user_id;
|
delete info.property_company_user_id;
|
||||||
|
|
||||||
|
const uid = utils.access.encrypt(info.id, info.building_id, VISTOR_ACCESS_QRCODE);
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
code: SUCCESS,
|
code: SUCCESS,
|
||||||
data: {
|
data: {
|
||||||
info,
|
info,
|
||||||
|
uid,
|
||||||
registrant
|
registrant
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -45,7 +45,7 @@ const PcVistorScanAction = <Action>{
|
|||||||
},
|
},
|
||||||
response: async ctx => {
|
response: async ctx => {
|
||||||
const { uid, community_id } = <RequestBody>ctx.request.body;
|
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) {
|
if (!success || type !== VISTOR_ACCESS_QRCODE) {
|
||||||
return (ctx.body = {
|
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;
|
replyed_at?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EjyyBuildingAccess {
|
|
||||||
id?: number;
|
|
||||||
building_id: number;
|
|
||||||
uid?: string;
|
|
||||||
created_at: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EjyyRepair {
|
interface EjyyRepair {
|
||||||
id?: number;
|
id?: number;
|
||||||
wechat_mp_user_id?: number;
|
wechat_mp_user_id?: number;
|
||||||
@ -656,7 +649,6 @@ declare namespace EjyyModel {
|
|||||||
vistor_phone: string;
|
vistor_phone: string;
|
||||||
car_number?: string;
|
car_number?: string;
|
||||||
have_vistor_info: typeof TRUE | typeof FALSE;
|
have_vistor_info: typeof TRUE | typeof FALSE;
|
||||||
uid?: string;
|
|
||||||
expire: number;
|
expire: number;
|
||||||
used_at?: number;
|
used_at?: number;
|
||||||
scan_by?: number;
|
scan_by?: number;
|
||||||
@ -1355,7 +1347,6 @@ declare module 'knex/types/tables' {
|
|||||||
ejyy_notice_to_user_readed: EjyyModel.EjyyNoticeToUserReaded;
|
ejyy_notice_to_user_readed: EjyyModel.EjyyNoticeToUserReaded;
|
||||||
ejyy_notice_tpl: EjyyModel.EjyyNoticeTpl;
|
ejyy_notice_tpl: EjyyModel.EjyyNoticeTpl;
|
||||||
ejyy_feedback: EjyyModel.EjyyFeedback;
|
ejyy_feedback: EjyyModel.EjyyFeedback;
|
||||||
ejyy_building_access: EjyyModel.EjyyBuildingAccess;
|
|
||||||
ejyy_repair: EjyyModel.EjyyRepair;
|
ejyy_repair: EjyyModel.EjyyRepair;
|
||||||
ejyy_repair_urge: EjyyModel.EjyyRepairUrge;
|
ejyy_repair_urge: EjyyModel.EjyyRepairUrge;
|
||||||
ejyy_complain: EjyyModel.EjyyComplain;
|
ejyy_complain: EjyyModel.EjyyComplain;
|
||||||
|
@ -10,39 +10,32 @@
|
|||||||
* +----------------------------------------------------------------------
|
* +----------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import crypto from 'crypto';
|
import * as crypto from './crypto';
|
||||||
import config from '~/config';
|
|
||||||
import { SELF_ACCESS_QRCODE, VISTOR_ACCESS_QRCODE } from '~/constant/open_access';
|
import { SELF_ACCESS_QRCODE, VISTOR_ACCESS_QRCODE } from '~/constant/open_access';
|
||||||
|
|
||||||
interface DecodeResult {
|
interface DecryptResult {
|
||||||
id?: number;
|
id?: number;
|
||||||
building_id?: number;
|
building_id?: number;
|
||||||
type?: typeof SELF_ACCESS_QRCODE | typeof VISTOR_ACCESS_QRCODE;
|
type?: typeof SELF_ACCESS_QRCODE | typeof VISTOR_ACCESS_QRCODE;
|
||||||
|
stamp?: number;
|
||||||
success: boolean;
|
success: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 区分访客二维码和自己的二维码
|
// 区分访客二维码和自己的二维码
|
||||||
export function encode(
|
// 访客二维码 id 为 vistor_id 自己的二维码为user_id
|
||||||
|
export function encrypt(
|
||||||
id: number,
|
id: number,
|
||||||
building_id: number,
|
building_id: number,
|
||||||
type: typeof SELF_ACCESS_QRCODE | typeof VISTOR_ACCESS_QRCODE
|
type: typeof SELF_ACCESS_QRCODE | typeof VISTOR_ACCESS_QRCODE
|
||||||
): string {
|
): string {
|
||||||
const cipher = crypto.createCipheriv('aes-256-cbc', config.crypto.key, config.crypto.iv);
|
return crypto.encrypt(`${id}#${building_id}#${type}${Date.now()}`);
|
||||||
let crypted = cipher.update(`${id}#${building_id}#${type}`, 'utf8', 'hex');
|
|
||||||
|
|
||||||
crypted += cipher.final('hex');
|
|
||||||
|
|
||||||
return crypted;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decode(uid: string): DecodeResult {
|
export function decrypt(uid: string): DecryptResult {
|
||||||
let origin = null;
|
const origin = crypto.decrypt(uid);
|
||||||
|
|
||||||
try {
|
if (!origin) {
|
||||||
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) {
|
|
||||||
return { success: false };
|
return { success: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,9 +43,15 @@ export function decode(uid: string): DecodeResult {
|
|||||||
const id = parseInt(arr[0], 10);
|
const id = parseInt(arr[0], 10);
|
||||||
const building_id = parseInt(arr[1], 10);
|
const building_id = parseInt(arr[1], 10);
|
||||||
const type = <typeof SELF_ACCESS_QRCODE | typeof VISTOR_ACCESS_QRCODE>parseInt(arr[2], 10);
|
const type = <typeof SELF_ACCESS_QRCODE | typeof VISTOR_ACCESS_QRCODE>parseInt(arr[2], 10);
|
||||||
|
const stamp = parseInt(arr[3], 10);
|
||||||
let success = true;
|
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;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +59,7 @@ export function decode(uid: string): DecodeResult {
|
|||||||
id,
|
id,
|
||||||
building_id,
|
building_id,
|
||||||
type,
|
type,
|
||||||
|
stamp,
|
||||||
success
|
success
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -10,8 +10,7 @@
|
|||||||
* +----------------------------------------------------------------------
|
* +----------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import crypto from 'crypto';
|
import * as crypto from './crypto';
|
||||||
import config from '~/config';
|
|
||||||
import { AUTHENTICTED_BY_PROPERTY_COMPANY, AUTHENTICTED_BY_FAMILY } from '~/constant/authenticated_type';
|
import { AUTHENTICTED_BY_PROPERTY_COMPANY, AUTHENTICTED_BY_FAMILY } from '~/constant/authenticated_type';
|
||||||
|
|
||||||
interface DecodeResult {
|
interface DecodeResult {
|
||||||
@ -106,7 +105,7 @@ function fake(len: number): string {
|
|||||||
return ret.join('');
|
return ret.join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function encode(
|
export function encrypt(
|
||||||
building_ids: number[],
|
building_ids: number[],
|
||||||
authenticated_type: typeof AUTHENTICTED_BY_PROPERTY_COMPANY | typeof AUTHENTICTED_BY_FAMILY,
|
authenticated_type: typeof AUTHENTICTED_BY_PROPERTY_COMPANY | typeof AUTHENTICTED_BY_FAMILY,
|
||||||
user_id: number
|
user_id: number
|
||||||
@ -120,25 +119,16 @@ export function encode(
|
|||||||
|
|
||||||
ret.push(fake(8));
|
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 {
|
return {
|
||||||
text: crypted,
|
text: crypto.encrypt(ret.join('')),
|
||||||
stamp
|
stamp
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decode(text: string): DecodeResult {
|
export function decrypt(text: string): DecodeResult {
|
||||||
let str = null;
|
let str = crypto.decrypt(text);
|
||||||
|
|
||||||
try {
|
if (!str) {
|
||||||
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) {
|
|
||||||
return { success: false };
|
return { success: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
|
import config from '~/config';
|
||||||
|
|
||||||
export function md5(str: string, upperCase = false): string {
|
export function md5(str: string, upperCase = false): string {
|
||||||
const hash = crypto.createHash('md5');
|
const hash = crypto.createHash('md5');
|
||||||
@ -21,3 +22,26 @@ export function md5(str: string, upperCase = false): string {
|
|||||||
|
|
||||||
return upperCase ? ret.toUpperCase() : ret;
|
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 crypto from './crypto';
|
||||||
import * as openAccess from './open_access';
|
import * as access from './access';
|
||||||
import * as phone from './phone';
|
import * as phone from './phone';
|
||||||
import * as community from './community';
|
import * as community from './community';
|
||||||
import * as building from './building';
|
import * as building from './building';
|
||||||
@ -20,10 +20,11 @@ import * as order from './order';
|
|||||||
import * as text from './text';
|
import * as text from './text';
|
||||||
import * as idcard from './idcard';
|
import * as idcard from './idcard';
|
||||||
import * as mail from './mail';
|
import * as mail from './mail';
|
||||||
|
import * as redis from './redis';
|
||||||
|
|
||||||
const utils = {
|
const utils = {
|
||||||
crypto,
|
crypto,
|
||||||
openAccess,
|
access,
|
||||||
phone,
|
phone,
|
||||||
community,
|
community,
|
||||||
building,
|
building,
|
||||||
@ -31,7 +32,8 @@ const utils = {
|
|||||||
order,
|
order,
|
||||||
text,
|
text,
|
||||||
idcard,
|
idcard,
|
||||||
mail
|
mail,
|
||||||
|
redis
|
||||||
};
|
};
|
||||||
|
|
||||||
export default utils;
|
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,
|
fetching: true,
|
||||||
detail: {
|
detail: {
|
||||||
info: {},
|
info: {},
|
||||||
registrant: {}
|
registrant: {},
|
||||||
|
uid: ''
|
||||||
},
|
},
|
||||||
now: Date.now()
|
now: Date.now()
|
||||||
};
|
};
|
||||||
@ -175,7 +176,7 @@ export default {
|
|||||||
this.detail = res.data;
|
this.detail = res.data;
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
qrcode.toCanvas(this.$refs.canvas, res.data.info.uid, {
|
qrcode.toCanvas(this.$refs.canvas, res.data.uid, {
|
||||||
width: 220,
|
width: 220,
|
||||||
height: 220,
|
height: 220,
|
||||||
margin: 2
|
margin: 2
|
||||||
|
Loading…
x
Reference in New Issue
Block a user