mirror of https://github.com/fantasticit/think.git
commit
2cce76efe9
|
@ -15,7 +15,7 @@ COPY --from=builder /app/output/ /app/
|
|||
WORKDIR /app
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
|
||||
RUN npm config set registry https://registry.npmmirror.com
|
||||
RUN set -x \
|
||||
RUN set -x \
|
||||
&& apk update \
|
||||
&& apk add --no-cache tzdata redis \
|
||||
&& chmod +x /app/docker/start.sh \
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
#! /bin/bash
|
||||
# 该脚本只保留生产环境运行所需文件到统一目录
|
||||
if [ ! -f './config/prod.yaml' ]; then
|
||||
echo "缺少 config/prod.yaml 文件,可参考 docker-prod-sample.yaml 进行配置"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 构建
|
||||
pnpm fetch --prod
|
||||
pnpm install
|
||||
pnpm run build
|
||||
|
||||
|
@ -71,7 +76,7 @@ cd ../../
|
|||
# @see https://github.com/typicode/husky/issues/914#issuecomment-826768549
|
||||
cd ${outputDir}
|
||||
npm set-script prepare ""
|
||||
pnpm install -r --prod
|
||||
pnpm install -r --offline --prod
|
||||
cd ../
|
||||
|
||||
echo "${outputDir} 打包完成"
|
||||
|
|
|
@ -29,9 +29,9 @@ server:
|
|||
user: ''
|
||||
password: ''
|
||||
admin:
|
||||
name: 'sytemadmin' # 注意修改
|
||||
password: 'sytemadmin' # 注意修改
|
||||
email: 'sytemadmin@think.com' # 注意修改为真实邮箱地址
|
||||
name: 'admin' # 注意修改
|
||||
password: 'admin' # 注意修改
|
||||
email: 'admin@think.com' # 注意修改为真实邮箱地址
|
||||
|
||||
# 数据库配置
|
||||
db:
|
||||
|
|
|
@ -29,9 +29,9 @@ server:
|
|||
user: ''
|
||||
password: ''
|
||||
admin:
|
||||
name: 'sytemadmin' # 注意修改
|
||||
password: 'sytemadmin' # 注意修改
|
||||
email: 'sytemadmin@think.com' # 注意修改为真实邮箱地址
|
||||
name: 'admin' # 注意修改
|
||||
password: 'admin' # 注意修改
|
||||
email: 'admin@think.com' # 注意修改为真实邮箱地址
|
||||
|
||||
# 数据库配置
|
||||
db:
|
||||
|
|
|
@ -2,13 +2,7 @@
|
|||
### Author:jonnyan404
|
||||
### date:2022年5月22日
|
||||
|
||||
CONFIG_FILE='/app/config/prod.yaml'
|
||||
|
||||
if [ ! -f $CONFIG_FILE ]; then
|
||||
cp -f /app/config/docker-prod-sample.yaml $CONFIG_FILE
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
|
||||
pnpm run pm2
|
||||
pm2 startup
|
||||
pm2 save
|
||||
pm2 logs
|
||||
|
|
|
@ -76,7 +76,7 @@ pm2 save
|
|||
|
||||
### docker-compose
|
||||
|
||||
也可以使用 docker-compose 进行项目部署。首先,根据需要修改 `docker-compose.yml` 中的数据库、Redis 相关用户名、密码等配置,然后,修改 `config/docker-prod-sample.yaml` 中对应的配置。
|
||||
也可以使用 docker-compose 进行项目部署。首先,根据需要修改 `docker-compose.yml` 中的数据库、Redis 相关用户名、密码等配置,然后,从 `config/docker-prod-sample.yaml` 复制出 `config/prod.yaml` 并修改其中对应的配置。
|
||||
|
||||
```bash
|
||||
# 首次安装
|
||||
|
|
|
@ -43,16 +43,23 @@ server {
|
|||
}
|
||||
|
||||
location /think/wss {
|
||||
proxy_pass http://think_wss;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_pass http://think_wss;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
}
|
||||
|
||||
location /static/ {
|
||||
gzip_static on;
|
||||
expires max;
|
||||
add_header Cache-Control public;
|
||||
alias /apps/think/packages/server/static/;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export const isEmail = (email) => {
|
||||
return !!String(email)
|
||||
.toLowerCase()
|
||||
.match(
|
||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
);
|
||||
};
|
|
@ -28,7 +28,7 @@ const Page: NextPage = () => {
|
|||
|
||||
return (
|
||||
<SingleColumnLayout>
|
||||
<Seo title="管理后台" />
|
||||
<Seo title="管理后台" key={tab} />
|
||||
<div className="container">
|
||||
{user && user.isSystemAdmin ? (
|
||||
<>
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
position: relative;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
height: calc(100% - 52px);
|
||||
padding: 10vh 24px;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -3,11 +3,13 @@ import { Author } from 'components/author';
|
|||
import { LogoImage, LogoText } from 'components/logo';
|
||||
import { Seo } from 'components/seo';
|
||||
import { useRegister, useVerifyCode } from 'data/user';
|
||||
import { isEmail } from 'helpers/validator';
|
||||
import { useInterval } from 'hooks/use-interval';
|
||||
import { useRouterQuery } from 'hooks/use-router-query';
|
||||
import { useToggle } from 'hooks/use-toggle';
|
||||
import Link from 'next/link';
|
||||
import Router from 'next/router';
|
||||
import { emit } from 'process';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
|
@ -25,7 +27,13 @@ const Page = () => {
|
|||
const { sendVerifyCode, loading: sendVerifyCodeLoading } = useVerifyCode();
|
||||
|
||||
const onFormChange = useCallback((formState) => {
|
||||
setEmail(formState.values.email);
|
||||
const email = formState.values.email;
|
||||
|
||||
if (isEmail(email)) {
|
||||
setEmail(email);
|
||||
} else {
|
||||
setEmail(null);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const { start, stop } = useInterval(() => {
|
||||
|
@ -89,6 +97,7 @@ const Page = () => {
|
|||
<Title type="tertiary" heading={5} style={{ marginBottom: 16, textAlign: 'center' }}>
|
||||
用户注册
|
||||
</Title>
|
||||
|
||||
<Form.Input
|
||||
noLabel
|
||||
field="name"
|
||||
|
@ -97,6 +106,7 @@ const Page = () => {
|
|||
placeholder="输入账户名称"
|
||||
rules={[{ required: true, message: '请输入账户' }]}
|
||||
></Form.Input>
|
||||
|
||||
<Form.Input
|
||||
noLabel
|
||||
mode="password"
|
||||
|
@ -106,15 +116,6 @@ const Page = () => {
|
|||
placeholder="输入用户密码"
|
||||
rules={[{ required: true, message: '请输入密码' }]}
|
||||
></Form.Input>
|
||||
<Form.Input
|
||||
noLabel
|
||||
mode="password"
|
||||
field="confirmPassword"
|
||||
label="密码"
|
||||
style={{ width: '100%' }}
|
||||
placeholder="确认用户密码"
|
||||
rules={[{ required: true, message: '请再次输入密码' }]}
|
||||
></Form.Input>
|
||||
|
||||
<Form.Input
|
||||
noLabel
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { IsEmail, IsNotEmpty, IsOptional, IsString, MaxLength, MinLength } from 'class-validator';
|
||||
import { IsEmail, IsNotEmpty, IsString, MaxLength, MinLength } from 'class-validator';
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
|
@ -15,11 +15,6 @@ export class RegisterUserDto {
|
|||
@IsNotEmpty({ message: '用户密码不能为空' })
|
||||
password: string;
|
||||
|
||||
@MinLength(5, { message: '用户二次确认密码至少5个字符' })
|
||||
@IsString({ message: '用户二次确认密码类型错误(正确类型为:String)' })
|
||||
@IsNotEmpty({ message: '用户二次确认密码不能为空' })
|
||||
confirmPassword: string;
|
||||
|
||||
@IsEmail({ message: '请输入正确的邮箱地址' })
|
||||
@IsString({ message: '用户邮箱类型错误(正确类型为:String)' })
|
||||
@IsNotEmpty({ message: '用户邮箱不能为空' })
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
import { getShortId } from '@helpers/shortid.herlper';
|
||||
import { DocumentStatus } from '@think/domains';
|
||||
import { Exclude } from 'class-transformer';
|
||||
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
|
||||
import { BeforeInsert, Column, CreateDateColumn, Entity, PrimaryColumn, UpdateDateColumn } from 'typeorm';
|
||||
|
||||
@Entity('document')
|
||||
export class DocumentEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
@BeforeInsert()
|
||||
getShortId() {
|
||||
this.id = getShortId();
|
||||
}
|
||||
|
||||
@PrimaryColumn()
|
||||
public id: string;
|
||||
|
||||
@Column({ type: 'varchar', comment: '文档所属知识库 Id' })
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
import { getShortId } from '@helpers/shortid.herlper';
|
||||
import { DEFAULT_WIKI_AVATAR } from '@think/constants';
|
||||
import { WikiStatus } from '@think/domains';
|
||||
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
|
||||
import { BeforeInsert, Column, CreateDateColumn, Entity, PrimaryColumn, UpdateDateColumn } from 'typeorm';
|
||||
|
||||
@Entity('wiki')
|
||||
export class WikiEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
@BeforeInsert()
|
||||
getShortId() {
|
||||
this.id = getShortId();
|
||||
}
|
||||
|
||||
@PrimaryColumn()
|
||||
public id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 200, comment: '知识库名称' })
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
import { randomFillSync } from 'node:crypto';
|
||||
|
||||
const urlAlphabet = 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict';
|
||||
|
||||
// It is best to make fewer, larger requests to the crypto module to
|
||||
// avoid system call overhead. So, random numbers are generated in a
|
||||
// pool. The pool is a Buffer that is larger than the initial random
|
||||
// request size by this multiplier. The pool is enlarged if subsequent
|
||||
// requests exceed the maximum buffer size.
|
||||
const POOL_SIZE_MULTIPLIER = 128;
|
||||
let pool, poolOffset;
|
||||
|
||||
const fillPool = (bytes) => {
|
||||
if (!pool || pool.length < bytes) {
|
||||
pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
|
||||
randomFillSync(pool);
|
||||
poolOffset = 0;
|
||||
} else if (poolOffset + bytes > pool.length) {
|
||||
randomFillSync(pool);
|
||||
poolOffset = 0;
|
||||
}
|
||||
poolOffset += bytes;
|
||||
};
|
||||
|
||||
const nanoid = (size = 21) => {
|
||||
// `-=` convert `size` to number to prevent `valueOf` abusing
|
||||
fillPool((size -= 0));
|
||||
let id = '';
|
||||
// We are reading directly from the random pool to avoid creating new array
|
||||
for (let i = poolOffset - size; i < poolOffset; i++) {
|
||||
// It is incorrect to use bytes exceeding the alphabet size.
|
||||
// The following mask reduces the random byte in the 0-255 value
|
||||
// range to the 0-63 value range. Therefore, adding hacks, such
|
||||
// as empty string fallback or magic numbers, is unneccessary because
|
||||
// the bitmask trims bytes down to the alphabet size.
|
||||
id += urlAlphabet[pool[i] & 63];
|
||||
}
|
||||
return id;
|
||||
};
|
||||
|
||||
export const getShortId = () => {
|
||||
return nanoid(12);
|
||||
};
|
|
@ -125,10 +125,6 @@ export class UserService {
|
|||
throw new HttpException('该账户已被注册', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (user.password !== user.confirmPassword) {
|
||||
throw new HttpException('两次密码不一致,请重试', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (await this.userRepo.findOne({ name: user.name })) {
|
||||
throw new HttpException('该账户已被注册', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue