feat:基本功能完成
This commit is contained in:
parent
8917a985b6
commit
50b5dbfe95
8
.env
Normal file
8
.env
Normal file
@ -0,0 +1,8 @@
|
||||
# 修改配置
|
||||
MYSQL_ADDRESS=localhost:3306
|
||||
MYSQL_USER=root
|
||||
MYSQL_PASSWORD=123456
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
# 服务端口
|
||||
NGINX_PORT=8080
|
109
.gitignore
vendored
109
.gitignore
vendored
@ -1,109 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
|
||||
sqliteHistory.json
|
||||
mysqlHistory.json
|
||||
database.db
|
20
.vscode/launch.json
vendored
20
.vscode/launch.json
vendored
@ -1,20 +0,0 @@
|
||||
{
|
||||
// 使用 IntelliSense 了解相关属性。
|
||||
// 悬停以查看现有属性的描述。
|
||||
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "pwa-node",
|
||||
"request": "launch",
|
||||
"name": "Launch Program",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"program": "${workspaceFolder}/dist/index.js",
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/dist/**/*.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
import { Context } from "koa";
|
||||
import { create } from "svg-captcha";
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { RedisHelper } from '../util/RedisHelper';
|
||||
const router = {};
|
||||
|
||||
/**
|
||||
* 获取验证码
|
||||
*/
|
||||
router["GET /captcha"] = async function (ctx: Context) {
|
||||
let key: string = uuid().replaceAll("-", "");
|
||||
let obj = create();
|
||||
await RedisHelper.client.set(key, obj.text);
|
||||
ctx.body = {
|
||||
key,
|
||||
data: obj.data
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
export default router;
|
@ -1,6 +0,0 @@
|
||||
import { Context } from "koa";
|
||||
|
||||
const router = {};
|
||||
|
||||
|
||||
export default router;
|
50
config.ts
50
config.ts
@ -1,50 +0,0 @@
|
||||
import * as path from 'path';
|
||||
|
||||
//后台所在绝对路径
|
||||
const rootPath = path.resolve(__dirname, '..');
|
||||
|
||||
let config = {
|
||||
rootPath,
|
||||
port: process.env.PORT ? parseInt(process.env.PORT) : 8089,
|
||||
urlPrefix: '/qiezi/api',
|
||||
//是否为windows平台
|
||||
isWindows: process.platform.toLocaleLowerCase().includes("win"),
|
||||
//redis相关配置
|
||||
redis: {
|
||||
enable: true,
|
||||
url: "redis://localhost:6379"
|
||||
},
|
||||
//sqlite相关配置
|
||||
sqlite: {
|
||||
enable: false, //是否启用sqlite
|
||||
//相对于项目根目录
|
||||
filePath: "database.db",
|
||||
//sql存放地址,用于执行sql
|
||||
sqlFolder: "sqliteSqls"
|
||||
},
|
||||
//mysql相关配置
|
||||
mysql: {
|
||||
enable: true, //是否启用mysql
|
||||
sqlFolder: "mysqlSqls",
|
||||
connection: {
|
||||
host: "localhost",
|
||||
port: 3306,
|
||||
user: "root",
|
||||
password: "123456",
|
||||
database: "qiezi",
|
||||
}
|
||||
|
||||
},
|
||||
bodyLimit: {
|
||||
formLimit: '2mb',
|
||||
urlencoded: true,
|
||||
multipart: true,
|
||||
formidable: {
|
||||
uploadDir: path.join(rootPath, 'files', 'temp', 'uploads'),
|
||||
keepExtenstions: true,
|
||||
maxFieldsSize: 1024 * 1024
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
52
config/nginx.conf
Normal file
52
config/nginx.conf
Normal file
@ -0,0 +1,52 @@
|
||||
user root;
|
||||
worker_processes auto;
|
||||
pid /run/nginx.pid;
|
||||
events {
|
||||
worker_connections 768;
|
||||
}
|
||||
http {
|
||||
# Basic Settings
|
||||
sendfile on;
|
||||
# SSL Settings
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
# Logging Settings
|
||||
access_log /dev/stdout;
|
||||
error_log /dev/stderr;
|
||||
# Gzip Settings
|
||||
gzip on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 2048;
|
||||
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png application/vnd.ms-fontobject font/ttf font/opentype font/x-woff image/svg+xml;
|
||||
gzip_min_length 1K;
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
server {
|
||||
listen 8080;
|
||||
listen [::]:8080;
|
||||
index index.html;
|
||||
root /opt/dist/;
|
||||
server_name _;
|
||||
location /bookmark/api/ {
|
||||
proxy_pass http://backend:8088;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
client_max_body_size 100m;
|
||||
}
|
||||
|
||||
location /files/public/{
|
||||
root /opt;
|
||||
}
|
||||
|
||||
location / {
|
||||
root /opt/dist;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
1
config/timezone
Normal file
1
config/timezone
Normal file
@ -0,0 +1 @@
|
||||
Asia/Shanghai
|
@ -1,16 +0,0 @@
|
||||
import ErrorHelper from "../util/ErrorHelper";
|
||||
import SqliteHelper from "../util/SqliteHelper";
|
||||
|
||||
export default class ApplicationRuleDao {
|
||||
/**
|
||||
* 查询所有
|
||||
* @param obj
|
||||
* @returns
|
||||
*/
|
||||
static async getAll(): Promise<Array<any>> {
|
||||
let res = await SqliteHelper.pool.all('select id,createdDate,updatedDate,name,comment,content from application_rule');
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
}
|
38
docker-compose.yml
Normal file
38
docker-compose.yml
Normal file
@ -0,0 +1,38 @@
|
||||
version: "2"
|
||||
services:
|
||||
front:
|
||||
image: nginx
|
||||
networks:
|
||||
- bookmark
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime
|
||||
- ./config/timezone:/etc/timezone
|
||||
- ./qiezi_front/dist:/opt/dist
|
||||
- ./data/nginx/nginx.conf:/etc/nginx/nginx.conf
|
||||
ports:
|
||||
- ${NGINX_PORT}:8080
|
||||
|
||||
backend:
|
||||
image: openjdk:11.0
|
||||
networks:
|
||||
- bookmark
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime
|
||||
- ./config/timezone:/etc/timezone
|
||||
- ./qieziBackend/target/backend-0.0.1-SNAPSHOT.jar:/opt/app/service.jar
|
||||
working_dir: /opt/app
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
sleep 5 && \
|
||||
java -jar -Dspring.profiles.active=prd \
|
||||
-Dmybatis-plus.configuration.log-impl=org.apache.ibatis.logging.nologging.NoLoggingImpl \
|
||||
-Dspring.datasource.user='${MYSQL_USER}' \
|
||||
-Dspring.datasource.password='${MYSQL_PASSWORD}' \
|
||||
-Dspring.datasource.url=jdbc:mysql://${MYSQL_ADDRESS}/bookmark?useUnicode=true\&characterEncoding=utf-8\&useSSL=false\&useJDBCCompliantTimezoneShift=true\&useLegacyDatetimeCode=false\&serverTimezone=UTC \
|
||||
-Dspring.redis.host=${REDIS_HOST} \
|
||||
-Dspring.redis.port=${REDIS_PORT} \
|
||||
service.jar
|
||||
networks:
|
||||
bookmark:
|
@ -1,30 +0,0 @@
|
||||
export default class HostPo {
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* 标识key,用于表明身份
|
||||
*/
|
||||
key: string;
|
||||
/**
|
||||
* 密钥
|
||||
*/
|
||||
secret: string;
|
||||
/**
|
||||
* 网站名称
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* 网站域名
|
||||
*/
|
||||
host: string;
|
||||
/**
|
||||
* pv
|
||||
*/
|
||||
pv: number;
|
||||
/**
|
||||
* uv
|
||||
*/
|
||||
uv: number;
|
||||
}
|
52
index.ts
52
index.ts
@ -1,52 +0,0 @@
|
||||
import koa from "koa";
|
||||
import Router from "koa-router";
|
||||
import koaBody from "koa-body";
|
||||
import * as path from "path";
|
||||
import RouterMW from "./middleware/controllerEngine";
|
||||
|
||||
import config from "./config";
|
||||
import handleError from "./middleware/handleError";
|
||||
import init from "./middleware/init";
|
||||
import SqliteUtil from './util/SqliteHelper';
|
||||
import log from './util/LogUtil';
|
||||
import { MysqlUtil } from "./util/MysqlHelper";
|
||||
import { RedisHelper } from "./util/RedisHelper";
|
||||
|
||||
|
||||
log.info(config);
|
||||
const app = new koa();
|
||||
|
||||
let router = new Router({
|
||||
prefix: config.urlPrefix
|
||||
});
|
||||
|
||||
app.use(require('koa-static')(path.join(config.rootPath, 'static')));
|
||||
|
||||
//表单解析
|
||||
app.use(koaBody(config.bodyLimit));
|
||||
//请求预处理
|
||||
app.use(init);
|
||||
//错误处理
|
||||
app.use(handleError);
|
||||
|
||||
app.use(RouterMW(router, path.join(config.rootPath, "dist/api")));
|
||||
(async () => {
|
||||
//初始化sqlite
|
||||
if (config.sqlite.enable) {
|
||||
await SqliteUtil.createPool();
|
||||
}
|
||||
//初始化mysql
|
||||
if (config.mysql.enable) {
|
||||
await MysqlUtil.createPool();
|
||||
}
|
||||
//初始化redis
|
||||
if (config.redis.enable) {
|
||||
await RedisHelper.create();
|
||||
}
|
||||
app.listen(config.port);
|
||||
log.info(`server listened `, config.port);
|
||||
})();
|
||||
|
||||
app.on("error", (error) => {
|
||||
console.error(error);
|
||||
})
|
@ -1,50 +0,0 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import log from '../util/LogUtil';
|
||||
|
||||
async function addMapping(router, filePath: string) {
|
||||
let mapping = require(filePath).default;
|
||||
for (let url in mapping) {
|
||||
if (url.startsWith('GET ')) {
|
||||
let temp = url.substring(4);
|
||||
router.get(temp, mapping[url]);
|
||||
log.info(`----GET:${temp}`);
|
||||
} else if (url.startsWith('POST ')) {
|
||||
let temp = url.substring(5);
|
||||
router.post(temp, mapping[url]);
|
||||
log.info(`----POST:${temp}`);
|
||||
} else if (url.startsWith('PUT ')) {
|
||||
let temp = url.substring(4);
|
||||
router.put(temp, mapping[url]);
|
||||
log.info(`----PUT:${temp}`);
|
||||
} else if (url.startsWith('DELETE ')) {
|
||||
let temp = url.substring(7);
|
||||
router.delete(temp, mapping[url]);
|
||||
log.info(`----DELETE: ${temp}`);
|
||||
} else {
|
||||
log.info(`xxxxx无效路径:${url}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addControllers(router, filePath) {
|
||||
let files = fs.readdirSync(filePath).filter(item => item.endsWith('.js'));
|
||||
for (let index in files) {
|
||||
let element = files[index];
|
||||
let temp = path.join(filePath, element);
|
||||
let state = fs.statSync(temp);
|
||||
if (state.isDirectory()) {
|
||||
addControllers(router, temp);
|
||||
} else {
|
||||
if (!temp.endsWith('Helper.js')) {
|
||||
log.info('\n--开始处理: ' + element + '路由');
|
||||
addMapping(router, temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function engine(router, folder) {
|
||||
addControllers(router, folder);
|
||||
return router.routes();
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
import log from '../util/LogUtil';
|
||||
|
||||
let f = async (ctx, next) => {
|
||||
try {
|
||||
await next();
|
||||
} catch (error: any) {
|
||||
if (error.status != undefined) {
|
||||
ctx.status = error.status;
|
||||
} else {
|
||||
ctx.status = 500;
|
||||
}
|
||||
ctx.body = error.message;
|
||||
log.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
export default f;
|
@ -1,51 +0,0 @@
|
||||
import config from '../config';
|
||||
import ObjectHelper from '../util/ObjectOperate';
|
||||
|
||||
let doSuccess = (ctx, body) => {
|
||||
switch (ctx.method) {
|
||||
case 'GET':
|
||||
ctx.status = body !== null ? 200 : 204;
|
||||
ctx.body = body;
|
||||
break;
|
||||
case 'POST':
|
||||
ctx.status = body !== null ? 201 : 204;
|
||||
ctx.body = body;
|
||||
break;
|
||||
case 'PUT':
|
||||
ctx.status = body !== null ? 200 : 204;
|
||||
ctx.body = body;
|
||||
break;
|
||||
case 'DELETE':
|
||||
ctx.status = body !== null ? 200 : 204;
|
||||
ctx.body = body;
|
||||
break;
|
||||
}
|
||||
Object.assign(ctx.allParams, ctx.params);
|
||||
}
|
||||
|
||||
export default async (ctx, next) => {
|
||||
//跨域
|
||||
ctx.set("Access-Control-Allow-Origin", "*");
|
||||
ctx.set("Access-Control-Allow-Headers", "X-Requested-With");
|
||||
ctx.set("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
|
||||
ctx.set("X-Powered-By", ' 3.2.1');
|
||||
ctx.set("Content-Type", "application/json;charset=utf-8");
|
||||
//合并请求参数到allParams
|
||||
let objs = new Array();
|
||||
if (ctx.method == "POST" || ctx.method == "PUT") {
|
||||
if (ctx.request.body) {
|
||||
if (ctx.request.body.fields != undefined && ctx.request.body.files != undefined) {
|
||||
objs.push(ctx.request.body.fields, ctx.request.body.files);
|
||||
} else {
|
||||
objs.push(ctx.request.body);
|
||||
}
|
||||
}
|
||||
}
|
||||
objs.push(ctx.query);
|
||||
ctx.allParams = ObjectHelper.combineObject(objs);
|
||||
|
||||
ctx.onSuccess = function (body = null) {
|
||||
doSuccess(ctx, body);
|
||||
};
|
||||
await next();
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
CREATE TABLE qiezi.host (
|
||||
id INT auto_increment NOT NULL,
|
||||
`key` CHAR(32) NOT NULL COMMENT 'key,用于标识',
|
||||
secret char(32) NOT NULL COMMENT '密钥',
|
||||
name varchar(100) NOT NULL COMMENT '网站名',
|
||||
host varchar(100) NOT NULL COMMENT '网站域名(不含http前缀以及路径)',
|
||||
pv INT UNSIGNED DEFAULT 0 NOT NULL,
|
||||
uv INT UNSIGNED DEFAULT 0 NOT NULL,
|
||||
CONSTRAINT host_pk PRIMARY KEY (id)
|
||||
)
|
||||
ENGINE=InnoDB
|
||||
DEFAULT CHARSET=utf8mb4
|
||||
COLLATE=utf8mb4_0900_ai_ci
|
||||
COMMENT='host表,记录某个站点总的pv,uv数据';
|
||||
CREATE UNIQUE INDEX host_key_IDX USING BTREE ON qiezi.host (`key`);
|
||||
|
||||
CREATE TABLE qiezi.host_day(
|
||||
id INT auto_increment NOT NULL,
|
||||
hostId INT NOT NULL COMMENT 'hostId',
|
||||
dateNum INT NOT NULL COMMENT '日期比如20200202',
|
||||
pv INT UNSIGNED DEFAULT 0 NOT NULL,
|
||||
uv INT UNSIGNED DEFAULT 0 NOT NULL,
|
||||
CONSTRAINT detail_page_pk PRIMARY KEY (id)
|
||||
)
|
||||
ENGINE=InnoDB
|
||||
DEFAULT CHARSET=utf8mb4
|
||||
COLLATE=utf8mb4_0900_ai_ci
|
||||
COMMENT='记录域名日pv/uv';
|
||||
CREATE INDEX detail_page_host_id_date_IDX USING BTREE ON qiezi.host_day(`hostId`,`dateNum`);
|
||||
|
||||
|
||||
|
||||
|
||||
CREATE TABLE qiezi.detail_page(
|
||||
id INT auto_increment NOT NULL,
|
||||
hostId INT NOT NULL COMMENT 'hostId',
|
||||
pv INT UNSIGNED DEFAULT 0 NOT NULL,
|
||||
uv INT UNSIGNED DEFAULT 0 NOT NULL,
|
||||
CONSTRAINT detail_page_pk PRIMARY KEY (id)
|
||||
)
|
||||
ENGINE=InnoDB
|
||||
DEFAULT CHARSET=utf8mb4
|
||||
COLLATE=utf8mb4_0900_ai_ci
|
||||
COMMENT='detail表,记录细分页面pv/uv';
|
||||
CREATE INDEX detail_page_host_id_IDX USING BTREE ON qiezi.detail_page(`hostId`);
|
||||
|
32
package.json
32
package.json
@ -1,32 +0,0 @@
|
||||
{
|
||||
"name": "nas_backup",
|
||||
"version": "1.0.0",
|
||||
"description": "文件备份用",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "fxb",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/fs-extra": "^5.0.4",
|
||||
"@types/koa": "^2.0.47",
|
||||
"@types/node": "^11.13.4",
|
||||
"axios": "^0.21.1",
|
||||
"fs-extra": "^7.0.0",
|
||||
"koa": "^2.13.4",
|
||||
"koa-body": "^4.2.0",
|
||||
"koa-router": "^10.1.1",
|
||||
"koa-static": "^5.0.0",
|
||||
"koa2-cors": "^2.0.6",
|
||||
"log4js": "^6.3.0",
|
||||
"moment": "^2.22.2",
|
||||
"mysql2": "^2.3.3",
|
||||
"redis": "^4.0.3",
|
||||
"sqlite": "^4.0.23",
|
||||
"sqlite3": "^5.0.2",
|
||||
"svg-captcha": "^1.4.0",
|
||||
"uuid": "^8.3.2",
|
||||
"winston": "^3.1.0"
|
||||
}
|
||||
}
|
33
qieziBackend/.gitignore
vendored
Normal file
33
qieziBackend/.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
HELP.md
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
95
qieziBackend/pom.xml
Normal file
95
qieziBackend/pom.xml
Normal file
@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.6.3</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.fanxb</groupId>
|
||||
<artifactId>backend</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>qieziBackend</name>
|
||||
<description>qieziBackend</description>
|
||||
<properties>
|
||||
<java.version>11</java.version>
|
||||
<skipTests>true</skipTests>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||
<version>2.2.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>4.9.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-core</artifactId>
|
||||
<version>5.2.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>1.2.79</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.7.21</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -0,0 +1,34 @@
|
||||
package com.fanxb.backend;
|
||||
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import com.alibaba.fastjson.support.config.FastJsonConfig;
|
||||
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = "com.fanxb.backend")
|
||||
@EnableScheduling
|
||||
@EnableTransactionManagement
|
||||
public class QieziBackendApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(QieziBackendApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HttpMessageConverters fastJsonHttpMessageConverters() {
|
||||
// 1.定义一个converters转换消息的对象
|
||||
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
|
||||
// 2.添加fastjson的配置信息,比如: 是否需要格式化返回的json数据
|
||||
FastJsonConfig fastJsonConfig = new FastJsonConfig();
|
||||
fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect);
|
||||
// 3.在converter中添加配置信息
|
||||
fastConverter.setFastJsonConfig(fastJsonConfig);
|
||||
// 5.返回HttpMessageConverters对象
|
||||
return new HttpMessageConverters(fastConverter);
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package com.fanxb.backend.config;
|
||||
|
||||
import com.fanxb.backend.entity.ResultObject;
|
||||
import com.fanxb.backend.entity.exception.CustomBaseException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 全局异常处理类
|
||||
*
|
||||
* @author fanxb
|
||||
* @date 2022/2/16 15:28
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
@Slf4j
|
||||
public class ExceptionHandleConfig {
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResultObject handleException(Exception e) {
|
||||
CustomBaseException be;
|
||||
if (e instanceof CustomBaseException) {
|
||||
be = (CustomBaseException) e;
|
||||
//手动抛出的异常,仅记录message
|
||||
log.info("baseException:{}", be.getMessage());
|
||||
} else if (e instanceof ConstraintViolationException) {
|
||||
//url参数、数组类参数校验报错类
|
||||
ConstraintViolationException ce = (ConstraintViolationException) e;
|
||||
//针对参数校验异常,建立了一个异常类
|
||||
be = new CustomBaseException(ce.getMessage());
|
||||
} else if (e instanceof MethodArgumentNotValidException) {
|
||||
//json对象类参数校验报错类
|
||||
MethodArgumentNotValidException ce = (MethodArgumentNotValidException) e;
|
||||
be = new CustomBaseException(Objects.requireNonNull(ce.getFieldError()).getDefaultMessage());
|
||||
} else {
|
||||
//其它异常,非自动抛出的,无需给前端返回具体错误内容(用户不需要看见空指针之类的异常信息)
|
||||
log.error("other exception:{}", e.getMessage(), e);
|
||||
be = new CustomBaseException("系统异常,请稍后重试", e);
|
||||
}
|
||||
return new ResultObject(be.getCode(), be.getMessage(), null);
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package com.fanxb.backend.constants;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @author fanxb
|
||||
* @date 2022/2/15 16:41
|
||||
*/
|
||||
@Component
|
||||
public class CommonConstant {
|
||||
|
||||
/**
|
||||
* 是否开发环境
|
||||
*/
|
||||
public static boolean IS_DEV = false;
|
||||
|
||||
@Value("${spring.profiles.active}")
|
||||
public void setIsDev(String env) {
|
||||
IS_DEV = env.contains("dev");
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.fanxb.backend.constants;
|
||||
|
||||
/**
|
||||
* redis相关前后缀
|
||||
*
|
||||
* @author fanxb
|
||||
* @date 2022/2/15 15:28
|
||||
*/
|
||||
public class RedisConstant {
|
||||
|
||||
/**
|
||||
* 应用注册用
|
||||
*/
|
||||
public static final String APPLICATION_SIGN_PRE = "application_sign_";
|
||||
|
||||
/**
|
||||
* key对应hostId关系
|
||||
*/
|
||||
public static final String KEY_ID_PRE = "key_id_";
|
||||
/**
|
||||
* 页面uv前缀
|
||||
*/
|
||||
public static final String HOST_UV_PRE = "host_uv_pre_";
|
||||
public static final String PAGE_UV_PRE = "page_uv_pre_";
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package com.fanxb.backend.controller;
|
||||
|
||||
import com.fanxb.backend.entity.ResultObject;
|
||||
import com.fanxb.backend.entity.dto.ApplicationSignDto;
|
||||
import com.fanxb.backend.service.ApplicationService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author fanxb
|
||||
* @date 2022/2/15 15:04
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/application")
|
||||
@Validated
|
||||
public class ApplicationController {
|
||||
private final ApplicationService applicationService;
|
||||
|
||||
@Autowired
|
||||
public ApplicationController(ApplicationService applicationService) {
|
||||
this.applicationService = applicationService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
@PostMapping("/sign")
|
||||
public ResultObject sign(@RequestBody ApplicationSignDto signDto) {
|
||||
return ResultObject.success(applicationService.sign(signDto));
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面访问
|
||||
*
|
||||
* @param callBack 回调函数
|
||||
* @param key key
|
||||
* @author fanxb
|
||||
* date 2022/2/16 15:24
|
||||
*/
|
||||
@GetMapping("/visit")
|
||||
public void visit(HttpServletRequest request, HttpServletResponse response,
|
||||
@NotBlank(message = "回调函数不能为空") String callBack, @NotBlank(message = "key不能为空") String key) throws IOException {
|
||||
applicationService.visit(request, response, callBack, key);
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.fanxb.backend.controller;
|
||||
|
||||
import com.fanxb.backend.constants.RedisConstant;
|
||||
import com.fanxb.backend.entity.ResultObject;
|
||||
import com.fanxb.backend.service.CaptchaService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 验证码
|
||||
*
|
||||
* @author fanxb
|
||||
* @date 2022/2/15 16:01
|
||||
*/
|
||||
@RestController()
|
||||
@RequestMapping("/captcha")
|
||||
public class CaptchaController {
|
||||
|
||||
private final CaptchaService captchaService;
|
||||
|
||||
@Autowired
|
||||
public CaptchaController(CaptchaService captchaService) {
|
||||
this.captchaService = captchaService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册用验证码
|
||||
*/
|
||||
@GetMapping("/sign")
|
||||
public ResultObject signCaptcha() {
|
||||
return ResultObject.success(captchaService.create(RedisConstant.APPLICATION_SIGN_PRE));
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.fanxb.backend.dao;
|
||||
|
||||
import com.fanxb.backend.entity.po.DetailPagePo;
|
||||
import com.fanxb.backend.entity.po.HostPo;
|
||||
import org.apache.ibatis.annotations.*;
|
||||
|
||||
/**
|
||||
* @author fanxb
|
||||
* @date 2022/2/15 16:37
|
||||
*/
|
||||
@Mapper
|
||||
public interface DetailPageDao {
|
||||
|
||||
/***
|
||||
* 新增一个(uv,pv初始化为1)
|
||||
*
|
||||
* @param detailPagePo 页面详情
|
||||
*
|
||||
* @author fanxb
|
||||
* date 2022/2/15 16:39
|
||||
*/
|
||||
@Insert("insert into detail_page(hostId,path,pv,uv) value(#{hostId},#{path},#{pv},#{uv})")
|
||||
void insertOne(DetailPagePo detailPagePo);
|
||||
|
||||
/**
|
||||
* 获取uv,pv
|
||||
*
|
||||
* @param hostId hostId
|
||||
* @param path path
|
||||
* @return {@link HostPo}
|
||||
* @author fanxb
|
||||
* date 2022/2/16 11:11
|
||||
*/
|
||||
@Select("select id,hostId,path,uv,pv from detail_page where hostId=#{hostId} and path = #{path}")
|
||||
DetailPagePo getUvPvById(@Param("hostId") int hostId, @Param("path") String path);
|
||||
|
||||
/**
|
||||
* 更新uv,pv
|
||||
*
|
||||
* @param id id
|
||||
* @param uvIncrement uv增量
|
||||
* @author fanxb
|
||||
* date 2022/2/16 11:13
|
||||
*/
|
||||
@Update("update detail_page set uv=uv+#{uvIncrement},pv=pv+1 where id=#{id}")
|
||||
void updateUvPv(@Param("id") int id, @Param("uvIncrement") int uvIncrement);
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package com.fanxb.backend.dao;
|
||||
|
||||
import com.fanxb.backend.entity.po.HostPo;
|
||||
import org.apache.ibatis.annotations.*;
|
||||
|
||||
/**
|
||||
* @author fanxb
|
||||
* @date 2022/2/15 16:37
|
||||
*/
|
||||
@Mapper
|
||||
public interface HostDao {
|
||||
|
||||
/***
|
||||
* 新增一个
|
||||
*
|
||||
* @param host host
|
||||
*
|
||||
* @author fanxb
|
||||
* date 2022/2/15 16:39
|
||||
*/
|
||||
@Insert("insert into host(`key`,secret,name,host,pv,uv) value(#{key},#{secret},#{name},#{host},0,0)")
|
||||
@Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id")
|
||||
void insertOne(HostPo host);
|
||||
|
||||
/**
|
||||
* 根据key获取id
|
||||
*
|
||||
* @param key key
|
||||
* @author fanxb
|
||||
* date 2022/2/16 10:33
|
||||
*/
|
||||
@Select("select id from host where `key` = #{key}")
|
||||
Integer getIdByKey(String key);
|
||||
|
||||
/**
|
||||
* 获取uv,pv
|
||||
*
|
||||
* @param id id
|
||||
* @return {@link HostPo}
|
||||
* @author fanxb
|
||||
* date 2022/2/16 11:11
|
||||
*/
|
||||
@Select("select id,uv,pv from host where id=#{id}")
|
||||
HostPo getUvPvById(int id);
|
||||
|
||||
/**
|
||||
* 更新uv,pv
|
||||
*
|
||||
* @param id id
|
||||
* @param uvIncrement uv增量
|
||||
* @author fanxb
|
||||
* date 2022/2/16 11:13
|
||||
*/
|
||||
@Update("update host set uv=uv+#{uvIncrement},pv=pv+1 where id=#{id}")
|
||||
void updateUvPv(@Param("id") int id, @Param("uvIncrement") int uvIncrement);
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package com.fanxb.backend.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 统一返回类
|
||||
*
|
||||
* @author fanxb
|
||||
* @date 2019/3/19 18:05
|
||||
*/
|
||||
@Data
|
||||
public class ResultObject {
|
||||
|
||||
/**
|
||||
* 状态,1:正常,0:异常,-1:未认证
|
||||
*/
|
||||
private int code;
|
||||
private String message;
|
||||
private Object data;
|
||||
|
||||
public ResultObject() {
|
||||
}
|
||||
|
||||
public ResultObject(int code, String message, Object data) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static ResultObject unAuth() {
|
||||
return new ResultObject(-1, "", null);
|
||||
}
|
||||
|
||||
public static ResultObject success() {
|
||||
return new ResultObject(1, null, null);
|
||||
}
|
||||
|
||||
public static ResultObject success(Object data) {
|
||||
return new ResultObject(1, null, data);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.fanxb.backend.entity.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*
|
||||
* @author fanxb
|
||||
* @date 2022/2/15 16:29
|
||||
*/
|
||||
@Data
|
||||
public class ApplicationSignDto {
|
||||
/**
|
||||
* 验证码code
|
||||
*/
|
||||
private String code;
|
||||
/**
|
||||
* 网站名
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 域名
|
||||
*/
|
||||
private String host;
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package com.fanxb.backend.entity.exception;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* 类功能简述: 自定义错误类,默认错误码为0,表示一般错误
|
||||
* 类功能详述:
|
||||
*
|
||||
* @author fanxb
|
||||
* @date 2019/3/19 18:09
|
||||
*/
|
||||
public class CustomBaseException extends RuntimeException {
|
||||
|
||||
private String message;
|
||||
/**
|
||||
* 默认0
|
||||
*/
|
||||
private final int code = 0;
|
||||
|
||||
public CustomBaseException() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public CustomBaseException(String message) {
|
||||
this(message, null);
|
||||
}
|
||||
|
||||
public CustomBaseException(Exception e) {
|
||||
this(null, e);
|
||||
}
|
||||
|
||||
public CustomBaseException(String message, Exception e) {
|
||||
super(e);
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
if (StrUtil.isEmpty(this.message)) {
|
||||
return super.getMessage();
|
||||
} else {
|
||||
return this.message;
|
||||
}
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.fanxb.backend.entity.po;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 页面路径详情
|
||||
*
|
||||
* @author fanxb
|
||||
* date 2022/2/15 11:12
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Accessors(chain = true)
|
||||
public class DetailPagePo {
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
* 域名
|
||||
*/
|
||||
private int hostId;
|
||||
/**
|
||||
* 页面路径
|
||||
*/
|
||||
private String path;
|
||||
private long pv;
|
||||
private long uv;
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package com.fanxb.backend.entity.po;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class HostDayPo {
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
private int id;
|
||||
/**
|
||||
* hostId
|
||||
*/
|
||||
private int hostId;
|
||||
/**
|
||||
* 日期20200202
|
||||
*/
|
||||
private int dateNum;
|
||||
private long pv;
|
||||
private long uv;
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package com.fanxb.backend.entity.po;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public class HostPo {
|
||||
private int id;
|
||||
/**
|
||||
* 标识key
|
||||
*/
|
||||
private String key;
|
||||
/**
|
||||
* 密钥
|
||||
*/
|
||||
private String secret;
|
||||
/**
|
||||
* 网站名
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 网站域名
|
||||
*/
|
||||
private String host;
|
||||
private long pv;
|
||||
private long uv;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.fanxb.backend.entity.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 应用注册vo
|
||||
*
|
||||
* @author fanxb
|
||||
* @date 2022/2/15 16:26
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ApplicationSignVo {
|
||||
private String key;
|
||||
private String secret;
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.fanxb.backend.entity.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* uv,pv数据
|
||||
*
|
||||
* @author fanxb
|
||||
* @date 2022/2/16 15:16
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class UvPvVo {
|
||||
private long totalUv;
|
||||
private long totalPv;
|
||||
private long pageUv;
|
||||
private long pagePv;
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package com.fanxb.backend.factory;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
/**
|
||||
* 类功能简述:
|
||||
* 类功能详述:线程工厂
|
||||
*
|
||||
* @author fanxb
|
||||
* @date 2018/10/12 11:29
|
||||
*/
|
||||
public class CustomThreadFactory implements ThreadFactory {
|
||||
|
||||
/**
|
||||
* 记录创建线程数
|
||||
*/
|
||||
private int counter;
|
||||
/**
|
||||
* 线程工程名
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 记录最近1000条创建历史
|
||||
*/
|
||||
private List<String> history;
|
||||
|
||||
private int historyLength;
|
||||
|
||||
public CustomThreadFactory(String name) {
|
||||
this.name = name;
|
||||
this.history = new LinkedList<>();
|
||||
this.historyLength = 1000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread t = new Thread(r, name + "-Thread-" + counter);
|
||||
this.counter++;
|
||||
history.add(String.format("Created thread %d with name %s on %s \n", t.getId(), t.getName(), new Date().toString()));
|
||||
if (history.size() >= this.historyLength) {
|
||||
history.remove(0);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
this.history.forEach(builder::append);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
package com.fanxb.backend.factory;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 类功能简述: 线程池工厂,可使用此工厂创建线程池,等待队列使用:ArrayBlockingQueue
|
||||
* 类功能详述:
|
||||
*
|
||||
* @author fanxb
|
||||
* @date 2018/11/1 10:57
|
||||
*/
|
||||
public class ThreadPoolFactory {
|
||||
/**
|
||||
* 默认核心池大小
|
||||
*/
|
||||
public static final int DEFAULT_CORE_POOL_SIZE = 5;
|
||||
/**
|
||||
* 默认最大线程数
|
||||
*/
|
||||
public static final int DEFAULT_MAX_POOL_SIZE = 30;
|
||||
|
||||
/**
|
||||
* 默认空闲线程回收时间(毫秒)
|
||||
*/
|
||||
public static final long DEFAULT_KEEP_ACTIVE_TIME = 1000;
|
||||
|
||||
/**
|
||||
* 默认等待队列长度
|
||||
*/
|
||||
public static final int DEFAULT_QUEUE_LENGTH = 2000;
|
||||
|
||||
/**
|
||||
* Description: 使用默认配置创建一个连接池
|
||||
*
|
||||
* @param factoryName 线程工厂名
|
||||
* @return java.util.concurrent.ThreadPoolExecutor
|
||||
* @author fanxb
|
||||
* @date 2018/10/12 13:38
|
||||
*/
|
||||
public static ThreadPoolExecutor createPool(String factoryName) {
|
||||
ThreadFactory threadFactory = new CustomThreadFactory(factoryName);
|
||||
return createPool(DEFAULT_CORE_POOL_SIZE, DEFAULT_MAX_POOL_SIZE, DEFAULT_KEEP_ACTIVE_TIME, DEFAULT_QUEUE_LENGTH, threadFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Description: 提供参数创建一个连接池
|
||||
*
|
||||
* @param corePoolSize 核心池大小
|
||||
* @param maxPoolSize 线程池最大线程数
|
||||
* @param keepActiveTime 空闲线程回收时间(ms)
|
||||
* @param queueLength 等待队列长度
|
||||
* @return java.util.concurrent.ThreadPoolExecutor
|
||||
* @author fanxb
|
||||
* @date 2018/10/12 13:39
|
||||
*/
|
||||
public static ThreadPoolExecutor createPool(int corePoolSize, int maxPoolSize, long keepActiveTime
|
||||
, int queueLength, String threadName) {
|
||||
return new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepActiveTime
|
||||
, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(queueLength), new CustomThreadFactory(threadName));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Description: 提供参数创建一个连接池,自己提供线程工厂
|
||||
*
|
||||
* @param corePoolSize 核心池大小
|
||||
* @param maxPoolSize 线程池最大线程数
|
||||
* @param keepActiveTime 空闲线程回收时间(ms)
|
||||
* @param queueLength 等待队列长度
|
||||
* @param factory 线程工厂
|
||||
* @return java.util.concurrent.ThreadPoolExecutor
|
||||
* @author fanxb
|
||||
* @date 2018/10/12 13:39
|
||||
*/
|
||||
public static ThreadPoolExecutor createPool(int corePoolSize, int maxPoolSize, long keepActiveTime
|
||||
, int queueLength, ThreadFactory factory) {
|
||||
return new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepActiveTime
|
||||
, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(queueLength), factory);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Description: 强制关闭线程池,不等待已有任务的完成
|
||||
*
|
||||
* @param executor 被关闭线程池对象
|
||||
* @return List<Runnable>
|
||||
* @author fanxb
|
||||
* @date 2018/10/12 13:42
|
||||
*/
|
||||
public static List<Runnable> forceShutdown(ThreadPoolExecutor executor) {
|
||||
if (executor != null) {
|
||||
return executor.shutdownNow();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Description: 关闭一个连接池,等待已有任务完成
|
||||
*
|
||||
* @param executor 被关闭线程池对象
|
||||
* @return void
|
||||
* @author fanxb
|
||||
* @date 2018/10/12 13:45
|
||||
*/
|
||||
public static void shutdown(ThreadPoolExecutor executor) {
|
||||
if (executor == null) {
|
||||
return;
|
||||
}
|
||||
executor.shutdown();
|
||||
try {
|
||||
int count = 0;
|
||||
int timeOut = 2;
|
||||
while (executor.awaitTermination(timeOut, TimeUnit.SECONDS)) {
|
||||
count++;
|
||||
if (count == 100) {
|
||||
executor.shutdownNow();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package com.fanxb.backend.service;
|
||||
|
||||
import com.fanxb.backend.entity.dto.ApplicationSignDto;
|
||||
import com.fanxb.backend.entity.vo.ApplicationSignVo;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 应用管理
|
||||
*
|
||||
* @author fanxb
|
||||
* @date 2022/2/15 16:25
|
||||
*/
|
||||
public interface ApplicationService {
|
||||
|
||||
/**
|
||||
* 应用注册
|
||||
*
|
||||
* @param signDto dto
|
||||
* @return {@link ApplicationSignVo}
|
||||
* @author fanxb
|
||||
* date 2022/2/15 16:32
|
||||
*/
|
||||
ApplicationSignVo sign(ApplicationSignDto signDto);
|
||||
|
||||
/**
|
||||
* 页面访问
|
||||
*
|
||||
* @param request request
|
||||
* @param response response
|
||||
* @param callBack callBack
|
||||
* @param key key
|
||||
* @author fanxb
|
||||
* date 2022/2/16 10:20
|
||||
*/
|
||||
void visit(HttpServletRequest request, HttpServletResponse response, String callBack, String key) throws IOException;
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.fanxb.backend.service;
|
||||
|
||||
/**
|
||||
* 验证码相关类
|
||||
*
|
||||
* @author fanxb
|
||||
* @date 2022/2/15 15:27
|
||||
*/
|
||||
public interface CaptchaService {
|
||||
|
||||
/***
|
||||
* 返回验证码base64
|
||||
*
|
||||
* @param type 验证码类别
|
||||
* @return {@link String}
|
||||
* @author fanxb
|
||||
* date 2022/2/15 16:04
|
||||
*/
|
||||
String create(String type);
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
package com.fanxb.backend.service.impl;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.ContentType;
|
||||
import cn.hutool.http.Header;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.fanxb.backend.constants.RedisConstant;
|
||||
import com.fanxb.backend.constants.CommonConstant;
|
||||
import com.fanxb.backend.dao.DetailPageDao;
|
||||
import com.fanxb.backend.dao.HostDao;
|
||||
import com.fanxb.backend.entity.dto.ApplicationSignDto;
|
||||
import com.fanxb.backend.entity.exception.CustomBaseException;
|
||||
import com.fanxb.backend.entity.po.DetailPagePo;
|
||||
import com.fanxb.backend.entity.po.HostPo;
|
||||
import com.fanxb.backend.entity.vo.ApplicationSignVo;
|
||||
import com.fanxb.backend.entity.vo.UvPvVo;
|
||||
import com.fanxb.backend.service.ApplicationService;
|
||||
import com.fanxb.backend.util.NetUtil;
|
||||
import com.fanxb.backend.util.ThreadPoolUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 应用管理
|
||||
*
|
||||
* @author fanxb
|
||||
* @date 2022/2/15 16:34
|
||||
*/
|
||||
@Service
|
||||
public class ApplicationServiceImpl implements ApplicationService {
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
private final HostDao hostDao;
|
||||
private final DetailPageDao detailPageDao;
|
||||
|
||||
@Autowired
|
||||
public ApplicationServiceImpl(StringRedisTemplate stringRedisTemplate, HostDao hostDao, DetailPageDao detailPageDao) {
|
||||
this.stringRedisTemplate = stringRedisTemplate;
|
||||
this.hostDao = hostDao;
|
||||
this.detailPageDao = detailPageDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApplicationSignVo sign(ApplicationSignDto signDto) {
|
||||
String redisKey = RedisConstant.APPLICATION_SIGN_PRE + signDto.getCode();
|
||||
if (!CommonConstant.IS_DEV && stringRedisTemplate.opsForValue().get(redisKey) == null) {
|
||||
throw new CustomBaseException("验证码错误");
|
||||
}
|
||||
stringRedisTemplate.delete(redisKey);
|
||||
HostPo po = HostPo.builder().key(IdUtil.fastSimpleUUID()).secret(IdUtil.fastSimpleUUID())
|
||||
.host(signDto.getHost())
|
||||
.name(signDto.getName()).build();
|
||||
hostDao.insertOne(po);
|
||||
stringRedisTemplate.opsForValue().set(RedisConstant.KEY_ID_PRE + po.getKey(), String.valueOf(po.getId()));
|
||||
return new ApplicationSignVo(po.getKey(), po.getSecret());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(HttpServletRequest request, HttpServletResponse response, String callBack, String key) throws IOException {
|
||||
String refer = request.getHeader("Referer");
|
||||
if (StrUtil.isEmpty(refer)) {
|
||||
throw new CustomBaseException("未获取到来源路径");
|
||||
}
|
||||
String path;
|
||||
try {
|
||||
URL url = new URL(refer);
|
||||
path = StrUtil.isEmpty(url.getPath()) ? "/" : url.getPath();
|
||||
} catch (Exception e) {
|
||||
throw new CustomBaseException("url解析错误", e);
|
||||
}
|
||||
if (path.length() > 100) {
|
||||
throw new CustomBaseException("路径长度不能大于100," + path);
|
||||
}
|
||||
|
||||
int hostId = getHostId(key);
|
||||
HostPo uvPvData = hostDao.getUvPvById(hostId);
|
||||
DetailPagePo detailUvPvData = detailPageDao.getUvPvById(hostId, path);
|
||||
if (detailUvPvData == null) {
|
||||
detailUvPvData = new DetailPagePo().setHostId(hostId).setPath(path).setUv(1).setPv(1);
|
||||
}
|
||||
UvPvVo uvPvVo = new UvPvVo(uvPvData.getUv(), uvPvData.getPv(), detailUvPvData.getUv(), detailUvPvData.getPv());
|
||||
String uvPvVoStr = JSON.toJSONString(uvPvVo);
|
||||
String res = String.format("try{%s(%s);}catch(e){console.error(e);console.log(%s)}", callBack, uvPvVoStr, uvPvVoStr);
|
||||
response.setHeader(Header.CONTENT_TYPE.getValue(), ContentType.JSON.getValue());
|
||||
response.getOutputStream().write(res.getBytes(StandardCharsets.UTF_8));
|
||||
response.getOutputStream().flush();
|
||||
|
||||
//异步数据更新
|
||||
DetailPagePo finalDetailUvPvData = detailUvPvData;
|
||||
ThreadPoolUtil.execute(() -> updateData(NetUtil.getClientIp(request), uvPvData, finalDetailUvPvData));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新数据
|
||||
*
|
||||
* @param ip 来源ip
|
||||
* @param hostPo hostPo
|
||||
* @param detailPagePo detailPagePo
|
||||
* @author fanxb
|
||||
* date 2022/2/16 15:40
|
||||
*/
|
||||
private void updateData(String ip, HostPo hostPo, DetailPagePo detailPagePo) {
|
||||
String hostKey = RedisConstant.HOST_UV_PRE + hostPo.getId() + ip;
|
||||
String hostVal = stringRedisTemplate.opsForValue().get(hostKey);
|
||||
hostDao.updateUvPv(hostPo.getId(), hostVal == null ? 1 : 0);
|
||||
long tomorrowZero = LocalDate.now().plusDays(1).atStartOfDay().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||
if (hostVal == null) {
|
||||
stringRedisTemplate.opsForValue().set(hostKey, "", tomorrowZero - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
String pageKey = RedisConstant.PAGE_UV_PRE + hostPo.getId() + ip + detailPagePo.getPath();
|
||||
String pageVal = stringRedisTemplate.opsForValue().get(pageKey);
|
||||
if (detailPagePo.getId() == null) {
|
||||
detailPageDao.insertOne(detailPagePo);
|
||||
} else {
|
||||
detailPageDao.updateUvPv(detailPagePo.getId(), pageVal == null ? 1 : 0);
|
||||
}
|
||||
if (pageVal == null) {
|
||||
stringRedisTemplate.opsForValue().set(pageKey, "", tomorrowZero - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取hostID
|
||||
*
|
||||
* @param key key
|
||||
* @return {@link int}
|
||||
* @author fanxb
|
||||
* date 2022/2/16 15:37
|
||||
*/
|
||||
private int getHostId(String key) {
|
||||
String id = stringRedisTemplate.opsForValue().get(key);
|
||||
if (id != null) {
|
||||
return Integer.parseInt(id);
|
||||
}
|
||||
Integer hostId = hostDao.getIdByKey(key);
|
||||
if (hostId == null) {
|
||||
throw new CustomBaseException("key无效:" + key);
|
||||
}
|
||||
stringRedisTemplate.opsForValue().set(key, hostId.toString());
|
||||
return hostId;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package com.fanxb.backend.service.impl;
|
||||
|
||||
import cn.hutool.captcha.AbstractCaptcha;
|
||||
import cn.hutool.captcha.CaptchaUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.fanxb.backend.service.CaptchaService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 验证码service
|
||||
*
|
||||
* @author fanxb
|
||||
* @date 2022/2/15 15:47
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class CaptchaServiceImpl implements CaptchaService {
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Autowired
|
||||
public CaptchaServiceImpl(StringRedisTemplate stringRedisTemplate) {
|
||||
this.stringRedisTemplate = stringRedisTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String create(String type) {
|
||||
String key = IdUtil.fastSimpleUUID();
|
||||
AbstractCaptcha captcha = CaptchaUtil.createCircleCaptcha(200, 100);
|
||||
stringRedisTemplate.opsForValue().set(type + captcha.getCode(), "1", 5, TimeUnit.MINUTES);
|
||||
return captcha.getImageBase64();
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package com.fanxb.backend.util;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.Header;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 网络相关工具类
|
||||
*
|
||||
* @author fanxb
|
||||
* @date 2022/2/16 16:04
|
||||
*/
|
||||
public class NetUtil {
|
||||
private static final String[] HEADERS = new String[]{"x-forwarded-for", "X-Real-IP"};
|
||||
|
||||
/**
|
||||
* 获取客户端真实ip
|
||||
*
|
||||
* @param request request
|
||||
* @return {@link String}
|
||||
* @author fanxb
|
||||
* date 2022/2/16 16:10
|
||||
*/
|
||||
public static String getClientIp(HttpServletRequest request) {
|
||||
for (String header : HEADERS) {
|
||||
String ip = request.getHeader(header);
|
||||
if (StrUtil.isNotEmpty(ip)) {
|
||||
return ip.split(",")[0];
|
||||
}
|
||||
}
|
||||
return request.getRemoteAddr();
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package com.fanxb.backend.util;
|
||||
|
||||
|
||||
|
||||
import com.fanxb.backend.factory.ThreadPoolFactory;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 类功能简述:当短时间需要少量线程时,使用本类。长时间占用或大量线程需求,请用线程工厂ThreadPoolFactory创建新的线程池,
|
||||
* 类功能详述:
|
||||
*
|
||||
* @author fanxb
|
||||
* @date 2018/11/1 11:04
|
||||
*/
|
||||
public class ThreadPoolUtil {
|
||||
private static final ThreadPoolExecutor POOL_EXECUTOR = ThreadPoolFactory.createPool("global-pool-");
|
||||
|
||||
/**
|
||||
* Description: 执行线程任务
|
||||
*
|
||||
* @param runnable 待执行对象
|
||||
* @author fanxb
|
||||
* @date 2018/11/1 11:27
|
||||
*/
|
||||
synchronized private static void executeTask(Runnable runnable) {
|
||||
POOL_EXECUTOR.execute(runnable);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Description: 执行一个有返回值的任务
|
||||
*
|
||||
* @param callable 待执行对象
|
||||
* @param timeOut 获取结果操时时间 , 操时抛出错误 单位:秒
|
||||
* @return T
|
||||
* @author fanxb
|
||||
* @date 2018/11/1 11:10
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T execute(Callable<T> callable, int timeOut) throws Exception {
|
||||
FutureTask<?> futureTask = new FutureTask<>(callable);
|
||||
executeTask(futureTask);
|
||||
return (T) futureTask.get(timeOut, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Description: 执行一个无返回值任务
|
||||
*
|
||||
* @param runnable 待执行对象
|
||||
* @author fanxb
|
||||
* @date 2018/11/1 11:11
|
||||
*/
|
||||
public static void execute(Runnable runnable) {
|
||||
executeTask(runnable);
|
||||
}
|
||||
|
||||
}
|
48
qieziBackend/src/main/resources/application.yml
Normal file
48
qieziBackend/src/main/resources/application.yml
Normal file
@ -0,0 +1,48 @@
|
||||
server:
|
||||
port: 8088
|
||||
servlet:
|
||||
# 不要在最后加/
|
||||
context-path: /qiezi/api
|
||||
spring:
|
||||
jackson:
|
||||
default-property-inclusion: non_null
|
||||
servlet:
|
||||
# 表单配置
|
||||
multipart:
|
||||
max-request-size: 10MB
|
||||
max-file-size: 10MB
|
||||
profiles:
|
||||
active: dev
|
||||
application:
|
||||
name: qiezi
|
||||
flyway:
|
||||
baseline-on-migrate: true
|
||||
cache:
|
||||
type: redis
|
||||
redis:
|
||||
database: 0
|
||||
host: localhost
|
||||
port: 6379
|
||||
password:
|
||||
timeout: 20000ms
|
||||
lettuce:
|
||||
pool:
|
||||
max-active: 20
|
||||
max-wait: 500ms
|
||||
max-idle: 20
|
||||
min-idle: 2
|
||||
datasource:
|
||||
name: mysql
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
type: com.zaxxer.hikari.HikariDataSource
|
||||
username: root
|
||||
password: 123456
|
||||
url: jdbc:mysql://localhost:3306/qiezi?useUnicode=true&characterEncoding=utf-8&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
|
||||
hikari:
|
||||
maximum-pool-size: 10
|
||||
mybatis:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
# classpath后面加*,值里面的*才起作用
|
||||
mapper-locations: classpath*:mapper/*.xml
|
||||
debug: false
|
46
qieziBackend/src/main/resources/db/migration/V1__init.sql
Normal file
46
qieziBackend/src/main/resources/db/migration/V1__init.sql
Normal file
@ -0,0 +1,46 @@
|
||||
CREATE TABLE qiezi.host
|
||||
(
|
||||
id INT auto_increment NOT NULL,
|
||||
`key` CHAR(32) NOT NULL COMMENT 'key,用于标识',
|
||||
secret char(32) NOT NULL COMMENT '密钥',
|
||||
name varchar(100) NOT NULL COMMENT '网站名',
|
||||
host varchar(100) NOT NULL COMMENT '网站域名(不含http前缀以及路径)',
|
||||
pv INT UNSIGNED DEFAULT 0 NOT NULL,
|
||||
uv INT UNSIGNED DEFAULT 0 NOT NULL,
|
||||
CONSTRAINT host_pk PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB
|
||||
DEFAULT CHARSET=utf8mb4
|
||||
COLLATE=utf8mb4_0900_ai_ci
|
||||
COMMENT='host表,记录某个站点总的pv,uv数据';
|
||||
CREATE UNIQUE INDEX host_key_IDX USING BTREE ON qiezi.host (`key`);
|
||||
|
||||
CREATE TABLE qiezi.host_day
|
||||
(
|
||||
id INT auto_increment NOT NULL,
|
||||
hostId INT NOT NULL COMMENT 'hostId',
|
||||
dateNum INT NOT NULL COMMENT '日期比如20200202',
|
||||
pv INT UNSIGNED DEFAULT 0 NOT NULL,
|
||||
uv INT UNSIGNED DEFAULT 0 NOT NULL,
|
||||
CONSTRAINT detail_page_pk PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB
|
||||
DEFAULT CHARSET=utf8mb4
|
||||
COLLATE=utf8mb4_0900_ai_ci
|
||||
COMMENT='记录域名日pv/uv';
|
||||
CREATE INDEX detail_page_host_id_date_IDX USING BTREE ON qiezi.host_day(`hostId`,`dateNum`);
|
||||
|
||||
|
||||
|
||||
CREATE TABLE qiezi.detail_page
|
||||
(
|
||||
id INT auto_increment NOT NULL,
|
||||
hostId INT NOT NULL COMMENT 'hostId',
|
||||
path varchar(100) NOT NULL COMMENT '页面路径',
|
||||
pv INT UNSIGNED DEFAULT 0 NOT NULL,
|
||||
uv INT UNSIGNED DEFAULT 0 NOT NULL,
|
||||
CONSTRAINT detail_page_pk PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB
|
||||
DEFAULT CHARSET=utf8mb4
|
||||
COLLATE=utf8mb4_0900_ai_ci
|
||||
COMMENT='detail表,记录细分页面pv/uv';
|
||||
CREATE INDEX detail_page_host_id_IDX USING BTREE ON qiezi.detail_page(`hostId`,`path`(20));
|
||||
|
@ -0,0 +1,13 @@
|
||||
package com.fanxb.backend;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class QieziBackendApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
23
qiezi_front/.gitignore
vendored
Normal file
23
qiezi_front/.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
19
qiezi_front/README.md
Normal file
19
qiezi_front/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
# qiezi_front
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
5
qiezi_front/babel.config.js
Normal file
5
qiezi_front/babel.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
26168
qiezi_front/package-lock.json
generated
Normal file
26168
qiezi_front/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
qiezi_front/package.json
Normal file
29
qiezi_front/package.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "qiezi_front",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build"
|
||||
},
|
||||
"dependencies": {
|
||||
"ant-design-vue": "^2.2.8",
|
||||
"axios": "^0.26.0",
|
||||
"core-js": "^3.6.5",
|
||||
"vue": "^3.0.0",
|
||||
"vue-router": "^4.0.0-0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-router": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/compiler-sfc": "^3.0.0",
|
||||
"less": "^3.0.4",
|
||||
"less-loader": "^5.0.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
BIN
qiezi_front/public/favicon.ico
Normal file
BIN
qiezi_front/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
22
qiezi_front/public/index.html
Normal file
22
qiezi_front/public/index.html
Normal file
@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong
|
||||
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to
|
||||
continue.</strong
|
||||
>
|
||||
</noscript>
|
||||
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
<script src="/indexResource/qieziStatistic_v1.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
1
qiezi_front/public/indexResource/index.html
Normal file
1
qiezi_front/public/indexResource/index.html
Normal file
@ -0,0 +1 @@
|
||||
<!-- 真正的主页 -->
|
34
qiezi_front/public/indexResource/qieziStatistic_v1.js
Normal file
34
qiezi_front/public/indexResource/qieziStatistic_v1.js
Normal file
@ -0,0 +1,34 @@
|
||||
|
||||
(function () {
|
||||
var name = "qieziStatistic9527";
|
||||
var callback = name + "CallBack";
|
||||
window[callback] = function (a) {
|
||||
var hostNode = document.getElementById(name + "Host");
|
||||
if (hostNode != null) {
|
||||
document.getElementById(name + "HostPv").innerText = a.totalPv;
|
||||
document.getElementById(name + "HostUv").innerText = a.totalUv;
|
||||
hostNode.style.display = "inline";
|
||||
}
|
||||
|
||||
var postNode = document.getElementById(name + "Post");
|
||||
if (postNode != null) {
|
||||
document.getElementById(name + "PostPv").innerText = a.pagePv;
|
||||
postNode.style.display = "inline";
|
||||
}
|
||||
};
|
||||
setTimeout(
|
||||
() => {
|
||||
var script = document.createElement("script");
|
||||
script.type = "text/javascript";
|
||||
script.defer = true;
|
||||
script.src =
|
||||
(window.qieziStatisticHost == undefined ? "https://qiezi.fleyx.com" : window.qieziStatisticHost) +
|
||||
"/qiezi/api/application/visit?callBack=" +
|
||||
callback +
|
||||
"&key=" +
|
||||
window.qieziStatisticKey;
|
||||
document.getElementsByTagName("head")[0].appendChild(script);
|
||||
},
|
||||
window.qieziStatisticKey == undefined ? 1000 : 1,
|
||||
);
|
||||
})();
|
13
qiezi_front/src/App.vue
Normal file
13
qiezi_front/src/App.vue
Normal file
@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
html {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
}
|
||||
</style>
|
BIN
qiezi_front/src/assets/logo.png
Normal file
BIN
qiezi_front/src/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
58
qiezi_front/src/components/HelloWorld.vue
Normal file
58
qiezi_front/src/components/HelloWorld.vue
Normal file
@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="hello">
|
||||
<h1>{{ msg }}</h1>
|
||||
<p>
|
||||
For a guide and recipes on how to configure / customize this project,<br>
|
||||
check out the
|
||||
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
|
||||
</p>
|
||||
<h3>Installed CLI Plugins</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
|
||||
</ul>
|
||||
<h3>Essential Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
|
||||
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
|
||||
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
|
||||
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
|
||||
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
|
||||
</ul>
|
||||
<h3>Ecosystem</h3>
|
||||
<ul>
|
||||
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
|
||||
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
|
||||
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
|
||||
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HelloWorld',
|
||||
props: {
|
||||
msg: String
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped lang="less">
|
||||
h3 {
|
||||
margin: 40px 0 0;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
</style>
|
10
qiezi_front/src/main.js
Normal file
10
qiezi_front/src/main.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { createApp } from "vue";
|
||||
import { message, Button, Form, Input, Typography } from "ant-design-vue";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import "ant-design-vue/dist/antd.css";
|
||||
|
||||
const app = createApp(App);
|
||||
app.use(router).use(Button).use(Form).use(Input).use(Typography).mount("#app");
|
||||
app.config.globalProperties.$message = message;
|
||||
window.globalVue = app;
|
22
qiezi_front/src/router/index.js
Normal file
22
qiezi_front/src/router/index.js
Normal file
@ -0,0 +1,22 @@
|
||||
import { createRouter, createWebHashHistory } from "vue-router";
|
||||
import Home from "../views/Home.vue";
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
name: "Home",
|
||||
component: Home,
|
||||
},
|
||||
{
|
||||
path: "/application/sign",
|
||||
name: "ApplicationSign",
|
||||
component: () => import("../views/ApplicationSign"),
|
||||
},
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes,
|
||||
});
|
||||
|
||||
export default router;
|
106
qiezi_front/src/util/HttpUtil.js
Normal file
106
qiezi_front/src/util/HttpUtil.js
Normal file
@ -0,0 +1,106 @@
|
||||
import * as http from "axios";
|
||||
import router from "../router/index";
|
||||
import { message as aMessage, notification } from "ant-design-vue";
|
||||
|
||||
/**
|
||||
* 请求
|
||||
* @param {*} url url
|
||||
* @param {*} method 方法
|
||||
* @param {*} params url参数
|
||||
* @param {*} body 请求体
|
||||
* @param {*} isForm 是否form
|
||||
* @param {*} redirect 接口返回未认证是否跳转到登陆
|
||||
* @returns 数据
|
||||
*/
|
||||
async function request(url, method, params, body, isForm, redirect) {
|
||||
let options = {
|
||||
url,
|
||||
baseURL: "/qiezi/api",
|
||||
method,
|
||||
params,
|
||||
headers: {
|
||||
// "jwt-token": vuex.state.globalConfig.token,
|
||||
},
|
||||
};
|
||||
//如果是表单类型的请求,添加请求头
|
||||
if (isForm) {
|
||||
options.headers["Content-Type"] = "multipart/form-data";
|
||||
}
|
||||
if (body) {
|
||||
options.data = body;
|
||||
}
|
||||
let res;
|
||||
try {
|
||||
res = await http.default.request(options);
|
||||
} catch (err) {
|
||||
aMessage.error("发生了某些异常问题");
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
const { code, data, message } = res.data;
|
||||
if (code === 1) {
|
||||
return data;
|
||||
} else if (code === -1 && redirect) {
|
||||
//未登陆,根据redirect参数判断是否需要跳转到登陆页
|
||||
aMessage.error("您尚未登陆,请先登陆");
|
||||
router.replace(`/public/login?redirect=${encodeURIComponent(router.currentRoute.fullPath)}`);
|
||||
throw new Error(message);
|
||||
} else if (code === 0) {
|
||||
//通用异常,使用error提示
|
||||
notification.error({
|
||||
message: "异常",
|
||||
description: message,
|
||||
});
|
||||
throw new Error(message);
|
||||
} else if (code === -2) {
|
||||
//表单异常,使用message提示
|
||||
aMessage.error(message);
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get方法
|
||||
* @param {*} url url
|
||||
* @param {*} params url参数
|
||||
* @param {*} redirect 未登陆是否跳转到登陆页
|
||||
*/
|
||||
async function get(url, params = null, redirect = true) {
|
||||
return request(url, "get", params, null, false, redirect);
|
||||
}
|
||||
|
||||
/**
|
||||
* post方法
|
||||
* @param {*} url url
|
||||
* @param {*} params url参数
|
||||
* @param {*} body body参数
|
||||
* @param {*} isForm 是否表单数据
|
||||
* @param {*} redirect 是否重定向
|
||||
*/
|
||||
async function post(url, params, body, isForm = false, redirect = true) {
|
||||
return request(url, "post", params, body, isForm, redirect);
|
||||
}
|
||||
|
||||
/**
|
||||
* put方法
|
||||
* @param {*} url url
|
||||
* @param {*} params url参数
|
||||
* @param {*} body body参数
|
||||
* @param {*} isForm 是否表单数据
|
||||
* @param {*} redirect 是否重定向
|
||||
*/
|
||||
async function put(url, params, body, isForm = false, redirect = true) {
|
||||
return request(url, "put", params, body, isForm, redirect);
|
||||
}
|
||||
|
||||
/**
|
||||
* delete方法
|
||||
* @param {*} url url
|
||||
* @param {*} params url参数
|
||||
* @param {*} redirect 是否重定向
|
||||
*/
|
||||
async function hDelete(url, params = null, redirect = true) {
|
||||
return request(url, "delete", params, null, redirect);
|
||||
}
|
||||
|
||||
export { get, post, put, hDelete };
|
61
qiezi_front/src/views/ApplicationSign.vue
Normal file
61
qiezi_front/src/views/ApplicationSign.vue
Normal file
@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div class="title">应用注册</div>
|
||||
<a-form :model="form" :label-col="{ span: 4 }" :wrapper-col="{ span: 14 }" style="width: 80%; margin: 0 auto">
|
||||
<a-form-item label="网站名">
|
||||
<a-input type="text" v-model:value="form.name" />
|
||||
</a-form-item>
|
||||
<a-form-item label="域名">
|
||||
<a-input type="text" v-model:value="form.host" />
|
||||
</a-form-item>
|
||||
<a-form-item label="验证码">
|
||||
<div class="captchaItem">
|
||||
<a-input type="text" v-model:value="form.code" style="flex: 1" />
|
||||
<img style="height: 3em; cursor: pointer" :src="imgData" alt="验证码" @click="onCaptchaClick" title="点击刷新" />
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{ span: 14, offset: 4 }">
|
||||
<a-button type="primary" @click="onSubmit">创建</a-button>
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{ span: 14 }" v-if="keySecret.key" style="text-align: left">
|
||||
<div>请注意记录以下的key和secret,key用于uv,pv统计时身份标识,secret用于后续进阶功能的身份认证</div>
|
||||
key: <a-typography-text strong>{{ keySecret.key }}</a-typography-text
|
||||
><br />
|
||||
secret: <a-typography-text strong>{{ keySecret.secret }}</a-typography-text>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref, reactive } from "vue";
|
||||
import { get, post } from "../util/HttpUtil";
|
||||
|
||||
let imgData = ref("");
|
||||
let getCaptcha = async () => "data:image/png;base64," + (await get("/captcha/sign"));
|
||||
onMounted(async () => {
|
||||
window.qieziStatisticHost = "http://localhost:8080";
|
||||
window.qieziStatisticKey = "238f2c45fa53454b95644280a12bc735";
|
||||
imgData.value = await getCaptcha();
|
||||
});
|
||||
let onCaptchaClick = async () => (imgData.value = await getCaptcha());
|
||||
|
||||
let form = reactive({
|
||||
code: "",
|
||||
name: "",
|
||||
host: "",
|
||||
});
|
||||
let keySecret = ref({});
|
||||
let onSubmit = async () => {
|
||||
keySecret.value = reactive(await post("/application/sign", null, form));
|
||||
onCaptchaClick();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.title {
|
||||
font-size: 2em;
|
||||
font-weight: 600;
|
||||
}
|
||||
.captchaItem {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
17
qiezi_front/src/views/Home.vue
Normal file
17
qiezi_front/src/views/Home.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div class="home">
|
||||
<router-link to="/application/sign">应用注册</router-link><br />
|
||||
<router-link to="/manage">应用管理</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// @ is an alias to /src
|
||||
|
||||
export default {
|
||||
name: "Home",
|
||||
components: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
16
qiezi_front/vue.config.js
Normal file
16
qiezi_front/vue.config.js
Normal file
@ -0,0 +1,16 @@
|
||||
module.exports = {
|
||||
devServer: {
|
||||
proxy: {
|
||||
"/qiezi/api": {
|
||||
//这里最好有一个 /
|
||||
target: "http://localhost:8088", // 服务器端接口地址
|
||||
ws: true, //如果要代理 websockets,配置这个参数
|
||||
// 如果是https接口,需要配置这个参数
|
||||
changeOrigin: true, //是否跨域
|
||||
pathRewrite: {
|
||||
"^/qiezi/api": "/qiezi/api",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
@ -1,15 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"noImplicitAny": false,
|
||||
"module": "commonjs",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl":".",
|
||||
"rootDir": "./",
|
||||
"watch": false,
|
||||
"strict": true,
|
||||
"strictNullChecks": false,
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
class ErrorHelper {
|
||||
/**
|
||||
* 返回一个自定义错误
|
||||
* @param {String} message
|
||||
* @param {Number} status
|
||||
*/
|
||||
static newError(message, status) {
|
||||
return getError(message, status);
|
||||
}
|
||||
|
||||
static Error403(message){
|
||||
return getError(message,403);
|
||||
}
|
||||
static Error404(message){
|
||||
return getError(message,404);
|
||||
}
|
||||
static Error406(message){
|
||||
return getError(message,406);
|
||||
}
|
||||
static Error400(message){
|
||||
return getError(message,400);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let getError = (message, status) => {
|
||||
let error = new Error(message);
|
||||
error['status'] = status;
|
||||
return error;
|
||||
}
|
||||
|
||||
export default ErrorHelper;
|
@ -1,9 +0,0 @@
|
||||
import { getLogger, configure } from "log4js";
|
||||
configure({
|
||||
appenders: { cheese: { type: "console" } },
|
||||
categories: { default: { appenders: ["cheese"], level: "info" } }
|
||||
});
|
||||
const logger = getLogger();
|
||||
logger.level = "debug";
|
||||
|
||||
export default logger;
|
@ -1,108 +0,0 @@
|
||||
import mysql from "mysql2/promise";
|
||||
import config from '../config';
|
||||
import * as fs from "fs-extra";
|
||||
import * as path from 'path';
|
||||
import log from '../util/LogUtil';
|
||||
|
||||
const HISTORY_NAME = "mysqlHistory.json";
|
||||
|
||||
interface Res {
|
||||
rows: any;
|
||||
fields: mysql.FieldPacket;
|
||||
}
|
||||
|
||||
class MysqlUtil {
|
||||
public static pool: mysql.Pool = null;
|
||||
|
||||
static async createPool() {
|
||||
MysqlUtil.pool = await mysql.createPool(config.mysql.connection);
|
||||
let basePath = path.join(config.rootPath, config.mysql.sqlFolder);
|
||||
let hisPath = path.join(config.rootPath, HISTORY_NAME);
|
||||
let history: Array<string>;
|
||||
if (fs.existsSync(hisPath)) {
|
||||
history = JSON.parse(await fs.readFile(hisPath, "utf-8"));
|
||||
} else {
|
||||
history = new Array();
|
||||
}
|
||||
//执行数据库
|
||||
let files = (await fs.readdir(basePath)).sort((a, b) => a.localeCompare(b)).filter(item => !(item === HISTORY_NAME));
|
||||
let error = null;
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
if (history.indexOf(files[i]) > -1) {
|
||||
log.info("sql无需重复执行:", files[i]);
|
||||
continue;
|
||||
}
|
||||
let sqlLines = (await fs.readFile(path.join(basePath, files[i]), 'utf-8')).split(/[\r\n]/g);
|
||||
try {
|
||||
let sql = "";
|
||||
for (let j = 0; j < sqlLines.length; j++) {
|
||||
sql = sql + " " + sqlLines[j];
|
||||
if (sqlLines[j].endsWith(";")) {
|
||||
await MysqlUtil.pool.execute(sql);
|
||||
sql = "";
|
||||
}
|
||||
}
|
||||
log.info("sql执行成功:", files[i]);
|
||||
history.push(files[i]);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
break;
|
||||
}
|
||||
}
|
||||
await fs.writeFile(hisPath, JSON.stringify(history));
|
||||
if (error != null) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async getRows(sql: string, params: Array<any>, connection: mysql.PoolConnection = null): Promise<Array<any>> {
|
||||
return (await MysqlUtil.execute(sql, params, connection)).rows;
|
||||
}
|
||||
|
||||
|
||||
static async getRow(sql: string, params: Array<any>, connection: mysql.PoolConnection = null): Promise<any> {
|
||||
let rows = (await MysqlUtil.execute(sql, params, connection)).rows;
|
||||
return rows.length > 0 ? rows[0] : null;
|
||||
}
|
||||
|
||||
static async getSingle(sql: string, params: Array<any>, connection: mysql.PoolConnection = null): Promise<any> {
|
||||
let rows = (await MysqlUtil.execute(sql, params, connection)).rows;
|
||||
if (rows.length == 0) {
|
||||
return null;
|
||||
}
|
||||
let row = rows[0];
|
||||
return row[Object.keys(row)[0]];
|
||||
}
|
||||
|
||||
|
||||
|
||||
static async execute(sql: string, params: Array<any>, connection: mysql.PoolConnection = null): Promise<Res> {
|
||||
let res: any = {};
|
||||
if (connection == null) {
|
||||
let [rows, fields] = await MysqlUtil.pool.query(sql, params);
|
||||
res['rows'] = fields === undefined ? null : rows;
|
||||
res['fields'] = fields === undefined ? rows : fields;
|
||||
} else {
|
||||
let [rows, fields] = await connection.query(sql, params);
|
||||
res['rows'] = rows;
|
||||
res['fields'] = fields;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
static async test() {
|
||||
let connection = await MysqlUtil.pool.getConnection();
|
||||
connection.beginTransaction();
|
||||
connection.query(`insert into url value(6,"GET","asd","public")`);
|
||||
connection.query(`insert into url value(7,"GET","asd","public")`);
|
||||
await connection.commit();
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
MysqlUtil,
|
||||
Res,
|
||||
mysql
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
|
||||
class NumberUtil {
|
||||
static getRandom(min: number, max: number): number {
|
||||
return Math.floor((Math.random() * (max - min + 1) + min));
|
||||
}
|
||||
}
|
||||
|
||||
export default NumberUtil;
|
@ -1,18 +0,0 @@
|
||||
/*
|
||||
合并node对象,对于相同的属性后面覆盖前面
|
||||
*/
|
||||
class ObjectOperation {
|
||||
static combineObject(...objs) {
|
||||
if (objs.length == 1 && objs[0] instanceof Array) {
|
||||
objs = objs[0];
|
||||
}
|
||||
let sum = {};
|
||||
let length = objs.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
sum = Object.assign(sum,objs[i]);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
|
||||
export default ObjectOperation
|
@ -1,24 +0,0 @@
|
||||
import * as childPrecess from 'child_process';
|
||||
|
||||
class ProcessHelper {
|
||||
static exec(cmd): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
childPrecess.exec(cmd, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} if (stderr) {
|
||||
reject(stderr);
|
||||
} else {
|
||||
resolve(stdout)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// (async()=>{
|
||||
// let res= await ProcessHelper.exec('cd /d e://workspace&&dir');
|
||||
// console.log(res);
|
||||
// })()
|
||||
|
||||
export default ProcessHelper
|
@ -1,14 +0,0 @@
|
||||
import { createClient, RedisClientType } from "redis";
|
||||
import config from "../config";
|
||||
class RedisHelper {
|
||||
public static client: RedisClientType<any>;
|
||||
|
||||
static async create() {
|
||||
this.client = await createClient({ url: config.redis.url });
|
||||
this.client.set("1","1")
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
RedisHelper
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
import sqlite3 from 'sqlite3';
|
||||
import { open, Database } from 'sqlite';
|
||||
import config from '../config';
|
||||
import * as fs from "fs-extra";
|
||||
import * as path from 'path';
|
||||
import log from './LogUtil';
|
||||
|
||||
const HISTORY_NAME = "sqliteHistory.json";
|
||||
|
||||
|
||||
class SqliteHelper {
|
||||
public static pool: Database = null;
|
||||
|
||||
static async createPool() {
|
||||
let fullPath = path.join(config.rootPath, config.sqlite.filePath);
|
||||
let dataFolder = path.dirname(fullPath);
|
||||
if (!fs.existsSync(dataFolder)) {
|
||||
fs.mkdir(dataFolder);
|
||||
}
|
||||
SqliteHelper.pool = await open({
|
||||
filename: fullPath,
|
||||
driver: sqlite3.Database
|
||||
});
|
||||
let sqlFolder = path.join(config.rootPath, config.sqlite.sqlFolder);
|
||||
let hisPath = path.join(config.rootPath, HISTORY_NAME);
|
||||
let history: Array<string>;
|
||||
if (fs.existsSync(hisPath)) {
|
||||
history = JSON.parse(await fs.readFile(hisPath, "utf-8"));
|
||||
} else {
|
||||
history = new Array();
|
||||
}
|
||||
//执行数据库
|
||||
let files = (await fs.readdir(sqlFolder)).sort((a, b) => a.localeCompare(b)).filter(item => !(item === HISTORY_NAME));
|
||||
let error = null;
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
if (history.indexOf(files[i]) > -1) {
|
||||
log.info("sql无需重复执行:", files[i]);
|
||||
continue;
|
||||
}
|
||||
let sqlLines = (await fs.readFile(path.join(sqlFolder, files[i]), 'utf-8')).split(/[\r\n]/g).map(item => item.trim()).filter(item => !item.startsWith("--"));
|
||||
try {
|
||||
let sql = "";
|
||||
for (let j = 0; j < sqlLines.length; j++) {
|
||||
sql = sql + sqlLines[j];
|
||||
if (sqlLines[j].endsWith(";")) {
|
||||
await SqliteHelper.pool.run(sql);
|
||||
sql = "";
|
||||
}
|
||||
}
|
||||
log.info("sql执行成功:", files[i]);
|
||||
history.push(files[i]);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
break;
|
||||
}
|
||||
}
|
||||
await fs.writeFile(hisPath, JSON.stringify(history));
|
||||
if (error != null) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default SqliteHelper;
|
@ -1,22 +0,0 @@
|
||||
import moment from 'moment';
|
||||
class TimeUtil {
|
||||
/**
|
||||
* 获取今天的零点
|
||||
*/
|
||||
static getZeroTime(): Date {
|
||||
return moment()
|
||||
.millisecond(0)
|
||||
.second(0)
|
||||
.minute(0)
|
||||
.hour(0)
|
||||
.toDate();
|
||||
}
|
||||
|
||||
static async sleep(duration: number): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => resolve(), duration);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default TimeUtil;
|
@ -1,14 +0,0 @@
|
||||
import path, { dirname } from 'path'
|
||||
|
||||
class pathUtil {
|
||||
static getPath(pathStr) {
|
||||
return path.resolve(pathUtil.getRootPath(), pathStr);
|
||||
}
|
||||
|
||||
static getRootPath() {
|
||||
return path.resolve(__dirname, '..');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default pathUtil
|
Loading…
x
Reference in New Issue
Block a user