Compare commits
47 Commits
Author | SHA1 | Date | |
---|---|---|---|
cd70b8ea8a | |||
|
42f9461643 | ||
|
a373cac904 | ||
|
65921743eb | ||
|
e4970bf07c | ||
|
b8afd8021b | ||
|
cc784cdd38 | ||
|
ffa9e77c81 | ||
|
c9b06ee5a4 | ||
|
aa30040f8c | ||
|
3a1d222fc2 | ||
|
b247f04a03 | ||
|
0f1e9acea3 | ||
|
acecc333b8 | ||
|
cc86def7e7 | ||
|
b07efef9c0 | ||
|
418dab6097 | ||
|
9d63518106 | ||
|
4b15dfaa0f | ||
|
31d177ca4b | ||
|
7eb71ff504 | ||
|
e07a873bc2 | ||
|
71b617e754 | ||
|
47b43d4013 | ||
|
9031f3fafa | ||
|
5cd022770e | ||
|
77048a07a9 | ||
|
a99d5a2705 | ||
|
ec70089d77 | ||
|
fa1ad54afb | ||
|
974d0e8543 | ||
|
e96c85cd55 | ||
|
6612a662cb | ||
|
939f64549c | ||
|
d4b1f9faf2 | ||
|
bb7e25729f | ||
|
0d95547798 | ||
|
1e1cdfa6c2 | ||
|
7275e8810e | ||
|
303530589c | ||
|
f413ea34ec | ||
|
44e74f4818 | ||
|
07a6d28e2e | ||
|
6e862ac968 | ||
|
a46949a181 | ||
|
8b117c6798 | ||
|
ed5975df7d |
6
.github/workflows/docker.yml
vendored
6
.github/workflows/docker.yml
vendored
@ -14,10 +14,6 @@ jobs:
|
||||
name: Check out the repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: Cache node modules NPM
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
@ -60,4 +56,4 @@ jobs:
|
||||
tags: ${{ secrets.DOCKERHUB_TAG }}:master
|
||||
|
||||
- name: 'Report Suecss'
|
||||
run: curl ${{ secrets.INOTIFY }}/Inotify/dockerBuildComplated!
|
||||
run: curl ${{ secrets.INOTIFY }}/Inotify/masterIsOk!
|
||||
|
59
.github/workflows/docker_dev.yml
vendored
Normal file
59
.github/workflows/docker_dev.yml
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
name: docker_dev
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ dev ]
|
||||
pull_request:
|
||||
branches: [ dev ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Check out the repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Cache node modules NPM
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-node-modules-NPM
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('./Inotify.Vue/package.json') }}
|
||||
restore-keys: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('./Inotify.Vue/package.json') }}
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-node-modules
|
||||
with:
|
||||
path: ./Inotify.Vue/node_modules
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('./Inotify.Vue/package.json') }}
|
||||
restore-keys: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('./Inotify.Vue/package.json') }}
|
||||
|
||||
- name: InstallNode and BuildVue
|
||||
run: |
|
||||
cd ./Inotify.Vue
|
||||
npm install
|
||||
npm run build:prod
|
||||
|
||||
-
|
||||
name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
-
|
||||
name: Docker Build & Push to Docker Hub For Service
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./Inotify/Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ secrets.DOCKERHUB_TAG }}:dev
|
||||
|
||||
- name: 'Report Suecss'
|
||||
run: curl ${{ secrets.INOTIFY }}/Inotify/DevIsOk!
|
6
.github/workflows/docker_release.yml
vendored
6
.github/workflows/docker_release.yml
vendored
@ -12,10 +12,6 @@ jobs:
|
||||
name: Check out the repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: Cache node modules NPM
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
@ -58,4 +54,4 @@ jobs:
|
||||
tags: ${{ secrets.DOCKERHUB_TAG }}:latest
|
||||
|
||||
- name: 'Report Suecss'
|
||||
run: curl ${{ secrets.INOTIFY }}/Inotify/dockerBuildComplated!
|
||||
run: curl ${{ secrets.INOTIFY }}/Inotify/latestIsOK!
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "Inotify-vue",
|
||||
"name": "inotify",
|
||||
"version": "0.0.1",
|
||||
"description": "WebControl for Inotify",
|
||||
"author": "<mrdemonson@gmail.com>",
|
||||
@ -17,13 +17,13 @@
|
||||
"axios": "0.18.1",
|
||||
"core-js": "3.6.5",
|
||||
"echarts": "^4.9.0",
|
||||
"element-plus": "^1.0.2-beta.32",
|
||||
"element-ui": "2.13.2",
|
||||
"js-cookie": "2.2.0",
|
||||
"moment": "^2.29.1",
|
||||
"normalize.css": "7.0.0",
|
||||
"nprogress": "0.2.0",
|
||||
"path-to-regexp": "2.4.0",
|
||||
"qrcodejs": "^1.0.0",
|
||||
"vue": "2.6.10",
|
||||
"vue-router": "3.0.6",
|
||||
"vuex": "3.1.0"
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 1.1 KiB |
@ -1,17 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= webpackConfig.name %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= webpackConfig.name %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
|
||||
<link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet">
|
||||
<% } %>
|
||||
<title>
|
||||
<%= webpackConfig.name %>
|
||||
</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
|
||||
<script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
|
||||
<% } %>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,8 +1,8 @@
|
||||
import Vue from 'vue'
|
||||
import 'normalize.css/normalize.css' // A modern alternative to CSS resets
|
||||
// import 'normalize.css/normalize.css' // A modern alternative to CSS resets
|
||||
import ElementUI from 'element-ui'
|
||||
|
||||
import 'element-ui/lib/theme-chalk/index.css'
|
||||
// import 'element-ui/lib/theme-chalk/index.css'
|
||||
import locale from 'element-ui/lib/locale/lang/zh-CN' // lang i18n
|
||||
|
||||
import '@/styles/index.scss' // global css
|
||||
@ -13,25 +13,7 @@ import router from './router'
|
||||
|
||||
import '@/icons' // icon
|
||||
import '@/permission' // permission control
|
||||
|
||||
/**
|
||||
* If you don't want to use mock-server
|
||||
* you want to use MockJs for mock api
|
||||
* you can execute: mockXHR()
|
||||
*
|
||||
* Currently MockJs will be used in the production environment,
|
||||
* please remove it before going online ! ! !
|
||||
*/
|
||||
// if (process.env.NODE_ENV === 'production') {
|
||||
// const { mockXHR } = require('../mock')
|
||||
// mockXHR()
|
||||
// }
|
||||
|
||||
// set ElementUI lang to EN
|
||||
Vue.use(ElementUI, { locale, size: 'small' })
|
||||
// 如果想要中文版 element-ui,按如下方式声明
|
||||
// Vue.use(ElementUI)
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
new Vue({
|
||||
|
@ -1,228 +1,45 @@
|
||||
<template>
|
||||
<div class="wscn-http404-container">
|
||||
<div class="wscn-http404">
|
||||
<div class="pic-404">
|
||||
<img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404">
|
||||
<img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404">
|
||||
<img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404">
|
||||
<img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404">
|
||||
</div>
|
||||
<div class="bullshit">
|
||||
<div class="bullshit__oops">OOPS!</div>
|
||||
<div class="bullshit__info">All rights reserved
|
||||
<a style="color:#20a0ff" href="https://wallstreetcn.com" target="_blank">wallstreetcn</a>
|
||||
</div>
|
||||
<div class="bullshit__headline">{{ message }}</div>
|
||||
<div class="bullshit__info">Please check that the URL you entered is correct, or click the button below to return to the homepage.</div>
|
||||
<a href="" class="bullshit__return-home">Back to home</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="circle">
|
||||
<div><span class="block"></span><span>404</span><span class="block"></span></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Page404',
|
||||
computed: {
|
||||
message() {
|
||||
return 'The webmaster said that you can not enter this page...'
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wscn-http404-container{
|
||||
transform: translate(-50%,-50%);
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
<style>
|
||||
.circle {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
border-radius: 200px;
|
||||
border: 15px solid #b22727;
|
||||
}
|
||||
.wscn-http404 {
|
||||
position: relative;
|
||||
width: 1200px;
|
||||
padding: 0 50px;
|
||||
overflow: hidden;
|
||||
.pic-404 {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 600px;
|
||||
overflow: hidden;
|
||||
&__parent {
|
||||
width: 100%;
|
||||
}
|
||||
&__child {
|
||||
position: absolute;
|
||||
&.left {
|
||||
width: 80px;
|
||||
top: 17px;
|
||||
left: 220px;
|
||||
opacity: 0;
|
||||
animation-name: cloudLeft;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: 1s;
|
||||
}
|
||||
&.mid {
|
||||
width: 46px;
|
||||
top: 10px;
|
||||
left: 420px;
|
||||
opacity: 0;
|
||||
animation-name: cloudMid;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: 1.2s;
|
||||
}
|
||||
&.right {
|
||||
width: 62px;
|
||||
top: 100px;
|
||||
left: 500px;
|
||||
opacity: 0;
|
||||
animation-name: cloudRight;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: linear;
|
||||
animation-fill-mode: forwards;
|
||||
animation-delay: 1s;
|
||||
}
|
||||
@keyframes cloudLeft {
|
||||
0% {
|
||||
top: 17px;
|
||||
left: 220px;
|
||||
opacity: 0;
|
||||
}
|
||||
20% {
|
||||
top: 33px;
|
||||
left: 188px;
|
||||
opacity: 1;
|
||||
}
|
||||
80% {
|
||||
top: 81px;
|
||||
left: 92px;
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
top: 97px;
|
||||
left: 60px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes cloudMid {
|
||||
0% {
|
||||
top: 10px;
|
||||
left: 420px;
|
||||
opacity: 0;
|
||||
}
|
||||
20% {
|
||||
top: 40px;
|
||||
left: 360px;
|
||||
opacity: 1;
|
||||
}
|
||||
70% {
|
||||
top: 130px;
|
||||
left: 180px;
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
top: 160px;
|
||||
left: 120px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes cloudRight {
|
||||
0% {
|
||||
top: 100px;
|
||||
left: 500px;
|
||||
opacity: 0;
|
||||
}
|
||||
20% {
|
||||
top: 120px;
|
||||
left: 460px;
|
||||
opacity: 1;
|
||||
}
|
||||
80% {
|
||||
top: 180px;
|
||||
left: 340px;
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
top: 200px;
|
||||
left: 300px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.bullshit {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 300px;
|
||||
padding: 30px 0;
|
||||
overflow: hidden;
|
||||
&__oops {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
line-height: 40px;
|
||||
color: #1482f0;
|
||||
opacity: 0;
|
||||
margin-bottom: 20px;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
&__headline {
|
||||
font-size: 20px;
|
||||
line-height: 24px;
|
||||
color: #222;
|
||||
font-weight: bold;
|
||||
opacity: 0;
|
||||
margin-bottom: 10px;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: 0.1s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
&__info {
|
||||
font-size: 13px;
|
||||
line-height: 21px;
|
||||
color: grey;
|
||||
opacity: 0;
|
||||
margin-bottom: 30px;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: 0.2s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
&__return-home {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 110px;
|
||||
height: 36px;
|
||||
background: #1482f0;
|
||||
border-radius: 100px;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
opacity: 0;
|
||||
font-size: 14px;
|
||||
line-height: 36px;
|
||||
cursor: pointer;
|
||||
animation-name: slideUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-delay: 0.3s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
@keyframes slideUp {
|
||||
0% {
|
||||
transform: translateY(60px);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.circle > div {
|
||||
color: #b22727;
|
||||
font: bolder 50px arial;
|
||||
transform: matrix(0.642788, -0.766044, 0.766044, 0.642788, 0, 95);
|
||||
-ms-transform: matrix(0.642788, -0.766044, 0.766044, 0.642788, 0, 95);
|
||||
-moz-transform: matrix(0.642788, -0.766044, 0.766044, 0.642788, 0, 95);
|
||||
-o-transform: matrix(0.642788, -0.766044, 0.766044, 0.642788, 0, 95);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.block {
|
||||
width: 60px;
|
||||
display: inline-block;
|
||||
height: 15px;
|
||||
background-color: #b22727;
|
||||
margin: 12px 10px;
|
||||
}
|
||||
</style>
|
@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class='app-container'>
|
||||
<el-dialog :title='title' :visible.sync='dialogVisible' :append-to-body='true'>
|
||||
<el-form ref="authform" :model="authform" label-width="100px" label-position='right' :rules="authformrules">
|
||||
<el-form ref="authform" :model="authform" label-width="120px" label-position='right' :rules="authformrules">
|
||||
<el-form-item label='通道类型'>
|
||||
<el-select :disabled='isModify' value-key="key" v-model='selectTemplate' placeholder='请选择' @change="selectTemplateChange">
|
||||
<el-option v-for="item in sendTemplates" :key="item.key" :label="item.name" :value="item">
|
||||
<el-select :disabled='isModify' value-key="type" v-model='selectTemplate' placeholder='请选择' @change="selectTemplateChange">
|
||||
<el-option v-for="item in sendTemplates" :key="item.type" :label="item.typeName" :value="item">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@ -12,7 +12,13 @@
|
||||
<el-input v-model="selectTemplate.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-for="(item) in selectTemplate.values" :label="item.description" :key="item.name" required>
|
||||
<el-input v-model="item.value" :placeholder="item.default"></el-input>
|
||||
<el-input v-model="item.value" :placeholder="item.default" :readonly="item.readonly"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="绑定地址" v-show="isBark">
|
||||
<el-input v-model="selectTemplate.barkUrl" readonly></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="二维码地址" v-show="isBark">
|
||||
<div id="bark"></div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
@ -32,7 +38,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column label='类型' width='110' align='center'>
|
||||
<template slot-scope='scope'>
|
||||
<span>{{ scope.row.type }}</span>
|
||||
<span>{{ scope.row.typeName }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label='名称' width='110' align='center'>
|
||||
@ -40,9 +46,9 @@
|
||||
<span>{{ scope.row.name }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label='配置信息' align='center'>>
|
||||
<el-table-column label='调用接口' align='center'>
|
||||
<template slot-scope='scope'>
|
||||
{{ scope.row.authData }}
|
||||
<el-link type="primary" target="_blank"> <a :href="scope.row.url" target="_blank" class="buttonText">{{scope.row.url}}</a></el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align='center' prop='created_at' label='编辑' width='200'>
|
||||
@ -71,6 +77,7 @@ import {
|
||||
getSendTemplates,
|
||||
addAuthInfo,
|
||||
deepClone,
|
||||
getSendKey
|
||||
|
||||
} from '@/api/setting'
|
||||
|
||||
@ -102,7 +109,8 @@ export default {
|
||||
isModify: false,
|
||||
authform: {},
|
||||
title: "设置",
|
||||
|
||||
sendKey: "",
|
||||
isBark: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@ -113,18 +121,44 @@ export default {
|
||||
this.listLoading = true
|
||||
getSendAuths().then((response) => {
|
||||
this.sendAuthinfos = response.data
|
||||
let origin = window.document.location.origin;
|
||||
for (var sendinfo of this.sendAuthinfos) {
|
||||
sendinfo.url = origin + '/' + sendinfo.key + ".send/{title}/{data}"
|
||||
}
|
||||
this.listLoading = false
|
||||
})
|
||||
getSendTemplates().then((response) => {
|
||||
if (response.code == 200) {
|
||||
|
||||
this.sendTemplates = response.data;
|
||||
}
|
||||
})
|
||||
getSendKey().then(response => {
|
||||
if (response.code == 200) {
|
||||
this.sendKey = response.data;
|
||||
}
|
||||
})
|
||||
},
|
||||
selectTemplateChange(selectTemplate) {
|
||||
|
||||
this.selectTemplate = deepClone(selectTemplate);
|
||||
this.selectTemplate = deepClone(selectTemplate)
|
||||
this.isBark = false;
|
||||
if (this.selectTemplate.warning) {
|
||||
this.$message({
|
||||
message: this.selectTemplate.warning,
|
||||
type: 'warning'
|
||||
})
|
||||
if (this.selectTemplate.typeName == 'Bark') {
|
||||
this.isBark = true;
|
||||
this.selectTemplate.values = [];
|
||||
let origin = window.document.location.origin;
|
||||
this.selectTemplate.barkUrl = origin + '?act=' + this.sendKey;
|
||||
document.getElementById("bark").innerHTML = '';
|
||||
new QRCode(document.getElementById('bark'), {
|
||||
text: this.selectTemplate.barkUrl,
|
||||
width: 250,
|
||||
height: 250,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
submitForm(formName) {
|
||||
this.$refs[formName].validate((valid) => {
|
||||
@ -171,6 +205,7 @@ export default {
|
||||
});
|
||||
},
|
||||
addAuth() {
|
||||
this.isBark = false;
|
||||
this.title = '新增设置'
|
||||
this.dialogVisible = true
|
||||
this.isModify = false
|
||||
@ -180,9 +215,21 @@ export default {
|
||||
modifyAuth(index, row) {
|
||||
this.title = '修改设置'
|
||||
this.isModify = true;
|
||||
this.isBark = false;
|
||||
this.selectTemplate = deepClone(row);
|
||||
this.dialogVisible = true;
|
||||
if (this.selectTemplate.typeName == "Bark") {
|
||||
this.isBark = true;
|
||||
let origin = window.document.location.origin;
|
||||
var devieItem = this.selectTemplate.values.find(item => {
|
||||
return item.name == "DeviceKey"
|
||||
})
|
||||
var sendUrlItem = this.selectTemplate.values.find(item => {
|
||||
return item.name == "SendUrl"
|
||||
});
|
||||
sendUrlItem.value = origin + "?act=" + this.sendKey + "/" + devieItem.value + "/{title}/{data}"
|
||||
}
|
||||
|
||||
this.dialogVisible = true;
|
||||
},
|
||||
deleteAuth(index, row) {
|
||||
deleteAuthInfo(row.sendAuthId).then((response) => {
|
||||
@ -209,28 +256,32 @@ export default {
|
||||
this.$message.error(state ? '激活失败' : '注销失败');
|
||||
}
|
||||
})
|
||||
},
|
||||
getBarkUrl() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
|
||||
<style lang="scss">
|
||||
@media screen and (min-width:800px) {
|
||||
.el-dialog__wrapper .el-dialog {
|
||||
width: 800px !important;
|
||||
}
|
||||
|
||||
.el-dialog__wrapper .el-dialog .el-dialog__body {
|
||||
overflow: auto
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
@media screen and (max-width: 800px) {
|
||||
.el-dialog__wrapper .el-dialog {
|
||||
width: 99% !important;
|
||||
}
|
||||
|
||||
.el-dialog__wrapper .el-dialog .el-dialog__body {
|
||||
overflow: auto
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@ -20,17 +20,27 @@
|
||||
|
||||
<el-divider content-position="left">重置授权</el-divider>
|
||||
<el-form ref="resetform" :model="keyForm" label-width="20%">
|
||||
<el-form-item label="当前SendKey">
|
||||
<el-form-item label="当前Token">
|
||||
<el-input v-model="keyForm.sendKey" :readonly="true"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="快捷地址(标题)">
|
||||
<el-form-item label="调用接口(标题)">
|
||||
<el-input type="textarea" v-model="keyForm.sendUrlTitle" :readonly="true"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="快捷地址(完整)">
|
||||
<el-form-item label="调用接口(完整)">
|
||||
<el-input type="textarea" v-model="keyForm.sendUrl" :readonly="true"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onReSendKey('resetform')">重置SendKey</el-button>
|
||||
<el-button type="primary" @click="onReSendKey('resetform')">重置Token</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-divider content-position="left">BARK授权</el-divider>
|
||||
<el-form ref="resetform" :model="keyForm" label-width="20%">
|
||||
<el-form-item label="绑定地址">
|
||||
<el-input v-model="keyForm.barkUrl" :readonly="true"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div id="qrcode"> </div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
@ -90,15 +100,16 @@ export default {
|
||||
getSendKey().then((response) => {
|
||||
this.keyForm.sendKey = response.data;
|
||||
this.messageForm.sendKey = response.data;
|
||||
let wPath = window.document.location.href;
|
||||
let pathName = this.$route.path;
|
||||
let pos = wPath.indexOf(pathName);
|
||||
let localhostPath = wPath.substring(0, pos);
|
||||
localhostPath = localhostPath.replace("#", "");
|
||||
this.keyForm.sendUrlTitle = localhostPath + 'api/'+ this.keyForm.sendKey+'.send' + "/{title}"
|
||||
this.keyForm.sendUrl = localhostPath + 'api/'+ this.keyForm.sendKey+'.send' + "/{title}/{data}"
|
||||
// this.keyForm.sendUrl = localhostPath + 'api/send?token=' + this.keyForm.sendKey + "&title={title}&data={data}"
|
||||
let origin = window.document.location.origin;
|
||||
this.keyForm.sendUrlTitle = origin + '/' + this.keyForm.sendKey + '.send' + "/{title}"
|
||||
this.keyForm.sendUrl = origin + '/' + this.keyForm.sendKey + '.send' + "/{title}/{data}"
|
||||
this.keyForm.barkUrl = origin + '?act=' + this.keyForm.sendKey
|
||||
this.listLoading = false;
|
||||
new QRCode(document.getElementById("qrcode"), {
|
||||
text: this.keyForm.barkUrl,
|
||||
width: 150,
|
||||
height: 150,
|
||||
});
|
||||
});
|
||||
},
|
||||
onMessage(fromname) {
|
||||
|
@ -36,6 +36,17 @@
|
||||
<el-switch active-color='#13ce66' v-model='settingForm.githubEnable' inactive-color='#ff4949'></el-switch>
|
||||
</template>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">BARK授权</el-divider>
|
||||
<el-form-item label="KeyID">
|
||||
<el-input v-model="settingForm.barkKeyId" placeholder="KeyID">></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="TempID">
|
||||
<el-input v-model="settingForm.barkTeamId" placeholder="TempID"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="P8Key">
|
||||
<el-input type="textarea" v-model="settingForm.barkPrivateKey" placeholder="P8Key"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitForm('settingForm')">确认修改</el-button>
|
||||
</el-form-item>
|
||||
|
@ -6,24 +6,49 @@ function resolve(dir) {
|
||||
return path.join(__dirname, dir)
|
||||
}
|
||||
|
||||
const name = defaultSettings.title || 'Inotify' // page title
|
||||
const name = defaultSettings.title || 'Inotify'
|
||||
const port = process.env.port || process.env.npm_config_port || 9000
|
||||
|
||||
// If your port is set to 80,
|
||||
// use administrator privileges to execute the command line.
|
||||
// For example, Mac: sudo npm run
|
||||
// You can change the port by the following methods:
|
||||
// port = 9528 npm run dev OR npm run dev --port = 9528
|
||||
const port = process.env.port || process.env.npm_config_port || 9528 // dev port
|
||||
const axiosV = require('axios/package.json').version
|
||||
const echartsV = require('echarts/package.json').version
|
||||
const elementV = require('element-ui/package.json').version
|
||||
const jscookieV = require('js-cookie/package.json').version
|
||||
const normalizeV = require('normalize.css/package.json').version
|
||||
const vueV = require('vue/package.json').version
|
||||
const routerV = require('vue-router/package.json').version
|
||||
const vuexV = require('vuex/package.json').version
|
||||
const cookieV = require('js-cookie/package.json').version
|
||||
const nprogressV = require('nprogress/package.json').version
|
||||
const momentV = require('moment/package.json').version
|
||||
const cdn = {
|
||||
externals: {
|
||||
axios: 'axios',
|
||||
echarts: 'echarts',
|
||||
'element-ui': 'ELEMENT',
|
||||
moment: 'moment',
|
||||
locale: 'locale',
|
||||
vue: 'Vue',
|
||||
vuex: 'Vuex',
|
||||
'vue-router': 'VueRouter'
|
||||
},
|
||||
css: [`https://cdn.bootcdn.net/ajax/libs/element-ui/${elementV}/theme-chalk/index.css`, `https://cdn.bootcdn.net/ajax/libs/nprogress/${nprogressV}/nprogress.min.css`, `https://lib.baomitu.com/normalize/${normalizeV}/normalize.css`],
|
||||
js: [
|
||||
`https://cdn.bootcdn.net/ajax/libs/vue/${vueV}/vue.min.js`,
|
||||
`https://cdn.bootcdn.net/ajax/libs/vuex/${vuexV}/vuex.min.js`,
|
||||
`https://cdn.bootcdn.net/ajax/libs/js-cookie/${jscookieV}/js.cookie.js`,
|
||||
`https://cdn.bootcdn.net/ajax/libs/element-ui/${elementV}/index.js`,
|
||||
`https://cdn.bootcdn.net/ajax/libs/axios/${axiosV}/axios.min.js`,
|
||||
`https://cdn.bootcdn.net/ajax/libs/moment.js/2.29.1/moment.min.js`,
|
||||
`https://cdn.bootcdn.net/ajax/libs/echarts/4.9.0-rc.1/echarts.min.js`,
|
||||
`https://cdn.bootcdn.net/ajax/libs/vue-router/${routerV}/vue-router.min.js`,
|
||||
`https://cdn.bootcdn.net/ajax/libs/element-ui/${elementV}/locale/zh-CN.js`,
|
||||
`https://cdn.bootcdn.net/ajax/libs/js-cookie/${cookieV}/js.cookie.min.js`,
|
||||
`https://cdn.bootcdn.net/ajax/libs/nprogress/${nprogressV}/nprogress.min.js`,
|
||||
`https://cdn.bootcdn.net/ajax/libs/qrcodejs/1.0.0/qrcode.js`
|
||||
]
|
||||
}
|
||||
|
||||
// All configuration item explanations can be find in https://cli.vuejs.org/config/
|
||||
module.exports = {
|
||||
/**
|
||||
* You will need to set publicPath if you plan to deploy your site under a sub path,
|
||||
* for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/,
|
||||
* then publicPath should be set to "/bar/".
|
||||
* In most cases please use '/' !!!
|
||||
* Detail: https://cli.vuejs.org/config/#publicpath
|
||||
*/
|
||||
publicPath: '/',
|
||||
outputDir: '../Inotify/wwwroot',
|
||||
assetsDir: 'static',
|
||||
@ -39,9 +64,6 @@ module.exports = {
|
||||
before: require('./mock/mock-server.js')
|
||||
},
|
||||
configureWebpack: {
|
||||
// provide the app's title in webpack's name field, so that
|
||||
// it can be accessed in index.html to inject the correct title.
|
||||
name: name,
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve('src')
|
||||
@ -49,19 +71,23 @@ module.exports = {
|
||||
},
|
||||
devtool: 'source-map',
|
||||
performance: {
|
||||
hints: 'warning', // 枚举
|
||||
hints: 'error', // 性能提示中抛出错误
|
||||
hints: false, // 关闭性能提示
|
||||
maxAssetSize: 200000, // 整数类型(以字节为单位)
|
||||
maxEntrypointSize: 400000, // 整数类型(以字节为单位)
|
||||
hints: 'warning',
|
||||
hints: 'error',
|
||||
hints: false,
|
||||
maxAssetSize: 200000,
|
||||
maxEntrypointSize: 400000,
|
||||
assetFilter: function(assetFilename) {
|
||||
// 提供资源文件名的断言函数
|
||||
return assetFilename.endsWith('.css') || assetFilename.endsWith('.js')
|
||||
}
|
||||
}
|
||||
},
|
||||
externals: cdn.externals
|
||||
},
|
||||
chainWebpack(config) {
|
||||
// it can improve the speed of the first screen, it is recommended to turn on preload
|
||||
config.plugin('html').tap(args => {
|
||||
args[0].cdn = cdn
|
||||
return args
|
||||
})
|
||||
|
||||
config.plugin('preload').tap(() => [
|
||||
{
|
||||
rel: 'preload',
|
||||
@ -72,10 +98,7 @@ module.exports = {
|
||||
}
|
||||
])
|
||||
|
||||
// when there are many pages, it will cause too many meaningless requests
|
||||
config.plugins.delete('prefetch')
|
||||
|
||||
// set svg-sprite-loader
|
||||
config.module
|
||||
.rule('svg')
|
||||
.exclude.add(resolve('src/icons'))
|
||||
|
@ -17,3 +17,9 @@ dotnet_diagnostic.CS8602.severity = none
|
||||
|
||||
# CS8600: 将 null 文本或可能的 null 值转换为不可为 null 类型。
|
||||
dotnet_diagnostic.CS8600.severity = none
|
||||
|
||||
# IDE0037: 使用推断的成员名称
|
||||
dotnet_diagnostic.IDE0037.severity = none
|
||||
|
||||
# IDE0008: 使用显式类型
|
||||
dotnet_diagnostic.IDE0008.severity = none
|
||||
|
@ -1,19 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Inotify.Common
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// MD5加密字符串(32位大写)
|
||||
/// </summary>
|
||||
/// <param name="source">源字符串</param>
|
||||
/// <returns>加密后的字符串</returns>
|
||||
private static int rep = 0;
|
||||
|
||||
public static string ToMd5(this string source)
|
||||
{
|
||||
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
|
||||
@ -22,5 +16,68 @@ namespace Inotify.Common
|
||||
return result.Replace("-", "");
|
||||
}
|
||||
|
||||
public static string UrlEncode(this string str)
|
||||
{
|
||||
string urlStr = System.Web.HttpUtility.UrlEncode(str);
|
||||
return urlStr;
|
||||
}
|
||||
|
||||
public static string Base64Encode(this string source)
|
||||
{
|
||||
if (string.IsNullOrEmpty(source))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
byte[] bytes = (Encoding.UTF8.GetBytes(source));
|
||||
return Convert.ToBase64String(bytes);
|
||||
|
||||
}
|
||||
|
||||
public static string Base64Decode(this string source)
|
||||
{
|
||||
if (string.IsNullOrEmpty(source))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
var bytes = Convert.FromBase64String(source);
|
||||
return System.Text.Encoding.Default.GetString(bytes);
|
||||
}
|
||||
|
||||
public static string GenerateCheckCode(this int codeCount)
|
||||
{
|
||||
string str = string.Empty;
|
||||
long num2 = DateTime.Now.Ticks + rep;
|
||||
rep++;
|
||||
Random random = new Random(((int)(((ulong)num2) & 0xffffffffL)) | ((int)(num2 >> rep)));
|
||||
for (int i = 0; i < codeCount; i++)
|
||||
{
|
||||
char ch;
|
||||
int num = random.Next();
|
||||
if ((num % 2) == 0)
|
||||
{
|
||||
ch = (char)(0x30 + ((ushort)(num % 10)));
|
||||
}
|
||||
else
|
||||
{
|
||||
ch = (char)(0x41 + ((ushort)(num % 0x1a)));
|
||||
}
|
||||
str += ch.ToString();
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
public static long ToUTC(this DateTime time)
|
||||
{
|
||||
TimeSpan ts = time - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
|
||||
return Convert.ToInt64(ts.TotalMilliseconds);
|
||||
}
|
||||
|
||||
public static long ToUnix(this DateTime time)
|
||||
{
|
||||
var expiration = time.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
|
||||
return (long)expiration.TotalSeconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
115
Inotify/Common/XmlHelper.cs
Normal file
115
Inotify/Common/XmlHelper.cs
Normal file
@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace Inotify.Common
|
||||
{
|
||||
public static class XmlHelper
|
||||
{
|
||||
private static void XmlSerializeInternal(Stream stream, object o, Encoding encoding)
|
||||
{
|
||||
if (o == null)
|
||||
throw new ArgumentNullException("o");
|
||||
if (encoding == null)
|
||||
throw new ArgumentNullException("encoding");
|
||||
|
||||
XmlSerializer serializer = new XmlSerializer(o.GetType());
|
||||
|
||||
XmlWriterSettings settings = new XmlWriterSettings();
|
||||
settings.Indent = true;
|
||||
settings.NewLineChars = "\r\n";
|
||||
settings.Encoding = encoding;
|
||||
settings.IndentChars = " ";
|
||||
|
||||
using (XmlWriter writer = XmlWriter.Create(stream, settings))
|
||||
{
|
||||
serializer.Serialize(writer, o);
|
||||
writer.Close();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一个对象序列化为XML字符串
|
||||
/// </summary>
|
||||
/// <param name="o">要序列化的对象</param>
|
||||
/// <param name="encoding">编码方式</param>
|
||||
/// <returns>序列化产生的XML字符串</returns>
|
||||
public static string XmlSerialize(object o, Encoding encoding)
|
||||
{
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
XmlSerializeInternal(stream, o, encoding);
|
||||
|
||||
stream.Position = 0;
|
||||
using (StreamReader reader = new StreamReader(stream, encoding))
|
||||
{
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一个对象按XML序列化的方式写入到一个文件
|
||||
/// </summary>
|
||||
/// <param name="o">要序列化的对象</param>
|
||||
/// <param name="path">保存文件路径</param>
|
||||
/// <param name="encoding">编码方式</param>
|
||||
public static void XmlSerializeToFile(object o, string path, Encoding encoding)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
throw new ArgumentNullException("path");
|
||||
|
||||
using (FileStream file = new FileStream(path, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
XmlSerializeInternal(file, o, encoding);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从XML字符串中反序列化对象
|
||||
/// </summary>
|
||||
/// <typeparam name="T">结果对象类型</typeparam>
|
||||
/// <param name="s">包含对象的XML字符串</param>
|
||||
/// <param name="encoding">编码方式</param>
|
||||
/// <returns>反序列化得到的对象</returns>
|
||||
public static T XmlDeserialize<T>(string s, Encoding encoding)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s))
|
||||
throw new ArgumentNullException("s");
|
||||
if (encoding == null)
|
||||
throw new ArgumentNullException("encoding");
|
||||
|
||||
XmlSerializer mySerializer = new XmlSerializer(typeof(T));
|
||||
using (MemoryStream ms = new MemoryStream(encoding.GetBytes(s)))
|
||||
{
|
||||
using (StreamReader sr = new StreamReader(ms, encoding))
|
||||
{
|
||||
return (T)mySerializer.Deserialize(sr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读入一个文件,并按XML的方式反序列化对象。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">结果对象类型</typeparam>
|
||||
/// <param name="path">文件路径</param>
|
||||
/// <param name="encoding">编码方式</param>
|
||||
/// <returns>反序列化得到的对象</returns>
|
||||
public static T XmlDeserializeFromFile<T>(string path, Encoding encoding)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
throw new ArgumentNullException("path");
|
||||
if (encoding == null)
|
||||
throw new ArgumentNullException("encoding");
|
||||
|
||||
string xml = File.ReadAllText(path, encoding);
|
||||
return XmlDeserialize<T>(xml, encoding);
|
||||
}
|
||||
}
|
||||
}
|
133
Inotify/Controllers/BarkControllor.cs
Normal file
133
Inotify/Controllers/BarkControllor.cs
Normal file
@ -0,0 +1,133 @@
|
||||
using Inotify.Common;
|
||||
using Inotify.Data;
|
||||
using Inotify.Data.Models;
|
||||
using Inotify.Sends;
|
||||
using Inotify.Sends.Products;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Inotify.Controllers
|
||||
{
|
||||
|
||||
[ApiController]
|
||||
[Route("/")]
|
||||
public class BarkControllor : BaseController
|
||||
{
|
||||
[HttpGet, Route("Ping")]
|
||||
public JsonResult Ping()
|
||||
{
|
||||
return Me("pong");
|
||||
}
|
||||
|
||||
[HttpGet, Route("Info")]
|
||||
public JsonResult Info()
|
||||
{
|
||||
var dateTime = System.IO.File.GetLastWriteTime(GetType().Assembly.Location);
|
||||
var devices = DBManager.Instance.DBase.Query<SendAuthInfo>().Count();
|
||||
return Json(new
|
||||
{
|
||||
version = "v2.0.1",
|
||||
build = dateTime.ToString("yyyy-MM-dd HH:mm:ss"),
|
||||
arch = RuntimeInformation.OSDescription,
|
||||
commit = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(),
|
||||
devices
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet, Route("Healthz")]
|
||||
|
||||
public string Healthz()
|
||||
{
|
||||
return "ok";
|
||||
}
|
||||
|
||||
[HttpGet, Route("Register")]
|
||||
public JsonResult Register(string? act, string? key, string? devicetoken, string? device_key)
|
||||
{
|
||||
return !string.IsNullOrEmpty(device_key) ? Register(device_key) : Register(act, key, devicetoken);
|
||||
}
|
||||
|
||||
[HttpPost, Route("Register")]
|
||||
public JsonResult Register(string? act, string? device_key, string? device_token)
|
||||
{
|
||||
if (string.IsNullOrEmpty(act))
|
||||
{
|
||||
return Fail(400, "request bind failed : act is empty");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(device_token))
|
||||
{
|
||||
return Fail(400, "request bind failed : device_token is empty");
|
||||
}
|
||||
|
||||
var userInfo = DBManager.Instance.DBase.Query<SendUserInfo>().FirstOrDefault(e => e.Token == act);
|
||||
if (userInfo == null)
|
||||
{
|
||||
return Fail(400, "request bind failed : act is not registered");
|
||||
}
|
||||
else
|
||||
{
|
||||
BarkAuth barkAuth = null;
|
||||
SendAuthInfo barkSendAuthInfo = null;
|
||||
var barkTemplateAttribute = typeof(BarkSendTemplate).GetCustomAttributes(typeof(SendMethodKeyAttribute), false).OfType<SendMethodKeyAttribute>().First();
|
||||
|
||||
if (!string.IsNullOrEmpty(device_key))
|
||||
{
|
||||
barkSendAuthInfo = DBManager.Instance.DBase.Query<SendAuthInfo>().FirstOrDefault(e => e.Key == device_key);
|
||||
if (barkSendAuthInfo != null)
|
||||
{
|
||||
barkAuth = JsonConvert.DeserializeObject<BarkAuth>(barkSendAuthInfo.AuthData);
|
||||
barkAuth.DeviceToken = device_token;
|
||||
barkSendAuthInfo.AuthData = JsonConvert.SerializeObject(barkAuth);
|
||||
barkSendAuthInfo.ModifyTime = DateTime.Now;
|
||||
DBManager.Instance.DBase.Update(barkSendAuthInfo);
|
||||
}
|
||||
}
|
||||
|
||||
if (barkSendAuthInfo == null)
|
||||
{
|
||||
if(string.IsNullOrEmpty(device_key))
|
||||
device_key = Guid.NewGuid().ToString("N").ToUpper();
|
||||
barkAuth = new BarkAuth() { DeviceKey = device_key, DeviceToken = device_token, IsArchive = "1", AutoMaticallyCopy = "1", Sound = "1107" };
|
||||
barkSendAuthInfo = new SendAuthInfo()
|
||||
{
|
||||
Name = barkTemplateAttribute.Name,
|
||||
SendMethodTemplate = barkTemplateAttribute.Key,
|
||||
Key = device_key,
|
||||
AuthData = JsonConvert.SerializeObject(barkAuth),
|
||||
UserId = userInfo.Id,
|
||||
CreateTime = DateTime.Now,
|
||||
ModifyTime = DateTime.Now,
|
||||
Active = true,
|
||||
};
|
||||
DBManager.Instance.DBase.Insert(barkSendAuthInfo);
|
||||
}
|
||||
|
||||
return Json(new
|
||||
{
|
||||
key = device_key,
|
||||
device_key = device_key,
|
||||
device_token = device_token
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet, Route("RegisterCheck")]
|
||||
public JsonResult Register(string device_key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(device_key))
|
||||
{
|
||||
return Fail(400, "device key is empty");
|
||||
}
|
||||
if (!DBManager.Instance.DBase.Query<SendAuthInfo>().Any(e => e.Key == device_key))
|
||||
{
|
||||
return Fail(400, "device not registered");
|
||||
}
|
||||
|
||||
return OK();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Inotify.Common;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
|
||||
namespace Inotify.Controllers
|
||||
{
|
||||
@ -64,30 +66,54 @@ namespace Inotify.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
protected JsonResult OK(object? obj = null)
|
||||
protected JsonResult OK()
|
||||
{
|
||||
return new JsonResult(new
|
||||
return Json(new
|
||||
{
|
||||
code = 200,
|
||||
data = obj ?? "sucess"
|
||||
message = "sucess",
|
||||
timestamp = DateTime.Now.ToUnix()
|
||||
});
|
||||
}
|
||||
|
||||
protected JsonResult OK(object obj)
|
||||
{
|
||||
return Json(new
|
||||
{
|
||||
code = 200,
|
||||
message = "sucess",
|
||||
data = obj ?? "",
|
||||
timestamp = DateTime.Now.ToUnix()
|
||||
});
|
||||
}
|
||||
|
||||
protected JsonResult Me(string message)
|
||||
{
|
||||
return Json(new
|
||||
{
|
||||
code = 200,
|
||||
message = message,
|
||||
timestamp = DateTime.Now.ToUnix()
|
||||
});
|
||||
}
|
||||
|
||||
protected JsonResult Fail()
|
||||
{
|
||||
return new JsonResult(new
|
||||
return Json(new
|
||||
{
|
||||
code = 404,
|
||||
data = "fail"
|
||||
message = "failed",
|
||||
timestamp = DateTime.Now.ToUnix()
|
||||
});
|
||||
}
|
||||
|
||||
protected JsonResult Fail(int code)
|
||||
{
|
||||
return new JsonResult(new
|
||||
return Json(new
|
||||
{
|
||||
code,
|
||||
data = "fail"
|
||||
code = code,
|
||||
message = "failed",
|
||||
timestamp = DateTime.Now.ToUnix()
|
||||
});
|
||||
}
|
||||
|
||||
@ -95,12 +121,48 @@ namespace Inotify.Controllers
|
||||
{
|
||||
return new JsonResult(new
|
||||
{
|
||||
code,
|
||||
message
|
||||
code = code,
|
||||
message = message,
|
||||
timestamp = DateTime.Now.ToUnix()
|
||||
});
|
||||
}
|
||||
|
||||
protected JsonResult Json(object obj)
|
||||
{
|
||||
return new JsonResult(obj);
|
||||
}
|
||||
|
||||
protected string GetPostParams(HttpContext context)
|
||||
{
|
||||
string param = string.Empty;
|
||||
if (context.Request.Method.ToLower().Equals("post"))
|
||||
{
|
||||
param += "[post]";
|
||||
foreach (var key in context.Request.Form.Keys.ToList())
|
||||
{
|
||||
param += key + ":" + context.Request.Form[key].ToString();
|
||||
}
|
||||
}
|
||||
else if (context.Request.Method.ToLower().Equals("get"))
|
||||
{
|
||||
param += "[get]" + context.Request.QueryString.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
param += "[" + context.Request.Method + "]";
|
||||
}
|
||||
|
||||
return param;
|
||||
}
|
||||
|
||||
protected string GetPostXML()
|
||||
{
|
||||
Stream reqStream = Request.Body;
|
||||
using (StreamReader reader = new StreamReader(reqStream))
|
||||
{
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,22 +5,16 @@ using Inotify.Sends;
|
||||
using Inotify.ThridOauth.Common;
|
||||
using Inotify.ThridOauth.IService;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NPoco;
|
||||
using System;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Inotify.Controllers
|
||||
{
|
||||
@ -47,7 +41,10 @@ namespace Inotify.Controllers
|
||||
if (userInfo != null)
|
||||
{
|
||||
if (!userInfo.Active)
|
||||
{
|
||||
return Fail(401, "用户被禁用");
|
||||
}
|
||||
|
||||
if (userInfo.Password == password.ToMd5())
|
||||
{
|
||||
var token = GenToken(username);
|
||||
@ -97,13 +94,19 @@ namespace Inotify.Controllers
|
||||
string? avtar = null;
|
||||
string email = "";
|
||||
if (res.Result.TryGetValue("login", out JToken? jToken))
|
||||
{
|
||||
githubUserName = jToken.ToString();
|
||||
}
|
||||
|
||||
if (res.Result.TryGetValue("avatar_url", out jToken))
|
||||
{
|
||||
avtar = jToken.ToString();
|
||||
}
|
||||
|
||||
if (res.Result.TryGetValue("email", out jToken))
|
||||
{
|
||||
email = jToken.ToString();
|
||||
}
|
||||
|
||||
if (githubUserName != null && avtar != null)
|
||||
{
|
||||
@ -114,7 +117,9 @@ namespace Inotify.Controllers
|
||||
user.Avatar = avtar;
|
||||
DBManager.Instance.DBase.Update(user);
|
||||
if (!user.Active)
|
||||
{
|
||||
return Fail(401, "用户被禁用");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
66
Inotify/Controllers/SendController.cs
Normal file
66
Inotify/Controllers/SendController.cs
Normal file
@ -0,0 +1,66 @@
|
||||
using Inotify.Data;
|
||||
using Inotify.Sends;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Inotify.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api")]
|
||||
public class SendController : BaseController
|
||||
{
|
||||
[HttpGet, Route("send")]
|
||||
public JsonResult Send(string? token, string? title, string? data)
|
||||
{
|
||||
if (DBManager.Instance.IsToken(token, out bool hasActive))
|
||||
{
|
||||
if (!hasActive)
|
||||
{
|
||||
return Fail(400, "you have no tunnel is acitve");
|
||||
}
|
||||
|
||||
var message = new SendMessage()
|
||||
{
|
||||
Token = token,
|
||||
Title = title,
|
||||
Data = data
|
||||
};
|
||||
|
||||
if (SendTaskManager.Instance.SendMessage(message))
|
||||
{
|
||||
return OK();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var key = token;
|
||||
if (DBManager.Instance.IsSendKey(key, out bool isActive, out token))
|
||||
{
|
||||
if (!isActive)
|
||||
{
|
||||
return Fail(400, $"device:{key} tunnel is not acitve");
|
||||
}
|
||||
var message = new SendMessage()
|
||||
{
|
||||
Token = token,
|
||||
Title = title,
|
||||
Data = data,
|
||||
Key = key,
|
||||
};
|
||||
|
||||
if (SendTaskManager.Instance.SendMessage(message))
|
||||
{
|
||||
return OK();
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return Fail(400, $"device:{key} is not registered");
|
||||
}
|
||||
}
|
||||
|
||||
return Fail(400, $"token:{token} is not registered");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
using Inotify.Data;
|
||||
using Inotify.Sends;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using System;
|
||||
|
||||
namespace Inotify.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api")]
|
||||
public class SendController : BaseController
|
||||
{
|
||||
[HttpGet, Route("send")]
|
||||
public JsonResult Send(string token, string title, string? data)
|
||||
{
|
||||
if (DBManager.Instance.IsSendKey(token))
|
||||
{
|
||||
var message = new SendMessage()
|
||||
{
|
||||
Token = token,
|
||||
Title = title,
|
||||
Data = data,
|
||||
};
|
||||
if (SendTaskManager.Instance.SendMessage(message))
|
||||
return OK();
|
||||
}
|
||||
return Fail();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +1,18 @@
|
||||
using Inotify.Data;
|
||||
using Inotify.Data.Models;
|
||||
using Inotify.Sends;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Inotify.Sends.Products;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Inotify.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/setting")]
|
||||
public class SettingControlor : BaseController
|
||||
public class SettingController : BaseController
|
||||
{
|
||||
[HttpGet, Authorize(Policys.SystemOrUsers)]
|
||||
public JsonResult Index()
|
||||
@ -38,6 +33,7 @@ namespace Inotify.Controllers
|
||||
var userInfo = DBManager.Instance.GetUser(UserName);
|
||||
if (userInfo != null)
|
||||
{
|
||||
var barkTemplateAttribute = typeof(BarkSendTemplate).GetCustomAttributes(typeof(SendMethodKeyAttribute), false).OfType<SendMethodKeyAttribute>().First();
|
||||
var sendAuthInfos = DBManager.Instance.DBase.Query<SendAuthInfo>().Where(e => e.UserId == userInfo.Id).ToArray();
|
||||
var userSendTemplates = new List<InputTemeplate>();
|
||||
foreach (var sendAuthInfo in sendAuthInfos)
|
||||
@ -45,12 +41,17 @@ namespace Inotify.Controllers
|
||||
var sendTemplate = SendTaskManager.Instance.GetInputTemplate(sendAuthInfo.SendMethodTemplate);
|
||||
if (sendTemplate != null)
|
||||
{
|
||||
|
||||
sendTemplate.Key = sendAuthInfo.Key;
|
||||
sendTemplate.SendAuthId = sendAuthInfo.Id;
|
||||
sendTemplate.Name = sendAuthInfo.Name;
|
||||
sendTemplate.AuthData = sendAuthInfo.AuthData;
|
||||
sendTemplate.SendAuthId = sendAuthInfo.Id;
|
||||
sendTemplate.IsActive = sendAuthInfo.Id == userInfo.SendAuthId;
|
||||
sendTemplate.IsActive = sendAuthInfo.Active;
|
||||
sendTemplate.AuthToTemplate(sendAuthInfo.AuthData);
|
||||
userSendTemplates.Add(sendTemplate);
|
||||
|
||||
var bark = sendTemplate.Values.FirstOrDefault(e => e.Name == nameof(BarkSendTemplate.Auth.SendUrl));
|
||||
if(bark!=null) bark .Value = "";
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,9 +69,9 @@ namespace Inotify.Controllers
|
||||
var authInfo = DBManager.Instance.DBase.Query<SendAuthInfo>().FirstOrDefault(e => e.Id == sendAuthId && e.UserId == userInfo.Id);
|
||||
if (authInfo != null)
|
||||
{
|
||||
userInfo.SendAuthId = state ? sendAuthId : -1;
|
||||
DBManager.Instance.DBase.Update(userInfo);
|
||||
return OK(userInfo);
|
||||
authInfo.Active = state;
|
||||
DBManager.Instance.DBase.Update(authInfo);
|
||||
return OK(authInfo);
|
||||
}
|
||||
}
|
||||
return Fail();
|
||||
@ -96,20 +97,30 @@ namespace Inotify.Controllers
|
||||
public JsonResult AddSendAuth(InputTemeplate inputTemeplate)
|
||||
{
|
||||
var userInfo = DBManager.Instance.GetUser(UserName);
|
||||
if (userInfo != null && inputTemeplate.Key != null && inputTemeplate.Name != null)
|
||||
if (userInfo != null && inputTemeplate.Type != null && inputTemeplate.Name != null)
|
||||
{
|
||||
var authInfo = inputTemeplate.TemplateToAuth();
|
||||
var sendAuth = new SendAuthInfo()
|
||||
var barkKey = typeof(BarkSendTemplate).GetCustomAttributes(typeof(SendMethodKeyAttribute), false).OfType<SendMethodKeyAttribute>().First().Key;
|
||||
if (barkKey == inputTemeplate.Type)
|
||||
{
|
||||
UserId = userInfo.Id,
|
||||
SendMethodTemplate = inputTemeplate.Key,
|
||||
AuthData = authInfo,
|
||||
Name = inputTemeplate.Name,
|
||||
CreateTime = DateTime.Now,
|
||||
ModifyTime = DateTime.Now,
|
||||
};
|
||||
DBManager.Instance.DBase.Insert(sendAuth);
|
||||
return OK(sendAuth);
|
||||
return Fail(406, "BARK通道勿手动添加,请使用APP添加BARK地址绑定");
|
||||
}
|
||||
else
|
||||
{
|
||||
var authInfo = inputTemeplate.TemplateToAuth();
|
||||
var sendAuth = new SendAuthInfo()
|
||||
{
|
||||
UserId = userInfo.Id,
|
||||
SendMethodTemplate = inputTemeplate.Type,
|
||||
AuthData = authInfo,
|
||||
Name = inputTemeplate.Name,
|
||||
Key = Guid.NewGuid().ToString("N").ToUpper(),
|
||||
CreateTime = DateTime.Now,
|
||||
ModifyTime = DateTime.Now,
|
||||
};
|
||||
DBManager.Instance.DBase.Insert(sendAuth);
|
||||
return OK(sendAuth);
|
||||
|
||||
}
|
||||
}
|
||||
return Fail();
|
||||
}
|
||||
@ -120,6 +131,7 @@ namespace Inotify.Controllers
|
||||
var userInfo = DBManager.Instance.GetUser(UserName);
|
||||
if (userInfo != null)
|
||||
{
|
||||
var barkTemplateAttribute = typeof(BarkSendTemplate).GetCustomAttributes(typeof(SendMethodKeyAttribute), false).OfType<SendMethodKeyAttribute>().First();
|
||||
var oldSendInfo = DBManager.Instance.DBase.Query<SendAuthInfo>().FirstOrDefault(e => e.Id == inputTemeplate.SendAuthId);
|
||||
if (oldSendInfo != null && inputTemeplate.Name != null)
|
||||
{
|
||||
@ -138,7 +150,10 @@ namespace Inotify.Controllers
|
||||
{
|
||||
var userInfo = DBManager.Instance.GetUser(UserName);
|
||||
if (userInfo != null)
|
||||
{
|
||||
return OK(userInfo.Token);
|
||||
}
|
||||
|
||||
return Fail();
|
||||
|
||||
}
|
@ -4,17 +4,13 @@ using Inotify.Data.Models.System;
|
||||
using Inotify.Sends;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NPoco;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Inotify.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/settingsys")]
|
||||
public class SetttingSysControlor : BaseController
|
||||
public class SetttingSysController : BaseController
|
||||
{
|
||||
[HttpGet, Route("GetGlobal"), Authorize(Policys.Systems)]
|
||||
public IActionResult GetGlobal()
|
||||
@ -30,6 +26,9 @@ namespace Inotify.Controllers
|
||||
githubClientID = SendCacheStore.GetSystemValue("githubClientID"),
|
||||
githubClientSecret = SendCacheStore.GetSystemValue("githubClientSecret"),
|
||||
githubEnable = githubEnable != "" && bool.Parse(githubEnable),
|
||||
barkKeyId = SendCacheStore.GetSystemValue("barkKeyId"),
|
||||
barkTeamId = SendCacheStore.GetSystemValue("barkTeamId"),
|
||||
barkPrivateKey = SendCacheStore.GetSystemValue("barkPrivateKey"),
|
||||
});
|
||||
}
|
||||
|
||||
@ -41,7 +40,10 @@ namespace Inotify.Controllers
|
||||
string? proxyenable,
|
||||
string? githubClientID,
|
||||
string? githubClientSecret,
|
||||
string? githubEnable)
|
||||
string? githubEnable,
|
||||
string? barkKeyId,
|
||||
string? barkTeamId,
|
||||
string? barkPrivateKey)
|
||||
{
|
||||
SendCacheStore.SetSystemValue("sendthread", sendthread);
|
||||
SendCacheStore.SetSystemValue("administrators", administrators);
|
||||
@ -50,6 +52,9 @@ namespace Inotify.Controllers
|
||||
SendCacheStore.SetSystemValue("githubClientID", githubClientID);
|
||||
SendCacheStore.SetSystemValue("githubClientSecret", githubClientSecret);
|
||||
SendCacheStore.SetSystemValue("githubEnable", githubEnable);
|
||||
SendCacheStore.SetSystemValue("barkKeyId", barkKeyId);
|
||||
SendCacheStore.SetSystemValue("barkTeamId", barkTeamId);
|
||||
SendCacheStore.SetSystemValue("barkPrivateKey", barkPrivateKey);
|
||||
|
||||
SendTaskManager.Instance.Stop();
|
||||
SendTaskManager.Instance.Run();
|
||||
@ -102,16 +107,20 @@ namespace Inotify.Controllers
|
||||
public IActionResult GetUsers(string? query, int page, int pageSize)
|
||||
{
|
||||
if (query == null)
|
||||
{
|
||||
return OK(DBManager.Instance.DBase.Query<SendUserInfo>().ToPage(page, pageSize));
|
||||
else return OK(DBManager.Instance.DBase.Query<SendUserInfo>().Where(e => e.UserName.Contains(query) || e.Email.Contains(query)).ToPage(page, pageSize));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return OK(DBManager.Instance.DBase.Query<SendUserInfo>().Where(e => e.UserName.Contains(query) || e.Email.Contains(query)).ToPage(page, pageSize));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet, Route("GetSendInfos"), Authorize(Policys.Systems)]
|
||||
public IActionResult GetSendInfos(string? start, string? end)
|
||||
{
|
||||
var templates = SendTaskManager.Instance.GetInputTemeplates();
|
||||
var sendInfos = DBManager.Instance.DBase.Fetch<SendInfo>();
|
||||
var sendInfos = DBManager.Instance.DBase.Fetch<SendInfo>().Where(e=>!string.IsNullOrEmpty( e.TemplateID)).ToList();
|
||||
var sendInfoQuerys = sendInfos.Where(e => int.Parse(e.Date) >= int.Parse(start) && int.Parse(e.Date) <= int.Parse(end)).ToList();
|
||||
var sendInfoGroups = sendInfoQuerys.GroupBy(e => e.Date).Select(e => new { date = e.Key, count = e.Sum(item => item.Count) }).ToList();
|
||||
var sendTypeInfoGroups = sendInfoQuerys.GroupBy(e => e.TemplateID).Select(e => new { date = e.Key, count = e.Sum(item => item.Count) }).ToList();
|
544
Inotify/Controllers/WeiXinCallBackController.cs
Normal file
544
Inotify/Controllers/WeiXinCallBackController.cs
Normal file
@ -0,0 +1,544 @@
|
||||
using Inotify.Common;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace Inotify.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/weixin")]
|
||||
public class WeiXinCallBackController : BaseController
|
||||
{
|
||||
[HttpGet]
|
||||
public string Get(string msg_signature, string timestamp, string nonce, string echostr)
|
||||
{
|
||||
|
||||
var replyEchoStr = string.Empty;
|
||||
var WXBizMsgCrypt = new WXBizMsgCrypt("", "", "");
|
||||
var result = WXBizMsgCrypt.VerifyURL(msg_signature, timestamp, nonce, echostr, ref replyEchoStr);
|
||||
if (result == 0)
|
||||
{
|
||||
return replyEchoStr;
|
||||
}
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public JsonResult Post(string? msg_signature, string? timestamp, string? nonce)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
var reqStream = Request.Body;
|
||||
string postData = "";
|
||||
using (StreamReader reader = new StreamReader(reqStream))
|
||||
{
|
||||
postData = reader.ReadToEnd();
|
||||
}
|
||||
|
||||
var msg = string.Empty;
|
||||
var WXBizMsgCrypt = new WXBizMsgCrypt("", "", "");
|
||||
var result = WXBizMsgCrypt.DecryptMsg(msg_signature, timestamp, nonce, postData, ref msg);
|
||||
if (result == 0)
|
||||
{
|
||||
var serializer = new XmlSerializer(typeof(xml));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
return OK();
|
||||
}
|
||||
}
|
||||
|
||||
public class xml
|
||||
{
|
||||
public string ToUserName { get; set; }
|
||||
|
||||
public string FromUserName { get; set; }
|
||||
|
||||
public string CreateTime { get; set; }
|
||||
|
||||
public string MsgType { get; set; }
|
||||
|
||||
public string Content { get; set; }
|
||||
|
||||
public string MsgId { get; set; }
|
||||
|
||||
public string AgentID { get; set; }
|
||||
|
||||
}
|
||||
|
||||
class Cryptography
|
||||
{
|
||||
public static UInt32 HostToNetworkOrder(UInt32 inval)
|
||||
{
|
||||
UInt32 outval = 0;
|
||||
for (int i = 0; i < 4; i++)
|
||||
outval = (outval << 8) + ((inval >> (i * 8)) & 255);
|
||||
return outval;
|
||||
}
|
||||
|
||||
public static Int32 HostToNetworkOrder(Int32 inval)
|
||||
{
|
||||
Int32 outval = 0;
|
||||
for (int i = 0; i < 4; i++)
|
||||
outval = (outval << 8) + ((inval >> (i * 8)) & 255);
|
||||
return outval;
|
||||
}
|
||||
/// <summary>
|
||||
/// 解密方法
|
||||
/// </summary>
|
||||
/// <param name="Input">密文</param>
|
||||
/// <param name="EncodingAESKey"></param>
|
||||
/// <returns></returns>
|
||||
///
|
||||
public static string AES_decrypt(String Input, string EncodingAESKey, ref string corpid)
|
||||
{
|
||||
byte[] Key;
|
||||
Key = Convert.FromBase64String(EncodingAESKey + "=");
|
||||
byte[] Iv = new byte[16];
|
||||
Array.Copy(Key, Iv, 16);
|
||||
byte[] btmpMsg = AES_decrypt(Input, Iv, Key);
|
||||
|
||||
int len = BitConverter.ToInt32(btmpMsg, 16);
|
||||
len = IPAddress.NetworkToHostOrder(len);
|
||||
|
||||
|
||||
byte[] bMsg = new byte[len];
|
||||
byte[] bCorpid = new byte[btmpMsg.Length - 20 - len];
|
||||
Array.Copy(btmpMsg, 20, bMsg, 0, len);
|
||||
Array.Copy(btmpMsg, 20 + len, bCorpid, 0, btmpMsg.Length - 20 - len);
|
||||
string oriMsg = Encoding.UTF8.GetString(bMsg);
|
||||
corpid = Encoding.UTF8.GetString(bCorpid);
|
||||
|
||||
|
||||
return oriMsg;
|
||||
}
|
||||
|
||||
public static String AES_encrypt(String Input, string EncodingAESKey, string corpid)
|
||||
{
|
||||
byte[] Key;
|
||||
Key = Convert.FromBase64String(EncodingAESKey + "=");
|
||||
byte[] Iv = new byte[16];
|
||||
Array.Copy(Key, Iv, 16);
|
||||
string Randcode = CreateRandCode(16);
|
||||
byte[] bRand = Encoding.UTF8.GetBytes(Randcode);
|
||||
byte[] bCorpid = Encoding.UTF8.GetBytes(corpid);
|
||||
byte[] btmpMsg = Encoding.UTF8.GetBytes(Input);
|
||||
byte[] bMsgLen = BitConverter.GetBytes(HostToNetworkOrder(btmpMsg.Length));
|
||||
byte[] bMsg = new byte[bRand.Length + bMsgLen.Length + bCorpid.Length + btmpMsg.Length];
|
||||
|
||||
Array.Copy(bRand, bMsg, bRand.Length);
|
||||
Array.Copy(bMsgLen, 0, bMsg, bRand.Length, bMsgLen.Length);
|
||||
Array.Copy(btmpMsg, 0, bMsg, bRand.Length + bMsgLen.Length, btmpMsg.Length);
|
||||
Array.Copy(bCorpid, 0, bMsg, bRand.Length + bMsgLen.Length + btmpMsg.Length, bCorpid.Length);
|
||||
|
||||
return AES_encrypt(bMsg, Iv, Key);
|
||||
|
||||
}
|
||||
private static string CreateRandCode(int codeLen)
|
||||
{
|
||||
string codeSerial = "2,3,4,5,6,7,a,c,d,e,f,h,i,j,k,m,n,p,r,s,t,A,C,D,E,F,G,H,J,K,M,N,P,Q,R,S,U,V,W,X,Y,Z";
|
||||
if (codeLen == 0)
|
||||
{
|
||||
codeLen = 16;
|
||||
}
|
||||
string[] arr = codeSerial.Split(',');
|
||||
string code = "";
|
||||
int randValue = -1;
|
||||
Random rand = new Random(unchecked((int)DateTime.Now.Ticks));
|
||||
for (int i = 0; i < codeLen; i++)
|
||||
{
|
||||
randValue = rand.Next(0, arr.Length - 1);
|
||||
code += arr[randValue];
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
private static String AES_encrypt(String Input, byte[] Iv, byte[] Key)
|
||||
{
|
||||
var aes = new RijndaelManaged();
|
||||
//秘钥的大小,以位为单位
|
||||
aes.KeySize = 256;
|
||||
//支持的块大小
|
||||
aes.BlockSize = 128;
|
||||
//填充模式
|
||||
aes.Padding = PaddingMode.PKCS7;
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Key = Key;
|
||||
aes.IV = Iv;
|
||||
var encrypt = aes.CreateEncryptor(aes.Key, aes.IV);
|
||||
byte[] xBuff = null;
|
||||
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
using (var cs = new CryptoStream(ms, encrypt, CryptoStreamMode.Write))
|
||||
{
|
||||
byte[] xXml = Encoding.UTF8.GetBytes(Input);
|
||||
cs.Write(xXml, 0, xXml.Length);
|
||||
}
|
||||
xBuff = ms.ToArray();
|
||||
}
|
||||
String Output = Convert.ToBase64String(xBuff);
|
||||
return Output;
|
||||
}
|
||||
|
||||
private static String AES_encrypt(byte[] Input, byte[] Iv, byte[] Key)
|
||||
{
|
||||
var aes = new RijndaelManaged();
|
||||
//秘钥的大小,以位为单位
|
||||
aes.KeySize = 256;
|
||||
//支持的块大小
|
||||
aes.BlockSize = 128;
|
||||
//填充模式
|
||||
//aes.Padding = PaddingMode.PKCS7;
|
||||
aes.Padding = PaddingMode.None;
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Key = Key;
|
||||
aes.IV = Iv;
|
||||
var encrypt = aes.CreateEncryptor(aes.Key, aes.IV);
|
||||
byte[] xBuff = null;
|
||||
|
||||
#region 自己进行PKCS7补位,用系统自己带的不行
|
||||
byte[] msg = new byte[Input.Length + 32 - Input.Length % 32];
|
||||
Array.Copy(Input, msg, Input.Length);
|
||||
byte[] pad = KCS7Encoder(Input.Length);
|
||||
Array.Copy(pad, 0, msg, Input.Length, pad.Length);
|
||||
#endregion
|
||||
|
||||
#region 注释的也是一种方法,效果一样
|
||||
//ICryptoTransform transform = aes.CreateEncryptor();
|
||||
//byte[] xBuff = transform.TransformFinalBlock(msg, 0, msg.Length);
|
||||
#endregion
|
||||
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
using (var cs = new CryptoStream(ms, encrypt, CryptoStreamMode.Write))
|
||||
{
|
||||
cs.Write(msg, 0, msg.Length);
|
||||
}
|
||||
xBuff = ms.ToArray();
|
||||
}
|
||||
|
||||
String Output = Convert.ToBase64String(xBuff);
|
||||
return Output;
|
||||
}
|
||||
|
||||
private static byte[] KCS7Encoder(int text_length)
|
||||
{
|
||||
int block_size = 32;
|
||||
// 计算需要填充的位数
|
||||
int amount_to_pad = block_size - (text_length % block_size);
|
||||
if (amount_to_pad == 0)
|
||||
{
|
||||
amount_to_pad = block_size;
|
||||
}
|
||||
// 获得补位所用的字符
|
||||
char pad_chr = chr(amount_to_pad);
|
||||
string tmp = "";
|
||||
for (int index = 0; index < amount_to_pad; index++)
|
||||
{
|
||||
tmp += pad_chr;
|
||||
}
|
||||
return Encoding.UTF8.GetBytes(tmp);
|
||||
}
|
||||
/**
|
||||
* 将数字转化成ASCII码对应的字符,用于对明文进行补码
|
||||
*
|
||||
* @param a 需要转化的数字
|
||||
* @return 转化得到的字符
|
||||
*/
|
||||
static char chr(int a)
|
||||
{
|
||||
|
||||
byte target = (byte)(a & 0xFF);
|
||||
return (char)target;
|
||||
}
|
||||
private static byte[] AES_decrypt(String Input, byte[] Iv, byte[] Key)
|
||||
{
|
||||
RijndaelManaged aes = new RijndaelManaged();
|
||||
aes.KeySize = 256;
|
||||
aes.BlockSize = 128;
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Padding = PaddingMode.None;
|
||||
aes.Key = Key;
|
||||
aes.IV = Iv;
|
||||
var decrypt = aes.CreateDecryptor(aes.Key, aes.IV);
|
||||
byte[] xBuff = null;
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
using (var cs = new CryptoStream(ms, decrypt, CryptoStreamMode.Write))
|
||||
{
|
||||
byte[] xXml = Convert.FromBase64String(Input);
|
||||
byte[] msg = new byte[xXml.Length + 32 - xXml.Length % 32];
|
||||
Array.Copy(xXml, msg, xXml.Length);
|
||||
cs.Write(xXml, 0, xXml.Length);
|
||||
}
|
||||
xBuff = decode2(ms.ToArray());
|
||||
}
|
||||
return xBuff;
|
||||
}
|
||||
private static byte[] decode2(byte[] decrypted)
|
||||
{
|
||||
int pad = (int)decrypted[decrypted.Length - 1];
|
||||
if (pad < 1 || pad > 32)
|
||||
{
|
||||
pad = 0;
|
||||
}
|
||||
byte[] res = new byte[decrypted.Length - pad];
|
||||
Array.Copy(decrypted, 0, res, 0, decrypted.Length - pad);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
class WXBizMsgCrypt
|
||||
{
|
||||
string m_sToken;
|
||||
string m_sEncodingAESKey;
|
||||
string m_sReceiveId;
|
||||
enum WXBizMsgCryptErrorCode
|
||||
{
|
||||
WXBizMsgCrypt_OK = 0,
|
||||
WXBizMsgCrypt_ValidateSignature_Error = -40001,
|
||||
WXBizMsgCrypt_ParseXml_Error = -40002,
|
||||
WXBizMsgCrypt_ComputeSignature_Error = -40003,
|
||||
WXBizMsgCrypt_IllegalAesKey = -40004,
|
||||
WXBizMsgCrypt_ValidateCorpid_Error = -40005,
|
||||
WXBizMsgCrypt_EncryptAES_Error = -40006,
|
||||
WXBizMsgCrypt_DecryptAES_Error = -40007,
|
||||
WXBizMsgCrypt_IllegalBuffer = -40008,
|
||||
WXBizMsgCrypt_EncodeBase64_Error = -40009,
|
||||
WXBizMsgCrypt_DecodeBase64_Error = -40010
|
||||
};
|
||||
|
||||
//构造函数
|
||||
// @param sToken: 企业微信后台,开发者设置的Token
|
||||
// @param sEncodingAESKey: 企业微信后台,开发者设置的EncodingAESKey
|
||||
// @param sReceiveId: 不同场景含义不同,详见文档说明
|
||||
public WXBizMsgCrypt(string sToken, string sEncodingAESKey, string sReceiveId)
|
||||
{
|
||||
m_sToken = sToken;
|
||||
m_sReceiveId = sReceiveId;
|
||||
m_sEncodingAESKey = sEncodingAESKey;
|
||||
}
|
||||
|
||||
//验证URL
|
||||
// @param sMsgSignature: 签名串,对应URL参数的msg_signature
|
||||
// @param sTimeStamp: 时间戳,对应URL参数的timestamp
|
||||
// @param sNonce: 随机串,对应URL参数的nonce
|
||||
// @param sEchoStr: 随机串,对应URL参数的echostr
|
||||
// @param sReplyEchoStr: 解密之后的echostr,当return返回0时有效
|
||||
// @return:成功0,失败返回对应的错误码
|
||||
public int VerifyURL(string sMsgSignature, string sTimeStamp, string sNonce, string sEchoStr, ref string sReplyEchoStr)
|
||||
{
|
||||
int ret = 0;
|
||||
if (m_sEncodingAESKey.Length != 43)
|
||||
{
|
||||
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;
|
||||
}
|
||||
ret = VerifySignature(m_sToken, sTimeStamp, sNonce, sEchoStr, sMsgSignature);
|
||||
if (0 != ret)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
sReplyEchoStr = "";
|
||||
string cpid = "";
|
||||
try
|
||||
{
|
||||
sReplyEchoStr = Cryptography.AES_decrypt(sEchoStr, m_sEncodingAESKey, ref cpid); //m_sReceiveId);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
sReplyEchoStr = "";
|
||||
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecryptAES_Error;
|
||||
}
|
||||
if (cpid != m_sReceiveId)
|
||||
{
|
||||
sReplyEchoStr = "";
|
||||
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateCorpid_Error;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 检验消息的真实性,并且获取解密后的明文
|
||||
// @param sMsgSignature: 签名串,对应URL参数的msg_signature
|
||||
// @param sTimeStamp: 时间戳,对应URL参数的timestamp
|
||||
// @param sNonce: 随机串,对应URL参数的nonce
|
||||
// @param sPostData: 密文,对应POST请求的数据
|
||||
// @param sMsg: 解密后的原文,当return返回0时有效
|
||||
// @return: 成功0,失败返回对应的错误码
|
||||
public int DecryptMsg(string sMsgSignature, string sTimeStamp, string sNonce, string sPostData, ref string sMsg)
|
||||
{
|
||||
if (m_sEncodingAESKey.Length != 43)
|
||||
{
|
||||
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;
|
||||
}
|
||||
XmlDocument doc = new XmlDocument();
|
||||
XmlNode root;
|
||||
string sEncryptMsg;
|
||||
try
|
||||
{
|
||||
doc.LoadXml(sPostData);
|
||||
root = doc.FirstChild;
|
||||
sEncryptMsg = root["Encrypt"].InnerText;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ParseXml_Error;
|
||||
}
|
||||
//verify signature
|
||||
int ret = 0;
|
||||
ret = VerifySignature(m_sToken, sTimeStamp, sNonce, sEncryptMsg, sMsgSignature);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
//decrypt
|
||||
string cpid = "";
|
||||
try
|
||||
{
|
||||
sMsg = Cryptography.AES_decrypt(sEncryptMsg, m_sEncodingAESKey, ref cpid);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
sMsg = "";
|
||||
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecodeBase64_Error;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
sMsg = "";
|
||||
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecryptAES_Error;
|
||||
}
|
||||
if (cpid != m_sReceiveId)
|
||||
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateCorpid_Error;
|
||||
return 0;
|
||||
}
|
||||
|
||||
//将企业号回复用户的消息加密打包
|
||||
// @param sReplyMsg: 企业号待回复用户的消息,xml格式的字符串
|
||||
// @param sTimeStamp: 时间戳,可以自己生成,也可以用URL参数的timestamp
|
||||
// @param sNonce: 随机串,可以自己生成,也可以用URL参数的nonce
|
||||
// @param sEncryptMsg: 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串,
|
||||
// 当return返回0时有效
|
||||
// return:成功0,失败返回对应的错误码
|
||||
public int EncryptMsg(string sReplyMsg, string sTimeStamp, string sNonce, ref string sEncryptMsg)
|
||||
{
|
||||
if (m_sEncodingAESKey.Length != 43)
|
||||
{
|
||||
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;
|
||||
}
|
||||
string raw = "";
|
||||
try
|
||||
{
|
||||
raw = Cryptography.AES_encrypt(sReplyMsg, m_sEncodingAESKey, m_sReceiveId);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_EncryptAES_Error;
|
||||
}
|
||||
string MsgSigature = "";
|
||||
int ret = 0;
|
||||
ret = GenarateSinature(m_sToken, sTimeStamp, sNonce, raw, ref MsgSigature);
|
||||
if (0 != ret)
|
||||
return ret;
|
||||
sEncryptMsg = "";
|
||||
|
||||
string EncryptLabelHead = "<Encrypt><![CDATA[";
|
||||
string EncryptLabelTail = "]]></Encrypt>";
|
||||
string MsgSigLabelHead = "<MsgSignature><![CDATA[";
|
||||
string MsgSigLabelTail = "]]></MsgSignature>";
|
||||
string TimeStampLabelHead = "<TimeStamp><![CDATA[";
|
||||
string TimeStampLabelTail = "]]></TimeStamp>";
|
||||
string NonceLabelHead = "<Nonce><![CDATA[";
|
||||
string NonceLabelTail = "]]></Nonce>";
|
||||
sEncryptMsg = sEncryptMsg + "<xml>" + EncryptLabelHead + raw + EncryptLabelTail;
|
||||
sEncryptMsg = sEncryptMsg + MsgSigLabelHead + MsgSigature + MsgSigLabelTail;
|
||||
sEncryptMsg = sEncryptMsg + TimeStampLabelHead + sTimeStamp + TimeStampLabelTail;
|
||||
sEncryptMsg = sEncryptMsg + NonceLabelHead + sNonce + NonceLabelTail;
|
||||
sEncryptMsg += "</xml>";
|
||||
return 0;
|
||||
}
|
||||
|
||||
public class DictionarySort : System.Collections.IComparer
|
||||
{
|
||||
public int Compare(object oLeft, object oRight)
|
||||
{
|
||||
string sLeft = oLeft as string;
|
||||
string sRight = oRight as string;
|
||||
int iLeftLength = sLeft.Length;
|
||||
int iRightLength = sRight.Length;
|
||||
int index = 0;
|
||||
while (index < iLeftLength && index < iRightLength)
|
||||
{
|
||||
if (sLeft[index] < sRight[index])
|
||||
return -1;
|
||||
else if (sLeft[index] > sRight[index])
|
||||
return 1;
|
||||
else
|
||||
index++;
|
||||
}
|
||||
return iLeftLength - iRightLength;
|
||||
|
||||
}
|
||||
}
|
||||
//Verify Signature
|
||||
private static int VerifySignature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt, string sSigture)
|
||||
{
|
||||
string hash = "";
|
||||
int ret = 0;
|
||||
ret = GenarateSinature(sToken, sTimeStamp, sNonce, sMsgEncrypt, ref hash);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
if (hash == sSigture)
|
||||
return 0;
|
||||
else
|
||||
{
|
||||
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateSignature_Error;
|
||||
}
|
||||
}
|
||||
|
||||
public static int GenarateSinature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt, ref string sMsgSignature)
|
||||
{
|
||||
ArrayList AL = new ArrayList();
|
||||
AL.Add(sToken);
|
||||
AL.Add(sTimeStamp);
|
||||
AL.Add(sNonce);
|
||||
AL.Add(sMsgEncrypt);
|
||||
AL.Sort(new DictionarySort());
|
||||
string raw = "";
|
||||
for (int i = 0; i < AL.Count; ++i)
|
||||
{
|
||||
raw += AL[i];
|
||||
}
|
||||
|
||||
SHA1 sha;
|
||||
ASCIIEncoding enc;
|
||||
string hash = "";
|
||||
try
|
||||
{
|
||||
sha = new SHA1CryptoServiceProvider();
|
||||
enc = new ASCIIEncoding();
|
||||
byte[] dataToHash = enc.GetBytes(raw);
|
||||
byte[] dataHashed = sha.ComputeHash(dataToHash);
|
||||
hash = BitConverter.ToString(dataHashed).Replace("-", "");
|
||||
hash = hash.ToLower();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ComputeSignature_Error;
|
||||
}
|
||||
sMsgSignature = hash;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +1,21 @@
|
||||
using Inotify.Common;
|
||||
using Inotify.Data.Models;
|
||||
using Inotify.Data.Models;
|
||||
using Inotify.Data.Models.System;
|
||||
using Inotify.Sends.Products;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.IdentityModel;
|
||||
using Newtonsoft.Json;
|
||||
using NPoco;
|
||||
using NPoco.Migrations;
|
||||
using NPoco.Migrations.CurrentVersion;
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Net.Mail;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Inotify.Data
|
||||
{
|
||||
public class DBManager
|
||||
{
|
||||
private readonly string MigrationName = "inotify";
|
||||
|
||||
private readonly SqliteConnection m_dbConnection;
|
||||
|
||||
private readonly Migrator m_migrator;
|
||||
@ -32,7 +30,7 @@ namespace Inotify.Data
|
||||
|
||||
public JwtInfo JWT
|
||||
{
|
||||
get { return m_JWT; }
|
||||
get => m_JWT;
|
||||
set
|
||||
{
|
||||
m_JWT = value;
|
||||
@ -40,6 +38,8 @@ namespace Inotify.Data
|
||||
}
|
||||
}
|
||||
|
||||
public readonly string Inotify_Data;
|
||||
|
||||
public Database DBase { get; private set; }
|
||||
|
||||
private static DBManager? m_Instance;
|
||||
@ -48,7 +48,11 @@ namespace Inotify.Data
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Instance == null) m_Instance = new DBManager();
|
||||
if (m_Instance == null)
|
||||
{
|
||||
m_Instance = new DBManager();
|
||||
}
|
||||
|
||||
return m_Instance;
|
||||
}
|
||||
}
|
||||
@ -58,13 +62,24 @@ namespace Inotify.Data
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
Inotify_Data = Path.Combine(Directory.GetCurrentDirectory(), m_dataPath);
|
||||
if (!Directory.Exists(Inotify_Data))
|
||||
{
|
||||
Directory.CreateDirectory(Inotify_Data);
|
||||
}
|
||||
|
||||
m_jwtPath = Path.Combine(Directory.GetCurrentDirectory(), "/" + m_dataPath + "/jwt.json");
|
||||
m_dbPath = Path.Combine(Directory.GetCurrentDirectory(), "/" + m_dataPath + "/data.db");
|
||||
|
||||
|
||||
}
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
var dataPath = Path.Combine(Directory.GetCurrentDirectory(), m_dataPath);
|
||||
if (!Directory.Exists(dataPath)) Directory.CreateDirectory(dataPath);
|
||||
Inotify_Data = Path.Combine(Directory.GetCurrentDirectory(), m_dataPath);
|
||||
if (!Directory.Exists(Inotify_Data))
|
||||
{
|
||||
Directory.CreateDirectory(Inotify_Data);
|
||||
}
|
||||
|
||||
m_jwtPath = Path.Combine(Directory.GetCurrentDirectory(), m_dataPath + "/jwt.json");
|
||||
m_dbPath = Path.Combine(Directory.GetCurrentDirectory(), m_dataPath + "/data.db");
|
||||
@ -95,16 +110,48 @@ namespace Inotify.Data
|
||||
|
||||
|
||||
m_dbConnection = new SqliteConnection(string.Format("Data Source={0}", m_dbPath));
|
||||
if (m_dbConnection.State == ConnectionState.Closed)
|
||||
m_dbConnection.Open();
|
||||
|
||||
DBase = new NPoco.Database(m_dbConnection, DatabaseType.SQLite);
|
||||
if (m_dbConnection.State == ConnectionState.Closed)
|
||||
{
|
||||
m_dbConnection.Open();
|
||||
}
|
||||
|
||||
DBase = new Database(m_dbConnection, DatabaseType.SQLite)
|
||||
{
|
||||
KeepConnectionAlive = true
|
||||
};
|
||||
m_migrator = new Migrator(DBase);
|
||||
}
|
||||
|
||||
public bool IsSendKey(string token)
|
||||
public bool IsToken(string token, out bool hasActive)
|
||||
{
|
||||
return DBase.Query<SendUserInfo>().Any(e => e.Token == token);
|
||||
hasActive = false;
|
||||
var userInfo = DBase.Query<SendUserInfo>().FirstOrDefault(e => e.Token == token);
|
||||
if (userInfo != null)
|
||||
{
|
||||
hasActive = DBase.Query<SendAuthInfo>().Any(e => e.UserId == userInfo.Id && e.Active);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsSendKey(string key, out bool isActive, out string token)
|
||||
{
|
||||
isActive = false;
|
||||
token = null;
|
||||
var sendAuthInfo = DBase.Query<SendAuthInfo>().FirstOrDefault(e => e.Key == key);
|
||||
if (sendAuthInfo != null)
|
||||
{
|
||||
var userInfo = DBase.Query<SendUserInfo>().FirstOrDefault(e => e.Id == sendAuthInfo.UserId);
|
||||
if (userInfo != null&& userInfo.Token!=null)
|
||||
{
|
||||
token = userInfo.Token;
|
||||
}
|
||||
isActive = sendAuthInfo.Active;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsUser(string userName)
|
||||
@ -114,59 +161,66 @@ namespace Inotify.Data
|
||||
|
||||
public SendUserInfo GetUser(string userName)
|
||||
{
|
||||
return DBase.Query<SendUserInfo>().First(e => e.UserName == userName);
|
||||
return DBase.Query<SendUserInfo>().FirstOrDefault(e => e.UserName == userName);
|
||||
}
|
||||
|
||||
public string GetAuth(string token, out string guid)
|
||||
public string GetSendAuthInfo(string key, out string guid)
|
||||
{
|
||||
guid = string.Empty;
|
||||
var upToekn = token.ToUpper();
|
||||
var userInfo = DBManager.Instance.DBase.Query<SendUserInfo>().FirstOrDefault(e => e.Token == upToekn && e.Active);
|
||||
if (userInfo == null) return null;
|
||||
|
||||
var authInfo = DBManager.Instance.DBase.Query<SendAuthInfo>().FirstOrDefault(e => e.Id == userInfo.SendAuthId && e.UserId == userInfo.Id);
|
||||
guid = null;
|
||||
var authInfo = DBManager.Instance.DBase.Query<SendAuthInfo>().FirstOrDefault(e => e.Key== key.ToUpper());
|
||||
if (authInfo == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
guid = authInfo.SendMethodTemplate;
|
||||
return authInfo.AuthData;
|
||||
}
|
||||
|
||||
public void GetSendAuthInfos(string token, string key, out SendAuthInfo[] sendAuthInfos)
|
||||
{
|
||||
sendAuthInfos = null;
|
||||
var upToekn = token.ToUpper();
|
||||
var userInfo = DBManager.Instance.DBase.Query<SendUserInfo>().FirstOrDefault(e => e.Token == upToekn && e.Active);
|
||||
if (userInfo != null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
sendAuthInfos = DBManager.Instance.DBase.Query<SendAuthInfo>().Where(e => e.UserId == userInfo.Id && e.Active).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
sendAuthInfos = DBManager.Instance.DBase.Query<SendAuthInfo>().Where(e => e.UserId == userInfo.Id && e.Active && e.Key == key).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
if (!m_migrator.TableExists<SendInfo>())
|
||||
m_migrator.CreateTable<SendInfo>(true).Execute();
|
||||
|
||||
var codeVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
|
||||
var versionProvider = new DatabaseCurrentVersionProvider(DBase);
|
||||
|
||||
if (!m_migrator.TableExists<SystemInfo>())
|
||||
{
|
||||
m_migrator.CreateTable<SystemInfo>(true).Execute();
|
||||
|
||||
DBase.Insert(new SystemInfo()
|
||||
{
|
||||
key = "administrators",
|
||||
Value = "admin"
|
||||
});
|
||||
var migrationBuilder = new MigrationBuilder(MigrationName, DBase);
|
||||
migrationBuilder.Append(new Version(codeVersion.ToString()), new LatestMigration());
|
||||
migrationBuilder.Execute();
|
||||
versionProvider.SetMigrationVersion(MigrationName, new Version(codeVersion.ToString()));
|
||||
}
|
||||
|
||||
if (!m_migrator.TableExists<SendUserInfo>())
|
||||
else
|
||||
{
|
||||
m_migrator.CreateTable<SendUserInfo>(true).Execute();
|
||||
SendUserInfo userInfo = new SendUserInfo()
|
||||
if (versionProvider.GetMigrationVersion(MigrationName).ToString() == "0.0")
|
||||
{
|
||||
Token = "112D77BAD9704FFEAECD716B5678DFBE".ToUpper(),
|
||||
UserName = "admin",
|
||||
Email = "admin@qq.com",
|
||||
CreateTime = DateTime.Now,
|
||||
Active = true,
|
||||
Password = "123456".ToMd5()
|
||||
};
|
||||
DBase.Insert(userInfo);
|
||||
}
|
||||
versionProvider.SetMigrationVersion(MigrationName, new Version(1, 0, 0, 0));
|
||||
}
|
||||
|
||||
if (!m_migrator.TableExists<SendAuthInfo>())
|
||||
{
|
||||
m_migrator.CreateTable<SendAuthInfo>(true).Execute();
|
||||
var builder = new MigrationBuilder(MigrationName, DBase);
|
||||
builder.Append(new Version(2, 0, 0, 0), new V2UpdateMigration());
|
||||
builder.Append(new Version(2, 0, 0, 1), new V2001UpdateMigration());
|
||||
builder.Append(new Version(2, 0, 0, 4), new V2004UpdateMigration());
|
||||
builder.Execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
136
Inotify/Data/DBMigrations.cs
Normal file
136
Inotify/Data/DBMigrations.cs
Normal file
@ -0,0 +1,136 @@
|
||||
using Inotify.Common;
|
||||
using Inotify.Data.Models;
|
||||
using NPoco.Migrations;
|
||||
using System;
|
||||
|
||||
namespace Inotify.Data
|
||||
{
|
||||
public class EmptyMigration : Migration, IMigration
|
||||
{
|
||||
protected override void execute()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class LatestMigration : Migration, IMigration
|
||||
{
|
||||
protected override void execute()
|
||||
{
|
||||
if (!Migrator.TableExists<SystemInfo>())
|
||||
{
|
||||
Migrator.CreateTable<SystemInfo>(true).Execute();
|
||||
Migrator.Database.Insert(new SystemInfo()
|
||||
{
|
||||
key = "administrators",
|
||||
Value = "admin"
|
||||
});
|
||||
Migrator.Database.Insert(new SystemInfo()
|
||||
{
|
||||
key = "barkKeyId",
|
||||
Value = "TEg0VDlWNVU0Ug==".Base64Decode(),
|
||||
});
|
||||
Migrator.Database.Insert(new SystemInfo()
|
||||
{
|
||||
key = "barkTeamId",
|
||||
Value = "NVU4TEJSWEczQQ==".Base64Decode(),
|
||||
});
|
||||
Migrator.Database.Insert(new SystemInfo()
|
||||
{
|
||||
key = "barkPrivateKey",
|
||||
Value = "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR1RBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJIa3dkd0lCQVFRZzR2dEMzZzVMNUhnS0dKMitUMWVBMHRPaXZSRXZFQVkyZytqdVJYSmtZTDJnQ2dZSUtvWkl6ajBEQVFlaFJBTkNBQVNtT3MzSmtTeW9HRVdac1VHeEZzLzRwdzFySWxTVjJJQzE5TTh1M0c1a3EzNnVwT3d5RldqOUdpM0VqYzlkM3NDNytTSFJxWHJFQUpvdzgvN3RScFYrCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=".Base64Decode()
|
||||
});
|
||||
}
|
||||
|
||||
if (!Migrator.TableExists<SendInfo>())
|
||||
{
|
||||
Migrator.CreateTable<SendInfo>(true).Execute();
|
||||
}
|
||||
|
||||
if (!Migrator.TableExists<SendUserInfo>())
|
||||
{
|
||||
Migrator.CreateTable<SendUserInfo>(true).Execute();
|
||||
SendUserInfo userInfo = new SendUserInfo()
|
||||
{
|
||||
Token = Guid.NewGuid().ToString("N").ToUpper(),
|
||||
UserName = "admin",
|
||||
Email = "admin@qq.com",
|
||||
CreateTime = DateTime.Now,
|
||||
Active = true,
|
||||
Password = "123456".ToMd5()
|
||||
};
|
||||
Migrator.Database.Insert(userInfo);
|
||||
}
|
||||
|
||||
if (!Migrator.TableExists<SendAuthInfo>())
|
||||
{
|
||||
Migrator.CreateTable<SendAuthInfo>(true).Execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class V2UpdateMigration : Migration, IMigration
|
||||
{
|
||||
protected override void execute()
|
||||
{
|
||||
//V2版本允许多通道,激活标记放入SendAuthInfo表中,增加Active列,同时更新原有用户的激活通道
|
||||
Migrator.AlterTable<SendAuthInfo>().AddColumn(e => e.Active).Execute();
|
||||
Migrator.AlterTable<SendAuthInfo>().AddColumn(e => e.Key).Execute();
|
||||
Migrator.Database.UpdateMany<SendAuthInfo>().OnlyFields(e => e.Active).Execute(new SendAuthInfo() { Active = false });
|
||||
var activeUsers = Migrator.Database.Query<SendUserInfo>().ToList();
|
||||
activeUsers.ForEach(user =>
|
||||
{
|
||||
var sendUserInfo = Migrator.Database.Query<SendAuthInfo>().FirstOrDefault(e => e.Id == user.SendAuthId);
|
||||
if (sendUserInfo != null)
|
||||
{
|
||||
sendUserInfo.Active = true;
|
||||
Migrator.Database.Update(sendUserInfo, e => e.Active);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class V2001UpdateMigration : Migration, IMigration
|
||||
{
|
||||
protected override void execute()
|
||||
{
|
||||
|
||||
var sendAuthInfos = Migrator.Database.Query<SendAuthInfo>().ToList();
|
||||
sendAuthInfos.ForEach(sendAuthInfo =>
|
||||
{
|
||||
sendAuthInfo.AuthData = sendAuthInfo.AuthDataSave;
|
||||
Migrator.Database.Update(sendAuthInfo);
|
||||
});
|
||||
|
||||
Migrator.Database.Insert(new SystemInfo()
|
||||
{
|
||||
key = "barkKeyId",
|
||||
Value = "TEg0VDlWNVU0Ug==".Base64Decode(),
|
||||
});
|
||||
Migrator.Database.Insert(new SystemInfo()
|
||||
{
|
||||
key = "barkTeamId",
|
||||
Value = "NVU4TEJSWEczQQ==".Base64Decode(),
|
||||
});
|
||||
Migrator.Database.Insert(new SystemInfo()
|
||||
{
|
||||
key = "barkPrivateKey",
|
||||
Value = "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JR1RBZ0VBTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEJIa3dkd0lCQVFRZzR2dEMzZzVMNUhnS0dKMitUMWVBMHRPaXZSRXZFQVkyZytqdVJYSmtZTDJnQ2dZSUtvWkl6ajBEQVFlaFJBTkNBQVNtT3MzSmtTeW9HRVdac1VHeEZzLzRwdzFySWxTVjJJQzE5TTh1M0c1a3EzNnVwT3d5RldqOUdpM0VqYzlkM3NDNytTSFJxWHJFQUpvdzgvN3RScFYrCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=".Base64Decode()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class V2004UpdateMigration : Migration, IMigration
|
||||
{
|
||||
protected override void execute()
|
||||
{
|
||||
var sendAuthInfos = Migrator.Database.Query<SendAuthInfo>().ToList();
|
||||
sendAuthInfos.ForEach(sendAuthInfo =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(sendAuthInfo.Key))
|
||||
sendAuthInfo.Key = Guid.NewGuid().ToString("N").ToUpper();
|
||||
Migrator.Database.Update(sendAuthInfo);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
using System;
|
||||
|
||||
using Inotify.Common;
|
||||
using System;
|
||||
namespace Inotify.Data.Models
|
||||
{
|
||||
[NPoco.TableName("sendAuthInfo")]
|
||||
@ -19,12 +19,26 @@ namespace Inotify.Data.Models
|
||||
public string SendMethodTemplate { get; set; }
|
||||
|
||||
[NPoco.Column("authData")]
|
||||
public string AuthData { get; set; }
|
||||
public string AuthDataSave { get; set; }
|
||||
|
||||
[NPoco.Ignore]
|
||||
public string AuthData
|
||||
{
|
||||
get => AuthDataSave.Base64Decode();
|
||||
set => AuthDataSave = value.Base64Encode();
|
||||
}
|
||||
|
||||
[NPoco.Column("modifyTime")]
|
||||
public DateTime ModifyTime { get; set; }
|
||||
|
||||
[NPoco.Column("createTime")]
|
||||
public DateTime CreateTime { get; set; }
|
||||
|
||||
[NPoco.Column("active")]
|
||||
public bool Active { get; set; }
|
||||
|
||||
[NPoco.Column("key")]
|
||||
public string Key { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Inotify.Data.Models
|
||||
namespace Inotify.Data.Models
|
||||
{
|
||||
[NPoco.TableName("sendInfo")]
|
||||
[NPoco.PrimaryKey(new string[] { "templateID", "date" }, AutoIncrement = false)]
|
||||
|
@ -1,9 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Inotify.Data.Models
|
||||
namespace Inotify.Data.Models
|
||||
{
|
||||
[NPoco.TableName("userInfo")]
|
||||
[NPoco.PrimaryKey("id")]
|
||||
|
@ -1,9 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Inotify.Data.Models.System
|
||||
namespace Inotify.Data.Models.System
|
||||
{
|
||||
public class JwtInfo
|
||||
{
|
||||
|
@ -1,9 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Inotify.Data.Models
|
||||
namespace Inotify.Data.Models
|
||||
{
|
||||
|
||||
[NPoco.TableName("systemInfo")]
|
||||
|
@ -6,28 +6,35 @@
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<StartupObject>Inotify.Program</StartupObject>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyVersion>2.3.0.0</AssemblyVersion>
|
||||
<FileVersion>2.3.0.0</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<WarningLevel>3</WarningLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\.editorconfig" Link=".editorconfig" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentEmail.Core" Version="3.0.0" />
|
||||
<PackageReference Include="FluentEmail.Liquid" Version="3.0.0" />
|
||||
<PackageReference Include="FluentEmail.Smtp" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.12" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.18" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="5.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.10.9" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="NPoco" Version="5.1.2" />
|
||||
<PackageReference Include="NPoco.Migrations" Version="0.3.2" />
|
||||
<PackageReference Include="Portable.BouncyCastle" Version="1.8.10" />
|
||||
<PackageReference Include="System.Runtime.Caching" Version="5.0.0" />
|
||||
<PackageReference Include="Telegram.Bot" Version="15.7.1" />
|
||||
<PackageReference Include="Telegram.Bot" Version="18.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="inotify_data\AuthKey_LH4T9V5U4R_5U8LBRXG3A.p8">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ProjectExtensions><VisualStudio><UserProperties properties_4launchsettings_1json__JsonSchema="" /></VisualStudio></ProjectExtensions>
|
||||
</Project>
|
||||
|
@ -5,7 +5,7 @@ VisualStudioVersion = 16.0.30804.86
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{80E8FF2E-B685-44E6-82A8-FDCA48EDAD47}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
..\.editorconfig = ..\.editorconfig
|
||||
.editorconfig = .editorconfig
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Inotify", "Inotify.csproj", "{9AF94AE0-E8C6-414C-A0AC-EDFB301CA05E}"
|
||||
|
@ -1,16 +1,7 @@
|
||||
using Inotify.Data;
|
||||
using Inotify.Sends;
|
||||
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Inotify
|
||||
{
|
||||
|
125
Inotify/Sends/Products/BarkSendTemplate.cs
Normal file
125
Inotify/Sends/Products/BarkSendTemplate.cs
Normal file
@ -0,0 +1,125 @@
|
||||
using CorePush.Apple;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Inotify.Sends.Products
|
||||
{
|
||||
|
||||
public class BarkAuth
|
||||
{
|
||||
[InputTypeAttribte(1, "Sound", "声音", "1107")]
|
||||
public string Sound { get; set; }
|
||||
|
||||
[InputTypeAttribte(2, "IsArchive", "自动保存", "1或0")]
|
||||
public string IsArchive { get; set; }
|
||||
|
||||
[InputTypeAttribte(3, "AutoMaticallyCopy", "自动复制", "1或0")]
|
||||
public string AutoMaticallyCopy { get; set; }
|
||||
|
||||
[InputTypeAttribte(4, "DeviceKey", "DeviceKey", "DeviceKey", true, true)]
|
||||
public string DeviceKey { get; set; }
|
||||
|
||||
[InputTypeAttribte(5, "DeviceToken", "DeviceToken", "DeviceToken", true, true)]
|
||||
public string DeviceToken { get; set; }
|
||||
|
||||
[InputTypeAttribte(6, "SendUrl", "SendUrl", "SendUrl", true, true)]
|
||||
public string SendUrl { get; set; }
|
||||
|
||||
}
|
||||
|
||||
[SendMethodKey("3B6DE04D-A9EF-4C91-A151-60B7425C5AB2", "Bark", Order = 2999, Waring = "BARK通道勿手动添加,请使用APP添加BARK地址绑定")]
|
||||
public class BarkSendTemplate : SendTemplate<BarkAuth>
|
||||
{
|
||||
private static ApnSender apnSender;
|
||||
|
||||
public override bool SendMessage(SendMessage message)
|
||||
{
|
||||
if (apnSender == null)
|
||||
{
|
||||
var keyID = SendCacheStore.GetSystemValue("barkKeyId");
|
||||
var teamID = SendCacheStore.GetSystemValue("barkTeamId");
|
||||
var privateKey = SendCacheStore.GetSystemValue("barkPrivateKey");
|
||||
var privateKeyContent = privateKey.Split('\n')[1];
|
||||
var apnSettings = new ApnSettings()
|
||||
{
|
||||
|
||||
TeamId = teamID,
|
||||
AppBundleIdentifier = "me.fin.bark",
|
||||
P8PrivateKey = privateKeyContent,
|
||||
ServerType = ApnServerType.Production,
|
||||
P8PrivateKeyId = keyID,
|
||||
};
|
||||
|
||||
apnSender = new ApnSender(apnSettings, new HttpClient());
|
||||
}
|
||||
|
||||
var payload = new AppleNotification(
|
||||
Guid.NewGuid(),
|
||||
message.Data,
|
||||
message.Title)
|
||||
{
|
||||
IsArchive = Auth.IsArchive,
|
||||
AutoMaticallyCopy=Auth.AutoMaticallyCopy,
|
||||
};
|
||||
payload.Aps.Sound = Auth.Sound;
|
||||
|
||||
var response = apnSender.Send(payload, Auth.DeviceToken);
|
||||
|
||||
if (response.IsSuccess)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class AppleNotification
|
||||
{
|
||||
public class ApsPayload
|
||||
{
|
||||
public class Alert
|
||||
{
|
||||
[JsonProperty("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[JsonProperty("body")]
|
||||
public string Body { get; set; }
|
||||
|
||||
}
|
||||
|
||||
[JsonProperty("sound")]
|
||||
public string Sound { get; set; }
|
||||
|
||||
[JsonProperty("alert")]
|
||||
public Alert AlertBody { get; set; }
|
||||
|
||||
[JsonProperty("apns-push-type")]
|
||||
public string PushType { get; set; } = "alert";
|
||||
}
|
||||
|
||||
public AppleNotification(Guid id, string message, string title = "")
|
||||
{
|
||||
Id = id;
|
||||
|
||||
Aps = new ApsPayload
|
||||
{
|
||||
AlertBody = new ApsPayload.Alert
|
||||
{
|
||||
Title = title,
|
||||
Body = message
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[JsonProperty("aps")]
|
||||
public ApsPayload Aps { get; set; }
|
||||
|
||||
[JsonProperty("id")]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
[JsonProperty("isarchive")]
|
||||
public string IsArchive { get; set; }
|
||||
|
||||
[JsonProperty("automaticallycopy")]
|
||||
public string AutoMaticallyCopy { get; set; }
|
||||
}
|
||||
}
|
88
Inotify/Sends/Products/DingtalkSendTemplate.cs
Normal file
88
Inotify/Sends/Products/DingtalkSendTemplate.cs
Normal file
@ -0,0 +1,88 @@
|
||||
using Inotify.Common;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
|
||||
namespace Inotify.Sends.Products
|
||||
{
|
||||
|
||||
public class DingtalkAuth
|
||||
{
|
||||
[InputTypeAttribte(0, "WebHook", "WebHook", "https://oapi.dingtalk.com/robot/send?access_token=xxxxx")]
|
||||
public string WebHook { get; set; }
|
||||
|
||||
|
||||
[InputTypeAttribte(0, "Secret", "签名校验", "SEC77xxxx")]
|
||||
public string Secret { get; set; }
|
||||
}
|
||||
|
||||
|
||||
[SendMethodKey("048297D4-D975-48F6-9A91-8B4EF75805C1", "钉钉群机器人", Order = 21)]
|
||||
public class DingtalkSendTemplate : SendTemplate<DingtalkAuth>
|
||||
{
|
||||
public override bool SendMessage(SendMessage message)
|
||||
{
|
||||
var bodyObject = new
|
||||
{
|
||||
markdown = new
|
||||
{
|
||||
title = $"{message.Title}",
|
||||
text = $"#### {message.Title}\n{message.Data}",
|
||||
},
|
||||
msgtype = "markdown",
|
||||
|
||||
};
|
||||
|
||||
var timestamp = DateTime.UtcNow.ToUTC();
|
||||
var sign = GetHmac(timestamp, Auth.Secret);
|
||||
var url = $"{Auth.WebHook}×tamp={timestamp}&sign={sign}";
|
||||
var webRequest = WebRequest.Create(url);
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(bodyObject));
|
||||
|
||||
webRequest.Method = "POST";
|
||||
webRequest.ContentType = "application/json;charset=utf-8";
|
||||
webRequest.ContentLength = 0;
|
||||
|
||||
using (var postStream = webRequest.GetRequestStream())
|
||||
{
|
||||
var requestStream = webRequest.GetRequestStream();
|
||||
webRequest.ContentLength = bytes.Length;
|
||||
requestStream.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = webRequest.GetResponse();
|
||||
using (Stream stream = response.GetResponseStream())
|
||||
{
|
||||
using StreamReader reader = new StreamReader(stream, Encoding.UTF8);
|
||||
var resuleJson = reader.ReadToEnd();
|
||||
if (resuleJson.Contains("errcode"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private string GetHmac(long timestamp, string secret)
|
||||
{
|
||||
var stringToSign = $"{timestamp}\n{secret}";
|
||||
using var hmacsha256 = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
|
||||
byte[] hashmessage = hmacsha256.ComputeHash(Encoding.UTF8.GetBytes(stringToSign));
|
||||
return HttpUtility.UrlEncode(Convert.ToBase64String(hashmessage), Encoding.UTF8);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,7 @@
|
||||
using FluentEmail.Core;
|
||||
using FluentEmail.Liquid;
|
||||
using FluentEmail.Smtp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Mail;
|
||||
using System.Text;
|
||||
|
||||
namespace Inotify.Sends.Products
|
||||
{
|
||||
@ -38,8 +34,6 @@ namespace Inotify.Sends.Products
|
||||
[SendMethodKey("EA2B43F7-956C-4C01-B583-0C943ABB36C3", "邮件推送", Order = 1)]
|
||||
public class EmailSendTemplate : SendTemplate<EmailAuth>
|
||||
{
|
||||
public override EmailAuth Auth { get; set; }
|
||||
|
||||
public override bool SendMessage(SendMessage message)
|
||||
{
|
||||
var smtpSender = new SmtpSender(new SmtpClient()
|
||||
@ -52,8 +46,7 @@ namespace Inotify.Sends.Products
|
||||
Credentials = new NetworkCredential(Auth.From, Auth.Password),
|
||||
});
|
||||
|
||||
var email =
|
||||
Email.From(Auth.From, Auth.FromName)
|
||||
var email = Email.From(Auth.From, Auth.FromName)
|
||||
.Subject(message.Title)
|
||||
.Body(message.Data ?? "")
|
||||
.To(Auth.To);
|
||||
|
96
Inotify/Sends/Products/FeishuSendTemplate.cs
Normal file
96
Inotify/Sends/Products/FeishuSendTemplate.cs
Normal file
@ -0,0 +1,96 @@
|
||||
using Inotify.Common;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Inotify.Sends.Products
|
||||
{
|
||||
public class FeishuAuth
|
||||
{
|
||||
[InputTypeAttribte(0, "WebHook", "WebHook", "https://open.feishu.cn/open-apis/bot/v2/hook/5d7b917e-bfb8-4c7e-ba8c-337xxxx")]
|
||||
public string WebHook { get; set; }
|
||||
|
||||
|
||||
[InputTypeAttribte(0, "Secret", "签名校验", "VcgAbeuZOhTZPSP0zxxxx")]
|
||||
public string Secret { get; set; }
|
||||
}
|
||||
|
||||
|
||||
[SendMethodKey("C01A08B4-3A71-452B-9D4B-D8EC7EF1D68F", "飞书群机器人", Order = 22)]
|
||||
public class FeishuASendTemplate : SendTemplate<FeishuAuth>
|
||||
{
|
||||
public override bool SendMessage(SendMessage message)
|
||||
{
|
||||
var timestamp = DateTime.Now.ToUnix() - 10;
|
||||
var sign = GetHmac(timestamp, Auth.Secret);
|
||||
|
||||
var bodyObject = new
|
||||
{
|
||||
content = new
|
||||
{
|
||||
text = $"{message.Title}\n{message.Data}",
|
||||
},
|
||||
msg_type = "text",
|
||||
sign = sign,
|
||||
timestamp = timestamp,
|
||||
};
|
||||
Console.WriteLine(bodyObject);
|
||||
|
||||
var webRequest = WebRequest.Create(Auth.WebHook);
|
||||
var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(bodyObject));
|
||||
|
||||
webRequest.Method = "POST";
|
||||
webRequest.ContentType = "application/json";
|
||||
webRequest.ContentLength = 0;
|
||||
|
||||
using (var postStream = webRequest.GetRequestStream())
|
||||
{
|
||||
var requestStream = webRequest.GetRequestStream();
|
||||
webRequest.ContentLength = bytes.Length;
|
||||
requestStream.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var response = webRequest.GetResponse();
|
||||
using (Stream stream = response.GetResponseStream())
|
||||
{
|
||||
using StreamReader reader = new StreamReader(stream, Encoding.UTF8);
|
||||
var resuleJson = reader.ReadToEnd();
|
||||
if (resuleJson.Contains("code"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetHmac(long timestamp, string secret)
|
||||
{
|
||||
var stringToSign = $"{timestamp}\n{secret}";
|
||||
using var hmacsha256 = new HMACSHA256Final(Encoding.UTF8.GetBytes(stringToSign));
|
||||
return Convert.ToBase64String(hmacsha256.GetHashFinal());
|
||||
}
|
||||
}
|
||||
|
||||
public class HMACSHA256Final : HMACSHA256
|
||||
{
|
||||
public HMACSHA256Final(byte[] bytes) : base(bytes)
|
||||
{
|
||||
|
||||
}
|
||||
public byte[] GetHashFinal()
|
||||
{
|
||||
|
||||
return base.HashFinal();
|
||||
}
|
||||
}
|
||||
}
|
37
Inotify/Sends/Products/HttpGetTemplate.cs
Normal file
37
Inotify/Sends/Products/HttpGetTemplate.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using System.Net;
|
||||
|
||||
namespace Inotify.Sends.Products
|
||||
{
|
||||
public class HttpGetAuth
|
||||
{
|
||||
[InputTypeAttribte(0, "URL", "请求地址", "https://api.day.app/token/{title}/{data}")]
|
||||
public string URL { get; set; }
|
||||
}
|
||||
|
||||
|
||||
[SendMethodKey("ADB11045-F2C8-457E-BF7E-1698AD37ED53", "自定义GET", Order = 4)]
|
||||
public class HttpGetTemplate : SendTemplate<HttpGetAuth>
|
||||
{
|
||||
|
||||
public override bool SendMessage(SendMessage message)
|
||||
{
|
||||
var url = Auth.URL.Replace("{title}", message.Title).Replace("{data}", message.Data);
|
||||
var webRequest = WebRequest.Create(url);
|
||||
|
||||
if (webRequest != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
webRequest.GetResponse().GetResponseStream();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
74
Inotify/Sends/Products/HttpPostTemplate.cs
Normal file
74
Inotify/Sends/Products/HttpPostTemplate.cs
Normal file
@ -0,0 +1,74 @@
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace Inotify.Sends.Products
|
||||
{
|
||||
public class HttpPostAuth
|
||||
{
|
||||
[InputTypeAttribte(0, "URL", "请求地址", "https://api.day.app/token/{title}/{data}")]
|
||||
public string URL { get; set; }
|
||||
|
||||
[InputTypeAttribte(1, "Encoding", "Encoding", "utf-8")]
|
||||
public string Encoding { get; set; }
|
||||
|
||||
|
||||
[InputTypeAttribte(1, "ContentType", "ContentType", "application/json")]
|
||||
public string ContentType { get; set; }
|
||||
|
||||
[InputTypeAttribte(2, "Data", "POST参数", @"{""msgid"":""123456"",""title"":""{title}"",""data"":""{data}""}")]
|
||||
public string Data { get; set; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
[SendMethodKey("A3C1E614-717E-4CF1-BA9B-7242717FC037", "自定义POST", Order = 5)]
|
||||
public class HttpPostTemplate : SendTemplate<HttpPostAuth>
|
||||
{
|
||||
|
||||
public override bool SendMessage(SendMessage message)
|
||||
{
|
||||
if (Auth.Data == null)
|
||||
{
|
||||
Auth.Data = "";
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(Auth.ContentType))
|
||||
{
|
||||
Auth.ContentType = "application/json";
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(Auth.Encoding))
|
||||
{
|
||||
Auth.Encoding = "utf-8";
|
||||
}
|
||||
|
||||
var url = Auth.URL.Replace("{title}", message.Title).Replace("{data}", message.Data);
|
||||
var webRequest = WebRequest.Create(url);
|
||||
|
||||
if (webRequest != null)
|
||||
{
|
||||
var data = Auth.Data.Replace("{title}", message.Title).Replace("{data}", message.Data);
|
||||
var bytes = Encoding.GetEncoding(Auth.Encoding).GetBytes(data);
|
||||
webRequest.Method = "POST";
|
||||
webRequest.ContentType = Auth.ContentType;
|
||||
webRequest.ContentLength = bytes.Length;
|
||||
var requestStream = webRequest.GetRequestStream();
|
||||
requestStream.Write(bytes, 0, bytes.Length);
|
||||
requestStream.Close();
|
||||
|
||||
try
|
||||
{
|
||||
webRequest.GetResponse().GetResponseStream();
|
||||
return true;
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types.InputFiles;
|
||||
|
||||
@ -26,15 +22,20 @@ namespace Inotify.Sends.Products
|
||||
public class TelegramBotSendTemplate : SendTemplate<TelegramBotAuth>
|
||||
{
|
||||
|
||||
public override TelegramBotAuth Auth { get; set; }
|
||||
|
||||
public override bool SendMessage(SendMessage message)
|
||||
{
|
||||
|
||||
var proxy = GetProxy();
|
||||
var client = proxy == null ? new TelegramBotClient(Auth.BotToken) : new TelegramBotClient(Auth.BotToken, proxy);
|
||||
var proxyHttpClientHandler = new HttpClientHandler
|
||||
{
|
||||
Proxy = proxy,
|
||||
UseProxy = true,
|
||||
};
|
||||
var httpClient = new HttpClient(proxyHttpClientHandler);
|
||||
var client = proxy == null ? new TelegramBotClient(Auth.BotToken) : new TelegramBotClient(Auth.BotToken, httpClient);
|
||||
var content = string.IsNullOrEmpty(message.Title) ? message.Title : message.Title + "\n" + message.Data;
|
||||
var isIMG = !String.IsNullOrEmpty(message.Title) && IsUrl(message.Title) && IsImage(message.Title) && String.IsNullOrEmpty(message.Data);
|
||||
var isIMG = !string.IsNullOrEmpty(message.Title) && IsUrl(message.Title) && IsImage(message.Title) && string.IsNullOrEmpty(message.Data);
|
||||
if (isIMG)
|
||||
{
|
||||
client.SendPhotoAsync(Auth.Chat_id, new InputOnlineFile(new Uri(message.Title)));
|
||||
|
@ -1,9 +1,6 @@
|
||||
using Inotify.Common;
|
||||
using Inotify.Sends;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
@ -30,64 +27,34 @@ namespace Inotify.Sends.Products
|
||||
[SendMethodKey("409A30D5-ABE8-4A28-BADD-D04B9908D763", "企业微信", Order = 0)]
|
||||
public class WeixiSendTemplate : SendTemplate<WeixiAuth>
|
||||
{
|
||||
public override WeixiAuth Auth { get; set; }
|
||||
|
||||
public override bool SendMessage(SendMessage message)
|
||||
{
|
||||
if (Auth == null) return false;
|
||||
|
||||
var token = GetAccessToken();
|
||||
if (token == null) return false;
|
||||
|
||||
return PostMail(token, message.Title, message.Data);
|
||||
}
|
||||
|
||||
/// 获取AccessToken
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private string GetAccessToken()
|
||||
{
|
||||
var key = Auth.Corpid + Auth.AgentID + Auth.Corpsecret;
|
||||
var toekn = SendCacheStore.Get(key);
|
||||
if (toekn == null)
|
||||
if (Auth == null)
|
||||
{
|
||||
var url = string.Format(@"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={0}&corpsecret={1}", Auth.Corpid, Auth.Corpsecret);
|
||||
|
||||
WebRequest request = WebRequest.Create(url);
|
||||
request.Credentials = CredentialCache.DefaultCredentials;
|
||||
using WebResponse response = request.GetResponse();
|
||||
using Stream streamResponse = response.GetResponseStream();
|
||||
StreamReader reader = new StreamReader(streamResponse);
|
||||
string responseFromServer = reader.ReadToEnd();
|
||||
if (!string.IsNullOrEmpty(responseFromServer))
|
||||
{
|
||||
if (JsonConvert.DeserializeObject(responseFromServer) is JObject res)
|
||||
{
|
||||
if (res.TryGetValue("access_token", out JToken? jtoken))
|
||||
{
|
||||
toekn = jtoken.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
reader.Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (toekn != null)
|
||||
SendCacheStore.Set(key, toekn, DateTimeOffset.Now.AddHours(2));
|
||||
var token = CreateAcessToken();
|
||||
if (token == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return toekn;
|
||||
return CreatePush(token, message.Title, message.Data);
|
||||
}
|
||||
|
||||
private bool PostMail(string accessToken, string title, string? data)
|
||||
private bool CreatePush(string accessToken, string title, string? data)
|
||||
{
|
||||
var uri = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" + accessToken;
|
||||
|
||||
var content = string.IsNullOrEmpty(data) ? title : title + "\n" + data;
|
||||
var isImage = !String.IsNullOrEmpty(title) && IsUrl(title) && IsImage(title) && String.IsNullOrEmpty(data);
|
||||
var isImage = !string.IsNullOrEmpty(title) && IsUrl(title) && IsImage(title) && string.IsNullOrEmpty(data);
|
||||
var imageData = isImage ? GetImage(title) : null;
|
||||
string mediaId = null;
|
||||
string mediaId = string.Empty;
|
||||
if (imageData != null)
|
||||
mediaId = UpLoadIMage(accessToken, imageData);
|
||||
{
|
||||
mediaId = CreateImage(accessToken, imageData);
|
||||
}
|
||||
|
||||
//创建请求
|
||||
WebRequest myWebRequest = WebRequest.Create(uri);
|
||||
@ -148,7 +115,42 @@ namespace Inotify.Sends.Products
|
||||
return true;
|
||||
}
|
||||
|
||||
private string UpLoadIMage(string accessToken, byte[] bytes)
|
||||
private string CreateAcessToken()
|
||||
{
|
||||
var key = Auth.Corpid + Auth.AgentID + Auth.Corpsecret;
|
||||
var toekn = SendCacheStore.Get(key);
|
||||
if (toekn == null)
|
||||
{
|
||||
var url = string.Format(@"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={0}&corpsecret={1}", Auth.Corpid, Auth.Corpsecret);
|
||||
|
||||
WebRequest request = WebRequest.Create(url);
|
||||
request.Credentials = CredentialCache.DefaultCredentials;
|
||||
using WebResponse response = request.GetResponse();
|
||||
using Stream streamResponse = response.GetResponseStream();
|
||||
StreamReader reader = new StreamReader(streamResponse);
|
||||
string responseFromServer = reader.ReadToEnd();
|
||||
if (!string.IsNullOrEmpty(responseFromServer))
|
||||
{
|
||||
if (JsonConvert.DeserializeObject(responseFromServer) is JObject res)
|
||||
{
|
||||
if (res.TryGetValue("access_token", out JToken? jtoken))
|
||||
{
|
||||
toekn = jtoken.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
reader.Close();
|
||||
}
|
||||
|
||||
if (toekn != null)
|
||||
{
|
||||
SendCacheStore.Set(key, toekn, DateTimeOffset.Now.AddHours(2));
|
||||
}
|
||||
|
||||
return toekn;
|
||||
}
|
||||
|
||||
private string CreateImage(string accessToken, byte[] bytes)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -171,17 +173,21 @@ namespace Inotify.Sends.Products
|
||||
postStream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length);
|
||||
postStream.Close();
|
||||
|
||||
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
|
||||
Stream instream = response.GetResponseStream();
|
||||
StreamReader sr = new StreamReader(instream, Encoding.UTF8);
|
||||
string content = sr.ReadToEnd();
|
||||
var result = JObject.Parse(content);
|
||||
return result.Value<string>("media_id").ToString();
|
||||
if (request.GetResponse() is HttpWebResponse response)
|
||||
{
|
||||
Stream instream = response.GetResponseStream();
|
||||
StreamReader sr = new StreamReader(instream, Encoding.UTF8);
|
||||
string content = sr.ReadToEnd();
|
||||
var result = JObject.Parse(content);
|
||||
return result.Value<string>("media_id").ToString();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,9 @@
|
||||
using Inotify;
|
||||
using Inotify.Data;
|
||||
using Inotify.Data;
|
||||
using Inotify.Data.Models;
|
||||
using Inotify.Sends;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Caching;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
|
||||
namespace Inotify.Sends
|
||||
{
|
||||
@ -27,7 +23,10 @@ namespace Inotify.Sends
|
||||
{
|
||||
object obj = m_cache.Get(key);
|
||||
if (obj != null && obj is string)
|
||||
{
|
||||
return obj as string;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
@ -40,7 +39,10 @@ namespace Inotify.Sends
|
||||
public static string GetSystemValue(string key)
|
||||
{
|
||||
if (m_systemInfos.ContainsKey(key))
|
||||
{
|
||||
return m_systemInfos[key];
|
||||
}
|
||||
|
||||
return "";
|
||||
|
||||
}
|
||||
|
@ -1,13 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Inotify.Sends
|
||||
namespace Inotify.Sends
|
||||
{
|
||||
public class SendMessage
|
||||
{
|
||||
public SendMessage()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public SendMessage(SendMessage sendMessage)
|
||||
{
|
||||
Token = sendMessage.Token;
|
||||
Title = sendMessage.Title;
|
||||
Data = sendMessage.Data;
|
||||
Key = sendMessage.Key;
|
||||
|
||||
}
|
||||
public string Token;
|
||||
public string Title;
|
||||
public string? Data;
|
||||
public string? Key;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,30 +1,27 @@
|
||||
using Inotify;
|
||||
using Inotify.Data;
|
||||
using Inotify.Data;
|
||||
using Inotify.Data.Models;
|
||||
using Inotify.Sends;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
|
||||
namespace Inotify.Sends
|
||||
{
|
||||
|
||||
public class SendTaskManager
|
||||
{
|
||||
private static SendTaskManager m_Instance;
|
||||
|
||||
public static SendTaskManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Instance == null) m_Instance = new SendTaskManager();
|
||||
if (m_Instance == null)
|
||||
{
|
||||
m_Instance = new SendTaskManager();
|
||||
}
|
||||
|
||||
return m_Instance;
|
||||
}
|
||||
}
|
||||
@ -39,7 +36,6 @@ namespace Inotify.Sends
|
||||
|
||||
private readonly Dictionary<string, Type> m_sendMethodTemplateTypes;
|
||||
|
||||
|
||||
private SendTaskManager()
|
||||
{
|
||||
m_sendMessages = new BlockingCollection<SendMessage>();
|
||||
@ -50,7 +46,7 @@ namespace Inotify.Sends
|
||||
var sendMethodTemplates = Assembly.GetExecutingAssembly()
|
||||
.GetTypes()
|
||||
.Where(e => e.GetCustomAttribute<SendMethodKeyAttribute>() != null)
|
||||
.OrderBy(e => e.GetCustomAttribute<SendMethodKeyAttribute>().Order)
|
||||
.OrderBy(e => e.GetCustomAttribute<SendMethodKeyAttribute>().Order.ToString())
|
||||
.ToList();
|
||||
|
||||
sendMethodTemplates.ForEach(sendMethodTemplate =>
|
||||
@ -72,7 +68,6 @@ namespace Inotify.Sends
|
||||
m_analyseThread.Start();
|
||||
}
|
||||
|
||||
|
||||
public EventHandler<SendMessage> OnMessageAdd;
|
||||
|
||||
public EventHandler<SendMessage> OnSendSucessed;
|
||||
@ -109,7 +104,9 @@ namespace Inotify.Sends
|
||||
public bool SendMessage(SendMessage message)
|
||||
{
|
||||
if (m_sendMessages.Count > 10000)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_sendMessages.Add(message);
|
||||
|
||||
@ -127,8 +124,10 @@ namespace Inotify.Sends
|
||||
var getTemeplateMethod = sendMethodTemplateType.GetMethod("GetTemeplate");
|
||||
if (getTemeplateMethod != null)
|
||||
{
|
||||
if (getTemeplateMethod.Invoke(obj, null) is InputTemeplate temeplate && temeplate.Key != null)
|
||||
sendTemplates.Add(temeplate.Key, temeplate);
|
||||
if (getTemeplateMethod.Invoke(obj, null) is InputTemeplate temeplate && temeplate.Type != null)
|
||||
{
|
||||
sendTemplates.Add(temeplate.Type, temeplate);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sendTemplates;
|
||||
@ -154,42 +153,50 @@ namespace Inotify.Sends
|
||||
try
|
||||
{
|
||||
var message = m_sendMessages.Take();
|
||||
var authData = DBManager.Instance.GetAuth(message.Token, out string temeplateId);
|
||||
if (temeplateId != null && authData != null)
|
||||
DBManager.Instance.GetSendAuthInfos(message.Token, message.Key, out SendAuthInfo[] sendAuthInfos);
|
||||
foreach (var authInfo in sendAuthInfos)
|
||||
{
|
||||
if (m_sendMethodTemplateTypes.ContainsKey(temeplateId))
|
||||
var authData = authInfo.AuthData;
|
||||
var temeplateId = authInfo.SendMethodTemplate;
|
||||
if (temeplateId != null && authData != null)
|
||||
{
|
||||
var sendMethodTemplateActor = Activator.CreateInstance(m_sendMethodTemplateTypes[temeplateId]);
|
||||
if (sendMethodTemplateActor != null)
|
||||
if (m_sendMethodTemplateTypes.ContainsKey(temeplateId))
|
||||
{
|
||||
var sendMethodType = sendMethodTemplateActor.GetType();
|
||||
var compositonMethod = sendMethodType.GetMethod("Composition");
|
||||
if (compositonMethod != null)
|
||||
var sendMethodTemplateActor = Activator.CreateInstance(m_sendMethodTemplateTypes[temeplateId]);
|
||||
if (sendMethodTemplateActor != null)
|
||||
{
|
||||
compositonMethod.Invoke(sendMethodTemplateActor, new object[] { authData });
|
||||
}
|
||||
|
||||
var sendMessageMethod = sendMethodType.GetMethod("SendMessage");
|
||||
if (sendMessageMethod != null)
|
||||
{
|
||||
var result = sendMessageMethod.Invoke(sendMethodTemplateActor, new object[] { message });
|
||||
if (result != null)
|
||||
var sendMethodType = sendMethodTemplateActor.GetType();
|
||||
var compositonMethod = sendMethodType.GetMethod("Composition");
|
||||
if (compositonMethod != null)
|
||||
{
|
||||
m_analyseMessages.Add(message);
|
||||
if ((bool)result)
|
||||
{
|
||||
OnSendSucessed?.Invoke(this, message);
|
||||
continue;
|
||||
}
|
||||
compositonMethod.Invoke(sendMethodTemplateActor, new object[] { authData });
|
||||
}
|
||||
|
||||
var sendMessageMethod = sendMethodType.GetMethod("SendMessage");
|
||||
if (sendMessageMethod != null)
|
||||
{
|
||||
var result = sendMessageMethod.Invoke(sendMethodTemplateActor, new object[] { message });
|
||||
if (result != null)
|
||||
{
|
||||
var logMessage = new SendMessage(message);
|
||||
logMessage.Key = authInfo.Key;
|
||||
m_analyseMessages.Add(logMessage);
|
||||
if ((bool)result)
|
||||
{
|
||||
OnSendSucessed?.Invoke(this, logMessage);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
OnSendFailed?.Invoke(this, logMessage);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OnSendFailed?.Invoke(this, message);
|
||||
|
||||
}
|
||||
catch (ThreadInterruptedException)
|
||||
{
|
||||
@ -208,7 +215,7 @@ namespace Inotify.Sends
|
||||
{
|
||||
var message = m_analyseMessages.Take();
|
||||
var date = DateTime.Now.ToString("yyyyMMdd");
|
||||
var authData = DBManager.Instance.GetAuth(message.Token, out string temeplateId);
|
||||
var authData = DBManager.Instance.GetSendAuthInfo(message.Key, out string temeplateId);
|
||||
|
||||
if (temeplateId != null)
|
||||
{
|
||||
|
@ -1,14 +1,10 @@
|
||||
using Inotify.Sends;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Mail;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Inotify.Sends
|
||||
@ -31,30 +27,47 @@ namespace Inotify.Sends
|
||||
|
||||
public int Order;
|
||||
|
||||
public SendMethodKeyAttribute(string key, string name, bool open = true)
|
||||
public string Waring;
|
||||
|
||||
public SendMethodKeyAttribute(string key, string name, bool open = true, string waring = "")
|
||||
{
|
||||
Key = key;
|
||||
Name = name;
|
||||
Open = open;
|
||||
Waring = waring;
|
||||
}
|
||||
}
|
||||
|
||||
public class InputTypeValue
|
||||
{
|
||||
|
||||
public InputTypeValue()
|
||||
{
|
||||
Show = true;
|
||||
Readonly = false;
|
||||
}
|
||||
public string? Name { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public string? Default { get; set; }
|
||||
public InputType? Type { get; set; }
|
||||
public int? Order { get; set; }
|
||||
public string? Value { get; set; }
|
||||
|
||||
public string? Warning { get; set; }
|
||||
|
||||
public bool Show { get; set; }
|
||||
|
||||
public bool Readonly { get; set; }
|
||||
}
|
||||
|
||||
public class InputTemeplate
|
||||
{
|
||||
public string? Key { get; set; }
|
||||
public string? Type { get; set; }
|
||||
public string? TypeName { get; set; }
|
||||
public string? Key { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public string Warning { get; set; }
|
||||
public string? AuthData { get; set; }
|
||||
public int? SendAuthId { get; set; }
|
||||
public List<InputTypeValue>? Values { get; set; }
|
||||
@ -70,8 +83,13 @@ namespace Inotify.Sends
|
||||
if (item.Name != null && item.Value != null)
|
||||
{
|
||||
if (item.Type == InputType.CHECK)
|
||||
{
|
||||
jObject.Add(item.Name, item.Value.ToLower() == "true");
|
||||
else jObject.Add(item.Name, item.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
jObject.Add(item.Name, item.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -109,7 +127,8 @@ namespace Inotify.Sends
|
||||
{
|
||||
public InputTypeValue InputTypeData { get; set; }
|
||||
|
||||
private InputTypeAttribte(int order, string name, string description, string defaultValue, InputType type)
|
||||
|
||||
private InputTypeAttribte(int order, string name, string description, string defaultValue, InputType type, bool show = true, bool readOnly = false)
|
||||
{
|
||||
InputTypeData = new InputTypeValue
|
||||
{
|
||||
@ -117,18 +136,20 @@ namespace Inotify.Sends
|
||||
Description = description,
|
||||
Default = defaultValue,
|
||||
Order = order,
|
||||
Type = type
|
||||
Type = type,
|
||||
Show = show,
|
||||
Readonly = readOnly
|
||||
};
|
||||
}
|
||||
|
||||
public InputTypeAttribte(int order, string name, string description, string defaultValue)
|
||||
: this(order, name, description, defaultValue, InputType.TEXT)
|
||||
public InputTypeAttribte(int order, string name, string description, string defaultValue, bool show = true, bool readOnly = false)
|
||||
: this(order, name, description, defaultValue, InputType.TEXT, show, readOnly)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public InputTypeAttribte(int order, string name, string description, bool defaultValue)
|
||||
: this(order, name, description, "", InputType.CHECK)
|
||||
public InputTypeAttribte(int order, string name, string description, bool defaultValue, bool show = true, bool readOnly = false)
|
||||
: this(order, name, description, "", InputType.CHECK, show, readOnly)
|
||||
{
|
||||
InputTypeData.Default = defaultValue ? "是" : "否";
|
||||
}
|
||||
@ -137,8 +158,7 @@ namespace Inotify.Sends
|
||||
|
||||
public abstract class SendTemplate<T>
|
||||
{
|
||||
|
||||
public abstract T Auth { get; set; }
|
||||
public T Auth { get; set; }
|
||||
|
||||
public void Composition(string authInfo)
|
||||
{
|
||||
@ -154,15 +174,17 @@ namespace Inotify.Sends
|
||||
.Select(e => e.InputTypeData)
|
||||
.ToList();
|
||||
|
||||
var sendMethodKeyAttribute = this.GetType().GetCustomAttribute<SendMethodKeyAttribute>();
|
||||
var sendMethodKeyAttribute = GetType().GetCustomAttribute<SendMethodKeyAttribute>();
|
||||
|
||||
if (sendMethodKeyAttribute != null)
|
||||
{
|
||||
return new InputTemeplate()
|
||||
{
|
||||
Key = "",
|
||||
Name = sendMethodKeyAttribute.Name,
|
||||
Type = sendMethodKeyAttribute.Name,
|
||||
Key = sendMethodKeyAttribute.Key,
|
||||
Type = sendMethodKeyAttribute.Key,
|
||||
TypeName = sendMethodKeyAttribute.Name,
|
||||
Warning = sendMethodKeyAttribute.Waring,
|
||||
Values = values
|
||||
};
|
||||
}
|
||||
@ -170,10 +192,8 @@ namespace Inotify.Sends
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual bool SendMessage(SendMessage message)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
public abstract bool SendMessage(SendMessage message);
|
||||
|
||||
|
||||
protected WebProxy GetProxy()
|
||||
{
|
||||
@ -183,7 +203,7 @@ namespace Inotify.Sends
|
||||
if (proxyurl != null)
|
||||
{
|
||||
|
||||
WebProxy proxy = new WebProxy
|
||||
WebProxy proxy = new WebProxy()
|
||||
{
|
||||
Address = new Uri(proxyurl)
|
||||
};
|
||||
@ -217,8 +237,11 @@ namespace Inotify.Sends
|
||||
{
|
||||
if (IsUrl(url) && IsImage(url))
|
||||
{
|
||||
WebClient mywebclient = new WebClient();
|
||||
return mywebclient.DownloadData(url);
|
||||
using (WebClient mywebclient = new WebClient())
|
||||
{
|
||||
return mywebclient.DownloadData(url);
|
||||
}
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -2,24 +2,21 @@ using Inotify.Controllers;
|
||||
using Inotify.Data;
|
||||
using Inotify.Sends;
|
||||
using Inotify.ThridOauth;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Rewrite;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Newtonsoft.Json;
|
||||
using NPoco;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Text.Unicode;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -56,7 +53,7 @@ namespace Inotify
|
||||
{
|
||||
OnAuthenticationFailed = context =>
|
||||
{
|
||||
var payload = JsonConvert.SerializeObject(new { message = "ÈÏ֤ʧ°Ü", code = 403 });
|
||||
var payload = JsonConvert.SerializeObject(new { message = "认证失败", code = 403 });
|
||||
context.Response.ContentType = "application/json";
|
||||
context.Response.StatusCode = StatusCodes.Status200OK;
|
||||
context.Response.WriteAsync(payload);
|
||||
@ -65,7 +62,7 @@ namespace Inotify
|
||||
},
|
||||
OnForbidden = context =>
|
||||
{
|
||||
var payload = JsonConvert.SerializeObject(new { message = "δ¾ÊÚȨ", code = 405 });
|
||||
var payload = JsonConvert.SerializeObject(new { message = "未经授权", code = 405 });
|
||||
context.Response.ContentType = "application/json";
|
||||
context.Response.StatusCode = StatusCodes.Status200OK;
|
||||
context.Response.WriteAsync(payload);
|
||||
@ -92,25 +89,82 @@ namespace Inotify
|
||||
{
|
||||
options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
|
||||
});
|
||||
|
||||
services.Configure<KestrelServerOptions>(x => x.AllowSynchronousIO = true)
|
||||
.Configure<IISServerOptions>(x => x.AllowSynchronousIO = true);
|
||||
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
#if !DEBUG
|
||||
app.UseStaticFiles();
|
||||
app.UseFileServer();
|
||||
#endif
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.Use(next => context =>
|
||||
{
|
||||
context.Request.EnableBuffering();
|
||||
return next(context);
|
||||
});
|
||||
|
||||
var options = new RewriteOptions();
|
||||
options.AddRewrite(@"api/(.*).send/(.*)/(.*)", "api/send?token=$1&title=$2&data=$3", true);
|
||||
options.AddRewrite(@"api/(.*).send/(.*)", "api/send?token=$1&title=$2", true);
|
||||
options.Add(rewriteContext =>
|
||||
{
|
||||
Match match;
|
||||
if (rewriteContext.HttpContext.Request.Path == "/")
|
||||
{
|
||||
var queryValue = rewriteContext.HttpContext.Request.QueryString.Value;
|
||||
match = Regex.Match(queryValue, @"^\?act=(.*)/(.*)/(.*)/(.*)$");
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
var groups = match.Groups;
|
||||
rewriteContext.HttpContext.Request.Path = @"/api/send";
|
||||
rewriteContext.HttpContext.Request.QueryString = new QueryString($"?token={groups[2]}&title={groups[3]}&data={groups[4]}");
|
||||
}
|
||||
else
|
||||
{
|
||||
match = Regex.Match(queryValue, @"^\?act=(.*)/(.*)/(.*)$");
|
||||
if (match.Success)
|
||||
{
|
||||
var groups = match.Groups;
|
||||
rewriteContext.HttpContext.Request.Path = @"/api/send";
|
||||
rewriteContext.HttpContext.Request.QueryString = new QueryString($"?token={groups[2]}&title={groups[3]}");
|
||||
}
|
||||
else
|
||||
{
|
||||
match = Regex.Match(queryValue, @"^\?act=(.*)/(.*)$");
|
||||
if (match.Success)
|
||||
{
|
||||
var groups = match.Groups;
|
||||
rewriteContext.HttpContext.Request.Path = @"/api/send";
|
||||
rewriteContext.HttpContext.Request.QueryString = new QueryString($"?token={groups[2]}");
|
||||
}
|
||||
else if (rewriteContext.HttpContext.Request.QueryString.Value.StartsWith("?"))
|
||||
{
|
||||
var groups = match.Groups;
|
||||
rewriteContext.HttpContext.Request.Path = @"/info";
|
||||
rewriteContext.HttpContext.Request.QueryString = new QueryString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
rewriteContext.Result = RuleResult.ContinueRules;
|
||||
});
|
||||
options.AddRewrite(@"^api/(.*).send/(.*)/(.*)", "api/send?token=$1&title=$2&data=$3", true);
|
||||
options.AddRewrite(@"^api/(.*).send/(.*)", "api/send?token=$1&title=$2", true);
|
||||
|
||||
options.AddRewrite(@"^(.*).send/(.*)/(.*)", "api/send?token=$1&title=$2&data=$3", true);
|
||||
options.AddRewrite(@"^(.*).send/(.*)", "api/send?token=$1&title=$2", true);
|
||||
|
||||
app.UseRewriter(options);
|
||||
app.UseRouting();
|
||||
|
||||
app.UseStaticFiles();
|
||||
app.UseFileServer();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.UseEndpoints(endpoints =>
|
||||
|
@ -1,10 +1,6 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Inotify
|
||||
{
|
||||
@ -27,7 +23,9 @@ namespace Inotify
|
||||
public static StartUpManager Load()
|
||||
{
|
||||
if (_appManager == null)
|
||||
{
|
||||
_appManager = new StartUpManager();
|
||||
}
|
||||
|
||||
return _appManager;
|
||||
}
|
||||
@ -35,10 +33,14 @@ namespace Inotify
|
||||
public void Start(string[] args)
|
||||
{
|
||||
if (_running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_tokenSource != null && _tokenSource.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_tokenSource = new CancellationTokenSource();
|
||||
_tokenSource.Token.ThrowIfCancellationRequested();
|
||||
@ -56,7 +58,9 @@ namespace Inotify
|
||||
public void Stop()
|
||||
{
|
||||
if (!_running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_tokenSource.Cancel();
|
||||
_running = false;
|
||||
|
@ -1,5 +1,4 @@
|
||||
using Inotify.ThridOauth.Common;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
using Inotify.ThridOauth.Common;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
|
||||
|
@ -1,8 +1,4 @@
|
||||
using Inotify.ThridOauth.Entity;
|
||||
|
||||
|
||||
|
||||
namespace Inotify.ThridOauth.Entity
|
||||
namespace Inotify.ThridOauth.Entity
|
||||
{
|
||||
public class CredentialSetting
|
||||
{
|
||||
|
@ -1,7 +1,3 @@
|
||||
using Inotify.ThridOauth.Entity;
|
||||
|
||||
|
||||
|
||||
namespace Inotify.ThridOauth.Entity
|
||||
{
|
||||
public class FaceBookCredential : CredentialSetting
|
||||
|
@ -1,7 +1,3 @@
|
||||
using Inotify.ThridOauth.Entity;
|
||||
|
||||
|
||||
|
||||
namespace Inotify.ThridOauth.Entity
|
||||
{
|
||||
public class GitHubCredential : CredentialSetting
|
||||
|
@ -1,7 +1,3 @@
|
||||
using Inotify.ThridOauth.Entity;
|
||||
|
||||
|
||||
|
||||
namespace Inotify.ThridOauth.Entity
|
||||
{
|
||||
public class QQCredential : CredentialSetting
|
||||
|
@ -1,7 +1,3 @@
|
||||
using Inotify.ThridOauth.Entity;
|
||||
|
||||
|
||||
|
||||
namespace Inotify.ThridOauth.Entity
|
||||
{
|
||||
public class WechatCredential : CredentialSetting
|
||||
|
@ -1,7 +1,3 @@
|
||||
using Inotify.ThridOauth.Entity;
|
||||
|
||||
|
||||
|
||||
namespace Inotify.ThridOauth.Entity
|
||||
{
|
||||
public class WeiBoCredential : CredentialSetting
|
||||
|
@ -1,8 +1,4 @@
|
||||
using Inotify.ThridOauth.IService;
|
||||
|
||||
|
||||
|
||||
namespace Inotify.ThridOauth.IService
|
||||
namespace Inotify.ThridOauth.IService
|
||||
{
|
||||
public interface IFacebookLogin : ILogin
|
||||
{
|
||||
|
@ -1,7 +1,3 @@
|
||||
using Inotify.ThridOauth.IService;
|
||||
|
||||
|
||||
|
||||
namespace Inotify.ThridOauth.IService
|
||||
{
|
||||
public interface IGitHubLogin : ILogin
|
||||
|
@ -1,5 +1,4 @@
|
||||
using Inotify.ThridOauth.Common;
|
||||
using Inotify.ThridOauth.IService;
|
||||
|
||||
|
||||
|
||||
|
@ -1,8 +1,4 @@
|
||||
using Inotify.ThridOauth.IService;
|
||||
|
||||
|
||||
|
||||
namespace Inotify.ThridOauth.IService
|
||||
namespace Inotify.ThridOauth.IService
|
||||
{
|
||||
public interface IQqLogin : ILogin
|
||||
{
|
||||
|
@ -1,8 +1,4 @@
|
||||
using Inotify.ThridOauth.IService;
|
||||
|
||||
|
||||
|
||||
namespace Inotify.ThridOauth.IService
|
||||
namespace Inotify.ThridOauth.IService
|
||||
{
|
||||
public interface ISinaLogin : ILogin
|
||||
{
|
||||
|
@ -1,8 +1,4 @@
|
||||
using Inotify.ThridOauth.IService;
|
||||
|
||||
|
||||
|
||||
namespace Inotify.ThridOauth.IService
|
||||
namespace Inotify.ThridOauth.IService
|
||||
{
|
||||
public interface IWeChatLogin : ILogin
|
||||
{
|
||||
|
@ -1,7 +1,6 @@
|
||||
using Inotify.ThridOauth.Common;
|
||||
using Inotify.ThridOauth.Entity;
|
||||
using Inotify.ThridOauth.IService;
|
||||
using Inotify.ThridOauth.Service;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json.Linq;
|
||||
@ -47,7 +46,10 @@ namespace Inotify.ThridOauth.Service
|
||||
var token = GetAccessToken(code, ref errMsg);
|
||||
|
||||
if (!string.IsNullOrEmpty(errMsg))
|
||||
{
|
||||
return new AuthorizeResult { Code = Code.UserInfoErrorMsg, Error = errMsg };
|
||||
}
|
||||
|
||||
var accessToken = token.Value<string>("access_token");
|
||||
|
||||
var user = UserInfo(accessToken, ref errMsg);
|
||||
|
@ -2,14 +2,10 @@ using Inotify.Sends;
|
||||
using Inotify.ThridOauth.Common;
|
||||
using Inotify.ThridOauth.Entity;
|
||||
using Inotify.ThridOauth.IService;
|
||||
using Inotify.ThridOauth.Service;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
|
||||
|
||||
@ -26,7 +22,7 @@ namespace Inotify.ThridOauth.Service
|
||||
private readonly string _authorizeUrl;
|
||||
|
||||
|
||||
public GitHubLogin(IHttpContextAccessor contextAccessor, IOptions<GitHubCredential> options) : base(
|
||||
public GitHubLogin(IHttpContextAccessor contextAccessor) : base(
|
||||
contextAccessor)
|
||||
{
|
||||
Credential = new CredentialSetting()
|
||||
@ -56,11 +52,14 @@ namespace Inotify.ThridOauth.Service
|
||||
var token = GetAccessToken(code, ref errorMsg);
|
||||
|
||||
if (!string.IsNullOrEmpty(errorMsg))
|
||||
{
|
||||
return new AuthorizeResult
|
||||
{
|
||||
Code = Code.UserInfoErrorMsg,
|
||||
Error = errorMsg
|
||||
};
|
||||
}
|
||||
|
||||
var accessToken = token.Value<string>("access_token");
|
||||
|
||||
var user = UserInfo(accessToken, ref errorMsg);
|
||||
@ -140,11 +139,17 @@ namespace Inotify.ThridOauth.Service
|
||||
{
|
||||
startindex = sourse.IndexOf(startstr, StringComparison.Ordinal);
|
||||
if (startindex == -1)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
string tmpstr = sourse[(startindex + startstr.Length)..];
|
||||
endindex = tmpstr.IndexOf(endstr, StringComparison.Ordinal);
|
||||
if (endindex == -1)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result = tmpstr.Remove(endindex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -1,6 +1,5 @@
|
||||
using Inotify.Sends;
|
||||
using Inotify.ThridOauth.Entity;
|
||||
using Inotify.ThridOauth.Service;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
using System.Net;
|
||||
|
@ -1,7 +1,6 @@
|
||||
using Inotify.ThridOauth.Common;
|
||||
using Inotify.ThridOauth.Entity;
|
||||
using Inotify.ThridOauth.IService;
|
||||
using Inotify.ThridOauth.Service;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json.Linq;
|
||||
@ -58,7 +57,10 @@ namespace Inotify.ThridOauth.Service
|
||||
var token = GetAccessToken(code, ref errorMsg);
|
||||
|
||||
if (!string.IsNullOrEmpty(errorMsg))
|
||||
{
|
||||
return new AuthorizeResult { Code = Code.UserInfoErrorMsg, Error = errorMsg };
|
||||
}
|
||||
|
||||
var accessToken = token["access_token"];
|
||||
|
||||
var user = UserInfo(accessToken, ref errorMsg);
|
||||
|
@ -1,7 +1,6 @@
|
||||
using Inotify.ThridOauth.Common;
|
||||
using Inotify.ThridOauth.Entity;
|
||||
using Inotify.ThridOauth.IService;
|
||||
using Inotify.ThridOauth.Service;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json.Linq;
|
||||
@ -55,7 +54,11 @@ namespace Inotify.ThridOauth.Service
|
||||
|
||||
var token = GetAccessToken(code, ref errorMsg);
|
||||
|
||||
if (!string.IsNullOrEmpty(errorMsg)) return new AuthorizeResult { Code = Code.UserInfoErrorMsg, Error = errorMsg };
|
||||
if (!string.IsNullOrEmpty(errorMsg))
|
||||
{
|
||||
return new AuthorizeResult { Code = Code.UserInfoErrorMsg, Error = errorMsg };
|
||||
}
|
||||
|
||||
var accessToken = token.Value<string>("access_token");
|
||||
|
||||
var uid = token.Value<string>("openid");
|
||||
|
@ -1,7 +1,6 @@
|
||||
using Inotify.ThridOauth.Common;
|
||||
using Inotify.ThridOauth.Entity;
|
||||
using Inotify.ThridOauth.IService;
|
||||
using Inotify.ThridOauth.Service;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json.Linq;
|
||||
@ -53,7 +52,10 @@ namespace Inotify.ThridOauth.Service
|
||||
var token = GetAccessToken(code, ref errorMsg);
|
||||
|
||||
if (!string.IsNullOrEmpty(errorMsg))
|
||||
{
|
||||
return new AuthorizeResult { Code = Code.UserInfoErrorMsg, Error = errorMsg };
|
||||
}
|
||||
|
||||
var accessToken = token.Value<string>("access_token");
|
||||
|
||||
var uid = token.Value<string>("uid");
|
||||
|
@ -1,5 +1,4 @@
|
||||
using Inotify.ThridOauth;
|
||||
using Inotify.ThridOauth.Entity;
|
||||
using Inotify.ThridOauth.Entity;
|
||||
using Inotify.ThridOauth.IService;
|
||||
using Inotify.ThridOauth.Service;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@ -16,7 +15,11 @@ namespace Inotify.ThridOauth
|
||||
public static IServiceCollection AddWeChatLogin(this IServiceCollection services,
|
||||
Action<WechatCredential> credential)
|
||||
{
|
||||
if (services == null) throw new ArgumentNullException(nameof(services));
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
services.Configure(credential);
|
||||
services.AddScoped<IWeChatLogin, WeChatLogin>();
|
||||
return services;
|
||||
@ -25,7 +28,11 @@ namespace Inotify.ThridOauth
|
||||
public static IServiceCollection AddQqLogin(this IServiceCollection services,
|
||||
Action<QQCredential> credential)
|
||||
{
|
||||
if (services == null) throw new ArgumentNullException(nameof(services));
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
services.Configure(credential);
|
||||
services.AddScoped<IQqLogin, QqLogin>();
|
||||
return services;
|
||||
@ -34,7 +41,11 @@ namespace Inotify.ThridOauth
|
||||
public static IServiceCollection AddSinaLogin(this IServiceCollection services,
|
||||
Action<WeiBoCredential> credential)
|
||||
{
|
||||
if (services == null) throw new ArgumentNullException(nameof(services));
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
services.Configure(credential);
|
||||
services.AddScoped<ISinaLogin, WeiBoLogin>();
|
||||
return services;
|
||||
@ -43,7 +54,11 @@ namespace Inotify.ThridOauth
|
||||
public static IServiceCollection AddFackbookLogin(this IServiceCollection services,
|
||||
Action<FaceBookCredential> credential)
|
||||
{
|
||||
if (services == null) throw new ArgumentNullException(nameof(services));
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
services.Configure(credential);
|
||||
services.AddScoped<IFacebookLogin, FacebookLogin>();
|
||||
return services;
|
||||
@ -52,7 +67,11 @@ namespace Inotify.ThridOauth
|
||||
public static IServiceCollection AddGitHubLogin(this IServiceCollection services,
|
||||
Action<GitHubCredential> credential)
|
||||
{
|
||||
if (services == null) throw new ArgumentNullException(nameof(services));
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
services.Configure(credential);
|
||||
services.AddScoped<IGitHubLogin, GitHubLogin>();
|
||||
return services;
|
||||
|
172
Inotify/ThridPart/CorePush/Apple/ApnSender.cs
Normal file
172
Inotify/ThridPart/CorePush/Apple/ApnSender.cs
Normal file
@ -0,0 +1,172 @@
|
||||
using CorePush.Interfaces;
|
||||
using CorePush.Utils;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CorePush.Apple
|
||||
{
|
||||
/// <summary>
|
||||
/// HTTP2 Apple Push Notification sender
|
||||
/// </summary>
|
||||
public class ApnSender : IApnSender
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, Tuple<string, DateTime>> tokens = new ConcurrentDictionary<string, Tuple<string, DateTime>>();
|
||||
private static readonly Dictionary<ApnServerType, string> servers = new Dictionary<ApnServerType, string>
|
||||
{
|
||||
{ApnServerType.Development, "https://api.development.push.apple.com:443" },
|
||||
{ApnServerType.Production, "https://api.push.apple.com:443" }
|
||||
};
|
||||
|
||||
private const string apnidHeader = "apns-id";
|
||||
private const int tokenExpiresMinutes = 50;
|
||||
|
||||
private readonly ApnSettings settings;
|
||||
private readonly HttpClient http;
|
||||
|
||||
/// <summary>
|
||||
/// Apple push notification sender constructor
|
||||
/// </summary>
|
||||
/// <param name="settings">Apple Push Notification settings</param>
|
||||
public ApnSender(ApnSettings settings, HttpClient http)
|
||||
{
|
||||
this.settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
||||
this.http = http ?? throw new ArgumentNullException(nameof(http));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize and send notification to APN. Please see how your message should be formatted here:
|
||||
/// https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW1
|
||||
/// Payload will be serialized using Newtonsoft.Json package.
|
||||
/// !IMPORTANT: If you send many messages at once, make sure to retry those calls. Apple typically doesn't like
|
||||
/// to receive too many requests and may ocasionally respond with HTTP 429. Just try/catch this call and retry as needed.
|
||||
/// </summary>
|
||||
/// <exception cref="HttpRequestException">Throws exception when not successful</exception>
|
||||
///
|
||||
public ApnsResponse Send(object notification,
|
||||
string deviceToken,
|
||||
string apnsId = null,
|
||||
int apnsExpiration = 0,
|
||||
int apnsPriority = 10,
|
||||
bool isBackground = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
||||
var task= SendAsync(notification, deviceToken, apnsId, apnsExpiration, apnsPriority, isBackground, cancellationToken);
|
||||
task.Wait();
|
||||
return task.Result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public async Task<ApnsResponse> SendAsync(
|
||||
object notification,
|
||||
string deviceToken,
|
||||
string apnsId = null,
|
||||
int apnsExpiration = 0,
|
||||
int apnsPriority = 10,
|
||||
bool isBackground = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var path = $"/3/device/{deviceToken}";
|
||||
var json = JsonHelper.Serialize(notification);
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, new Uri(servers[settings.ServerType] + path))
|
||||
{
|
||||
Version = new Version(2, 0),
|
||||
Content = new StringContent(json)
|
||||
};
|
||||
|
||||
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", GetJwtToken());
|
||||
request.Headers.TryAddWithoutValidation(":method", "POST");
|
||||
request.Headers.TryAddWithoutValidation(":path", path);
|
||||
request.Headers.Add("apns-topic", settings.AppBundleIdentifier);
|
||||
request.Headers.Add("apns-expiration", apnsExpiration.ToString());
|
||||
request.Headers.Add("apns-priority", apnsPriority.ToString());
|
||||
request.Headers.Add("apns-push-type", isBackground ? "background" : "alert"); // for iOS 13 required
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(apnsId))
|
||||
{
|
||||
request.Headers.Add(apnidHeader, apnsId);
|
||||
}
|
||||
|
||||
using (var response = await http.SendAsync(request, cancellationToken))
|
||||
{
|
||||
var succeed = response.IsSuccessStatusCode;
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
var error = JsonHelper.Deserialize<ApnsError>(content);
|
||||
|
||||
return new ApnsResponse
|
||||
{
|
||||
IsSuccess = succeed,
|
||||
Error = error
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private string GetJwtToken()
|
||||
{
|
||||
var (token, date) = tokens.GetOrAdd(settings.AppBundleIdentifier, _ => new Tuple<string, DateTime>(CreateJwtToken(), DateTime.UtcNow));
|
||||
if (date < DateTime.UtcNow.AddMinutes(-tokenExpiresMinutes))
|
||||
{
|
||||
tokens.TryRemove(settings.AppBundleIdentifier, out _);
|
||||
return GetJwtToken();
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
private string CreateJwtToken()
|
||||
{
|
||||
var header = JsonHelper.Serialize(new { alg = "ES256", kid = CleanP8Key(settings.P8PrivateKeyId) });
|
||||
var payload = JsonHelper.Serialize(new { iss = settings.TeamId, iat = ToEpoch(DateTime.UtcNow) });
|
||||
var headerBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(header));
|
||||
var payloadBasae64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(payload));
|
||||
var unsignedJwtData = $"{headerBase64}.{payloadBasae64}";
|
||||
var unsignedJwtBytes = Encoding.UTF8.GetBytes(unsignedJwtData);
|
||||
|
||||
using (var dsa = AppleCryptoHelper.GetEllipticCurveAlgorithm(CleanP8Key(settings.P8PrivateKey)))
|
||||
{
|
||||
var signature = dsa.SignData(unsignedJwtBytes, 0, unsignedJwtBytes.Length, HashAlgorithmName.SHA256);
|
||||
return $"{unsignedJwtData}.{Convert.ToBase64String(signature)}";
|
||||
}
|
||||
}
|
||||
|
||||
private static int ToEpoch(DateTime time)
|
||||
{
|
||||
var span = DateTime.UtcNow - new DateTime(1970, 1, 1);
|
||||
return Convert.ToInt32(span.TotalSeconds);
|
||||
}
|
||||
|
||||
private static string CleanP8Key(string p8Key)
|
||||
{
|
||||
// If we have an empty p8Key, then don't bother doing any tasks.
|
||||
if (string.IsNullOrEmpty(p8Key))
|
||||
{
|
||||
return p8Key;
|
||||
}
|
||||
|
||||
var lines = p8Key.Split(new [] { '\n' }).ToList();
|
||||
|
||||
if (0 != lines.Count && lines[0].StartsWith("-----BEGIN PRIVATE KEY-----"))
|
||||
{
|
||||
lines.RemoveAt(0);
|
||||
}
|
||||
|
||||
if (0 != lines.Count && lines[lines.Count - 1].StartsWith("-----END PRIVATE KEY-----"))
|
||||
{
|
||||
lines.RemoveAt(lines.Count - 1);
|
||||
}
|
||||
|
||||
var result = string.Join(string.Empty, lines);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
8
Inotify/ThridPart/CorePush/Apple/ApnServerType.cs
Normal file
8
Inotify/ThridPart/CorePush/Apple/ApnServerType.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace CorePush.Apple
|
||||
{
|
||||
public enum ApnServerType
|
||||
{
|
||||
Development,
|
||||
Production
|
||||
}
|
||||
}
|
30
Inotify/ThridPart/CorePush/Apple/ApnSettings.cs
Normal file
30
Inotify/ThridPart/CorePush/Apple/ApnSettings.cs
Normal file
@ -0,0 +1,30 @@
|
||||
namespace CorePush.Apple
|
||||
{
|
||||
public class ApnSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// p8 certificate string
|
||||
/// </summary>
|
||||
public string P8PrivateKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 10 digit p8 certificate id. Usually a part of a downloadable certificate filename
|
||||
/// </summary>
|
||||
public string P8PrivateKeyId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Apple 10 digit team id
|
||||
/// </summary>
|
||||
public string TeamId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// App slug / bundle name
|
||||
/// </summary>
|
||||
public string AppBundleIdentifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Development or Production server
|
||||
/// </summary>
|
||||
public ApnServerType ServerType { get; set; }
|
||||
}
|
||||
}
|
50
Inotify/ThridPart/CorePush/Apple/ApnsResponse.cs
Normal file
50
Inotify/ThridPart/CorePush/Apple/ApnsResponse.cs
Normal file
@ -0,0 +1,50 @@
|
||||
namespace CorePush.Apple
|
||||
{
|
||||
public class ApnsResponse
|
||||
{
|
||||
public bool IsSuccess { get; set; }
|
||||
|
||||
public ApnsError Error { get; set; }
|
||||
}
|
||||
|
||||
public class ApnsError
|
||||
{
|
||||
public ReasonEnum Reason {get; set;}
|
||||
public long? Timestamp {get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html#//apple_ref/doc/uid/TP40008194-CH11-SW15
|
||||
/// </summary>
|
||||
public enum ReasonEnum
|
||||
{
|
||||
BadCollapseId,
|
||||
BadDeviceToken,
|
||||
BadExpirationDate,
|
||||
BadMessageId,
|
||||
BadPriority,
|
||||
BadTopic,
|
||||
DeviceTokenNotForTopic,
|
||||
DuplicateHeaders,
|
||||
IdleTimeout,
|
||||
MissingDeviceToken,
|
||||
MissingTopic,
|
||||
PayloadEmpty,
|
||||
TopicDisallowed,
|
||||
BadCertificate,
|
||||
BadCertificateEnvironment,
|
||||
ExpiredProviderToken,
|
||||
Forbidden,
|
||||
InvalidProviderToken,
|
||||
MissingProviderToken,
|
||||
BadPath,
|
||||
MethodNotAllowed,
|
||||
Unregistered,
|
||||
PayloadTooLarge,
|
||||
TooManyProviderTokenUpdates,
|
||||
TooManyRequests,
|
||||
InternalServerError,
|
||||
ServiceUnavailable,
|
||||
Shutdown,
|
||||
}
|
||||
}
|
37
Inotify/ThridPart/CorePush/Google/FcmResponse.cs
Normal file
37
Inotify/ThridPart/CorePush/Google/FcmResponse.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CorePush.Google
|
||||
{
|
||||
public class FcmResponse
|
||||
{
|
||||
[JsonProperty("multicast_id")]
|
||||
public string MulticastId { get; set; }
|
||||
|
||||
[JsonProperty("canonical_ids")]
|
||||
public int CanonicalIds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Success count
|
||||
/// </summary>
|
||||
public int Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Failure count
|
||||
/// </summary>
|
||||
public int Failure { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Results
|
||||
/// </summary>
|
||||
public List<FcmResult> Results { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns value indicating notification sent success or failure
|
||||
/// </summary>
|
||||
public bool IsSuccess()
|
||||
{
|
||||
return Success > 0 && Failure == 0;
|
||||
}
|
||||
}
|
||||
}
|
15
Inotify/ThridPart/CorePush/Google/FcmResult.cs
Normal file
15
Inotify/ThridPart/CorePush/Google/FcmResult.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CorePush.Google
|
||||
{
|
||||
public class FcmResult
|
||||
{
|
||||
[JsonProperty("message_id")]
|
||||
public string MessageId { get; set; }
|
||||
|
||||
[JsonProperty("registration_id")]
|
||||
public string RegistrationId { get; set; }
|
||||
|
||||
public string Error { get; set; }
|
||||
}
|
||||
}
|
81
Inotify/ThridPart/CorePush/Google/FcmSender.cs
Normal file
81
Inotify/ThridPart/CorePush/Google/FcmSender.cs
Normal file
@ -0,0 +1,81 @@
|
||||
using CorePush.Interfaces;
|
||||
using CorePush.Utils;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
|
||||
namespace CorePush.Google
|
||||
{
|
||||
/// <summary>
|
||||
/// Firebase message sender
|
||||
/// </summary>
|
||||
public class FcmSender : IFcmSender
|
||||
{
|
||||
private readonly string fcmUrl = "https://fcm.googleapis.com/fcm/send";
|
||||
private readonly FcmSettings settings;
|
||||
private readonly HttpClient http;
|
||||
|
||||
public FcmSender(FcmSettings settings, HttpClient http)
|
||||
{
|
||||
this.settings = settings;
|
||||
this.http = http;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send firebase notification.
|
||||
/// Please check out payload formats:
|
||||
/// https://firebase.google.com/docs/cloud-messaging/concept-options#notifications
|
||||
/// The SendAsync method will add/replace "to" value with deviceId
|
||||
/// </summary>
|
||||
/// <param name="deviceId">Device token (will add `to` to the payload)</param>
|
||||
/// <param name="payload">Notification payload that will be serialized using Newtonsoft.Json package</param>
|
||||
/// <cref="HttpRequestException">Throws exception when not successful</exception>
|
||||
public Task<FcmResponse> SendAsync(string deviceId, object payload, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var jsonObject = JObject.FromObject(payload);
|
||||
jsonObject.Remove("to");
|
||||
jsonObject.Add("to", JToken.FromObject(deviceId));
|
||||
|
||||
return SendAsync(jsonObject, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send firebase notification.
|
||||
/// Please check out payload formats:
|
||||
/// https://firebase.google.com/docs/cloud-messaging/concept-options#notifications
|
||||
/// The SendAsync method will add/replace "to" value with deviceId
|
||||
/// </summary>
|
||||
/// <param name="payload">Notification payload that will be serialized using Newtonsoft.Json package</param>
|
||||
/// <exception cref="HttpRequestException">Throws exception when not successful</exception>
|
||||
public async Task<FcmResponse> SendAsync(object payload, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var serialized = JsonHelper.Serialize(payload);
|
||||
|
||||
using (var httpRequest = new HttpRequestMessage(HttpMethod.Post, fcmUrl))
|
||||
{
|
||||
httpRequest.Headers.Add("Authorization", $"key = {settings.ServerKey}");
|
||||
|
||||
if (!string.IsNullOrEmpty(settings.SenderId))
|
||||
{
|
||||
httpRequest.Headers.Add("Sender", $"id = {settings.SenderId}");
|
||||
}
|
||||
|
||||
httpRequest.Content = new StringContent(serialized, Encoding.UTF8, "application/json");
|
||||
|
||||
using (var response = await http.SendAsync(httpRequest, cancellationToken))
|
||||
{
|
||||
var responseString = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
throw new HttpRequestException("Firebase notification error: " + responseString);
|
||||
}
|
||||
|
||||
return JsonHelper.Deserialize<FcmResponse>(responseString);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
Inotify/ThridPart/CorePush/Google/FcmSettings.cs
Normal file
15
Inotify/ThridPart/CorePush/Google/FcmSettings.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace CorePush.Google
|
||||
{
|
||||
public class FcmSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// FCM Sender ID
|
||||
/// </summary>
|
||||
public string SenderId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// FCM Server Key
|
||||
/// </summary>
|
||||
public string ServerKey { get; set; }
|
||||
}
|
||||
}
|
18
Inotify/ThridPart/CorePush/Interfaces/IApnSender.cs
Normal file
18
Inotify/ThridPart/CorePush/Interfaces/IApnSender.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using CorePush.Apple;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CorePush.Interfaces
|
||||
{
|
||||
public interface IApnSender
|
||||
{
|
||||
Task<ApnsResponse> SendAsync(
|
||||
object notification,
|
||||
string deviceToken,
|
||||
string apnsId = null,
|
||||
int apnsExpiration = 0,
|
||||
int apnsPriority = 10,
|
||||
bool isBackground = false,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
12
Inotify/ThridPart/CorePush/Interfaces/IFcmSender.cs
Normal file
12
Inotify/ThridPart/CorePush/Interfaces/IFcmSender.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using CorePush.Google;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CorePush.Interfaces
|
||||
{
|
||||
public interface IFcmSender
|
||||
{
|
||||
Task<FcmResponse> SendAsync(string deviceId, object payload, CancellationToken cancellationToken = default);
|
||||
Task<FcmResponse> SendAsync(object payload, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
28
Inotify/ThridPart/CorePush/Utils/AppleCryptoHelper.cs
Normal file
28
Inotify/ThridPart/CorePush/Utils/AppleCryptoHelper.cs
Normal file
@ -0,0 +1,28 @@
|
||||
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.Security;
|
||||
|
||||
namespace CorePush.Utils
|
||||
{
|
||||
public static class AppleCryptoHelper
|
||||
{
|
||||
public static ECDsa GetEllipticCurveAlgorithm(string privateKey)
|
||||
{
|
||||
var keyParams = (ECPrivateKeyParameters) PrivateKeyFactory.CreateKey(Convert.FromBase64String(privateKey));
|
||||
var q = keyParams.Parameters.G.Multiply(keyParams.D).Normalize();
|
||||
|
||||
return ECDsa.Create(new ECParameters
|
||||
{
|
||||
Curve = ECCurve.CreateFromValue(keyParams.PublicKeyParamSet.Id),
|
||||
D = keyParams.D.ToByteArrayUnsigned(),
|
||||
Q =
|
||||
{
|
||||
X = q.XCoord.GetEncoded(),
|
||||
Y = q.YCoord.GetEncoded()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
24
Inotify/ThridPart/CorePush/Utils/JsonHelper.cs
Normal file
24
Inotify/ThridPart/CorePush/Utils/JsonHelper.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace CorePush.Utils
|
||||
{
|
||||
public static class JsonHelper
|
||||
{
|
||||
private static readonly JsonSerializerSettings settings = new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver(),
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
};
|
||||
|
||||
public static string Serialize(object obj)
|
||||
{
|
||||
return JsonConvert.SerializeObject(obj, settings);
|
||||
}
|
||||
|
||||
public static TObject Deserialize<TObject>(string json)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<TObject>(json, settings);
|
||||
}
|
||||
}
|
||||
}
|
50
README.md
50
README.md
@ -1,6 +1,6 @@
|
||||
# inotify
|
||||
|
||||
[](https://github.com/xpnas/inotify/actions/workflows/docker.yml)
|
||||
[](https://github.com/xpnas/inotify/actions/workflows/docker.yml)
|
||||
|
||||
一个简易的消息通知系统,支持企业微信、电报机器人、邮件推送
|
||||
|
||||
@ -20,33 +20,47 @@
|
||||
- [x] 企业微信应用消息
|
||||
- [x] 电报机器人消息
|
||||
- [x] SMTP邮箱消息
|
||||
- [ ] 钉钉群机器人
|
||||
- [ ] 飞书群机器人
|
||||
- [ ] 自定义
|
||||
- [x] BARK
|
||||
- [x] 钉钉群机器人
|
||||
- [x] 飞书群机器人
|
||||
- [x] 自定义
|
||||
|
||||
## 使用方法
|
||||
1. Docker安装
|
||||
```
|
||||
docker run --name=inotify -d -p 8000:80 -v inotify_data:/inotify_data --restart=always xpnas/inotify:master
|
||||
```
|
||||
|
||||
* 发布版
|
||||
```
|
||||
docker run --name=inotify -d -p 8000:80 -v inotify_data:/inotify_data --restart=always xpnas/inotify:latest
|
||||
```
|
||||
* 开发版
|
||||
```
|
||||
docker run --name=inotify -d -p 8000:80 -v inotify_data:/inotify_data --restart=always xpnas/inotify:master
|
||||
```
|
||||
2. 配置Nginx代理
|
||||
```
|
||||
server
|
||||
{
|
||||
```
|
||||
server
|
||||
{
|
||||
location / { proxy_pass http://127.0.0.1:8000; }
|
||||
}
|
||||
```
|
||||
}
|
||||
```
|
||||
|
||||
3. 默认用户名admin,密码123456
|
||||
3. 进入`Github/Settings/Developer settings/OAuth Apps`创建应用
|
||||
* 记录`Client ID`,创建`Client secrets`
|
||||
* `Authorization callback URL`回调地址填写https://{您的域名}/api/oauth/githubLogin
|
||||
4. 使用`默认用户名admin,密码123456`登陆后台/全局参数,修改Github登陆的`应用ID`、`应用密钥`并启动登陆
|
||||
5. 建议将`管理权限`的用户名设置成自己的github用户名,再使用Github登陆后,在用户管理页面`删除默认账号admin`
|
||||
|
||||
## BARK设置
|
||||
1. 本项目依据Bark-Server接口规范实现了内置BARK服务端
|
||||
2. 复制或扫码`消息验证\BARK授权`中的地址,填入BARK应用的服务器地址中,如`https://inotify.cf?act=6D474C0DB1474F19BD8F7342D570C0FC`
|
||||
3. BARK的APP会自动在本系统注册数据,记录将直接出现在`消息通道`
|
||||
|
||||
## 系统截图
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
|
BIN
public/A.png
BIN
public/A.png
Binary file not shown.
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 104 KiB |
25
sonar.sln
Normal file
25
sonar.sln
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.31129.286
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Inotify", "Inotify\Inotify.csproj", "{EEDAC4E1-2C4F-4400-B2AE-E7027EE83C83}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{EEDAC4E1-2C4F-4400-B2AE-E7027EE83C83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EEDAC4E1-2C4F-4400-B2AE-E7027EE83C83}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EEDAC4E1-2C4F-4400-B2AE-E7027EE83C83}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EEDAC4E1-2C4F-4400-B2AE-E7027EE83C83}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {E659C816-7213-4399-9AB3-0E575CAD9C97}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
Loading…
Reference in New Issue
Block a user