diff --git a/front/App.tsx b/front/App.tsx
index 9f87763..5920d3b 100644
--- a/front/App.tsx
+++ b/front/App.tsx
@@ -1,9 +1,14 @@
-import React from 'react'
+import React, {useEffect} from 'react'
import './assets/global.scss'
import {AppRouter} from "./pages/Router.tsx";
+import {useUserinfoStore} from "./store/userinfoStore.ts";
export function App() {
+ const {init} = useUserinfoStore();
+ useEffect(()=>{
+ init(); // 初始化用户信息
+ }, [])
return (
)
diff --git a/front/components/AppList/index.tsx b/front/components/AppList/index.tsx
new file mode 100644
index 0000000..ec8c52f
--- /dev/null
+++ b/front/components/AppList/index.tsx
@@ -0,0 +1,24 @@
+import {List, Space} from "@douyinfe/semi-ui"
+import {Link} from "react-router-dom";
+import {formatDate} from "../../../utils/date.ts";
+import {AppModel} from "../../../model";
+
+export const AppList = (props: {
+ list: AppModel[];
+ onItemClick?: (app: AppModel) => void;
+}) => {
+ return (
+ {props.list.map(it => (
+
{
+ props.onItemClick?.(it)
+ }}>
+
+ {it.id}
+ {it.title}
+ {formatDate(it.create_time)}
+
+ 进入
+
+ ))}
+
)
+}
diff --git a/front/components/Result.tsx b/front/components/Result.tsx
deleted file mode 100644
index cc069bd..0000000
--- a/front/components/Result.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import React from "react";
-
-export type ResultProps = {
- extra: React.ReactNode;
- title: React.ReactNode;
- status: number | string;
-};
-export const Result: React.FC = (props) => {
- return
-
{props.title}
-
{props.extra}
-
-}
diff --git a/front/components/Result/index.tsx b/front/components/Result/index.tsx
new file mode 100644
index 0000000..f7e2aa0
--- /dev/null
+++ b/front/components/Result/index.tsx
@@ -0,0 +1,38 @@
+import React from "react";
+import './result.scss'
+import {CheckCircleFilled} from "../icons/CheckCircleFilled.tsx";
+import {NotFound} from "../icons/NotFound.tsx";
+
+type Status = 'success' | 'error' | 'info' | 'warning' | '404' | '403' | '500';
+export type ResultProps = {
+ extra?: React.ReactNode;
+ title?: React.ReactNode;
+ subTitle?: React.ReactNode;
+ icon?: React.ReactNode;
+ status?: Status //number | string;
+};
+export const IconMap: {
+ [key in Status]: React.ReactNode
+} = {
+ success: ,
+ error: ,
+ info: ,
+ warning: ,
+// error: CloseCircleFilled,
+// info: ExclamationCircleFilled,
+// warning: WarningFilled,
+ '404': ,
+ '500': ,
+ '403': ,
+// '500': serverError,
+// '403': unauthorized,
+};
+export const Index: React.FC = (props) => {
+ const icon = props.icon || (props.status ? IconMap[props.status] : <>>)
+ return
+
{icon}
+
{props.title}
+ {props.subTitle &&
{props.subTitle}
}
+
{props.extra}
+
+}
diff --git a/front/components/Result/result.scss b/front/components/Result/result.scss
new file mode 100644
index 0000000..387d5a9
--- /dev/null
+++ b/front/components/Result/result.scss
@@ -0,0 +1,26 @@
+.result-wrapper {
+ text-align: center;
+ padding: 48px 32px;
+
+ .icon {
+ margin-bottom: 24px;
+ color:#52c41a;
+ font-size: 72px;
+ }
+
+ .title {
+ color: rgba(0, 0, 0, 0.88);
+ font-size: 24px;
+ line-height: 1.3333333333333333;
+ margin-block: 8px;
+ }
+ .sub-title{
+ color: rgba(0, 0, 0, 0.45);
+ font-size: 14px;
+ line-height: 1.5714285714285714;
+ }
+
+ .extra {
+ margin: 24px 0 0 0;
+ }
+}
diff --git a/front/components/icons/CheckCircleFilled.tsx b/front/components/icons/CheckCircleFilled.tsx
new file mode 100644
index 0000000..0ec54bf
--- /dev/null
+++ b/front/components/icons/CheckCircleFilled.tsx
@@ -0,0 +1,16 @@
+import React from "react";
+
+export type SvgProps = {} & React.SVGProps;
+export const CheckCircleFilled: React.FC = (props) => {
+ return ()
+}
diff --git a/front/components/icons/NotFound.tsx b/front/components/icons/NotFound.tsx
new file mode 100644
index 0000000..ac36d7a
--- /dev/null
+++ b/front/components/icons/NotFound.tsx
@@ -0,0 +1,157 @@
+import React from "react";
+
+export const NotFound: React.FC = () => {
+ return ()
+}
diff --git a/front/components/panel/panel.module.scss b/front/components/panel/panel.module.scss
index a3c1b10..e729bed 100644
--- a/front/components/panel/panel.module.scss
+++ b/front/components/panel/panel.module.scss
@@ -9,6 +9,7 @@
}
.panelTitle{
font-size: 14px;
+ font-weight: bold;
}
.panelExtra{
@@ -18,4 +19,4 @@
padding:10px;
border-radius: 4px;;
}
-.noPadding{padding:0;}
\ No newline at end of file
+.noPadding{padding:0;}
diff --git a/front/main.tsx b/front/main.tsx
index 9226034..3aff0a1 100644
--- a/front/main.tsx
+++ b/front/main.tsx
@@ -4,7 +4,7 @@ import ReactDOM from 'react-dom/client';
import {App} from './App'
ReactDOM.createRoot(document.querySelector('#root')!).render(
-
+ //
-
+ //
)
diff --git a/front/pages/Router.tsx b/front/pages/Router.tsx
index 8088a39..cc8f103 100644
--- a/front/pages/Router.tsx
+++ b/front/pages/Router.tsx
@@ -3,7 +3,8 @@ import {BrowserRouter, HashRouter, Navigate, Route, Routes, useNavigate} from "r
import {APP_CONFIG} from "../config.ts";
import {Button} from "@douyinfe/semi-ui";
import DefaultPage from "./index";
-import {Result} from "../components/Result.tsx";
+import {Index} from "../components/Result";
+import {DashboardIndex} from "./dashboard";
const routerMode: 'browser' | 'hash' | string = APP_CONFIG.ROUTER_MODE;
@@ -15,7 +16,7 @@ const WebRouter: React.FC<{
const NotFound: React.FC = () => {
const navigate = useNavigate();
- return (
}/>
+ }/>
}/>
diff --git a/front/pages/dashboard/index.tsx b/front/pages/dashboard/index.tsx
new file mode 100644
index 0000000..cf7c617
--- /dev/null
+++ b/front/pages/dashboard/index.tsx
@@ -0,0 +1,130 @@
+import React from "react";
+import {Panel} from "../../components/panel";
+import {AppList} from "../../components/AppList";
+import {AppModel, EventDataModel, EventModel} from "../../../model";
+import {appList, appEventList, appEventDataList} from "../../service/api.app.ts";
+import './style.scss'
+import {Space, Table} from "@douyinfe/semi-ui";
+import {formatDate} from "../../../utils/date.ts";
+import {useInterval} from "ahooks";
+
+export const DashboardIndex: React.FC = () => {
+ // 数据
+ const [apps, setApps] = React.useState([]);
+ const [currentAppID, setCurrentAppID] = React.useState(0);
+ const [events, setEvents] = React.useState([]);
+ const [eventDataList, setEventDataList] = React.useState([]);
+ const [page, setPage] = React.useState(1);
+ const [dataTotal, setDataTotal] = React.useState(0);
+
+ const eventDataColumns = [
+ {
+ title: '编号',
+ dataIndex: 'id',
+ width: 60
+ },
+ {
+ title: '事件',
+ dataIndex: 'title',
+ width: 80
+ },
+ {
+ title: '路径',
+ dataIndex: 'path',
+ width: 120
+ },
+ {
+ title: '访问者浏览器',
+ ellipsis: true,
+ dataIndex: 'browser'
+ },
+ {
+ title: '来源',
+ dataIndex: 'ip',
+ width: 80
+ },
+ {
+ title: '分辨率',
+ dataIndex: 'resolution',
+ width: 120
+ },
+ {
+ title: '日期',
+ dataIndex: 'create_time',
+ render: (text: string) => formatDate(text),
+ width: 180
+ },
+ ]
+ // 加载应用所有的事件
+ const getAppEvents = () => {
+ appEventList(currentAppID).then(list => setEvents(list))
+ }
+
+ // 加载引用所有事件上报的数据
+ const loadAppEventsAndDataList = (page = 1) => {
+ appEventDataList({
+ appId: currentAppID,
+ pageSize: 10,
+ page
+ }).then(list => {
+ setEventDataList(list)
+ setPage(page)
+ })
+ }
+
+ const getAppEventAndData = ()=>{
+ if (currentAppID > 0) {
+ getAppEvents()
+ loadAppEventsAndDataList()
+ }
+ }
+ // 10s 自动刷新
+ useInterval(loadAppEventsAndDataList, 10000)
+
+ React.useEffect(() => {
+ appList().then(list => setApps(list))
+ }, [])
+ React.useEffect(getAppEventAndData, [currentAppID])
+
+
+ return (
+
+
+
Dashboard
+
+
+
+ setCurrentAppID(it.id)}
+ />
+
+ {currentAppID > 0 && <>
+
+
+ {events.map(it => (
+
+
+ {it.id}
+ {it.title}
+
+ 查看
+
+ ))}
+
+
+
+
+
+ >}
+
+ );
+};
diff --git a/front/pages/dashboard/style.scss b/front/pages/dashboard/style.scss
new file mode 100644
index 0000000..886addc
--- /dev/null
+++ b/front/pages/dashboard/style.scss
@@ -0,0 +1,9 @@
+.app-list-wrapper{
+ .list-link-item{
+ padding:10px;
+ border-bottom: solid 1px #eee;
+ &:last-child{
+ border-bottom: none;
+ }
+ }
+}
diff --git a/front/pages/index/index.tsx b/front/pages/index/index.tsx
index da9952c..496716c 100644
--- a/front/pages/index/index.tsx
+++ b/front/pages/index/index.tsx
@@ -1,4 +1,4 @@
-import React, {useRef, useState} from "react";
+import React, {useEffect, useRef, useState} from "react";
import {IconGithubLogo, IconWeibo} from "@douyinfe/semi-icons";
import css from './index.module.scss'
import {LoginComponent} from "../../components/LoginComponent.tsx";
@@ -11,13 +11,12 @@ const DefaultPage: React.FC = () => {
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
- const {login} = useUserinfoStore()
+ const {login, userinfo} = useUserinfoStore()
const onFinish = async (values: any) => {
setLoading(true);
try {
const user = await login(values)
- setVisible(true);
navigate('/dashboard')
} catch (err) {
// 登录失败
@@ -30,8 +29,12 @@ const DefaultPage: React.FC = () => {
setLoading(false);
};
- const [visible, setVisible] = useState(false);
- const hideModal = () => setVisible(false)
+
+ useEffect(() => {
+ if (userinfo && userinfo.id > 0) {
+ navigate('/dashboard')
+ }
+ }, [userinfo])
return (
{/*
*/}
diff --git a/front/service/api.app.ts b/front/service/api.app.ts
new file mode 100644
index 0000000..d506048
--- /dev/null
+++ b/front/service/api.app.ts
@@ -0,0 +1,22 @@
+import {get, post} from "./request.ts";
+import {AppModel, EventDataModel, EventModel, UserModel} from "../../model";
+
+// 获取用户归属应用列表
+export function appList() {
+ return get
('/api/app/list')
+}
+
+// 根据应用id获取应用所有的事件列表
+export function appEventList(appId: number) {
+ return get('/api/app/event', {appId})
+}
+
+// 根据应用id获取应用所有的事件数据列表
+export function appEventDataList(params: {
+ appId: number;
+ eventId?: number;
+ page?: number;
+ pageSize?: number;
+}) {
+ return get('/api/app/event-data', params)
+}
diff --git a/front/service/request.ts b/front/service/request.ts
index 9e3eaa5..d5a52df 100644
--- a/front/service/request.ts
+++ b/front/service/request.ts
@@ -59,8 +59,22 @@ Axios.interceptors.response.use(res => {
return Promise.reject(err)
})
+// 将data对象转化为url参数
+
+function dataToQueryString(data: any) {
+ if (!data) return '';
+ return Object.keys(data).map(key => {
+ return encodeURIComponent(key) + '=' + encodeURIComponent(data[key])
+ }).join('&')
+}
+
+
export function request(url: string, method: RequestMethod, data: any = null, getOriginResult = false) {
return new Promise((resolve, reject) => {
+ if (method == 'get' && data) {
+ url += `?${dataToQueryString(data)}`
+ data = null
+ }
Axios.request>({
url,
method,
@@ -103,10 +117,10 @@ export function uploadFile(url: string, file: File, data: any = {}, returnOri
return request(url, 'post', formData, returnOrigin)
}
-export function post(url: string, data: any = {}) {
+export function post(url: string, data: any = null) {
return request(url, 'post', data)
}
-export function get(url: string, data: any = {}) {
+export function get(url: string, data: any = null) {
return request(url, 'get', data)
}
diff --git a/front/store/userinfoStore.ts b/front/store/userinfoStore.ts
index 8495d64..54a85e1 100644
--- a/front/store/userinfoStore.ts
+++ b/front/store/userinfoStore.ts
@@ -33,7 +33,7 @@ export const useUserinfoStore = create<{
/**
* 初始化用户数据
*/
- init: () => Promise;
+ init: () => void;
}>((set, _, _state) => {
return {
userinfo: {
@@ -43,20 +43,28 @@ export const useUserinfoStore = create<{
init: () => {
const state = _state.getState()
if (state.token) {
- return Promise.resolve()
+ return;
}
const token = Storage.get(LOGIN_TOKEN_KEY);
- return new Promise((resolve) => {
- if (token) {
- getInfo().then((info) => {
- set({
- userinfo: info,
- token
- })
+ if (token) {
+ getInfo().then((info) => {
+ set({
+ userinfo: info,
+ token
})
- }
- resolve();
- });
+ })
+ }
+ // return new Promise((resolve) => {
+ // if (token) {
+ // getInfo().then((info) => {
+ // set({
+ // userinfo: info,
+ // token
+ // })
+ // })
+ // }
+ // resolve();
+ // });
},
logout: () => {
return new Promise((resolve) => {
diff --git a/model/index.ts b/model/index.ts
index 4882223..7e400ab 100644
--- a/model/index.ts
+++ b/model/index.ts
@@ -17,6 +17,8 @@ export type EventDataModel = {
uid: number;
uuid: number;
type: 'pv' | 'uv' | string;
+ title:string;
+ description:string;
location: string;
resolution: string;
browser: string;
diff --git a/package.json b/package.json
index 4f45682..5d30b1f 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
"@douyinfe/semi-ui": "^2.41.3",
"@emotion/css": "^11.11.2",
"@types/express": "^4.17.17",
+ "@types/md5": "^2.3.2",
"@types/mocha": "^10.0.1",
"@types/mysql": "^2.15.21",
"@types/node": "^20.4.9",
@@ -24,6 +25,7 @@
"@types/react-dom": "^18.2.7",
"@types/supertest": "^2.0.12",
"@vitejs/plugin-react": "^4.0.4",
+ "ahooks": "^3.7.8",
"axios": "^1.4.0",
"dayjs": "^1.11.9",
"mocha": "^10.2.0",
@@ -39,7 +41,9 @@
},
"dependencies": {
"express": "^4.18.2",
+ "md5": "^2.3.0",
"mysql": "^2.18.1",
+ "redis": "^4.6.7",
"ts-node": "^10.9.1"
}
}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..7e866ac
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/service/core/server.ts b/service/core/server.ts
index 908628b..4ac7b7c 100644
--- a/service/core/server.ts
+++ b/service/core/server.ts
@@ -1,4 +1,4 @@
-import { InitServerOption } from "./types";
+import {InitServerOption} from "./types";
// import { createServer as createServerOrigin } from "http";
// import express = require("express");
import * as express from "express";
@@ -8,9 +8,23 @@ export function createServer(options: Partial, callback?: () =
const app = express();
// 将请求体转换为JSON格式
app.use(express.json())
- app.use(express.urlencoded({ extended: true }))
+ app.use(express.urlencoded({extended: true}))
// 监听端口
app.listen(options.port, callback);
+ // 添加允许跨域中间件
+ app.use(function (req, res, next) {
+ const method = req.method.toLowerCase();
+ res.header("Access-Control-Allow-Origin", "*");
+ res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
+ // res.header("Access-Control-Allow-Credentials", "true");
+ res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
+ if (method === 'options') {
+ res.status(200).end('OK');
+ return;
+ }
+ next();
+ });
+
app.get('/ping', (_req, res) => {
res.appendHeader('app-ping', 'pong')
res.send('pong')
diff --git a/service/core/types.ts b/service/core/types.ts
index 4bb4de4..f3b24e1 100644
--- a/service/core/types.ts
+++ b/service/core/types.ts
@@ -1,4 +1,7 @@
-import { Response, Request } from 'express'
+import {Response, Request} from 'express'
+import {IncomingHttpHeaders} from "http";
+import {UserModel} from "../../model";
+
export type InitServerOption = {
port: number;
host: string;
@@ -11,8 +14,9 @@ export type RouteHandleFunctionParam = {
query: Record;
body: Record;
method: HttpMethod;
- headers: Record;
+ user?: UserModel | null;
+ headers: IncomingHttpHeaders;
res: Response>
req: Request
}
-export type RouteHandleFunction = (params: RouteHandleFunctionParam) => void | Promise;
\ No newline at end of file
+export type RouteHandleFunction = (params: RouteHandleFunctionParam) => void | Promise;
diff --git a/service/routes/index.ts b/service/routes/index.ts
index 34b2561..3bb2a0c 100644
--- a/service/routes/index.ts
+++ b/service/routes/index.ts
@@ -2,21 +2,42 @@ import {Application, Request, Response} from "express";
import {RouteHandleFunction, RouteHandleFunctionParam} from "../core/types";
import {home} from "./home";
import {appList, reportToServer, appEvent, eventData} from "./reportor";
-import {getUserInfo, loginHandler} from "./user.ts";
+import {decodeUserToken, getUserInfo, loginHandler} from "./user.ts";
+
+const excludes = [
+ '/home',
+ '/api/report',
+]
//
function createRoute(handler: RouteHandleFunction) {
return (req: Request, res: Response>) => {
- // console.log('params', req.params, req.query, req.body)
- handler({
+ // console.log('params', req.headers)
+ const params = {
path: req.path,
param: req.params,
query: req.query,
body: req.body,
method: req.method,
- headers: {},
+ headers: req.headers,
res,
req
+ };
+ const path = req.path;
+ console.log('request path:', path)
+ if (excludes.includes(path)) {
+ return handler(params);
+ }
+ const token = req.headers.authorization
+ if (!token) {
+ res.send({code: 403, message: '请先登录'})
+ return;
+ }
+ decodeUserToken(token).then(user => {
+ handler({
+ ...params,
+ user: typeof (user) == "string" ? JSON.parse(user) : user
+ })
})
}
}
diff --git a/service/routes/reportor.ts b/service/routes/reportor.ts
index bffca01..3c8343f 100644
--- a/service/routes/reportor.ts
+++ b/service/routes/reportor.ts
@@ -1,37 +1,46 @@
-import { RouteHandleFunction } from "../core/types";
-import { listAppByUID } from "../service/app";
-import { reportEvent } from "../service/report-service";
+import {RouteHandleFunction} from "../core/types";
+import {listAppByUID, listAppEvent, listAppEventData} from "../service/app";
+import {reportEvent} from "../service/report-service";
-export const reportToServer: RouteHandleFunction = ({
- param, res
-}) => {
- reportEvent(param).then(() => {
- res.send('got and saved!')
- }).catch((e: Error) => {
- console.log(e)
- res.send('got but not saved!')
- });
-}
+export const reportToServer: RouteHandleFunction =
+ ({
+ body, res
+ }) => {
+ reportEvent(body).then(() => {
+ res.send('got and saved!')
+ }).catch((e: Error) => {
+ console.log(e)
+ res.send('got but not saved!')
+ });
+ }
-export const appList: RouteHandleFunction = async ({
- param, res
-}) => {
- const apps = await listAppByUID(1);
- res.send({ code: 0, data: apps })
-}
+export const appList: RouteHandleFunction =
+ async ({
+ param, res
+ }) => {
+ const apps = await listAppByUID(1);
+ res.send({code: 0, data: apps})
+ }
-export const appEvent: RouteHandleFunction = async ({
- query, res, param, body
-}) => {
+export const appEvent: RouteHandleFunction =
+ async ({
+ query, res, param, body
+ }) => {
+ const events = await listAppEvent(query.appId)
+ res.send({
+ code: 0,
+ data: events
+ })
+ }
- res.send({
- query, param, body
- })
-}
-
-export const eventData: RouteHandleFunction = ({
- param, res
-}) => {
- res.send(JSON.stringify(param))
-}
+export const eventData: RouteHandleFunction =
+ async ({
+ query, res
+ }) => {
+ const data = await listAppEventData(Number(query.appId), Number(query.page), Number(query.pageSize))
+ res.send({
+ code: 0,
+ data
+ })
+ }
diff --git a/service/routes/user.ts b/service/routes/user.ts
index a699611..240fdc6 100644
--- a/service/routes/user.ts
+++ b/service/routes/user.ts
@@ -1,17 +1,22 @@
import {RouteHandleFunction} from "../core/types.ts";
import {login} from "../service/app.ts";
import {UserModel} from "../../model";
+import {getFromRedis, setToRedis} from "../service/redis.ts";
export function encodeUserToken(user: UserModel) {
if (user == null) throw new Error('user is null')
- const token = JSON.stringify(user)
- return btoa(encodeURIComponent(token));
+ // const token = JSON.stringify(user)
+ const token = btoa(user.id + ':' + Date.now())
+ setToRedis('app-report:user:' + token, JSON.stringify(user)).then(() => console.log('setToRedis success'));
+ return token;
}
export function decodeUserToken(token: string) {
// 将token转回UserModel对象数据
- const user = decodeURIComponent(atob(token));
- return JSON.parse(user) as UserModel
+ // const user = decodeURIComponent(atob(token));
+ console.log('decodeUserToken token==>app-report:user:', token)
+ return getFromRedis('app-report:user:' + token)
+ // return JSON.parse(user) as UserModel
}
@@ -38,8 +43,14 @@ export const loginHandler: RouteHandleFunction
}
export const getUserInfo: RouteHandleFunction =
- ({headers, res}) => {
- const token = headers.Authorization
- const user = decodeUserToken(token)
- res.send({code: 0, message: '登录成功', data: user})
+ async ({headers,user, res}) => {
+ const token = headers.authorization
+ if (!token) {
+ res.send({code: 403, message: '请先登录'})
+ return;
+ }
+ if (!user) {
+ res.send({code: 403, message: '请先登录'})
+ }
+ res.send({code: 0, message: 'success', data: user})
}
diff --git a/service/service/app.ts b/service/service/app.ts
index fc701cc..e821195 100644
--- a/service/service/app.ts
+++ b/service/service/app.ts
@@ -17,7 +17,7 @@ export function listAppEvent(id: number) {
// 查询应用所有的事件数据
export function listAppEventData(id: number, page = 1, pageSize = 10) {
- return selectArray('select * from events_data where app_id=? limit ?,?',
+ return selectArray('select d.*,e.title,e.type,e.description from events_data d,events e where d.event_id = e.id and d.app_id=? limit ?,?',
[id, (page - 1) * pageSize, pageSize])
}
diff --git a/service/service/mysql.ts b/service/service/mysql.ts
index 32ed3f8..99f7449 100644
--- a/service/service/mysql.ts
+++ b/service/service/mysql.ts
@@ -42,7 +42,7 @@ export async function selectOne(sql: string, params: any = null) {
// 统计数据
export async function queryCount(sql: string, params: any = null) {
const obj = await selectOne<{ [key: string]: number }>(sql, params);
- if(!obj) return 0;
+ if (!obj) return 0;
const keys = Object.keys(obj);
return obj[keys[0]];
}
@@ -68,13 +68,14 @@ export async function isExist(sql: string, params: any) {
function executeSQL(sql: string, params: any) {
return new Promise((resolve, reject) => {
- pool.query(sql, params, (err, ret) => {
+ const q = pool.query(sql, params, (err, ret) => {
if (err) {
reject(err)
} else {
resolve(ret)
}
})
+ console.log('[SQL]: ' + q.sql,params)
})
}
diff --git a/service/service/redis.ts b/service/service/redis.ts
new file mode 100644
index 0000000..6051b3b
--- /dev/null
+++ b/service/service/redis.ts
@@ -0,0 +1,32 @@
+import {createClient} from 'redis';
+
+const client = createClient();
+
+client.on('error', err => console.log('Redis Client', err));
+
+client.connect().then(() => {
+ console.log('connected to redis success')
+})
+
+export async function setToRedis(key: string, value: any, expire?: number) {
+ if (!value) throw new Error('value is required');
+ await client.set(key, JSON.stringify(value))
+ if (expire) await client.expire(key, expire);
+ return;
+}
+
+// export async function getString(key: string) {
+// const data = await client.get(key);
+// if (!data) return null;
+// return data;
+// }
+
+export async function getFromRedis(key: string) {
+ const data = await client.get(key);
+ if (!data) return null;
+ console.log('getFromRedis=>',typeof(data),data)
+ return JSON.parse(data) as T;
+}
+
+// 导出客户端)
+export default client;
diff --git a/service/service/report-service.ts b/service/service/report-service.ts
index f732e62..67532b4 100644
--- a/service/service/report-service.ts
+++ b/service/service/report-service.ts
@@ -1,19 +1,16 @@
import { PvUvModel } from "../../model";
import { isExist, insertAndGetInsertId } from './mysql'
-const table = {
- pv_uv: 'pv_uv'
-}
export async function reportEvent(data: Partial) {
//pv:用户每次打开一个页面便记录1次PV,多次打开同一页面则浏览量累计。
//uv:1天内同一访客的多次访问只记录为一个访客。通过IP和cookie是判断UV值的两种方式。
const { type } = data;
if (type == 'uv') {
// 判断今日的数据是否已经存在
- const existsToday = await isExist('select count(*) _ from pv_uv where DATE(created_at) = CURDATE() and uuid=?', [data.uuid])
+ const existsToday = await isExist('select count(*) _ from events_data where DATE(created_at) = CURDATE() and uuid=?', [data.uuid])
if (existsToday) {
return;
}
}
- await insertAndGetInsertId('pv_uv', data)
+ await insertAndGetInsertId('events_data', data)
}
diff --git a/yarn.lock b/yarn.lock
index 488c4e6..0d6c2b8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -564,6 +564,40 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
+"@redis/bloom@1.2.0":
+ version "1.2.0"
+ resolved "https://registry.npmmirror.com/@redis/bloom/-/bloom-1.2.0.tgz#d3fd6d3c0af3ef92f26767b56414a370c7b63b71"
+ integrity sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==
+
+"@redis/client@1.5.8":
+ version "1.5.8"
+ resolved "https://registry.npmmirror.com/@redis/client/-/client-1.5.8.tgz#a375ba7861825bd0d2dc512282b8bff7b98dbcb1"
+ integrity sha512-xzElwHIO6rBAqzPeVnCzgvrnBEcFL1P0w8P65VNLRkdVW8rOE58f52hdj0BDgmsdOm4f1EoXPZtH4Fh7M/qUpw==
+ dependencies:
+ cluster-key-slot "1.1.2"
+ generic-pool "3.9.0"
+ yallist "4.0.0"
+
+"@redis/graph@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.npmmirror.com/@redis/graph/-/graph-1.1.0.tgz#cc2b82e5141a29ada2cce7d267a6b74baa6dd519"
+ integrity sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==
+
+"@redis/json@1.0.4":
+ version "1.0.4"
+ resolved "https://registry.npmmirror.com/@redis/json/-/json-1.0.4.tgz#f372b5f93324e6ffb7f16aadcbcb4e5c3d39bda1"
+ integrity sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==
+
+"@redis/search@1.1.3":
+ version "1.1.3"
+ resolved "https://registry.npmmirror.com/@redis/search/-/search-1.1.3.tgz#b5a6837522ce9028267fe6f50762a8bcfd2e998b"
+ integrity sha512-4Dg1JjvCevdiCBTZqjhKkGoC5/BcB7k9j99kdMnaXFXg8x4eyOIVg9487CMv7/BUVkFLZCaIh8ead9mU15DNng==
+
+"@redis/time-series@1.0.4":
+ version "1.0.4"
+ resolved "https://registry.npmmirror.com/@redis/time-series/-/time-series-1.0.4.tgz#af85eb080f6934580e4d3b58046026b6c2b18717"
+ integrity sha512-ThUIgo2U/g7cCuZavucQTQzA9g9JbDDY2f64u3AbAoz/8vE2lt2U37LamDUVChhaDA3IRT9R6VvJwqnUfTJzng==
+
"@remix-run/router@1.8.0":
version "1.8.0"
resolved "https://registry.npmmirror.com/@remix-run/router/-/router-1.8.0.tgz#e848d2f669f601544df15ce2a313955e4bf0bafc"
@@ -634,6 +668,16 @@
resolved "https://registry.npmmirror.com/@types/http-errors/-/http-errors-2.0.1.tgz#20172f9578b225f6c7da63446f56d4ce108d5a65"
integrity sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==
+"@types/js-cookie@^2.x.x":
+ version "2.2.7"
+ resolved "https://registry.npmmirror.com/@types/js-cookie/-/js-cookie-2.2.7.tgz#226a9e31680835a6188e887f3988e60c04d3f6a3"
+ integrity sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==
+
+"@types/md5@^2.3.2":
+ version "2.3.2"
+ resolved "https://registry.npmmirror.com/@types/md5/-/md5-2.3.2.tgz#529bb3f8a7e9e9f621094eb76a443f585d882528"
+ integrity sha512-v+JFDu96+UYJ3/UWzB0mEglIS//MZXgRaJ4ubUPwOM0gvLc/kcQ3TWNYwENEK7/EcXGQVrW8h/XqednSjBd/Og==
+
"@types/mime@*":
version "3.0.1"
resolved "https://registry.npmmirror.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10"
@@ -767,6 +811,27 @@ acorn@^8.4.1:
resolved "https://registry.npmmirror.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
+ahooks-v3-count@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmmirror.com/ahooks-v3-count/-/ahooks-v3-count-1.0.0.tgz#ddeb392e009ad6e748905b3cbf63a9fd8262ca80"
+ integrity sha512-V7uUvAwnimu6eh/PED4mCDjE7tokeZQLKlxg9lCTMPhN+NjsSbtdacByVlR1oluXQzD3MOw55wylDmQo4+S9ZQ==
+
+ahooks@^3.7.8:
+ version "3.7.8"
+ resolved "https://registry.npmmirror.com/ahooks/-/ahooks-3.7.8.tgz#3fa3c491cd153e884a32b0c4192fc72cf84c4332"
+ integrity sha512-e/NMlQWoCjaUtncNFIZk3FG1ImSkV/JhScQSkTqnftakRwdfZWSw6zzoWSG9OMYqPNs2MguDYBUFFC6THelWXA==
+ dependencies:
+ "@babel/runtime" "^7.21.0"
+ "@types/js-cookie" "^2.x.x"
+ ahooks-v3-count "^1.0.0"
+ dayjs "^1.9.1"
+ intersection-observer "^0.12.0"
+ js-cookie "^2.x.x"
+ lodash "^4.17.21"
+ resize-observer-polyfill "^1.5.1"
+ screenfull "^5.0.0"
+ tslib "^2.4.1"
+
ansi-colors@4.1.1:
version "4.1.1"
resolved "https://registry.npmmirror.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
@@ -967,6 +1032,11 @@ chalk@^4.1.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
+charenc@0.0.2:
+ version "0.0.2"
+ resolved "https://registry.npmmirror.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
+ integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==
+
chokidar@3.5.3, "chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.2:
version "3.5.3"
resolved "https://registry.npmmirror.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
@@ -1001,6 +1071,11 @@ clsx@^1.1.1:
resolved "https://registry.npmmirror.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
+cluster-key-slot@1.1.2:
+ version "1.1.2"
+ resolved "https://registry.npmmirror.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac"
+ integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==
+
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
@@ -1105,6 +1180,11 @@ create-require@^1.1.0:
resolved "https://registry.npmmirror.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
+crypt@0.0.2:
+ version "0.0.2"
+ resolved "https://registry.npmmirror.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
+ integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==
+
csstype@^3.0.2:
version "3.1.2"
resolved "https://registry.npmmirror.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
@@ -1122,7 +1202,7 @@ date-fns@^2.29.3:
dependencies:
"@babel/runtime" "^7.21.0"
-dayjs@^1.11.9:
+dayjs@^1.11.9, dayjs@^1.9.1:
version "1.11.9"
resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.9.tgz#9ca491933fadd0a60a2c19f6c237c03517d71d1a"
integrity sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==
@@ -1395,6 +1475,11 @@ function-bind@^1.1.1:
resolved "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+generic-pool@3.9.0:
+ version "3.9.0"
+ resolved "https://registry.npmmirror.com/generic-pool/-/generic-pool-3.9.0.tgz#36f4a678e963f4fdb8707eab050823abc4e8f5e4"
+ integrity sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==
+
gensync@^1.0.0-beta.2:
version "1.0.0-beta.2"
resolved "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
@@ -1537,6 +1622,11 @@ inherits@2, inherits@2.0.4, inherits@~2.0.3:
resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+intersection-observer@^0.12.0:
+ version "0.12.2"
+ resolved "https://registry.npmmirror.com/intersection-observer/-/intersection-observer-0.12.2.tgz#4a45349cc0cd91916682b1f44c28d7ec737dc375"
+ integrity sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==
+
ipaddr.js@1.9.1:
version "1.9.1"
resolved "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
@@ -1554,6 +1644,11 @@ is-binary-path@~2.1.0:
dependencies:
binary-extensions "^2.0.0"
+is-buffer@~1.1.6:
+ version "1.1.6"
+ resolved "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
+ integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
+
is-core-module@^2.13.0:
version "2.13.0"
resolved "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db"
@@ -1598,6 +1693,11 @@ isarray@~1.0.0:
resolved "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
+js-cookie@^2.x.x:
+ version "2.2.1"
+ resolved "https://registry.npmmirror.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
+ integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==
+
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -1676,6 +1776,15 @@ make-error@^1.1.1:
resolved "https://registry.npmmirror.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
+md5@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.npmmirror.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f"
+ integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==
+ dependencies:
+ charenc "0.0.2"
+ crypt "0.0.2"
+ is-buffer "~1.1.6"
+
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@@ -2088,6 +2197,18 @@ readdirp@~3.6.0:
dependencies:
picomatch "^2.2.1"
+redis@^4.6.7:
+ version "4.6.7"
+ resolved "https://registry.npmmirror.com/redis/-/redis-4.6.7.tgz#c73123ad0b572776223f172ec78185adb72a6b57"
+ integrity sha512-KrkuNJNpCwRm5vFJh0tteMxW8SaUzkm5fBH7eL5hd/D0fAkzvapxbfGPP/r+4JAXdQuX7nebsBkBqA2RHB7Usw==
+ dependencies:
+ "@redis/bloom" "1.2.0"
+ "@redis/client" "1.5.8"
+ "@redis/graph" "1.1.0"
+ "@redis/json" "1.0.4"
+ "@redis/search" "1.1.3"
+ "@redis/time-series" "1.0.4"
+
regenerator-runtime@^0.14.0:
version "0.14.0"
resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45"
@@ -2155,6 +2276,11 @@ scheduler@^0.23.0:
dependencies:
loose-envify "^1.1.0"
+screenfull@^5.0.0:
+ version "5.2.0"
+ resolved "https://registry.npmmirror.com/screenfull/-/screenfull-5.2.0.tgz#6533d524d30621fc1283b9692146f3f13a93d1ba"
+ integrity sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==
+
scroll-into-view-if-needed@^2.2.24:
version "2.2.31"
resolved "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz#d3c482959dc483e37962d1521254e3295d0d1587"
@@ -2377,7 +2503,7 @@ ts-node@^10.9.1:
v8-compile-cache-lib "^3.0.1"
yn "3.1.1"
-tslib@^2.0.0:
+tslib@^2.0.0, tslib@^2.4.1:
version "2.6.2"
resolved "https://registry.npmmirror.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
@@ -2478,16 +2604,16 @@ y18n@^5.0.5:
resolved "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
+yallist@4.0.0, yallist@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
+ integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
+
yallist@^3.0.2:
version "3.1.1"
resolved "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
-yallist@^4.0.0:
- version "4.0.0"
- resolved "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
- integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
-
yaml@^1.10.0:
version "1.10.2"
resolved "https://registry.npmmirror.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"