diff --git a/bookMarkDocker/Dockerfile b/bookMarkDocker/Dockerfile new file mode 100644 index 0000000..2b99e4e --- /dev/null +++ b/bookMarkDocker/Dockerfile @@ -0,0 +1,14 @@ +FROM registry.cn-hangzhou.aliyuncs.com/fleyx/node:v1 +COPY settings.xml /opt/settings.xml +RUN cd /opt && \ + wget https://download.java.net/openjdk/jdk11/ri/openjdk-11+28_linux-x64_bin.tar.gz && \ + tar -xvf openjdk-11+28_linux-x64_bin.tar.gz && \ + mv jdk-11 jdk11 && \ + rm openjdk-11+28_linux-x64_bin.tar.gz && \ + wget http://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/3.6.1/binaries/apache-maven-3.6.1-bin.tar.gz && \ + tar -xvf apache-maven-3.6.1-bin.tar.gz && \ + mv apache-maven-3.6.1 maven && \ + rm apache-maven-3.6.1-bin.tar.gz && \ + mv maven/conf/settings.xml maven/conf/settings.xml.bak && \ + mv settings.xml maven/conf/settings.xml +ENV PATH=$PATH:/opt/jdk11/bin:/opt/maven/bin diff --git a/bookMarkDocker/docker-compose.yml b/bookMarkDocker/docker-compose.yml index 7638f17..e2fea88 100644 --- a/bookMarkDocker/docker-compose.yml +++ b/bookMarkDocker/docker-compose.yml @@ -14,7 +14,7 @@ services: - /etc/localtime:/etc/localtime - ./timezone:/etc/timezone environment: - - MYSQL_ROOT_PASSWORD=${MYSQL_PASS} + - MYSQL_ROOT_PASSWORD= - MYSQL_DATABASE=bookmark bookmark-redis: image: redis:3.2.10 @@ -25,9 +25,49 @@ services: networks: - bookmark ports: - - 6380:6379 - - - + # redis未设置密码,如端口暴露可能会被攻击 + # - 6380:6379 + bookmark-front: + image: nginx + container_name: bookmark-front + networks: + - bookmark + volumes: + - /etc/localtime:/etc/localtime + - ./timezone:/etc/timezone + - ../front/build:/opt/dist + - ./nginx/nginx.conf:/etc/nginx/nginx.conf + - ./nginx/log:/var/log/nginx + ports: + - 8083:8080 + bookmark-service: + image: registry.cn-hangzhou.aliyuncs.com/fleyx/node-java-env:v1 + container_name: bookmark-service + depends_on: + - bookmark-mysql + - bookmark-redis + networks: + - bookmark + volumes: + - /etc/localtime:/etc/localtime + - ./timezone:/etc/timezone + - ../bookMarkService/web/target/web-1.0-SNAPSHOT.jar:/opt/app/service.jar + working_dir: /opt/app + command: + - /bin/bash + - -c + - | + sleep 20 && \ + java -jar -DisDev=false \ + -DjwtSecret= \ + -Dmybatis.configuration.log-impl=org.apache.ibatis.logging.nologging.NoLoggingImpl \ + -Dspring.mail.host= \ + -Dspring.mail.username= \ + -Dspring.mail.password= \ + -Dspring.mail.port= \ + -Dspring.datasource.druid.password= \ + -Dspring.datasource.druid.url=jdbc:mysql://bookmark-mysql:3306/psn?useUnicode=true&characterEncoding=utf-8&useSSL=false \ + -Dspring.redis.host=bookmark-redis \ + service.jar networks: bookmark: \ No newline at end of file diff --git a/bookMarkDocker/init.sh b/bookMarkDocker/init.sh new file mode 100644 index 0000000..8524a35 --- /dev/null +++ b/bookMarkDocker/init.sh @@ -0,0 +1,6 @@ +#/bin/bash + +# 用于前后端打包 +docker run -it --rm --name buildBookmark -v ../front:/opt/front -v ../bookMarkService:/opt/backend registry.cn-hangzhou.aliyuncs.com/fleyx/node-java-env:v1 sh -c "cd /opt/front && npm --registry https://registry.npm.taobao.org install && npm run build && cd /opt/backend && mvn package" + +echo "打包完毕,export MYSQL_PASS=xxx 设置环境变量后执行docker-compose up -d即可" \ No newline at end of file diff --git a/bookMarkDocker/nginx/nginx.conf b/bookMarkDocker/nginx/nginx.conf new file mode 100644 index 0000000..905f1b8 --- /dev/null +++ b/bookMarkDocker/nginx/nginx.conf @@ -0,0 +1,48 @@ +user root; +worker_processes auto; +pid /run/nginx.pid; +events { + worker_connections 768; +} +http { +# Basic Settings + sendfile on; +# SSL Settings + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; +# Logging Settings + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; +# Gzip Settings + gzip on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png application/vnd.ms-fontobject font/ttf font/opentype font/x-woff image/svg+xml; + gzip_min_length 1K; + include /etc/nginx/mime.types; + default_type application/octet-stream; + ssl_prefer_server_ciphers on; + upstream bookmark-service{ + server bookmark-service:8088; + keepalive 2000; + } + server { + listen 8080; + listen [::]:8080; + index index.html; + root /opt/dist/; + server_name _; + location /bookmark/api/ { + proxy_pass http://bookmark-service; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + client_max_body_size 100m; + } + location / { + root /opt/dist; + index index.html; + try_files $uri $uri/ /index.html; + } + } +} \ No newline at end of file diff --git a/bookMarkDocker/settings.xml b/bookMarkDocker/settings.xml new file mode 100644 index 0000000..13c5ad0 --- /dev/null +++ b/bookMarkDocker/settings.xml @@ -0,0 +1,259 @@ + + + + + + + + /opt/mavenRep + + + + + + + + + + + + + + + + + + + + + + + + + + + + AliMaven + aliyun maven + http://maven.aliyun.com/nexus/content/groups/public/ + central + + + + + + + + + + + + + diff --git a/boomark.ndm b/bookmark.ndm similarity index 92% rename from boomark.ndm rename to bookmark.ndm index b83d40a..147501c 100644 Binary files a/boomark.ndm and b/bookmark.ndm differ diff --git a/front/.gitignore b/front/.gitignore index 4a5ec65..66c8e91 100644 --- a/front/.gitignore +++ b/front/.gitignore @@ -21,4 +21,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -.vscode \ No newline at end of file +.vscode +yarn.lock \ No newline at end of file diff --git a/front/package.json b/front/package.json index 206adfd..a68aea4 100644 --- a/front/package.json +++ b/front/package.json @@ -7,6 +7,7 @@ "antd": "^3.19.8", "axios": "^0.19.0", "babel-plugin-import": "^1.12.0", + "clipboard": "^2.0.4", "customize-cra": "^0.2.14", "less": "^3.9.0", "query-string": "^6.8.1", @@ -42,4 +43,4 @@ "devDependencies": { "less-loader": "^5.0.0" } -} \ No newline at end of file +} diff --git a/front/public/index.html b/front/public/index.html index b1a5133..dcf6794 100644 --- a/front/public/index.html +++ b/front/public/index.html @@ -19,7 +19,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - React App + 签签世界 @@ -34,5 +34,6 @@ To begin the development, run `npm start` or `yarn start`. To create a production bundle, use `npm run build` or `yarn build`. --> + diff --git a/front/src/App.jsx b/front/src/App.jsx index c61ac68..df81037 100644 --- a/front/src/App.jsx +++ b/front/src/App.jsx @@ -1,7 +1,9 @@ import React, { Component } from "react"; import { Route, Switch, Redirect } from "react-router-dom"; +import { message } from "antd"; import { withRouter } from "react-router-dom"; import { Provider } from "react-redux"; +import Clipboard from "clipboard"; import store from "./redux"; import NotFound from "./pages/public/notFound/NotFound"; @@ -17,6 +19,15 @@ class App extends Component { window.reactHistory = this.props.history; } + componentDidMount() { + //初始化clipboard + let clipboard = new Clipboard(".copy-to-board"); + clipboard.on("success", function(e) { + message.success("复制成功"); + e.clearSelection(); + }); + } + render() { const mainStyle = { fontSize: "0.14rem" diff --git a/front/src/pages/manage/OverView/AddModal.jsx b/front/src/pages/manage/OverView/AddModal.jsx new file mode 100644 index 0000000..ccfa831 --- /dev/null +++ b/front/src/pages/manage/OverView/AddModal.jsx @@ -0,0 +1,111 @@ +import React from "react"; +import { Modal, Form, Upload, Button, Radio, Input, Icon, message } from "antd"; +import httpUtil from "../../../util/httpUtil"; + +export default class AddModal extends React.Component { + constructor(props) { + super(props); + this.state = { + addType: 0, + addName: "", + addValue: "", + file: null + }; + } + + addOne = () => { + const { currentAddFolder, addToTree, closeModal } = this.props; + const path = currentAddFolder == null ? "" : currentAddFolder.path + "." + currentAddFolder.bookmarkId; + if (this.state.addType === 2) { + const form = new FormData(); + form.append("path", path); + form.append("file", this.state.file.originFileObj); + httpUtil + .put("/bookmark/uploadBookmarkFile", form, { + headers: { "Content-Type": "multipart/form-data" } + }) + .then(res => { + window.location.reload(); + }); + } else { + let body = { + type: this.state.addType, + path, + name: this.state.addName, + url: this.state.addValue + }; + httpUtil.put("/bookmark", body).then(res => { + addToTree(res); + message.success("加入成功"); + closeModal(); + }); + } + }; + + close = () => { + const { closeModal } = this.props; + this.setState({ file: null, addType: 0, addValue: "", addName: "" }); + closeModal(); + }; + + render() { + const { isShowModal } = this.props; + const { addType, addName, addValue } = this.state; + const formItemLayout = { + labelCol: { + xs: { span: 4 }, + sm: { span: 4 } + }, + wrapperCol: { + xs: { span: 20 }, + sm: { span: 20 } + } + }; + const uploadProps = { + accept: ".html,.htm", + onChange: e => { + this.setState({ file: e.fileList[0] }); + }, + beforeUpload: file => { + return false; + }, + fileList: [] + }; + return ( + +
+ + this.setState({ addType: e.target.value })}> + 书签 + 文件夹 + 上传书签html + + + {addType < 2 ? ( + + this.setState({ addName: e.target.value })} value={addName} /> + + ) : null} + {addType === 0 ? ( + + this.setState({ addValue: e.target.value })} /> + + ) : null} + {addType === 2 ? ( + + + + ) : null} +
+ +
+
+
+ ); + } +} diff --git a/front/src/pages/manage/OverView/function.js b/front/src/pages/manage/OverView/function.js index 0f1618c..6f1b8bc 100644 --- a/front/src/pages/manage/OverView/function.js +++ b/front/src/pages/manage/OverView/function.js @@ -1,5 +1,9 @@ import httpUtil from "../../../util/httpUtil"; -import { Modal, message } from "antd"; +import React from "react"; +import { Modal, Button, Tooltip, Tree } from "antd"; +import styles from "./index.module.less"; +import IconFont from "../../../components/IconFont"; +const { TreeNode } = Tree; /** * 选中的文件夹id列表 @@ -10,11 +14,6 @@ let folderIdList = []; */ let bookmarkIdList = []; -/** - * 新增书签的父节点node - */ -let parentNode = null; - /** * 展开/关闭 * @param {*} keys @@ -39,71 +38,108 @@ export function onCheck(keys, data) { this.setState({ checkedKeys: keys }); bookmarkIdList = []; folderIdList = []; - parentNode = null; data.checkedNodes.forEach(item => { const bookmark = item.props.dataRef; - parentNode = bookmark; bookmark.type === 0 ? bookmarkIdList.push(bookmark.bookmarkId) : folderIdList.push(bookmark.bookmarkId); }); } /** - * 弹出新增modal + * 渲染树节点中节点内容 + * @param {*} item */ -export function showAddModel() { - if (this.state.checkedKeys.length > 1) { - message.error("选中过多"); - return; - } else if (this.state.checkedKeys.length === 1) { - const id = this.state.checkedKeys[0]; - if (bookmarkIdList.indexOf(parseInt(id)) > -1) { - message.error("只能选择文件夹节点"); - return; - } - } - this.setState({ isShowModal: true }); +export function renderNodeContent(item) { + const { isEdit } = this.state; + // 节点内容后面的操作按钮 + const btns = ( +
+ {item.type === 0 ? ( +
+ ); + return ( + + + {item.name} + + {isEdit ? btns : null} + + ); } /** - * 新增书签 + * 渲染树节点 + * @param {*} items */ -export function addOne() { - console.log(1); - if (this.state.addType === 2) { - addHtmlFile(); - return; +export function renderTreeNodes(items) { + if (!(items && items.length >= 0)) { + return null; } - let body = { - type: this.state.addType, - path: parentNode == null ? "" : parentNode.path + "." + parentNode.bookmarkId, - name: this.state.addName, - url: this.state.addValue - }; - httpUtil.put("/bookmark", body).then(res => { - let arr; - if (parentNode == null) { - arr = this.data[""] ? this.data[""] : []; - } else { - arr = this.data[body.path] ? this.data[body.path] : []; + return items.map(item => { + const isLeaf = item.type === 0; + if (!isLeaf) { + return ( + } + isLeaf={isLeaf} + title={renderNodeContent.call(this, item)} + key={item.bookmarkId} + dataRef={item} + > + {renderTreeNodes.call(this, item.children)} + + ); } - arr.push(res); - if (this.state.treeData.length === 0) { - this.state.treeData.push(arr); - } - this.data[body.path] = arr; - this.setState({ treeData: [...this.state.treeData], addType: 0, addName: "", addValue: "", isShowModal: false }); + return ( + } + isLeaf={isLeaf} + title={renderNodeContent.call(this, item)} + key={item.bookmarkId} + dataRef={item} + /> + ); }); } -export function addHtmlFile() { - +/** + * 删除一个 + * @param {*} e + */ +export function deleteOne(item, e) { + e.stopPropagation(); + if (item.type === 0) { + deleteBookmark.call(this, [], [item.bookmarkId]); + } else { + deleteBookmark.call(this, [item.bookmarkId], []); + } } /** * 批量删除 */ export function batchDelete() { - console.log("1"); + deleteBookmark.call(this, folderIdList, bookmarkIdList); +} + +/** + * 删除书签 + * @param {*} folderIdList + * @param {*} bookmarkIdList + */ +function deleteBookmark(folderIdList, bookmarkIdList) { const _this = this; Modal.confirm({ title: "确认删除?", @@ -115,8 +151,8 @@ export function batchDelete() { .then(() => { //遍历节点树数据,并删除 const set = new Set(); - folderIdList.forEach(item => set.add(item)); - bookmarkIdList.forEach(item => set.add(item)); + folderIdList.forEach(item => set.add(parseInt(item))); + bookmarkIdList.forEach(item => set.add(parseInt(item))); deleteTreeData(_this.state.treeData, set); _this.setState({ treeData: [..._this.state.treeData], checkedKeys: [] }); resolve(); diff --git a/front/src/pages/manage/OverView/index.jsx b/front/src/pages/manage/OverView/index.jsx index 5820026..0e40c76 100644 --- a/front/src/pages/manage/OverView/index.jsx +++ b/front/src/pages/manage/OverView/index.jsx @@ -1,12 +1,10 @@ import React from "react"; -import { Icon, Tree, Empty, Button, Tooltip, Modal, Form, Input, Radio, Upload } from "antd"; +import { Tree, Empty, Button } from "antd"; import MainLayout from "../../../layout/MainLayout"; import httpUtil from "../../../util/httpUtil"; -import IconFont from "../../../components/IconFont"; import styles from "./index.module.less"; -import { batchDelete, onExpand, closeAll, onCheck, addOne, showAddModel } from "./function.js"; - -const { TreeNode } = Tree; +import { batchDelete, renderTreeNodes, onExpand, closeAll, onCheck } from "./function.js"; +import AddModal from "./AddModal"; export default class OverView extends React.Component { constructor(props) { @@ -17,131 +15,99 @@ export default class OverView extends React.Component { isLoading: true, checkedKeys: [], expandKeys: [], - //显示新增弹窗 + //是否显示新增书签弹窗 isShowModal: false, - //新增类别 - addType: 0, - addName: "", - addValue: "", - file: null + currentAddFolder: null }; } + /** + * 初始化第一级书签 + */ componentDidMount() { httpUtil - .get("/bookmark/currentUser") + .get("/bookmark/currentUser/path?path=") .then(res => { - this.data = res; - if (res[""]) { - this.setState({ treeData: res[""] }); - } - this.setState({ isLoading: false }); + this.setState({ treeData: res, isLoading: false }); }) .catch(() => this.setState({ isLoading: false })); } + /** + * 异步加载 + */ loadData = e => new Promise(resolve => { const item = e.props.dataRef; const newPath = item.path + "." + item.bookmarkId; - if (this.data[newPath]) { - item.children = this.data[newPath]; + httpUtil.get("/bookmark/currentUser/path?path=" + newPath).then(res => { + item.children = res; this.setState({ treeData: [...this.state.treeData] }); resolve(); - } else { - resolve(); - } + }); }); + /** + * 节点选择 + * @param {*} key + * @param {*} e + */ treeNodeSelect(key, e) { + if (e.nativeEvent.delegateTarget.name === "copy") { + return; + } const { expandKeys } = this.state; const item = e.node.props.dataRef; if (item.type === 0) { window.open(item.url); } else { - expandKeys.push(item.bookmarkId); + const id = item.bookmarkId.toString(); + const index = expandKeys.indexOf(id); + index === -1 ? expandKeys.push(id) : expandKeys.splice(index, 1); + this.setState({ expandKeys: [...expandKeys] }); } } /** - * 渲染树节点中节点内容 - * @param {*} item + * 将新增的数据加入到state中 */ - renderNodeContent(item) { - // const { isEdit } = this.state; - // const bts = ( - //
- //
- // ); - return ( - - - {item.name} - - {/* {isEdit ? bts : null} */} - - ); - } - - /** - * 渲染树节点 - * @param {*} items - */ - renderTreeNodes(items) { - if (!(items && items.length >= 0)) { - return null; - } - return items.map(item => { - const isLeaf = item.type === 0; - if (!isLeaf) { - return ( - } isLeaf={isLeaf} title={item.name} key={item.bookmarkId} dataRef={item}> - {this.renderTreeNodes(item.children)} - - ); + addToTree = node => { + const { currentAddFolder, treeData } = this.state; + if (currentAddFolder === null) { + treeData.push(node); + } else { + //存在children说明该子节点的孩子数据已经加载,需要重新将新的子书签加入进去 + if (currentAddFolder.children) { + currentAddFolder.children.push(node); + this.setState({ treeData: [...treeData] }); } - return ( - } - isLeaf={isLeaf} - title={this.renderNodeContent(item)} - key={item.bookmarkId} - dataRef={item} - /> - ); - }); - } + } + }; + + /** + * 关闭新增书签弹窗 + */ + closeAddModal = () => { + this.setState({ isShowModal: false }); + }; + + /** + * 新增书签 + */ + addOne = (item, e) => { + e.stopPropagation(); + this.setState({ currentAddFolder: item, isShowModal: true }); + }; render() { - const { isLoading, isEdit, treeData, expandKeys, checkedKeys, isShowModal, addType, addValue, addName } = this.state; - const formItemLayout = { - labelCol: { - xs: { span: 4 }, - sm: { span: 4 } - }, - wrapperCol: { - xs: { span: 20 }, - sm: { span: 20 } - } - }; - const uploadProps = { - accept: ".html,.htm", - onChange: e => { - this.setState({ file: e.fileList[0] }); - }, - beforeUpload: file => { - return false; - }, - fileList: [] - }; + const { isLoading, isEdit, treeData, expandKeys, checkedKeys, isShowModal, currentAddFolder } = this.state; return (
我的书签树 + {isEdit ? - ) : null}
- {/* {treeData.length ? ( */} - {this.renderTreeNodes(treeData)} + {renderTreeNodes.call(this, treeData)} - {/* ) : null} */} {isLoading === false && treeData.length === 0 ? : null} - - this.setState({ isShowModal: false })} footer={false}> -
- - this.setState({ addType: e.target.value })}> - 书签 - 文件夹 - 上传书签html - - - {addType < 2 ? ( - - this.setState({ addName: e.target.value })} value={addName} /> - - ) : null} - {addType === 0 ? ( - - this.setState({ addValue: e.target.value })} /> - - ) : null} - {addType === 2 ? ( - - - - ) : null} -
- -
-
-
+
); diff --git a/front/src/pages/manage/OverView/index.module.less b/front/src/pages/manage/OverView/index.module.less index e05ae03..ddcf867 100644 --- a/front/src/pages/manage/OverView/index.module.less +++ b/front/src/pages/manage/OverView/index.module.less @@ -28,7 +28,10 @@ .btns { display: inline-block; - position: relative; - top: -0.45em; + padding-left: 1em; + } + + .btns > button { + margin-right: 0.4em; } } diff --git a/front/src/pages/public/Login/index.jsx b/front/src/pages/public/Login/index.jsx index 20f7711..75a05c4 100644 --- a/front/src/pages/public/Login/index.jsx +++ b/front/src/pages/public/Login/index.jsx @@ -31,7 +31,11 @@ class Login extends Component { } valueChange = e => { - this.setState({ [e.target.name]: e.target.value }); + if (e.target.name === "rememberMe") { + this.setState({ rememberMe: e.target.checked }); + } else { + this.setState({ [e.target.name]: e.target.value }); + } }; submit = () => { @@ -81,7 +85,7 @@ class Login extends Component { placeholder="密码" />
- + 记住我 忘记密码