mirror of
https://github.com/certd/certd.git
synced 2026-04-24 12:27:25 +08:00
feat: 手机号登录、邮箱验证码注册
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div class="flex">
|
||||
<a-input :value="value" placeholder="请输入图片验证码" autocomplete="off" @update:value="onChange">
|
||||
<template #prefix>
|
||||
<fs-icon icon="ion:image-outline"></fs-icon>
|
||||
</template>
|
||||
</a-input>
|
||||
<div class="input-right pointer" title="点击刷新">
|
||||
<img class="image-code" :src="imageCodeUrl" @click="resetImageCode" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, useAttrs } from "vue";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
const props = defineProps<{
|
||||
randomStr?: string;
|
||||
value?: string;
|
||||
}>();
|
||||
const emit = defineEmits(["update:value", "update:randomStr", "change"]);
|
||||
|
||||
function onChange(value: string) {
|
||||
emit("update:value", value);
|
||||
emit("change", value);
|
||||
}
|
||||
|
||||
const imageCodeUrl = ref();
|
||||
function resetImageCode() {
|
||||
const randomStr = nanoid(10);
|
||||
let url = "/api/basic/code/captcha";
|
||||
imageCodeUrl.value = url + "?randomStr=" + randomStr;
|
||||
emit("update:randomStr", randomStr);
|
||||
}
|
||||
resetImageCode();
|
||||
</script>
|
||||
@@ -40,31 +40,17 @@
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item has-feedback name="imgCode">
|
||||
<div class="flex">
|
||||
<a-input v-model:value="formState.imgCode" placeholder="请输入图片验证码" autocomplete="off">
|
||||
<template #prefix>
|
||||
<fs-icon icon="ion:image-outline"></fs-icon>
|
||||
</template>
|
||||
</a-input>
|
||||
<div class="input-right">
|
||||
<img class="image-code" :src="imageCodeUrl" @click="resetImageCode" />
|
||||
</div>
|
||||
</div>
|
||||
<image-code v-model:value="formState.imgCode" v-model:random-str="formState.randomStr"></image-code>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item name="smsCode" :rules="rules.smsCode">
|
||||
<div class="flex">
|
||||
<a-input v-model:value="formState.smsCode" placeholder="短信验证码">
|
||||
<template #prefix>
|
||||
<fs-icon icon="ion:mail-outline"></fs-icon>
|
||||
</template>
|
||||
</a-input>
|
||||
<div class="input-right">
|
||||
<a-button class="getCaptcha" type="primary" tabindex="-1" :disabled="smsSendBtnDisabled" @click="sendSmsCode">
|
||||
{{ smsTime <= 0 ? "发送" : smsTime + " s" }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<sms-code
|
||||
v-model:value="formState.smsCode"
|
||||
:img-code="formState.imgCode"
|
||||
:mobile="formState.mobile"
|
||||
:phone-code="formState.phoneCode"
|
||||
:random-str="formState.randomStr"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
@@ -80,15 +66,16 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, ref, toRaw, computed } from "vue";
|
||||
import { defineComponent, reactive, ref, toRaw } from "vue";
|
||||
import { useUserStore } from "/src/store/modules/user";
|
||||
import { useSettingStore } from "/@/store/modules/settings";
|
||||
import { utils } from "@fast-crud/fast-crud";
|
||||
import * as api from "/src/api/modules/api.basic";
|
||||
import { nanoid } from "nanoid";
|
||||
import { notification } from "ant-design-vue";
|
||||
import ImageCode from "/@/views/framework/login/image-code.vue";
|
||||
import SmsCode from "/@/views/framework/login/sms-code.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "LoginPage",
|
||||
components: { SmsCode, ImageCode },
|
||||
setup() {
|
||||
const loading = ref(false);
|
||||
const userStore = useUserStore();
|
||||
@@ -161,41 +148,6 @@ export default defineComponent({
|
||||
|
||||
const isLoginError = ref();
|
||||
|
||||
const imageCodeUrl = ref();
|
||||
function resetImageCode() {
|
||||
formState.randomStr = nanoid(10);
|
||||
let url = "/api/basic/code/captcha";
|
||||
imageCodeUrl.value = url + "?randomStr=" + formState.randomStr;
|
||||
}
|
||||
resetImageCode();
|
||||
|
||||
const smsTime = ref(0);
|
||||
const smsSendBtnDisabled = computed(() => {
|
||||
if (smsTime.value === 0) {
|
||||
return false;
|
||||
}
|
||||
return !!formState.smsCode;
|
||||
});
|
||||
async function sendSmsCode() {
|
||||
if (!formState.mobile) {
|
||||
notification.error({ message: "请输入手机号" });
|
||||
return;
|
||||
}
|
||||
if (!formState.imgCode) {
|
||||
notification.error({ message: "请输入图片验证码" });
|
||||
return;
|
||||
}
|
||||
await api.sendSmsCode({
|
||||
phoneCode: formState.phoneCode,
|
||||
mobile: formState.mobile,
|
||||
imgCode: formState.imgCode,
|
||||
randomStr: formState.randomStr
|
||||
});
|
||||
smsTime.value = 60;
|
||||
setInterval(() => {
|
||||
smsTime.value--;
|
||||
}, 1000);
|
||||
}
|
||||
const sysPublicSettings = settingStore.getSysPublic;
|
||||
return {
|
||||
loading,
|
||||
@@ -207,11 +159,6 @@ export default defineComponent({
|
||||
handleFinish,
|
||||
resetForm,
|
||||
isLoginError,
|
||||
imageCodeUrl,
|
||||
resetImageCode,
|
||||
smsTime,
|
||||
smsSendBtnDisabled,
|
||||
sendSmsCode,
|
||||
sysPublicSettings
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<div class="flex">
|
||||
<a-input :value="value" placeholder="短信验证码" @update:value="onChange">
|
||||
<template #prefix>
|
||||
<fs-icon icon="ion:mail-outline"></fs-icon>
|
||||
</template>
|
||||
</a-input>
|
||||
<div class="input-right">
|
||||
<a-button class="getCaptcha" type="primary" tabindex="-1" :disabled="smsSendBtnDisabled" @click="sendSmsCode">
|
||||
{{ smsTime <= 0 ? "发送" : smsTime + " s" }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, useAttrs } from "vue";
|
||||
import { nanoid } from "nanoid";
|
||||
import { notification } from "ant-design-vue";
|
||||
import * as api from "/@/api/modules/api.basic";
|
||||
|
||||
const props = defineProps<{
|
||||
value?: string;
|
||||
mobile?: string;
|
||||
phoneCode?: string;
|
||||
imgCode?: string;
|
||||
randomStr?: string;
|
||||
}>();
|
||||
const emit = defineEmits(["update:value", "change"]);
|
||||
|
||||
function onChange(value: string) {
|
||||
emit("update:value", value);
|
||||
emit("change", value);
|
||||
}
|
||||
const loading = ref(false);
|
||||
const smsTime = ref(0);
|
||||
const smsSendBtnDisabled = computed(() => {
|
||||
if (loading.value) {
|
||||
return true;
|
||||
}
|
||||
if (smsTime.value === 0) {
|
||||
return false;
|
||||
}
|
||||
return smsTime.value > 0;
|
||||
});
|
||||
|
||||
async function sendSmsCode() {
|
||||
if (!props.mobile) {
|
||||
notification.error({ message: "请输入手机号" });
|
||||
return;
|
||||
}
|
||||
if (!props.imgCode) {
|
||||
notification.error({ message: "请输入图片验证码" });
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
await api.sendSmsCode({
|
||||
phoneCode: props.phoneCode,
|
||||
mobile: props.mobile,
|
||||
imgCode: props.imgCode,
|
||||
randomStr: props.randomStr
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
smsTime.value = 60;
|
||||
setInterval(() => {
|
||||
smsTime.value--;
|
||||
}, 1000);
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="flex">
|
||||
<a-input :value="value" placeholder="邮件验证码" @update:value="onChange">
|
||||
<template #prefix>
|
||||
<fs-icon icon="ion:mail-outline"></fs-icon>
|
||||
</template>
|
||||
</a-input>
|
||||
<div class="input-right">
|
||||
<a-button class="getCaptcha" type="primary" tabindex="-1" :disabled="smsSendBtnDisabled" @click="sendSmsCode">
|
||||
{{ smsTime <= 0 ? "发送" : smsTime + " s" }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, useAttrs } from "vue";
|
||||
import { nanoid } from "nanoid";
|
||||
import { notification } from "ant-design-vue";
|
||||
import * as api from "/@/api/modules/api.basic";
|
||||
|
||||
const props = defineProps<{
|
||||
value?: string;
|
||||
email?: string;
|
||||
imgCode?: string;
|
||||
randomStr?: string;
|
||||
}>();
|
||||
const emit = defineEmits(["update:value", "change"]);
|
||||
|
||||
function onChange(value: string) {
|
||||
emit("update:value", value);
|
||||
emit("change", value);
|
||||
}
|
||||
|
||||
const loading = ref(false);
|
||||
const smsTime = ref(0);
|
||||
const smsSendBtnDisabled = computed(() => {
|
||||
if (loading.value) {
|
||||
return true;
|
||||
}
|
||||
return smsTime.value > 0;
|
||||
});
|
||||
async function sendSmsCode() {
|
||||
if (!props.email) {
|
||||
notification.error({ message: "请输入邮箱" });
|
||||
return;
|
||||
}
|
||||
if (!props.imgCode) {
|
||||
notification.error({ message: "请输入图片验证码" });
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
await api.sendEmailCode({
|
||||
email: props.email,
|
||||
imgCode: props.imgCode,
|
||||
randomStr: props.randomStr
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
smsTime.value = 60;
|
||||
setInterval(() => {
|
||||
smsTime.value--;
|
||||
}, 1000);
|
||||
}
|
||||
</script>
|
||||
@@ -7,20 +7,37 @@
|
||||
:model="formState"
|
||||
:rules="rules"
|
||||
v-bind="layout"
|
||||
:label-col="{ span: 5 }"
|
||||
:label-col="{ span: 6 }"
|
||||
@finish="handleFinish"
|
||||
@finish-failed="handleFinishFailed"
|
||||
>
|
||||
<a-tabs v-model:value="registerType" :tab-bar-style="{ textAlign: 'center', borderBottom: 'unset' }">
|
||||
<a-tabs v-model:active-key="registerType">
|
||||
<a-tab-pane key="username" tab="用户名注册">
|
||||
<template v-if="registerType === 'username'">
|
||||
<a-form-item required has-feedback name="username" label="用户名">
|
||||
<a-form-item required has-feedback name="username" label="用户名" :rules="rules.username">
|
||||
<a-input v-model:value="formState.username" placeholder="用户名" size="large" autocomplete="off">
|
||||
<template #prefix>
|
||||
<span class="iconify" data-icon="ion:person" data-inline="false"></span>
|
||||
<fs-icon icon="ion:person-outline"></fs-icon>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item has-feedback name="password" label="密码" :rules="rules.password">
|
||||
<a-input-password v-model:value="formState.password" placeholder="密码" size="large" autocomplete="off">
|
||||
<template #prefix>
|
||||
<fs-icon icon="ion:lock-closed-outline"></fs-icon>
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
<a-form-item has-feedback name="confirmPassword" label="确认密码">
|
||||
<a-input-password v-model:value="formState.confirmPassword" placeholder="确认密码" size="large" autocomplete="off" :rules="rules.confirmPassword">
|
||||
<template #prefix>
|
||||
<fs-icon icon="ion:lock-closed-outline"></fs-icon>
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
<a-form-item has-feedback name="imgCode" label="图片验证码" :rules="rules.imgCode">
|
||||
<image-code v-model:value="formState.imgCode" v-model:random-str="formState.randomStr"></image-code>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="email" tab="邮箱注册">
|
||||
@@ -28,60 +45,36 @@
|
||||
<a-form-item required has-feedback name="email" label="邮箱">
|
||||
<a-input v-model:value="formState.email" placeholder="邮箱" size="large" autocomplete="off">
|
||||
<template #prefix>
|
||||
<span class="iconify" data-icon="ion:person" data-inline="false"></span>
|
||||
<fs-icon icon="ion:mail-outline"></fs-icon>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item has-feedback name="imgCode">
|
||||
<a-row :gutter="16">
|
||||
<a-col class="gutter-row" :span="16">
|
||||
<a-input v-model:value="formState.imgCode" placeholder="请输入图片验证码" size="large" autocomplete="off">
|
||||
<template #prefix>
|
||||
<span class="iconify" data-icon="ion:image-outline" data-inline="false"></span>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col class="gutter-row" :span="8">
|
||||
<img class="image-code" :src="imageCodeUrl" @click="resetImageCode" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item has-feedback name="password" label="密码">
|
||||
<a-input-password v-model:value="formState.password" placeholder="密码" size="large" autocomplete="off">
|
||||
<template #prefix>
|
||||
<fs-icon icon="ion:lock-closed-outline"></fs-icon>
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
<a-form-item has-feedback name="confirmPassword" label="确认密码">
|
||||
<a-input-password v-model:value="formState.confirmPassword" placeholder="确认密码" size="large" autocomplete="off">
|
||||
<template #prefix>
|
||||
<fs-icon icon="ion:lock-closed-outline"></fs-icon>
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item name="smsCode">
|
||||
<a-row :gutter="16">
|
||||
<a-col class="gutter-row" :span="16">
|
||||
<a-input v-model:value="formState.validateCode" size="large" placeholder="邮箱验证码">
|
||||
<template #prefix>
|
||||
<span class="iconify" data-icon="ion:mail-outline" data-inline="false"></span>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col class="gutter-row" :span="8">
|
||||
<a-button class="getCaptcha" tabindex="-1" :disabled="smsSendBtnDisabled" @click="sendSmsCode">
|
||||
{{ smsTime <= 0 ? "发送" : smsTime + " s" }}
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item has-feedback name="imgCode" label="图片验证码" :rules="rules.imgCode">
|
||||
<image-code v-model:value="formState.imgCode" v-model:random-str="formState.randomStr"></image-code>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item has-feedback name="validateCode" :rules="rules.validateCode" label="邮件验证码">
|
||||
<email-code v-model:value="formState.validateCode" :img-code="formState.imgCode" :email="formState.email" :random-str="formState.randomStr" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
||||
<a-form-item has-feedback name="password" label="密码">
|
||||
<a-input-password v-model:value="formState.password" placeholder="密码" size="large" autocomplete="off">
|
||||
<template #prefix>
|
||||
<span class="iconify" data-icon="ion:lock-closed" data-inline="false"></span>
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
<a-form-item has-feedback name="confirmPassword" label="确认密码">
|
||||
<a-input-password v-model:value="formState.confirmPassword" placeholder="确认密码" size="large" autocomplete="off">
|
||||
<template #prefix>
|
||||
<span class="iconify" data-icon="ion:lock-closed" data-inline="false"></span>
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" size="large" html-type="submit" class="login-button">注册</a-button>
|
||||
</a-form-item>
|
||||
@@ -96,10 +89,13 @@
|
||||
import { defineComponent, reactive, ref, toRaw } from "vue";
|
||||
import { useUserStore } from "/src/store/modules/user";
|
||||
import { utils } from "@fast-crud/fast-crud";
|
||||
import ImageCode from "/@/views/framework/login/image-code.vue";
|
||||
import EmailCode from "./email-code.vue";
|
||||
export default defineComponent({
|
||||
name: "RegisterPage",
|
||||
components: { EmailCode, ImageCode },
|
||||
setup() {
|
||||
const registerType = ref("email");
|
||||
const registerType = ref("username");
|
||||
const userStore = useUserStore();
|
||||
const formRef = ref();
|
||||
const formState: any = reactive({
|
||||
@@ -108,7 +104,8 @@ export default defineComponent({
|
||||
email: "",
|
||||
username: "",
|
||||
password: "",
|
||||
confirmPassword: ""
|
||||
confirmPassword: "",
|
||||
randomStr: ""
|
||||
});
|
||||
|
||||
const rules = {
|
||||
@@ -124,6 +121,10 @@ export default defineComponent({
|
||||
required: true,
|
||||
trigger: "change",
|
||||
message: "请输入邮箱"
|
||||
},
|
||||
{
|
||||
type: "email",
|
||||
message: "请输入正确的邮箱"
|
||||
}
|
||||
],
|
||||
password: [
|
||||
@@ -138,6 +139,33 @@ export default defineComponent({
|
||||
required: true,
|
||||
trigger: "change",
|
||||
message: "请确认密码"
|
||||
},
|
||||
{
|
||||
validator: async (rule: any, value: any) => {
|
||||
if (value !== formState.password) {
|
||||
throw new Error("两次输入密码不一致");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
imgCode: [
|
||||
{
|
||||
required: true,
|
||||
message: "请输入图片验证码"
|
||||
}
|
||||
],
|
||||
smsCode: [
|
||||
{
|
||||
required: true,
|
||||
message: "请输入短信验证码"
|
||||
}
|
||||
],
|
||||
validateCode: [
|
||||
{
|
||||
required: true,
|
||||
message: "请输入邮件验证码"
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -153,8 +181,13 @@ export default defineComponent({
|
||||
const handleFinish = async (values: any) => {
|
||||
await userStore.register(
|
||||
toRaw({
|
||||
type: registerType.value,
|
||||
password: formState.password,
|
||||
username: formState.username
|
||||
username: formState.username,
|
||||
imgCode: formState.imgCode,
|
||||
randomStr: formState.randomStr,
|
||||
email: formState.email,
|
||||
validateCode: formState.validateCode
|
||||
}) as any
|
||||
);
|
||||
};
|
||||
@@ -197,6 +230,28 @@ export default defineComponent({
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.ant-input-affix-wrapper {
|
||||
line-height: 1.8 !important;
|
||||
font-size: 14px !important;
|
||||
> * {
|
||||
line-height: 1.8 !important;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.getCaptcha {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.image-code {
|
||||
height: 34px;
|
||||
}
|
||||
.input-right {
|
||||
width: 160px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
// color: @primary-color;
|
||||
font-size: 18px;
|
||||
@@ -204,11 +259,6 @@ export default defineComponent({
|
||||
margin: 30px;
|
||||
margin-top: 50px;
|
||||
}
|
||||
.getCaptcha {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.forge-password {
|
||||
font-size: 14px;
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
koa:
|
||||
port: 7001
|
||||
https:
|
||||
port: 7002
|
||||
|
||||
preview:
|
||||
enabled: false
|
||||
|
||||
|
||||
@@ -28,12 +28,13 @@
|
||||
"tsc": "tsc --skipLibCheck"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/plugin-lib": "^1.27.9",
|
||||
"@alicloud/pop-core": "^1.7.10",
|
||||
"@certd/acme-client": "^1.27.9",
|
||||
"@certd/basic": "^1.27.9",
|
||||
"@certd/commercial-core": "^1.27.9",
|
||||
"@certd/lib-huawei": "^1.27.9",
|
||||
"@certd/lib-k8s": "^1.27.9",
|
||||
"@certd/lib-huawei": "^1.27.9",
|
||||
"@certd/lib-server": "^1.27.9",
|
||||
"@certd/midway-flyway-js": "^1.27.9",
|
||||
"@certd/pipeline": "^1.27.9",
|
||||
@@ -90,7 +91,6 @@
|
||||
"rimraf": "^5.0.5",
|
||||
"socks": "^2.8.3",
|
||||
"socks-proxy-agent": "^8.0.4",
|
||||
"ssh2": "^1.15.0",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"svg-captcha": "^1.4.0",
|
||||
"tencentcloud-sdk-nodejs": "^4.0.44",
|
||||
|
||||
@@ -71,7 +71,7 @@ const development = {
|
||||
type: 'better-sqlite3',
|
||||
database: './data/db.sqlite',
|
||||
synchronize: false, // 如果第一次使用,不存在表,有同步的需求可以写 true
|
||||
logging: false,
|
||||
logging: true,
|
||||
|
||||
// 配置实体模型 或者 entities: '/entity',
|
||||
entities: ['**/modules/**/entity/*.js', ...libServerEntities, ...commercialEntities, PipelineEntity, FlywayHistory, UserEntity],
|
||||
|
||||
@@ -12,7 +12,7 @@ export type RegisterReq = {
|
||||
phoneCode?: string;
|
||||
|
||||
validateCode: string;
|
||||
imageCode: string;
|
||||
imgCode: string;
|
||||
randomStr: string;
|
||||
};
|
||||
|
||||
@@ -40,16 +40,17 @@ export class RegisterController extends BaseController {
|
||||
}
|
||||
|
||||
if (body.type === 'username') {
|
||||
if (sysPublicSettings.usernameRegisterEnabled) {
|
||||
if (sysPublicSettings.usernameRegisterEnabled === false) {
|
||||
throw new Error('当前站点已禁止用户名注册功能');
|
||||
}
|
||||
await this.codeService.checkCaptcha(body.randomStr, body.imgCode);
|
||||
const newUser = await this.userService.register(body.type, {
|
||||
username: body.username,
|
||||
password: body.password,
|
||||
} as any);
|
||||
return this.ok(newUser);
|
||||
} else if (body.type === 'mobile') {
|
||||
if (sysPublicSettings.mobileRegisterEnabled) {
|
||||
if (sysPublicSettings.mobileRegisterEnabled === false) {
|
||||
throw new Error('当前站点已禁止手机号注册功能');
|
||||
}
|
||||
//验证短信验证码
|
||||
|
||||
@@ -88,7 +88,7 @@ export class CodeService {
|
||||
/**
|
||||
*/
|
||||
async sendEmailCode(email: string, randomStr: string) {
|
||||
console.assert(!email, '手机号不能为空');
|
||||
console.assert(!email, 'Email不能为空');
|
||||
console.assert(!randomStr, 'randomStr不能为空');
|
||||
|
||||
const code = randomNumber(4);
|
||||
@@ -98,7 +98,7 @@ export class CodeService {
|
||||
receivers: [email],
|
||||
});
|
||||
|
||||
const key = this.buildEmailCodeKey(email, code);
|
||||
const key = this.buildEmailCodeKey(email, randomStr);
|
||||
cache.set(key, code, {
|
||||
ttl: 5 * 60 * 1000, //5分钟
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AliyunAccess, AliyunClient } from '@certd/plugin-plus';
|
||||
import { logger } from '@certd/basic';
|
||||
import { ISmsService, PluginInputs, SmsPluginCtx } from './api.js';
|
||||
import { AliyunAccess, AliyunClient } from '@certd/plugin-lib';
|
||||
export type AliyunSmsConfig = {
|
||||
accessId: string;
|
||||
regionId: string;
|
||||
|
||||
@@ -154,30 +154,30 @@ export class UserService extends BaseService<UserEntity> {
|
||||
}
|
||||
|
||||
async register(type: string, user: UserEntity) {
|
||||
if (type !== 'username') {
|
||||
if (!user.password) {
|
||||
user.password = simpleNanoId();
|
||||
}
|
||||
if (!user.username) {
|
||||
user.username = 'user_' + simpleNanoId();
|
||||
if (!user.password) {
|
||||
user.password = simpleNanoId();
|
||||
}
|
||||
|
||||
if (type === 'username') {
|
||||
const username = user.username;
|
||||
const old = await this.findOne([{ username: username }, { mobile: username }, { email: username }]);
|
||||
if (old != null) {
|
||||
throw new CommonException('用户名已被注册');
|
||||
}
|
||||
}
|
||||
if (type === 'mobile') {
|
||||
user.nickName = user.mobile.substring(0, 3) + '****' + user.mobile.substring(7);
|
||||
}
|
||||
} else if (type === 'mobile') {
|
||||
const mobile = user.mobile;
|
||||
|
||||
const old = await this.findOne({ username: user.username });
|
||||
if (old != null) {
|
||||
throw new CommonException('用户名已被注册');
|
||||
}
|
||||
|
||||
if (user.mobile) {
|
||||
const old = await this.findOne({ mobile: user.mobile });
|
||||
user.nickName = mobile.substring(0, 3) + '****' + mobile.substring(7);
|
||||
const old = await this.findOne([{ username: mobile }, { mobile: mobile }, { email: mobile }]);
|
||||
if (old != null) {
|
||||
throw new CommonException('手机号已被注册');
|
||||
}
|
||||
}
|
||||
|
||||
if (user.email) {
|
||||
const old = await this.findOne({ email: user.email });
|
||||
} else if (type === 'email') {
|
||||
const email = user.email;
|
||||
const old = await this.findOne([{ username: email }, { mobile: email }, { email: email }]);
|
||||
if (old != null) {
|
||||
throw new CommonException('邮箱已被注册');
|
||||
}
|
||||
@@ -186,10 +186,10 @@ export class UserService extends BaseService<UserEntity> {
|
||||
let newUser: UserEntity = UserEntity.of({
|
||||
username: user.username,
|
||||
password: user.password,
|
||||
nickName: user.nickName || user.username,
|
||||
avatar: user.avatar || '',
|
||||
email: user.email || '',
|
||||
mobile: user.mobile || '',
|
||||
nickName: user.nickName || user.username,
|
||||
avatar: user.avatar || '',
|
||||
phoneCode: user.phoneCode || '86',
|
||||
status: 1,
|
||||
passwordVersion: 2,
|
||||
|
||||
@@ -12,3 +12,4 @@ export * from './plugin-qiniu/index.js';
|
||||
export * from './plugin-woai/index.js';
|
||||
export * from './plugin-cachefly/index.js';
|
||||
export * from './plugin-gcore/index.js';
|
||||
export * from './plugin-qnap/index.js';
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
|
||||
import { Autowire } from '@certd/pipeline';
|
||||
|
||||
import { AliyunAccess, AliyunClient } from '@certd/plugin-plus';
|
||||
import { AliyunAccess, AliyunClient } from '@certd/plugin-lib';
|
||||
|
||||
@IsDnsProvider({
|
||||
name: 'aliyun',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
|
||||
import { AliyunAccess, AliyunClient, AliyunSslClient, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-plus';
|
||||
import { AliyunAccess, AliyunClient, AliyunSslClient, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib';
|
||||
import { optionsUtils } from '@certd/basic/dist/utils/util.options.js';
|
||||
|
||||
@IsTaskPlugin({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
|
||||
import dayjs from 'dayjs';
|
||||
import { AliyunAccess, AliyunClient, createCertDomainGetterInputDefine } from '@certd/plugin-plus';
|
||||
import { AliyunAccess, AliyunClient, createCertDomainGetterInputDefine } from '@certd/plugin-lib';
|
||||
import { CertInfo } from '@certd/plugin-cert';
|
||||
|
||||
@IsTaskPlugin({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
|
||||
import { AliyunAccess } from '@certd/plugin-plus';
|
||||
import { AliyunAccess } from '@certd/plugin-lib';
|
||||
import { CertInfo } from '@certd/plugin-cert';
|
||||
@IsTaskPlugin({
|
||||
name: 'DeployCertToAliyunOSS',
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
|
||||
import { CertInfo } from '@certd/plugin-cert';
|
||||
import {
|
||||
AbstractPlusTaskPlugin,
|
||||
AliyunAccess,
|
||||
AliyunClient,
|
||||
AliyunSslClient,
|
||||
createCertDomainGetterInputDefine,
|
||||
createRemoteSelectInputDefine,
|
||||
} from '@certd/plugin-plus';
|
||||
import { AliyunAccess, AliyunClient, AliyunSslClient, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib';
|
||||
import { AbstractPlusTaskPlugin } from '@certd/plugin-plus';
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: 'AliyunDeployCertToWaf',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline';
|
||||
import { AliyunAccess } from '@certd/plugin-plus';
|
||||
import { AliyunSslClient } from '@certd/plugin-plus';
|
||||
import { AliyunAccess } from '@certd/plugin-lib';
|
||||
import { AliyunSslClient } from '@certd/plugin-lib';
|
||||
|
||||
/**
|
||||
* 华东1(杭州) cn-hangzhou cas.aliyuncs.com cas-vpc.cn-hangzhou.aliyuncs.com
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
|
||||
import { CertInfo, CertReader } from '@certd/plugin-cert';
|
||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-plus';
|
||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib';
|
||||
import { optionsUtils } from '@certd/basic/dist/utils/util.options.js';
|
||||
|
||||
@IsTaskPlugin({
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from './ssh-access.js';
|
||||
@@ -1,105 +0,0 @@
|
||||
import { AccessInput, BaseAccess, IsAccess } from '@certd/pipeline';
|
||||
import { ConnectConfig } from 'ssh2';
|
||||
|
||||
@IsAccess({
|
||||
name: 'ssh',
|
||||
title: '主机登录授权',
|
||||
desc: '',
|
||||
input: {},
|
||||
})
|
||||
export class SshAccess extends BaseAccess implements ConnectConfig {
|
||||
@AccessInput({
|
||||
title: '主机地址',
|
||||
component: {
|
||||
placeholder: '主机域名或IP地址',
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
host!: string;
|
||||
@AccessInput({
|
||||
title: '端口',
|
||||
value: 22,
|
||||
component: {
|
||||
name: 'a-input-number',
|
||||
placeholder: '22',
|
||||
},
|
||||
rules: [{ required: true, message: '此项必填' }],
|
||||
})
|
||||
port!: number;
|
||||
@AccessInput({
|
||||
title: '用户名',
|
||||
value: 'root',
|
||||
rules: [{ required: true, message: '此项必填' }],
|
||||
})
|
||||
username!: string;
|
||||
@AccessInput({
|
||||
title: '密码',
|
||||
component: {
|
||||
name: 'a-input-password',
|
||||
vModel: 'value',
|
||||
},
|
||||
encrypt: true,
|
||||
helper: '登录密码或密钥必填一项',
|
||||
})
|
||||
password!: string;
|
||||
@AccessInput({
|
||||
title: '私钥登录',
|
||||
helper: '私钥或密码必填一项',
|
||||
component: {
|
||||
name: 'a-textarea',
|
||||
vModel: 'value',
|
||||
},
|
||||
encrypt: true,
|
||||
})
|
||||
privateKey!: string;
|
||||
|
||||
@AccessInput({
|
||||
title: '私钥密码',
|
||||
helper: '如果你的私钥有密码的话',
|
||||
component: {
|
||||
name: 'a-input-password',
|
||||
vModel: 'value',
|
||||
},
|
||||
encrypt: true,
|
||||
})
|
||||
passphrase!: string;
|
||||
|
||||
@AccessInput({
|
||||
title: 'socks代理',
|
||||
helper: 'socks代理配置,格式:socks5://user:password@host:port',
|
||||
component: {
|
||||
name: 'a-input',
|
||||
vModel: 'value',
|
||||
placeholder: 'socks5://user:password@host:port',
|
||||
},
|
||||
encrypt: false,
|
||||
})
|
||||
socksProxy!: string;
|
||||
|
||||
@AccessInput({
|
||||
title: '是否Windows',
|
||||
helper: '如果是Windows主机,请勾选此项\n并且需要windows[安装OpenSSH](https://certd.docmirror.cn/guide/use/host/windows.html)',
|
||||
component: {
|
||||
name: 'a-switch',
|
||||
vModel: 'checked',
|
||||
},
|
||||
})
|
||||
windows = false;
|
||||
|
||||
@AccessInput({
|
||||
title: '命令编码',
|
||||
helper: '如果是Windows主机,且出现乱码了,请尝试设置为GBK',
|
||||
component: {
|
||||
name: 'a-select',
|
||||
vModel: 'value',
|
||||
options: [
|
||||
{ value: '', label: '默认' },
|
||||
{ value: 'GBK', label: 'GBK' },
|
||||
{ value: 'UTF8', label: 'UTF-8' },
|
||||
],
|
||||
},
|
||||
})
|
||||
encoding: string;
|
||||
}
|
||||
|
||||
new SshAccess();
|
||||
@@ -1,3 +1 @@
|
||||
export * from './access/index.js';
|
||||
export * from './lib/ssh.js';
|
||||
export * from './plugin/index.js';
|
||||
|
||||
@@ -1,368 +0,0 @@
|
||||
// @ts-ignore
|
||||
import ssh2, { ConnectConfig, ExecOptions } from 'ssh2';
|
||||
import path from 'path';
|
||||
import * as _ from 'lodash-es';
|
||||
import { ILogger } from '@certd/basic';
|
||||
import { SshAccess } from '../access/index.js';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
import { SocksClient } from 'socks';
|
||||
import { SocksProxy, SocksProxyType } from 'socks/typings/common/constants.js';
|
||||
|
||||
export class AsyncSsh2Client {
|
||||
conn: ssh2.Client;
|
||||
logger: ILogger;
|
||||
connConf: SshAccess & ssh2.ConnectConfig;
|
||||
windows = false;
|
||||
encoding: string;
|
||||
constructor(connConf: SshAccess, logger: ILogger) {
|
||||
this.connConf = connConf;
|
||||
this.logger = logger;
|
||||
this.windows = connConf.windows || false;
|
||||
this.encoding = connConf.encoding;
|
||||
}
|
||||
|
||||
convert(iconv: any, buffer: Buffer) {
|
||||
if (this.encoding) {
|
||||
return iconv.decode(buffer, this.encoding);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
async connect() {
|
||||
this.logger.info(`开始连接,${this.connConf.host}:${this.connConf.port}`);
|
||||
if (this.connConf.socksProxy) {
|
||||
this.logger.info(`使用代理${this.connConf.socksProxy}`);
|
||||
if (typeof this.connConf.port === 'string') {
|
||||
this.connConf.port = parseInt(this.connConf.port);
|
||||
}
|
||||
const proxyOption: SocksProxy = this.parseSocksProxyFromUri(this.connConf.socksProxy);
|
||||
const info = await SocksClient.createConnection({
|
||||
proxy: proxyOption,
|
||||
command: 'connect',
|
||||
destination: {
|
||||
host: this.connConf.host,
|
||||
port: this.connConf.port,
|
||||
},
|
||||
});
|
||||
this.logger.info('代理连接成功');
|
||||
this.connConf.sock = info.socket;
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const conn = new ssh2.Client();
|
||||
conn
|
||||
.on('error', (err: any) => {
|
||||
this.logger.error('连接失败', err);
|
||||
reject(err);
|
||||
})
|
||||
.on('ready', () => {
|
||||
this.logger.info('连接成功');
|
||||
this.conn = conn;
|
||||
resolve(this.conn);
|
||||
})
|
||||
.connect(this.connConf);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
async getSftp() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.logger.info('获取sftp');
|
||||
this.conn.sftp((err: any, sftp: any) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(sftp);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async fastPut(options: { sftp: any; localPath: string; remotePath: string }) {
|
||||
const { sftp, localPath, remotePath } = options;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.logger.info(`开始上传:${localPath} => ${remotePath}`);
|
||||
sftp.fastPut(localPath, remotePath, (err: Error) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
this.logger.error('请确认路径是否包含文件名,路径本身不能是目录,路径不能有*?之类的特殊符号,要有写入权限');
|
||||
return;
|
||||
}
|
||||
this.logger.info(`上传文件成功:${localPath} => ${remotePath}`);
|
||||
resolve({});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async exec(script: string) {
|
||||
if (!script) {
|
||||
this.logger.info('script 为空,取消执行');
|
||||
return;
|
||||
}
|
||||
let iconv: any = await import('iconv-lite');
|
||||
iconv = iconv.default;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.logger.info(`执行命令:[${this.connConf.host}][exec]: \n` + script);
|
||||
this.conn.exec(script, (err: Error, stream: any) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
let data = '';
|
||||
stream
|
||||
.on('close', (code: any, signal: any) => {
|
||||
this.logger.info(`[${this.connConf.host}][close]:code:${code}`);
|
||||
if (code === 0) {
|
||||
resolve(data);
|
||||
} else {
|
||||
reject(new Error(data));
|
||||
}
|
||||
})
|
||||
.on('data', (ret: Buffer) => {
|
||||
const out = this.convert(iconv, ret);
|
||||
data += out;
|
||||
this.logger.info(`[${this.connConf.host}][info]: ` + out.trimEnd());
|
||||
})
|
||||
.on('error', (err: any) => {
|
||||
reject(err);
|
||||
this.logger.error(err);
|
||||
})
|
||||
.stderr.on('data', (ret: Buffer) => {
|
||||
const err = this.convert(iconv, ret);
|
||||
data += err;
|
||||
this.logger.info(`[${this.connConf.host}][error]: ` + err.trimEnd());
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async shell(script: string | string[]): Promise<string[]> {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
this.logger.info(`执行shell脚本:[${this.connConf.host}][shell]: ` + script);
|
||||
this.conn.shell((err: Error, stream: any) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
const output: string[] = [];
|
||||
function ansiHandle(data: string) {
|
||||
data = data.replace(/\[[0-9]+;1H/g, '\n');
|
||||
data = stripAnsi(data);
|
||||
return data;
|
||||
}
|
||||
stream
|
||||
.on('close', () => {
|
||||
this.logger.info('Stream :: close');
|
||||
resolve(output);
|
||||
})
|
||||
.on('data', (ret: Buffer) => {
|
||||
const data = ansiHandle(ret.toString());
|
||||
this.logger.info(data);
|
||||
output.push(data);
|
||||
})
|
||||
.on('error', (err: any) => {
|
||||
reject(err);
|
||||
this.logger.error(err);
|
||||
})
|
||||
.stderr.on('data', (ret: Buffer) => {
|
||||
const data = ansiHandle(ret.toString());
|
||||
output.push(data);
|
||||
this.logger.info(`[${this.connConf.host}][error]: ` + data);
|
||||
});
|
||||
//保证windows下正常退出
|
||||
const exit = '\r\nexit\r\n';
|
||||
stream.end(script + exit);
|
||||
});
|
||||
});
|
||||
}
|
||||
end() {
|
||||
if (this.conn) {
|
||||
this.conn.end();
|
||||
this.conn.destroy();
|
||||
this.conn = null;
|
||||
}
|
||||
}
|
||||
|
||||
private parseSocksProxyFromUri(socksProxyUri: string): SocksProxy {
|
||||
const url = new URL(socksProxyUri);
|
||||
let type: SocksProxyType = 5;
|
||||
if (url.protocol.startsWith('socks4')) {
|
||||
type = 4;
|
||||
}
|
||||
const proxy: SocksProxy = {
|
||||
host: url.hostname,
|
||||
port: parseInt(url.port),
|
||||
type,
|
||||
};
|
||||
if (url.username) {
|
||||
proxy.userId = url.username;
|
||||
}
|
||||
if (url.password) {
|
||||
proxy.password = url.password;
|
||||
}
|
||||
return proxy;
|
||||
}
|
||||
}
|
||||
|
||||
export class SshClient {
|
||||
logger: ILogger;
|
||||
constructor(logger: ILogger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param connectConf
|
||||
{
|
||||
host: '192.168.100.100',
|
||||
port: 22,
|
||||
username: 'frylock',
|
||||
password: 'nodejsrules'
|
||||
}
|
||||
* @param options
|
||||
*/
|
||||
async uploadFiles(options: { connectConf: SshAccess; transports: any; mkdirs: boolean }) {
|
||||
const { connectConf, transports, mkdirs } = options;
|
||||
await this._call({
|
||||
connectConf,
|
||||
callable: async (conn: AsyncSsh2Client) => {
|
||||
const sftp = await conn.getSftp();
|
||||
this.logger.info('开始上传');
|
||||
for (const transport of transports) {
|
||||
if (mkdirs !== false) {
|
||||
const filePath = path.dirname(transport.remotePath);
|
||||
let mkdirCmd = `mkdir -p ${filePath} `;
|
||||
if (conn.windows) {
|
||||
if (filePath.indexOf('/') > -1) {
|
||||
this.logger.info('--------------------------');
|
||||
this.logger.info('请注意:windows下,文件目录分隔应该写成\\而不是/');
|
||||
this.logger.info('--------------------------');
|
||||
}
|
||||
const isCmd = await this.isCmd(conn);
|
||||
if (!isCmd) {
|
||||
mkdirCmd = `New-Item -ItemType Directory -Path "${filePath}" -Force`;
|
||||
} else {
|
||||
mkdirCmd = `if not exist "${filePath}" mkdir "${filePath}"`;
|
||||
}
|
||||
}
|
||||
await conn.exec(mkdirCmd);
|
||||
}
|
||||
await conn.fastPut({ sftp, ...transport });
|
||||
}
|
||||
this.logger.info('文件全部上传成功');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async isCmd(conn: AsyncSsh2Client) {
|
||||
const spec = await conn.exec('echo %COMSPEC%');
|
||||
if (spec.toString().trim() === '%COMSPEC%') {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Set-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
|
||||
* Start-Service sshd
|
||||
*
|
||||
* Set-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\cmd.exe"
|
||||
* @param options
|
||||
*/
|
||||
async exec(options: { connectConf: SshAccess; script: string | Array<string>; env?: any }): Promise<string[]> {
|
||||
let { script } = options;
|
||||
const { connectConf } = options;
|
||||
|
||||
// this.logger.info('命令:', script);
|
||||
return await this._call({
|
||||
connectConf,
|
||||
callable: async (conn: AsyncSsh2Client) => {
|
||||
let isWinCmd = false;
|
||||
const isLinux = !connectConf.windows;
|
||||
const envScripts = [];
|
||||
if (connectConf.windows) {
|
||||
isWinCmd = await this.isCmd(conn);
|
||||
}
|
||||
|
||||
if (options.env) {
|
||||
for (const key in options.env) {
|
||||
if (isLinux) {
|
||||
envScripts.push(`export ${key}=${options.env[key]}`);
|
||||
} else if (isWinCmd) {
|
||||
//win cmd
|
||||
envScripts.push(`set ${key}=${options.env[key]}`);
|
||||
} else {
|
||||
//powershell
|
||||
envScripts.push(`$env:${key}="${options.env[key]}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isWinCmd) {
|
||||
//组合成&&的形式
|
||||
if (typeof script === 'string') {
|
||||
script = script.split('\n');
|
||||
}
|
||||
script = envScripts.concat(script);
|
||||
script = script as Array<string>;
|
||||
script = script.join(' && ');
|
||||
} else {
|
||||
if (_.isArray(script)) {
|
||||
script = script as Array<string>;
|
||||
script = script.join('\n');
|
||||
}
|
||||
if (envScripts.length > 0) {
|
||||
script = envScripts.join('\n') + '\n' + script;
|
||||
}
|
||||
}
|
||||
await conn.exec(script);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
//废弃
|
||||
async shell(options: { connectConf: SshAccess; script: string | Array<string> }): Promise<string[]> {
|
||||
let { script } = options;
|
||||
const { connectConf } = options;
|
||||
if (_.isArray(script)) {
|
||||
script = script as Array<string>;
|
||||
if (connectConf.windows) {
|
||||
script = script.join('\r\n');
|
||||
} else {
|
||||
script = script.join('\n');
|
||||
}
|
||||
} else {
|
||||
if (connectConf.windows) {
|
||||
script = script.replaceAll('\n', '\r\n');
|
||||
}
|
||||
}
|
||||
return await this._call({
|
||||
connectConf,
|
||||
callable: async (conn: AsyncSsh2Client) => {
|
||||
return await conn.shell(script as string);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async _call(options: { connectConf: SshAccess; callable: any }): Promise<string[]> {
|
||||
const { connectConf, callable } = options;
|
||||
const conn = new AsyncSsh2Client(connectConf, this.logger);
|
||||
try {
|
||||
await conn.connect();
|
||||
} catch (e: any) {
|
||||
if (e.message?.indexOf('All configured authentication methods failed') > -1) {
|
||||
this.logger.error(e);
|
||||
throw new Error('登录失败,请检查用户名/密码/密钥是否正确');
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
try {
|
||||
return await callable(conn);
|
||||
} finally {
|
||||
conn.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
|
||||
import { SshClient } from '../../lib/ssh.js';
|
||||
import { SshClient } from '@certd/plugin-lib';
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: 'hostShellExecute',
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline';
|
||||
import { SshClient } from '../../lib/ssh.js';
|
||||
import { CertInfo, CertReader, CertReaderHandleContext } from '@certd/plugin-cert';
|
||||
import * as fs from 'fs';
|
||||
import { SshAccess } from '../../access/index.js';
|
||||
import dayjs from 'dayjs';
|
||||
import { SshAccess, SshClient } from '@certd/plugin-lib';
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: 'uploadCertToHost',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
|
||||
import { HuaweiAccess } from '../../access/index.js';
|
||||
import { CertInfo } from '@certd/plugin-cert';
|
||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-plus';
|
||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib';
|
||||
import { resetLogConfigure } from '@certd/basic';
|
||||
|
||||
@IsTaskPlugin({
|
||||
|
||||
@@ -2,10 +2,10 @@ import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipel
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import dayjs from 'dayjs';
|
||||
import { SshAccess, SshClient } from '../../plugin-host/index.js';
|
||||
import { AbstractPlusTaskPlugin } from '@certd/plugin-plus';
|
||||
import JSZip from 'jszip';
|
||||
import * as os from 'node:os';
|
||||
import { SshAccess, SshClient } from '@certd/plugin-lib';
|
||||
|
||||
const defaultBackupDir = 'certd_backup';
|
||||
const defaultFilePrefix = 'db-backup';
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
|
||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine, QiniuAccess, QiniuClient } from '@certd/plugin-plus';
|
||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib';
|
||||
import { CertInfo } from '@certd/plugin-cert';
|
||||
import { optionsUtils } from '@certd/basic/dist/utils/util.options.js';
|
||||
import { QiniuAccess, QiniuClient } from '@certd/plugin-plus';
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: 'QiniuDeployCertToCDN',
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
|
||||
import { CertInfo } from '@certd/plugin-cert';
|
||||
import { AbstractPlusTaskPlugin } from '@certd/plugin-plus';
|
||||
import { SshAccess, SshClient } from '../../plugin-host/index.js';
|
||||
import { tmpdir } from 'node:os';
|
||||
import fs from 'fs';
|
||||
import { SshAccess, SshClient } from '@certd/plugin-lib';
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: 'QnapDeploy',
|
||||
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
|
||||
import { createRemoteSelectInputDefine, TencentAccess } from '@certd/plugin-plus';
|
||||
import { TencentAccess } from '@certd/plugin-plus';
|
||||
import { CertInfo } from '@certd/plugin-cert';
|
||||
import { TencentSslClient } from '../../lib/index.js';
|
||||
import { createRemoteSelectInputDefine } from '@certd/plugin-lib';
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: 'TencentDeployCertToCDNv2',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
|
||||
import { CertInfo } from '@certd/plugin-cert';
|
||||
import { createRemoteSelectInputDefine } from '@certd/plugin-plus';
|
||||
import { createRemoteSelectInputDefine } from '@certd/plugin-lib';
|
||||
import { TencentSslClient } from '../../lib/index.js';
|
||||
|
||||
@IsTaskPlugin({
|
||||
|
||||
Reference in New Issue
Block a user