/*
Navicat MySQL Data Transfer

Source Server         : LocalMysql
Source Server Version : 50729
Source Host           : localhost:3306
Source Database       : graduation

Target Server Type    : MYSQL
Target Server Version : 50729
File Encoding         : 65001

Date: 2021-02-05 15:08:56
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for category
-- ----------------------------
DROP TABLE IF EXISTS `category`;
CREATE TABLE `category` (
  `cid` varchar(20) NOT NULL,
  `title` varchar(255) DEFAULT NULL,
  `status` varchar(1) DEFAULT '0' COMMENT '0 存在 1 删除',
  PRIMARY KEY (`cid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of category
-- ----------------------------
INSERT INTO `category` VALUES ('787413102411907072', 'Java', '0');
INSERT INTO `category` VALUES ('787413269127102464', 'JavaScript', '0');
INSERT INTO `category` VALUES ('787413340560293888', '数据结构与算法', '0');
INSERT INTO `category` VALUES ('787413645830127616', 'Vue.Js', '0');
INSERT INTO `category` VALUES ('787413694626660352', '后端', '0');
INSERT INTO `category` VALUES ('787417419063754752', '杂谈', '1');
INSERT INTO `category` VALUES ('792039069298196480', 'test1', '0');

-- ----------------------------
-- Table structure for comment
-- ----------------------------
DROP TABLE IF EXISTS `comment`;
CREATE TABLE `comment` (
  `id` varchar(20) NOT NULL COMMENT '评论id',
  `uid` varchar(20) DEFAULT NULL COMMENT '用户id',
  `content` text COMMENT '内容',
  `pid` varchar(20) DEFAULT NULL COMMENT '文章id',
  `is_true` varchar(1) DEFAULT '0',
  `tid` varchar(20) DEFAULT NULL COMMENT '父id',
  `publish_time` datetime DEFAULT NULL COMMENT '时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of comment
-- ----------------------------
INSERT INTO `comment` VALUES ('789836339578142720', '780837408789757952', 'hello', '787449216644415488', '1', null, '2020-12-19 12:47:38');
INSERT INTO `comment` VALUES ('789861045664481280', '780837408789757952', 'he咯哦', '789816400616095744', '0', null, '2020-12-19 14:25:48');
INSERT INTO `comment` VALUES ('789861071849521152', '780837408789757952', '你好呀', '789816400616095744', '0', '789861045664481280', '2020-12-19 14:25:54');
INSERT INTO `comment` VALUES ('789861098521100288', '780837408789757952', '哈哈', '789816400616095744', '0', null, '2020-12-19 14:26:01');
INSERT INTO `comment` VALUES ('790235398939869184', '790234089197142016', '<p>你好呀，下面是一段代码</p><pre class=\"ql-syntax\" spellcheck=\"false\">printf(\"hello,world!\")\n</pre>', '787449216644415488', '1', null, '2020-12-20 15:13:21');
INSERT INTO `comment` VALUES ('790248859790999552', '780837408789757952', '米哈游充钱不能决定一切，\n但是，\n腾讯游戏充钱使你强大', '787436031988203520', '0', null, '2020-12-20 16:06:50');
INSERT INTO `comment` VALUES ('790248970231218176', '780837408789757952', '<p>hello，你好呀</p><pre class=\"ql-syntax\" spellcheck=\"false\">&lt;script type=\"text/javascript\"&gt;\nvar app=new Vue({\n  el:\'#app\', //el:element 的简写 ，用来指定Vue应用程序接管的元素（包括所有的子元素）\n  data:{ //data:data就是Vue实例应用程序中的数据成员\n      message:\'Hello Vue!\'\n  }\n});\n&lt;/script&gt;\n</pre>', '787436031988203520', '0', null, '2020-12-20 16:07:17');
INSERT INTO `comment` VALUES ('790263801038503936', '780837408789757952', '我是子评论', '787449216644415488', '0', '790235398939869184', '2020-12-20 17:06:13');
INSERT INTO `comment` VALUES ('790303334308249600', '780837408789757952', '1', '787449216644415488', '0', null, '2020-12-20 19:43:18');
INSERT INTO `comment` VALUES ('790303341354680320', '780837408789757952', '2', '787449216644415488', '0', null, '2020-12-20 19:43:20');
INSERT INTO `comment` VALUES ('790303347826491392', '780837408789757952', '3', '787449216644415488', '0', null, '2020-12-20 19:43:21');
INSERT INTO `comment` VALUES ('790303355137163264', '780837408789757952', '4', '787449216644415488', '0', null, '2020-12-20 19:43:23');
INSERT INTO `comment` VALUES ('790303360950468608', '780837408789757952', '5', '787449216644415488', '0', null, '2020-12-20 19:43:24');
INSERT INTO `comment` VALUES ('790303379610927104', '780837408789757952', '8', '787449216644415488', '0', null, '2020-12-20 19:43:29');
INSERT INTO `comment` VALUES ('790303386137264128', '780837408789757952', '9', '787449216644415488', '0', null, '2020-12-20 19:43:30');
INSERT INTO `comment` VALUES ('790303393707982848', '780837408789757952', '0', '787449216644415488', '0', null, '2020-12-20 19:43:32');
INSERT INTO `comment` VALUES ('790303409877024768', '780837408789757952', '11', '787449216644415488', '0', null, '2020-12-20 19:43:36');
INSERT INTO `comment` VALUES ('790303422732566528', '780837408789757952', '22', '787449216644415488', '1', null, '2020-12-20 19:43:39');
INSERT INTO `comment` VALUES ('790303473617862656', '780837408789757952', 'hello9', '787449216644415488', '0', '790303334308249600', '2020-12-20 19:43:51');
INSERT INTO `comment` VALUES ('790523263494127616', '780837408789757952', '12312', '790253788505047040', '0', null, '2020-12-21 10:17:13');
INSERT INTO `comment` VALUES ('792038380245352448', '780837408789757952', '<p><strong>你好</strong></p>', '792038075155873792', '0', null, '2020-12-25 14:37:45');
INSERT INTO `comment` VALUES ('792038488332566528', '780837408789757952', '<p>nihao</p>', '792038075155873792', '0', '792038380245352448', '2020-12-25 14:38:11');

-- ----------------------------
-- Table structure for image
-- ----------------------------
DROP TABLE IF EXISTS `image`;
CREATE TABLE `image` (
  `id` varchar(20) NOT NULL COMMENT '图片ID',
  `uid` varchar(20) NOT NULL COMMENT '用户ID',
  `name` varchar(200) DEFAULT NULL COMMENT '文件名称',
  `path` varchar(512) DEFAULT NULL COMMENT '实际路径',
  `url` varchar(1024) NOT NULL COMMENT 'URI',
  `status` varchar(1) DEFAULT '0' COMMENT '状态 0 存在 1 删除',
  `create_time` datetime NOT NULL COMMENT '时间',
  `content_type` varchar(255) DEFAULT NULL COMMENT 'content_type',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of image
-- ----------------------------
INSERT INTO `image` VALUES ('787415008362364928', '780837408789757952', 'qq.jpg', 'D:\\WeCoding\\graduation\\images\\2020_12_12\\jpg\\787415008362364928.jpg', '1607775967456_787415008362364928.jpg', '0', '2020-12-12 20:26:07', 'image/jpeg');
INSERT INTO `image` VALUES ('791766856963719168', '780837408789757952', '21.jpg', 'D:\\WeCoding\\graduation\\images\\2020_12_24\\jpg\\791766856963719168.jpg', '1608813528963_791766856963719168.jpg', '0', '2020-12-24 20:38:49', 'image/jpeg');
INSERT INTO `image` VALUES ('791766954342875136', '780837408789757952', 'logo.png', 'D:\\WeCoding\\graduation\\images\\2020_12_24\\png\\791766954342875136.png', '1608813552234_791766954342875136.png', '0', '2020-12-24 20:39:12', 'image/png');
INSERT INTO `image` VALUES ('791987780216422400', '787427861714698240', '19.jpg', 'D:\\WeCoding\\graduation\\images\\2020_12_25\\jpg\\791987780216422400.jpg', '1608866201195_791987780216422400.jpg', '0', '2020-12-25 11:16:41', 'image/jpeg');
INSERT INTO `image` VALUES ('792037928023883776', '780837408789757952', '21.jpg', 'D:\\WeCoding\\graduation\\images\\2020_12_25\\jpg\\792037928023883776.jpg', '1608878157364_792037928023883776.jpg', '0', '2020-12-25 14:35:57', 'image/jpeg');
INSERT INTO `image` VALUES ('792038044868804608', '780837408789757952', '19.jpg', 'D:\\WeCoding\\graduation\\images\\2020_12_25\\jpg\\792038044868804608.jpg', '1608878185252_792038044868804608.jpg', '0', '2020-12-25 14:36:25', 'image/jpeg');

-- ----------------------------
-- Table structure for post
-- ----------------------------
DROP TABLE IF EXISTS `post`;
CREATE TABLE `post` (
  `pid` varchar(20) NOT NULL COMMENT 'ID',
  `title` varchar(255) DEFAULT NULL COMMENT '标题',
  `uid` varchar(20) DEFAULT NULL COMMENT '用户id',
  `cid` varchar(20) DEFAULT NULL COMMENT '文章类别id',
  `content` text COMMENT '文章内容',
  `type` varchar(1) NOT NULL COMMENT '类别 0 文章 1 问答',
  `cover` varchar(1024) DEFAULT NULL COMMENT '图片链接',
  `status` varchar(1) DEFAULT '0' COMMENT '0 存在 1下架',
  `views` int(11) DEFAULT NULL COMMENT '浏览量',
  `publish_time` datetime DEFAULT NULL COMMENT '发布时间',
  `tags` varchar(255) DEFAULT NULL COMMENT '标签',
  `desc` varchar(255) DEFAULT NULL COMMENT '描述',
  `answer` varchar(1) NOT NULL DEFAULT '0' COMMENT '0 未解决 1 已经解决',
  PRIMARY KEY (`pid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of post
-- ----------------------------
INSERT INTO `post` VALUES ('787417803517853696', 'markdown基础语法', '780837408789757952', '787417419063754752', '.md即markdown文件的基本常用编写语法,是一种快速标记、快速排版语言，现在很多前段项目中的说明文件readme等都是用.md文件编写的，而且很多企业也在在鼓励使用这种编辑方式，特别作为一个前端从业者更要学会使用这种语言。\n\n下面就简单和大家分享一些.md基本语法\n\n### 一、基本符号：* - +. >\n基本上所有的markdown标记都是基于这四个符号或组合，需要注意的是，如果以基本符号开头的标记，注意基本符号后有一个用于分割标记符和内容的空格。\n\n### 二、标题\n\n1.前面带#号，后面带文字，分别表示h1-h6,只到h6，而且h1下面会有一条横线\n```md\n# 一级标题\n## 二级标题\n### 三级标题\n#### 四级标题\n##### 五级标题\n###### 六级标题\n```\n\n### 三、列表\n#### 无序列表\n```md\n//形式一\n+ a\n+ b\n+ c\n//形式二\n- d\n- e\n- f\n//形式三\n* g\n* h\n* i\n```\n#### 有序列表\n\n```md\n//正常形式\n1. abc\n2. bcd\n3. cde\n//错序效果\n2. fgh\n3. ghi\n5. hij\n```\n> 注意，数字后面的点只能是英文的点，有序列表的序号是根据第一行列表的数字顺序来的，\n> 错序列表的序号本来是序号是乱的， 但是还是显示 2 3 5\n\n### 嵌套列表\n```md\n//无序列表嵌套\n+ 123\n    + abc\n    + bcd\n    + cde\n+ 465\n+ 789\n//有序列表嵌套\n1. abcd\n    1. abcde\n    2. abcde\n    3. abcde\n2. bcde\n3. cdef\n```\n\n> 列表可以嵌套，使用时在嵌套列表前按 tab 或 空格 来缩进,去控制列表的层数\n\n### 四、代码块\n在发布一些技术文章会涉及展示代码的问题，这时候代码块就显得尤为重要。\n\n少量代码，单行使用，直接用`包裹起来就行了\n```md\n` shaoliangdaima,danhangshiyong `\n```\n大量代码，需要多行使用，用```包裹起来\n```md\n    ```\n        daliangdaima,xuyaoduohangshiyong\n        daliangdaima,xuyaoduohangshiyong\n        daliangdaima,xuyaoduohangshiyong\n        daliangdaima,xuyaoduohangshiyong\n        daliangdaima,xuyaoduohangshiyong\n    ```\n```', '0', '', '0', '9', '2020-12-12 20:37:14', 'markdown', '.md即markdown文件的基本常用编写语法,是一种快速标记、快速排版语言，现在很多前段项目中的说明文件readme等都是用.md文件编写的，而且很多企业也在在鼓励使用这种编辑方式，特别作为一个前端从业者更要学会使用这种语言。', '0');
INSERT INTO `post` VALUES ('787419026300076032', '上架应用是不是要有个公司啊 ？ 申请个公司要多少钱呢？', '780837408789757952', '787417419063754752', '最近想写个app 上架市场，不知道怎么申请公司 哪个同学有经验呢', '1', null, '1', '14', '2020-12-12 20:42:05', 'app-java', '最近想写个app 上架市场，不知道怎么申请公司 哪个同学有经验呢', '0');
INSERT INTO `post` VALUES ('787421077138571264', '重建二叉树', '780837408789757952', '787413340560293888', '## 题目1-二叉树重建\n\n输入某二叉树的前序遍历和中序遍历的结果，请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。\n\n例如输入前序遍历序列`{1,2,4,7,3,5,6,8}`和中序遍历序列`{4,7,2,1,5,3,8,6}`，则重建二叉树并返回。\n\n## 思路\n\n- 前序遍历：根节点 + 左子树前序遍历 + 右子树前序遍历\n- 中序遍历：左子树中序遍历 + 根节点 + 右字数中序遍历\n- 后序遍历：左子树后序遍历 + 右子树后序遍历 + 根节点\n\n根据上面的规律：\n\n- 前序遍历找到根结点`root`\n- 找到`root`在中序遍历的位置 -> 左子树的长度和右子树的长度\n- 截取左子树的中序遍历、右子树的中序遍历\n- 截取左子树的前序遍历、右子树的前序遍历\n- 递归重建二叉树\n\n## 代码\n\n```js\n    function reConstructBinaryTree(pre, vin) {\n        if(pre.length === 0){\n            return null;\n        }\n        if(pre.length === 1){\n            return new TreeNode(pre[0]);\n        }\n        const value = pre[0];\n        const index = vin.indexOf(value);\n        const vinLeft = vin.slice(0,index);\n        const vinRight = vin.slice(index+1);\n        const preLeft = pre.slice(1,index+1);\n        const preRight = pre.slice(index+1);\n        const node = new TreeNode(value);\n        node.left = reConstructBinaryTree(preLeft, vinLeft);\n        node.right = reConstructBinaryTree(preRight, vinRight);\n        return node;\n    }\n```\n\n## 题目2-求二叉树的遍历\n\n给定一棵二叉树的前序遍历和中序遍历，求其后序遍历\n\n输入描述:\n\n两个字符串，其长度n均小于等于26。\n第一行为前序遍历，第二行为中序遍历。\n二叉树中的结点名称以大写字母表示：A，B，C....最多26个结点。\n\n输出描述:\n\n输入样例可能有多组，对于每组测试样例，\n输出一行，为后序遍历的字符串。\n\n样例：\n```\n输入\nABC\nBAC\nFDXEAG\nXDEFAG\n输出\nBCA\nXEDGAF\n```\n\n\n## 思路\n\n和上面题目的思路基本相同\n\n- 前序遍历找到根结点`root`\n- 找到`root`在中序遍历的位置 -> 左子树的长度和右子树的长度\n- 截取左子树的中序遍历、右子树的中序遍历\n- 截取左子树的前序遍历、右子树的前序遍历\n- 递归拼接二叉树的后序遍历\n\n## 代码\n\n```js\nlet pre;\nlet vin;\n \nwhile((pre = readline())!=null){\n    vin = readline();\n    print(getHRD(pre,vin));\n}\n \n    function getHRD(pre, vin) {\n      if (!pre) {\n        return \'\';\n      }\n      if (pre.length === 1) {\n        return pre;\n      }\n      const head = pre[0];\n      const splitIndex = vin.indexOf(head);\n      const vinLeft = vin.substring(0, splitIndex);\n      const vinRight = vin.substring(splitIndex + 1);\n      const preLeft = pre.substring(1, splitIndex + 1);\n      const preRight = pre.substring(splitIndex + 1);\n      return getHRD(preLeft, vinLeft) + getHRD(preRight, vinRight) + head;\n    }\n```\n\n\n## 考察点\n\n- 二叉树\n- 复杂问题拆解', '0', '', '0', '2', '2020-12-12 20:50:14', '二叉树', '输入某二叉树的前序遍历和中序遍历的结果，请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。', '0');
INSERT INTO `post` VALUES ('787421365857681408', '树的子结构', '780837408789757952', '787413340560293888', '## 题目\n\n输入两棵二叉树`A`，`B`，判断`B`是不是`A`的子结构。（ps：我们约定空树不是任意一个树的子结构）\n\n## 思路\n\n首先找到`A`树中和`B`树根节点相同的节点\n\n从此节点开始，递归`AB`树比较是否有不同节点\n\n## 代码\n\n```js\n    function HasSubtree(pRoot1, pRoot2) {\n      let result = false;\n      if (pRoot1 && pRoot2) {\n        if (pRoot1.val === pRoot2.val) {\n          result = compare(pRoot1, pRoot2);\n        }\n        if (!result) {\n          result = HasSubtree(pRoot1.right, pRoot2);\n        }\n        if (!result) {\n          result = HasSubtree(pRoot1.left, pRoot2);\n        }\n      }\n      return result;\n    }\n    function compare(pRoot1, pRoot2) {\n      if (pRoot2 === null) {\n        return true;\n      }\n      if (pRoot1 === null) {\n        return false;\n      }\n      if (pRoot1.val !== pRoot2.val) {\n        return false;\n      }\n      return compare(pRoot1.right, pRoot2.right) && compare(pRoot1.left, pRoot2.left);\n    }\n```', '0', '', '0', '2', '2020-12-12 20:51:23', '二叉树', '输入两棵二叉树A，B，判断B是不是A的子结构。（ps：我们约定空树不是任意一个树的子结构）', '0');
INSERT INTO `post` VALUES ('787421748004913152', '二叉树的前序遍历', '780837408789757952', '787413340560293888', '## 题目\n\n给定一个二叉树，返回它的 前序 遍历。\n\n 示例:\n```js\n输入: [1,null,2,3]  \n   1\n    \\\n     2\n    /\n   3 \n输出: [1,2,3]\n```\n\n进阶: 递归算法很简单，你可以通过迭代算法完成吗？\n\n## 代码\n\n递归实现\n\n```js\n    var preorderTraversal = function (root, array = []) {\n      if (root) {\n        array.push(root.val);\n        preorderTraversal(root.left, array);\n        preorderTraversal(root.right, array);\n      }\n      return array;\n    };\n```\n\n非递归实现\n\n- 取根节点为目标节点，开始遍历\n- 1.访问目标节点\n- 2.左孩子入栈 -> 直至左孩子为空的节点\n- 3.节点出栈，以右孩子为目标节点，再依次执行1、2、3\n\n```js\n    var preorderTraversal = function (root) {\n      const result = [];\n      const stack = [];\n      let current = root;\n      while (current || stack.length > 0) {\n        while (current) {\n          result.push(current.val);\n          stack.push(current);\n          current = current.left;\n        }\n        current = stack.pop();\n        current = current.right;\n      }\n      return result;\n    };\n```\n', '0', '', '0', '1', '2020-12-12 20:52:54', '二叉树', '二叉树的前序遍历，递归算法很简单，你可以通过迭代算法完成吗？', '0');
INSERT INTO `post` VALUES ('787422843863302144', '二叉树的中序遍历', '780837408789757952', '787413340560293888', '## 题目\n\n给定一个二叉树，返回它的 中序 遍历。\n\n 示例:\n```js\n输入: [1,null,2,3]\n   1\n    \\\n     2\n    /\n   3\n输出: [1,3,2]\n```\n\n进阶: 递归算法很简单，你可以通过迭代算法完成吗？\n\n## 代码\n\n递归实现\n\n```js\n    var inorderTraversal = function (root, array = []) {\n      if (root) {\n        inorderTraversal(root.left, array);\n        array.push(root.val);\n        inorderTraversal(root.right, array);\n      }\n      return array;\n    };\n```\n\n非递归实现\n\n- 取根节点为目标节点，开始遍历\n- 1.左孩子入栈 -> 直至左孩子为空的节点\n- 2.节点出栈 -> 访问该节点\n- 3.以右孩子为目标节点，再依次执行1、2、3\n\n```js\n    var inorderTraversal = function (root) {\n      const result = [];\n      const stack = [];\n      let current = root;\n      while (current || stack.length > 0) {\n        while (current) {\n          stack.push(current);\n          current = current.left;\n        }\n        current = stack.pop();\n        result.push(current.val);\n        current = current.right;\n      }\n      return result;\n    };\n```', '0', '', '0', '7', '2020-12-12 20:57:16', '二叉树', '二叉树的中序遍，历递归算法很简单，你可以通过迭代算法完成吗？', '0');
INSERT INTO `post` VALUES ('787425495875584000', 'SpringBoot上传图片', '780837408789757952', '787413102411907072', '```java\npublic class ImageService implements IImageService {\n\n    public static SimpleDateFormat simpleDateFormat = new SimpleDateFormat(\"yyyy_MM_dd\");\n\n    @Value(\"${nil.image.save-path}\")\n    public String imagePath;\n\n    @Value(\"${nil.image.max-size}\")\n    public long maxSize;\n\n    @Autowired\n    private IdWorker idWorker;\n\n    @Autowired\n    private ImageMapper imageMapper;\n\n    @Autowired\n    private IUserService userService;\n\n    @Override\n    public ResponseResult uploadImage(MultipartFile file) {\n        log.info(\"path == > \" + imagePath);\n        //判断是否有文件\n        if (file == null) {\n            return ResponseResult.FAILED(\"图片不可以为空.\");\n        }\n        //判断文件类型，我们只支持图片上传，比如说：png，jpg，gif\n        String contentType = file.getContentType();\n        log.info(\"contentType == > \" + contentType);\n        if (TextUtils.isEmpty(contentType)) {\n            return ResponseResult.FAILED(\"图片格式错误.\");\n        }\n        //获取相关数据，比如说文件类型，文件名称\n        String originalFilename = file.getOriginalFilename();\n        log.info(\"originalFilename == > \" + originalFilename);\n        String type = getType(contentType, originalFilename);\n        if (type == null) {\n            return ResponseResult.FAILED(\"不支持此图片类型\");\n        }\n        //限制文件的大小\n        long size = file.getSize();\n        log.info(\"maxSize === > \" + maxSize + \"  size ==> \" + size);\n        if (size > maxSize) {\n            return ResponseResult.FAILED(\"图片最大仅支持\" + (maxSize / 1024 / 1024) + \"Mb\");\n        }\n        //创建图片的保存目录\n        //规则：配置目录/日期/类型/ID.类型\n        long currentMillions = System.currentTimeMillis();\n        String currentDay = simpleDateFormat.format(currentMillions);\n        log.info(\"current day == > \" + currentDay);\n        String dayPath = imagePath + File.separator + currentDay;\n        File dayPathFile = new File(dayPath);\n        //判断日期文件夹是否存在//2020_06_26\n        if (!dayPathFile.exists()) {\n            dayPathFile.mkdirs();\n        }\n        String targetName = String.valueOf(idWorker.nextId());\n        String targetPath = dayPath +\n                File.separator + type + File.separator + targetName + \".\" + type;\n        File targetFile = new File(targetPath);\n        //判断类型文件夹是否存在//gif\n        if (!targetFile.getParentFile().exists()) {\n            targetFile.getParentFile().mkdirs();\n        }\n        log.info(\"targetFile ---- == > \" + targetFile);\n\n        try {\n            if (!targetFile.exists()) {\n                targetFile.createNewFile();\n            }\n            log.info(\"targetFile == > \" + targetFile);\n            //保存文件\n            file.transferTo(targetFile);\n            //返回结果：包含这个图片的名称和访问路径\n            //第一个是访问路径 -- > 得对应着解析来\n            Map<String, String> result = new HashMap<>();\n            String resultPath = currentMillions + \"_\" + targetName + \".\" + type;\n            result.put(\"id\", resultPath);\n            result.put(\"url\", Constants.Setting.G_PATH+resultPath);\n            //第二个是名称--->alt=\"图片描述\",如果不写，前端可以使用名称作为这个描述\n            result.put(\"name\", originalFilename);\n            Image image = new Image();\n            image.setContentType(contentType);\n            image.setId(targetName);\n            image.setCreateTime(new Date());\n            image.setPath(targetFile.getPath());\n            image.setName(originalFilename);\n            image.setUrl(resultPath);\n            image.setStatus(\"0\");\n            User user = userService.checkUser();\n            image.setUid(user.getUid());\n            //记录文件\n            //保存记录到数据里\n            imageMapper.save(image);\n            //返回结果\n            return ResponseResult.SUCCESS(\"文件上传成功\").setData(result);\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        return ResponseResult.FAILED(\"图片上传失败，请稍后重试\");\n    }\n\n    @Override\n    public void viewImage(HttpServletResponse response, String imageId) throws IOException {\n        log.info(\"imageId == > \" + imageId);\n        String[] paths = imageId.split(\"_\");\n//        images ID\n        String name = paths[1];\n        String imageIdDb = name.split(\"\\\\.\")[0];\n        Image image = imageMapper.getSimpleOne(imageIdDb);\n        if (image == null) {\n            return;\n        }\n        String targetPath = image.getPath();\n        log.info(\"targetPath == >\" + targetPath);\n        File file = new File(targetPath);\n        OutputStream writer = null;\n        FileInputStream ips = null;\n        try {\n            ips = new FileInputStream(file);\n//            返回类型\n            response.setContentType(image.getContentType());\n            writer = response.getOutputStream();\n            //读取文件流\n            int len;\n            byte[] buffer = new byte[1024];\n            while ((len = ips.read(buffer)) != -1) {\n                writer.write(buffer, 0, len);\n            }\n            writer.flush();\n        } catch (Exception e) {\n            e.printStackTrace();\n        } finally {\n            assert ips != null;\n            ips.close();\n            assert writer != null;\n            writer.close();\n        }\n    }\n\n\n    private String getType(String contentType, String name) {\n        String type = null;\n        if (Constants.ImageType.TYPE_PNG_WITH_PREFIX.equals(contentType) &&\n                name.endsWith(Constants.ImageType.TYPE_PNG)) {\n            type = Constants.ImageType.TYPE_PNG;\n        } else if (Constants.ImageType.TYPE_GIF_WITH_PREFIX.equals(contentType) &&\n                name.endsWith(Constants.ImageType.TYPE_GIF)) {\n            type = Constants.ImageType.TYPE_GIF;\n        } else if ((Constants.ImageType.TYPE_JPG_WITH_PREFIX.equals(contentType) ||\n                Constants.ImageType.TYPE_JPEG_WITH_PREFIX.equals(contentType)) &&\n                name.endsWith(Constants.ImageType.TYPE_JPG)) {\n            type = Constants.ImageType.TYPE_JPG;\n        }\n        return type;\n    }\n}\n```\n\n```java\ninterface ImageType {\n        String prefix = \"image/\";\n        String TYPE_JPG = \"jpg\";\n        String TYPE_PNG = \"png\";\n        String TYPE_GIF = \"gif\";\n        String TYPE_JPG_WITH_PREFIX = prefix + \"jpg\";\n        String TYPE_PNG_WITH_PREFIX = prefix + \"png\";\n        String TYPE_GIF_WITH_PREFIX = prefix + \"gif\";\n        String TYPE_JPEG_WITH_PREFIX = prefix + \"jpeg\";\n    }\n```', '0', '', '0', '6', '2020-12-12 21:07:48', 'java-上传图片', 'SpringBoot上传图片', '0');
INSERT INTO `post` VALUES ('787426770201608192', 'javascript工具函数', '780837408789757952', '787413269127102464', '## 为元素添加on方法\n\n```javascript\nElement.prototype.on = Element.prototype.addEventListener;\n\nNodeList.prototype.on = function (event, fn) {、\n    [][\'forEach\'].call(this, function (el) {\n        el.on(event, fn);\n    });\n    return this;\n};\n```\n\n## 为元素添加trigger方法\n\n```javascript\nElement.prototype.trigger = function(type, data) {\n  var event = document.createEvent(\"HTMLEvents\");\n  event.initEvent(type, true, true);\n  event.data = data || {};\n  event.eventName = type;\n  event.target = this;\n  this.dispatchEvent(event);\n  return this;\n};\n\nNodeList.prototype.trigger = function(event) {\n  [][\"forEach\"].call(this, function(el) {\n    el[\"trigger\"](event);\n  });\n  return this;\n};\n```\n\n## 转义html标签\n\n```javascript\nfunction HtmlEncode(text) {\n  return text\n    .replace(/&/g, \"&\")\n    .replace(/\\\"/g, \'\"\')\n    .replace(/</g, \"<\")\n    .replace(/>/g, \">\");\n}\n```\n\n## HTML标签转义\n\n```javascript\n// HTML 标签转义\n// @param {Array.<DOMString>} templateData 字符串类型的tokens\n// @param {...} ..vals 表达式占位符的运算结果tokens\n//\nfunction SaferHTML(templateData) {\n  var s = templateData[0];\n  for (var i = 1; i < arguments.length; i++) {\n    var arg = String(arguments[i]);\n    // Escape special characters in the substitution.\n    s += arg\n      .replace(/&/g, \"&amp;\")\n      .replace(/</g, \"&lt;\")\n      .replace(/>/g, \"&gt;\");\n    // Don\'t escape special characters in the template.\n    s += templateData[i];\n  }\n  return s;\n}\n// 调用\nvar html = SaferHTML`<p>这是关于字符串模板的介绍</p>`;\n```\n\n## 跨浏览器绑定事件\n\n```javascript\nfunction addEventSamp(obj, evt, fn) {\n  if (!oTarget) {\n    return;\n  }\n  if (obj.addEventListener) {\n    obj.addEventListener(evt, fn, false);\n  } else if (obj.attachEvent) {\n    obj.attachEvent(\"on\" + evt, fn);\n  } else {\n    oTarget[\"on\" + sEvtType] = fn;\n  }\n}\n```\n\n## 加入收藏夹\n\n```javascript\nfunction addFavorite(sURL, sTitle) {\n  try {\n    window.external.addFavorite(sURL, sTitle);\n  } catch (e) {\n    try {\n      window.sidebar.addPanel(sTitle, sURL, \"\");\n    } catch (e) {\n      alert(\"加入收藏失败，请使用Ctrl+D进行添加\");\n    }\n  }\n}\n```\n\n## 提取页面代码中所有网址\n\n```javascript\nvar aa = document.documentElement.outerHTML\n  .match(\n    /(url\\(|src=|href=)[\\\"\\\']*([^\\\"\\\'\\(\\)\\<\\>\\[\\] ]+)[\\\"\\\'\\)]*|(http:\\/\\/[\\w\\-\\.]+[^\\\"\\\'\\(\\)\\<\\>\\[\\] ]+)/gi\n  )\n  .join(\"\\r\\n\")\n  .replace(/^(src=|href=|url\\()[\\\"\\\']*|[\\\"\\\'\\>\\) ]*$/gim, \"\");\nalert(aa);\n```\n\n## 动态加载脚本文件\n\n```javascript\nfunction appendscript(src, text, reload, charset) {\n  var id = hash(src + text);\n  if (!reload && in_array(id, evalscripts)) return;\n  if (reload && $(id)) {\n    $(id).parentNode.removeChild($(id));\n  }\n\n  evalscripts.push(id);\n  var scriptNode = document.createElement(\"script\");\n  scriptNode.type = \"text/javascript\";\n  scriptNode.id = id;\n  scriptNode.charset = charset\n    ? charset\n    : BROWSER.firefox\n    ? document.characterSet\n    : document.charset;\n  try {\n    if (src) {\n      scriptNode.src = src;\n      scriptNode.onloadDone = false;\n      scriptNode.onload = function() {\n        scriptNode.onloadDone = true;\n        JSLOADED[src] = 1;\n      };\n      scriptNode.onreadystatechange = function() {\n        if (\n          (scriptNode.readyState == \"loaded\" ||\n            scriptNode.readyState == \"complete\") &&\n          !scriptNode.onloadDone\n        ) {\n          scriptNode.onloadDone = true;\n          JSLOADED[src] = 1;\n        }\n      };\n    } else if (text) {\n      scriptNode.text = text;\n    }\n    document.getElementsByTagName(\"head\")[0].appendChild(scriptNode);\n  } catch (e) {}\n}\n```\n\n## 返回顶部的通用方法\n\n```javascript\nfunction backTop(btnId) {\n  var btn = document.getElementById(btnId);\n  var d = document.documentElement;\n  var b = document.body;\n  window.onscroll = set;\n  btn.style.display = \"none\";\n  btn.onclick = function() {\n    btn.style.display = \"none\";\n    window.onscroll = null;\n    this.timer = setInterval(function() {\n      d.scrollTop -= Math.ceil((d.scrollTop + b.scrollTop) * 0.1);\n      b.scrollTop -= Math.ceil((d.scrollTop + b.scrollTop) * 0.1);\n      if (d.scrollTop + b.scrollTop == 0)\n        clearInterval(btn.timer, (window.onscroll = set));\n    }, 10);\n  };\n  function set() {\n    btn.style.display = d.scrollTop + b.scrollTop > 100 ? \"block\" : \"none\";\n  }\n}\nbackTop(\"goTop\");\n```\n\n## 实现base64解码\n\n```javascript\nfunction base64_decode(data) {\n  var b64 = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\";\n  var o1,\n    o2,\n    o3,\n    h1,\n    h2,\n    h3,\n    h4,\n    bits,\n    i = 0,\n    ac = 0,\n    dec = \"\",\n    tmp_arr = [];\n  if (!data) {\n    return data;\n  }\n  data += \"\";\n  do {\n    h1 = b64.indexOf(data.charAt(i++));\n    h2 = b64.indexOf(data.charAt(i++));\n    h3 = b64.indexOf(data.charAt(i++));\n    h4 = b64.indexOf(data.charAt(i++));\n    bits = (h1 << 18) | (h2 << 12) | (h3 << 6) | h4;\n    o1 = (bits >> 16) & 0xff;\n    o2 = (bits >> 8) & 0xff;\n    o3 = bits & 0xff;\n    if (h3 == 64) {\n      tmp_arr[ac++] = String.fromCharCode(o1);\n    } else if (h4 == 64) {\n      tmp_arr[ac++] = String.fromCharCode(o1, o2);\n    } else {\n      tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);\n    }\n  } while (i < data.length);\n  dec = tmp_arr.join(\"\");\n  dec = utf8_decode(dec);\n  return dec;\n}\n```\n', '0', '', '1', '4', '2020-12-12 21:12:52', '', 'javascript工具函数', '0');
INSERT INTO `post` VALUES ('787431673191989248', 'freemarker中无法使用内置对象request', '787427861714698240', '787413102411907072', '工具：IDEA\n架构：SpringBoot2+freemarker\n```\n       freemarker:\n        request-context-attribute: request\n        #禁止模板缓存\n        cache: false\n        template-loader-path: classpath:/templates\n        suffix: .html\n        settings:\n          classic_compatible: true\n```\n访问：http://192.168.0.2:8001/hello/freemarker?id=1\n```xml\n    <dl>\n        <dt>Request:${Request[\"method\"]} </dt>内置Request对象为空\n         <dt>request:${request.id} </dt>配置文件绑定的request也为空\n    </dl>\n```\n是怎么回事呢，请哪个师兄指点一下', '1', null, '0', '12', '2020-12-12 21:32:21', 'java', 'freemarker中无法使用内置对象request,是怎么回事呢，请哪个师兄指点一下', '0');
INSERT INTO `post` VALUES ('787433332974551040', '趣谈唯一邀请码生成方法', '787427861714698240', '787413102411907072', '趣谈唯一邀请码生成方法\n前段时间项目上需要生成唯一邀请码！嘿嘿，多简单的一件事，心里就已默默将代码写了一遍。但小小的邀请码生成却也小有乾坤，这就是后话了。\n\n### 一、最简单的实现\n很多人 肯定都和我一开始一样！嘿嘿，这还不简单，26个字母+10个数字，去掉容易混淆的，O、0、I、1等字符就剩下32个，\n```java\n        /**\n         * 随机字符串\n         */\n        private static final char[] CHARS = new char[] {\'F\', \'L\', \'G\', \'W\', \'5\', \'X\', \'C\', \'3\',\n            \'9\', \'Z\', \'M\', \'6\', \'7\', \'Y\', \'R\', \'T\', \'2\', \'H\', \'S\', \'8\', \'D\', \'V\', \'E\', \'J\', \'4\', \'K\',\n            \'Q\', \'P\', \'U\', \'A\', \'N\', \'B\'};\n```\n然后再生成随机的六个重复的数字，对应着CHARS中的数字下标，邀请码生成搞定\n```java\n            Random random = new Random();\n            List<Character> list =  random.ints(0,32)\n                .distinct().limit(6).boxed().map(t -> CHARS[t]).reduce().collect(toList());\n```\n好啦，邀请码生成好了。你还要不重复是吧，OK！没问题，我们把邀请码保存到缓存里面，例如redis。每次新生成的邀请码都先去缓存判断下是否存在，如果已经存在则再生成一个，直到找到合适的。 功能完成。喝下午茶去。。。。\n\n> 思考 ：这种实现是否真的最佳实现了呢？ 至少存在以下一些问题\n\n唯一性判断需要通过缓存，引入第三方组件，并且消耗内存资源\n在99.999%的情况下，缓存判断是否存在都是 FALSE 的，可以说是一种浪费\n用户与邀请码的对应关系需要记录，如果数据库查询的话，至少需要在邀请码字段上加个索引，又是要消耗资源\n那么我们有其它办法来解这个问题吗？\n\n### 二、数学和密码学来实现\n> 前提 ：存在唯一的标识，例如给用户生成邀请码，那么用户id就是唯一标识（id在任何情况下都不会重复）。\n\n那么我们首先想到的是根据唯一的id，是使用算法来生成。\n\n       /**\n         * 生成邀请码\n         *\n         * @param id 唯一的id主键\n         * @return code\n         */\n        String gen(Long id) {\n            //进行补位\n            id = id * PRIME1 + SLAT;\n            //将 id 转换成32进制的值\n            long[] b = new long[CODE_LENGTH];\n            //32进制数\n            b[0] = id;\n            for (int i = 0; i < CODE_LENGTH-1; i++) {\n                b[i + 1] = b[i] / CHARS_LENGTH;\n                b[i] = b[i] % CHARS_LENGTH;\n            }\n            b[5] = (b[2] + b[3] + b[4]) * PRIME1 % CHARS_LENGTH;\n            StringBuilder buffer = new StringBuilder();\n            Arrays.stream(b).boxed().map(Long::intValue).map(t -> CHARS[t]).forEach(buffer::append);\n            return buffer.toString();\n        }\n大概就是像上面代码一样，先将id扩大，然后再转换成32进制的数。为啥是32进制呢，因为 CHARS 里面是32个char，那么32进制正好对应 CHARS 的下标。\n\n好了，基于算法根据id来生成邀请码就完成了。然后再逆向下算法，就可以根据邀请码来还原成用户id，找到是哪个用户的了。\n\nYEAH、YEAH！任务完成\n\n然后代码一运行\n```java\n  public static void main(String [] args){\n        for(long id=10000L;id<10020L;id++){\n            String code = InviteCode.instance().gen(id);\n            System.out.println(\"code:\"+code);\n        }\n  }\ncode:HASCL6\ncode:DASCL6\ncode:JASCL6\ncode:QASCL6\ncode:AASCL6\ncode:FNSCL6\ncode:WNSCL6\ncode:CNSCL6\ncode:ZNSCL6\ncode:7NSCL6\n```\n连续的 id 产生的邀请码是惊人的相似，后四位是完全相同的！这样如果有心用户发现这个规律后，就有可能破解邀请码的生成算法。\n\n### 三、再进一步\n之前有说过密码学，密码学里面有两个重要概念，就是扩大和混淆。既然后四位如此的相似，那么我们希望能够扩大后四位的差异。\n\n       /**\n         * 生成邀请码\n         *\n         * @param id 唯一的id主键\n         * @return code\n         */\n        String gen(Long id) {\n            //补位，并扩大整体\n            id = id * PRIME1 + SLAT;\n            //将 id 转换成32进制的值\n            long[] b = new long[CODE_LENGTH];\n            //32进制数\n            b[0] = id;\n            for (int i = 0; i < CODE_LENGTH - 1; i++) {\n                b[i + 1] = b[i] / CHARS_LENGTH;\n                //扩大每一位的差异\n                b[i] = (b[i] + i * b[0]) % CHARS_LENGTH;\n            }\n            b[5] = (b[0] + b[1] + b[2] + b[3] + b[4]) * PRIME1 % CHARS_LENGTH;\n\n            //进行混淆\n            long[] codeIndexArray = new long[CODE_LENGTH];\n            for (int i = 0; i < CODE_LENGTH; i++) {\n                codeIndexArray[i] = b[i * PRIME2 % CODE_LENGTH];\n            }\n\n            StringBuilder buffer = new StringBuilder();\n            Arrays.stream(codeIndexArray).boxed().map(Long::intValue).map(t -> CHARS[t]).forEach(buffer::append);\n            return buffer.toString();\n        }\n这样我们就能对连续id生成差异化的邀请码了。\n\n   public static void main(String [] args){\n        for(long id=10000L;id<10020L;id++){\n            String code = InviteCode.instance().gen(id);\n            System.out.println(\"code:\"+code);\n        }\n\n    }\ncode:H8XKDR\ncode:DEHGQH\ncode:JKA6FD\ncode:QUZDCJ\ncode:ABVA7Q\ncode:FXLCSN\ncode:W9YT4L\ncode:C6K4N5\ncode:ZRXL53\n### 四、结语\n任务总算完成了，汇报给领导求表扬。但生成算法还是存在瑕疵的，因为对于六位数的邀请码，只取了5个有效位，第六位作为校验位。所有最大能表示的id到千万级，如果还需要更大的id，则需要扩大邀请码的位数。\n\n详细算法地址（包含将邀请码还原成id的算法哦）：https://gist.github.com/zjnxyz/6ef6007f493a0edea6837cc06e934abc\n\n整理了一下源码\n```java\nimport java.util.Arrays;\n\npublic class InviteCode {\n\n	/**\n	 * 随机字符串\n	 */\n	private static final char[] CHARS = new char[] { \'F\', \'L\', \'G\', \'W\', \'5\', \'X\', \'C\', \'3\', \'9\', \'Z\', \'M\', \'6\', \'7\',\n			\'Y\', \'R\', \'T\', \'2\', \'H\', \'S\', \'8\', \'D\', \'V\', \'E\', \'J\', \'4\', \'K\', \'Q\', \'P\', \'U\', \'A\', \'N\', \'B\' };\n\n	private final static int CHARS_LENGTH = 32;\n	/**\n	 * 邀请码长度\n	 */\n	private final static int CODE_LENGTH = 6;\n\n	/**\n	 * 随机数据\n	 */\n	private final static long SLAT = 1234561L;\n\n	/**\n	 * PRIME1 与 CHARS 的长度 L互质，可保证 ( id * PRIME1) % L 在 [0,L)上均匀分布\n	 */\n	private final static int PRIME1 = 3;\n\n	/**\n	 * PRIME2 与 CODE_LENGTH 互质，可保证 ( index * PRIME2) % CODE_LENGTH 在\n	 * [0，CODE_LENGTH）上均匀分布\n	 */\n	private final static int PRIME2 = 11;\n\n	/**\n	 * 生成邀请码\n	 *\n	 * @param id 唯一的id主键\n	 * @return code\n	 */\n	public static String gen(Long id) {\n		// 补位\n		id = id * PRIME1 + SLAT;\n		// 将 id 转换成32进制的值\n		long[] b = new long[CODE_LENGTH];\n		// 32进制数\n		b[0] = id;\n		for (int i = 0; i < CODE_LENGTH - 1; i++) {\n			b[i + 1] = b[i] / CHARS_LENGTH;\n			// 按位扩散\n			b[i] = (b[i] + i * b[0]) % CHARS_LENGTH;\n		}\n		b[5] = (b[0] + b[1] + b[2] + b[3] + b[4]) * PRIME1 % CHARS_LENGTH;\n\n		// 进行混淆\n		long[] codeIndexArray = new long[CODE_LENGTH];\n		for (int i = 0; i < CODE_LENGTH; i++) {\n			codeIndexArray[i] = b[i * PRIME2 % CODE_LENGTH];\n		}\n\n		StringBuilder buffer = new StringBuilder();\n		Arrays.stream(codeIndexArray).boxed().map(Long::intValue).map(t -> CHARS[t]).forEach(buffer::append);\n		return buffer.toString();\n	}\n\n	/**\n	 * 将邀请码解密成原来的id\n	 *\n	 * @param code 邀请码\n	 * @return id\n	 */\n	public static Long decode(String code) {\n		if (code.length() != CODE_LENGTH) {\n			return null;\n		}\n		// 将字符还原成对应数字\n		long[] a = new long[CODE_LENGTH];\n		for (int i = 0; i < CODE_LENGTH; i++) {\n			char c = code.charAt(i);\n			int index = findIndex(c);\n			if (index == -1) {\n				// 异常字符串\n				return null;\n			}\n			a[i * PRIME2 % CODE_LENGTH] = index;\n		}\n\n		long[] b = new long[CODE_LENGTH];\n		for (int i = CODE_LENGTH - 2; i >= 0; i--) {\n			b[i] = (a[i] - a[0] * i + CHARS_LENGTH * i) % CHARS_LENGTH;\n		}\n\n		long res = 0;\n		for (int i = CODE_LENGTH - 2; i >= 0; i--) {\n			res += b[i];\n			res *= (i > 0 ? CHARS_LENGTH : 1);\n		}\n		return (res - SLAT) / PRIME1;\n	}\n\n	/**\n	 * 查找对应字符的index\n	 *\n	 * @param c 字符\n	 * @return index\n	 */\n	private static int findIndex(char c) {\n		for (int i = 0; i < CHARS_LENGTH; i++) {\n			if (CHARS[i] == c) {\n				return i;\n			}\n		}\n		return -1;\n	}\n}\n```\n原文：https://my.oschina.net/bravozu/blog/1827254\n作者： BravoZu', '0', '', '0', '8', '2020-12-12 21:38:56', '', '前段时间项目上需要生成唯一邀请码！嘿嘿，多简单的一件事，心里就已默默将代码写了一遍。但小小的邀请码生成却也小有乾坤，这就是后话了。', '0');
INSERT INTO `post` VALUES ('787436031988203520', '遇见Vue.js——第一个Vue.js程序', '787427861714698240', '787413645830127616', '### 遇见Vue\n#### Vue.js是什么\n\nVue.js（读音 /vjuː/, 类似于 view） 是一套构建用户界面的 **渐进式框架**。\n与其他重量级框架不同的是，Vue 采用自底向上增量开发的设计。\nVue 的核心库只关注视图层，并且非常容易学习，非常容易与其它库或已有项目整合。\n另一方面，Vue 完全有能力驱动采用单文件组件和Vue生态系统支持的库开发的复杂单页应用。\n\n#### Vue.js 的目标\n是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。\nVue.js 是一个用于创建 Web 交互界面的库。它让你通过简单而灵活的 API 创建由数据驱动的 UI 组件。\n\n#### Vue.js的特性\n##### 简洁：\nHTML 模板 + JSON 数据，再创建一个 Vue 实例，就这么简单。\n\n##### 数据驱动：\n自动追踪依赖的模板表达式和计算属性。\n\n##### 组件化：\n用解耦、可复用的组件来构造界面。\n\n##### 轻量：\n~24kb min+gzip，无依赖。\n\n##### 快速：\n精确有效的异步批量 DOM 更新。\n\n##### 模块友好：\n通过 NPM 或 Bower 安装，无缝融入你的工作流。\n\n### 第一个Vue.js程序\n#### 安装\n我们可以在 Vue.js 的官网上直接下载 vue.min.js 并用 `<script>` 标签引入。Vue 会被注册为一个全局变量。\n\n> 重要提示：在开发时请用开发版本，遇到常见错误它会给出友好的警告。\n\n#### 代码示例\n```html\n<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"UTF-8\">\n        <title>it研习社-第一个Hello Vue程序</title>\n    </head>\n    <body>\n        <div id=\"app\">\n            {{message}}\n        </div>\n        <script src=\"vue.js\" type=\"text/javascript\" charset=\"utf-8\"></script>\n        <script type=\"text/javascript\">\n            var app=new Vue({\n                el:\'#app\',\n                data:{\n                    message:\'Hello Vue!\'\n                }\n            });\n        </script>\n    </body>\n</html>\n```\n\n### 第一个Hello Vue代码详解\n> 1.将vue.js文件引入到当前页面\n```js\n<script src=\"vue.js\" type=\"text/javascript\" charset=\"utf-8\"></script>\n```\n只要将vue.js文件引入页面，在当前环境就会多出一个全局变量：Vue\n> 2.通过全局构造函数：Vue ，实例化一个Vue应用程序接管的元素（包括所有的子元素）\n```js\n<script type=\"text/javascript\">\nvar app=new Vue({\n  el:\'#app\', //el:element 的简写 ，用来指定Vue应用程序接管的元素（包括所有的子元素）\n  data:{ //data:data就是Vue实例应用程序中的数据成员\n      message:\'Hello Vue!\'\n  }\n});\n</script>\n```\n> 3.代码执行流程解析\n\n - 1.浏览器从上到下依次进行解析\n\n浏览器对于id=app 的div 内部的 {{message}}不认识，直接作为普通文本渲染到网页上\n\n - 2.浏览器继续往后解析执行\n\n发现有一个js外链脚本，发起请求进行下载\n当当前页面环境拿到js脚本之后，vue.js就会执行，执行结束，就向全局暴露出了一个对象：Vue\n\n - 3.当解析执行到咱们自己的Script的时候，开始解析执行咱们自己的代码\n\n通过 el 属性 指定 Vue程序 的接管范围\n通过 data 向Vue 实例的应用程序中初始化了一个 message 成员\nVue 程序通过 el 属性指定id为 #app 的div\n开始解析执行 Vue 能识别的语法\n{{message}} 在Vue 中被称为双花括号插值表达式\n在双括号插值表达式中可以使用 当前元素 所属Vue程序 接管范围的data中的数据', '0', '', '0', '14', '2020-12-12 21:49:40', 'vue', 'Vue.js（读音 /vjuː/, 类似于 view） 是一套构建用户界面的 渐进式框架。\n与其他重量级框架不同的是，Vue 采用自底向上增量开发的设计。', '0');
INSERT INTO `post` VALUES ('787448235290525696', '内网穿透访问Vue项目 Invalid Host header', '780837408789757952', '787413645830127616', '### 内网穿透访问Vue项目的时候出现Invalid Host header解决办法\n\n出现Invalid Host header解决办法\n 内网穿透\n 适用场景:\n 在本地的Vue-cli3项目, 需要其他人浏览. 如果没有外网的服务器, 可以把自己的电脑当做服务器. 这时候需要外网的人能访问到自己的电脑.\n 内网穿透工具:\n 这个有很多, 比如花生壳, Frp, ngrok, 我用的是国内的sunny-ngrok. 使用非常简单, 根据官网操作就可以.\n Invalid Host header\n 当启动了sunny-ngrok的客户端以后, 就这个用sunny-ngrok提供的域名, 来访问本地的127.0.0.0:8080(端口自己设置), 如果Vue项目启动, 外网就可以通过域名来访问Vue项目. 但是如果出现 Invalid Host header, 需要设置一下\n\nvue-cli3中:\n // vue.config.js文件中\n module.exports = {\n   devServer: {\n     disableHostCheck: true\n   }\n }', '1', null, '0', '9', '2020-12-12 22:38:09', '', '内网穿透访问Vue项目的时候出现Invalid Host header解决办法', '1');
INSERT INTO `post` VALUES ('787449216644415488', '使用springboot中include的问题', '780837408789757952', '787413102411907072', '使用的springboot版本如下\n```\n<parent>\n	<groupId>org.springframework.boot</groupId>\n	<artifactId>spring-boot-starter-parent</artifactId>\n	<version>2.4.0</version>\n	<relativePath /> <!-- lookup parent from repository -->\n</parent>\n```\n我使用了application.yml，application-dev.yml，application-mysql.yml\n\napplication.yml如下\n```\nspring:\n  profiles:\n    active: dev\n\nlogging:\n  config: classpath:logback-spring.xml\n```\napplication-ddev.yml如下\n```\nserver:\n  port: 8088\n  servlet:\n    context-path: /\n    multipart:\n      maxFileSize: 100MB\n      maxRequestSize: 100MB\n\nspring:\n  profiles:\n    include:\n      - mysql\n```\napplication-mysql.yml如下\n```\nspring:\n  datasource:\n    mysql:\n      url: jdbc:mysql://*******:3306/epoch_study?useUnicode=true&characterEncoding=UTF-8&useSSL=false\n      driverClassName: com.mysql.jdbc.Driver\n      username: ****\n      password: ****\n```\n但是当我在\n```\n@Configuration\n@EnableConfigurationProperties\n@MapperScan(basePackages = MybatisDataSourceConfig.PACKAGE, sqlSessionFactoryRef = \"mybatisSqlSessionFactory\")\npublic class MybatisDataSourceConfig {\n\n    protected static final String PACKAGE = \"com.epoch.dao.mybatis\";\n    protected static final String MAPPER_LOCATION = \"classpath:mapper/mybatis/*.xml\";\n\n    @Value(\"${spring.datasource.mysql.url}\")\n    private String url;\n\n    @Value(\"${spring.datasource.mysql.username}\")\n    private String username;\n\n    @Value(\"${spring.datasource.mysql.password}\")\n    private String password;\n\n    @Value(\"${spring.datasource.mysql.driverClassName}\")\n    private String driverClassName;\n}\n```\n使用@Value的时候，缺报错如下\n```\nCould not resolve placeholder \'spring.datasource.mysql.url\' in value \"${spring.datasource.mysql.url}\"\n```\n但是当我吧application-mysql.yml中内容，移动到application-dev.yml中，又可以正常启动，其中\n我怀疑是spring.profiles.include使用错误，因此换了多种方式，均不行\n\n在POM中已引用了\n```\n<dependency>\n	<groupId>org.springframework.boot</groupId>\n	<artifactId>spring-boot-autoconfigure</artifactId>\n</dependency>\n```\n请问大家知道是如何造成这种现象的，怎么解决呢？', '1', null, '0', '71', '2020-12-12 22:42:03', 'java', '使用springboot中include的问题,请问大家知道是如何造成这种现象的，怎么解决呢？', '1');
INSERT INTO `post` VALUES ('789816400616095744', '关于评论通知', '787427861714698240', '787417419063754752', '目前设定有两种`Post`类型\n - 文章\n - 提问\n---\n提问中的评论，相当于解答\n因此 会有 最优解（最佳答案） 由文章作者 设定', '0', '', '0', '17', '2020-12-19 11:28:24', '', '关于评论通知', '0');
INSERT INTO `post` VALUES ('790253788505047040', '哈哈哈，这个教人写烂代码的项目在 GitHub 上火了。。。', '780837408789757952', '787417419063754752', '如果说到什么是好代码，我们肯定都能说出一堆规则，例如使用一致的格式和缩进、使用清晰的变量名和方法名、在必要时提供文档与注释、不要过度精简代码等等。\n\n但是对于什么是烂代码，你有比较清晰的认识吗？\n\n在 GitHub 上有一个新项目，它描述了「最佳垃圾代码」的十九条关键准则。从变量命名到注释编写。这些准则将指导你写出最亮眼的烂代码。\n\n为了保持与原 GitHub 项目一致的风格，下文没有进行转换。读者们可以以相反的角度来理解所有观点，这样就能完美避免写出垃圾代码。\n\n项目地址：https://github.com/trekhleb/state-of-the-art-shitcode\n\n当然，以下十九条垃圾代码书写准则并没有面面俱到，如果读者们发现有一些难以忍受的烂代码习惯，也可以留言发表你的看法。\n\n### ? 以一种代码已经被混淆的方式命名变量\n\n如果我们键入的东西越少，那么就有越多的时间去思考代码逻辑等问题。\n\n_Good ??_\n\n```javascript\nlet a = 42;\n```\n\n_Bad ??_\n\n```javascript\nlet age = 42;\n```\n\n### ? 变量/函数混合命名风格\n\n为不同庆祝一下。\n\n_Good ??_\n\n```javascript\nlet wWidth = 640;\nlet w_height = 480;\n```\n\n_Bad ??_\n\n```javascript\nlet windowWidth = 640;\nlet windowHeight = 480;\n```\n\n### ? 不要写注释\n\n反正没人会读你的代码。\n\n_Good ??_\n\n```javascript\nconst cdr = 700;\n```\n\n_Bad ??_\n\n更多时候，评论应该包含一些“为什么”，而不是一些“是什么”。如果“什么”在代码中不清楚，那么代码可能太混乱了。\n\n```javascript\n// 700ms的数量是根据UX A/B测试结果进行经验计算的。\n// @查看: <详细解释700的一个链接>\nconst callbackDebounceRate = 700;\n```\n\n### ? 使用母语写注释\n\n如果您违反了“无注释”原则，那么至少尝试用一种不同于您用来编写代码的语言来编写注释。如果你的母语是英语，你可能会违反这个原则。\n\n_Good ??_\n\n```javascript\n// Закриваємо модальне віконечко при виникненні помилки.\ntoggleModal(false);\n```\n\n_Bad ??_\n\n```javascript\n// 隐藏错误弹窗\ntoggleModal(false);\n```\n\n### ? 尽可能混合不同的格式\n\n为不同庆祝一下。\n\n_Good ??_\n\n```javascript\nlet i = [\'tomato\', \'onion\', \'mushrooms\'];\nlet d = [ \"ketchup\", \"mayonnaise\" ];\n```\n\n_Bad ??_\n\n```javascript\nlet ingredients = [\'tomato\', \'onion\', \'mushrooms\'];\nlet dressings = [\'ketchup\', \'mayonnaise\'];\n```\n\n### ? 尽可能把代码写成一行\n\n_Good ??_\n\n```javascript\ndocument.location.search.replace(/(^\\?)/,\'\').split(\'&\').reduce(function(o,n){n=n.split(\'=\');o[n[0]]=n[1];return o},{})\n```\n\n_Bad ??_\n\n```javascript\ndocument.location.search\n  .replace(/(^\\?)/, \'\')\n  .split(\'&\')\n  .reduce((searchParams, keyValuePair) => {\n    keyValuePair = keyValuePair.split(\'=\');\n    searchParams[keyValuePair[0]] = keyValuePair[1];\n    return searchParams;\n  },\n  {}\n)\n```\n\n### ? 不要处理错误\n\n无论何时发现错误，都没有必要让任何人知道它。没有日志，没有错误弹框。\n\n_Good ??_\n\n```javascript\ntry {\n  // 意料之外的情况。\n} catch (error) {\n  // tss... ?\n}\n```\n\n_Bad ??_\n\n```javascript\ntry {\n  // 意料之外的情况。\n} catch (error) {\n  setErrorMessage(error.message);\n  // and/or\n  logError(error);\n}\n```\n\n### ? 广泛使用全局变量\n\n全球化的原则。\n\n_Good ??_\n\n```javascript\nlet x = 5;\nfunction square() {\n  x = x ** 2;\n}\nsquare(); // 现在x是25\n```\n\n_Bad ??_\n\n```javascript\nlet x = 5;\nfunction square(num) {\n  return num ** 2;\n}\nx = square(x); // 现在x是25\n```\n\n### ? 创建你不会使用的变量\n\n以防万一。\n\n_Good ??_\n\n```javascript\nfunction sum(a, b, c) {\n  const timeout = 1300;\n  const result = a + b;\n  return a + b;\n}\n```\n\n_Bad ??_\n\n```javascript\nfunction sum(a, b) {\n  return a + b;\n}\n```\n\n### ? 如果语言允许，不要指定类型和/或不执行类型检查。\n\n_Good ??_\n\n```javascript\nfunction sum(a, b) {\n  return a + b;\n}\n// 在这里享受没有注释的快乐\nconst guessWhat = sum([], {}); // -> \"[object Object]\"\nconst guessWhatAgain = sum({}, []); // -> 0\n```\n\n_Bad ??_\n\n```javascript\nfunction sum(a: number, b: number): ?number {\n  // 当我们在JS中不做置换和/或流类型检查时，覆盖这种情况。\n  if (typeof a !== \'number\' && typeof b !== \'number\') {\n    return undefined;\n  }\n  return a + b;\n}\n// 这个应该在转换/编译期间失败。\nconst guessWhat = sum([], {}); // -> undefined\n```\n\n### ? 你应该有不能到达的代码\n\n这是你的 \"Plan B\".\n\n_Good ??_\n\n```javascript\nfunction square(num) {\n  if (typeof num === \'undefined\') {\n    return undefined;\n  }\n  else {\n    return num ** 2;\n  }\n  return null; // 这就是我的\"Plan B\".\n}\n```\n\n_Bad ??_\n\n```javascript\nfunction square(num) {\n  if (typeof num === \'undefined\') {\n    return undefined;\n  }\n  return num ** 2;\n}\n```\n\n### ? 三角法则\n\n就像鸟巢，鸟巢，鸟巢。\n\n_Good ??_\n\n```javascript\nfunction someFunction() {\n  if (condition1) {\n    if (condition2) {\n      asyncFunction(params, (result) => {\n        if (result) {\n          for (;;) {\n            if (condition3) {\n            }\n          }\n        }\n      })\n    }\n  }\n}\n```\n\n_Bad ??_\n\n```javascript\nasync function someFunction() {\n  if (!condition1 || !condition2) {\n    return;\n  }\n  \n  const result = await asyncFunction(params);\n  if (!result) {\n    return;\n  }\n  \n  for (;;) {\n    if (condition3) {\n    }\n  }\n}\n```\n\n### ? 混合缩进\n\n避免缩进，因为它们会使复杂的代码在编辑器中占用更多的空间。如果你不喜欢回避他们，那就和他们捣乱。\n\n_Good ??_\n\n```javascript\nconst fruits = [\'apple\',\n  \'orange\', \'grape\', \'pineapple\'];\n  const toppings = [\'syrup\', \'cream\', \n                    \'jam\', \n                    \'chocolate\'];\nconst desserts = [];\nfruits.forEach(fruit => {\ntoppings.forEach(topping => {\n    desserts.push([\nfruit,topping]);\n    });})\n```\n\n_Bad ??_\n\n```javascript\nconst fruits = [\'apple\', \'orange\', \'grape\', \'pineapple\'];\nconst toppings = [\'syrup\', \'cream\', \'jam\', \'chocolate\'];\nconst desserts = [];\nfruits.forEach(fruit => {\n  toppings.forEach(topping => {\n    desserts.push([fruit, topping]); \n  });\n})\n```\n\n### ? 不要锁住你的依赖项\n\n以非受控方式更新每个新安装的依赖项。为什么坚持使用过去的版本，让我们使用最先进的库版本。\n\n_Good ??_\n\n```\n$ ls -la\npackage.json\n```\n\n_Bad ??_\n\n```\n$ ls -la\npackage.json\npackage-lock.json\n```\n\n### ? 函数长的比短的好\n\n不要把程序逻辑分成可读的部分。如果IDE的搜索停止，而您无法找到所需的文件或函数，该怎么办?\n\n- 一个文件中10000行代码是OK的。\n- 一个函数体1000行代码是OK的。\n- 处理许多服务(第三方和内部，也有一些工具、数据库手写ORM和jQuery滑块)在一个\' service.js \' ?这是OK的。\n\n### ? 不要测试你的代码\n\n这是重复的并且不需要的工作。\n\n### ? 避免代码风格统一\n\n编写您想要的代码，特别是在一个团队中有多个开发人员的情况下。这是一个“自由”的原则。\n\n### ? 构建新项目不需要 README 文档\n\n一开始我们就应该保持。\n\n### ? 保存不必要的代码\n\n不要删除不用的代码，最多是注释掉。', '0', '', '1', '10', '2020-12-20 16:26:25', '', '教你怎么写好代码，通俗易懂~~~', '0');
INSERT INTO `post` VALUES ('791987801880002560', '【面试篇】寒冬求职之你必须要懂的Web安全', '787427861714698240', '787413269127102464', '随着互联网的发展，各种Web应用变得越来越复杂，满足了用户的各种需求的同时，各种网络安全问题也接踵而至。作为前端工程师的我们也逃不开这个问题，今天一起看一看Web前端有哪些安全问题以及我们如何去检测和防范这些问题。非前端的攻击本文不会讨论(如SQL注入，DDOS攻击等)，毕竟后端也非本人擅长的领域。\n\nQQ邮箱、新浪微博、YouTube、WordPress 和 百度 等知名网站都曾遭遇攻击，如果你从未有过安全方面的问题，不是因为你所开发的网站很安全，更大的可能是你的网站的流量非常低或者没有攻击的价值。\n\n本文主要讨论以下几种攻击方式: XSS攻击、CSRF攻击、点击劫持以及URL跳转漏洞。\n\n> **希望大家在阅读完本文之后，能够很好的回答以下几个面试题。**\n\n1.前端有哪些攻击方式？\n\n2.什么是XSS攻击？XSS攻击有几种类型？如果防范XSS攻击？\n\n3.什么是CSRF攻击？如何防范CSRF攻击\n\n4.如何检测网站是否安全？\n\n在开始之前，建议大家先clone代码，我为大家准备好了示例代码，并且写了详细的注释，大家可以对照代码来理解每一种攻击以及如何去防范攻击，毕竟看再多的文字，都不如实操。(Readme中详细得写了操作步骤)：https://github.com/YvetteLau/Blog/tree/master/Security\n\n## 1. XSS攻击\n\nXSS(Cross-Site Scripting，跨站脚本攻击)是一种代码注入攻击。攻击者在目标网站上注入恶意代码，当被攻击者登陆网站时就会执行这些恶意代码，这些脚本可以读取 cookie，session tokens，或者其它敏感的网站信息，对用户进行钓鱼欺诈，甚至发起蠕虫攻击等。\n\nXSS 的本质是：恶意代码未经过滤，与网站正常的代码混在一起；浏览器无法分辨哪些脚本是可信的，导致恶意脚本被执行。由于直接在用户的终端执行，恶意代码能够直接获取用户的信息，利用这些信息冒充用户向网站发起攻击者定义的请求。\n\n> XSS分类\n\n根据攻击的来源，XSS攻击可以分为存储型(持久性)、反射型(非持久型)和DOM型三种。下面我们来详细了解一下这三种XSS攻击：\n\n> ### 1.1 反射型XSS\n\n当用户点击一个恶意链接，或者提交一个表单，或者进入一个恶意网站时，注入脚本进入被攻击者的网站。Web服务器将注入脚本，比如一个错误信息，搜索结果等，未进行过滤直接返回到用户的浏览器上。\n\n> 反射型 XSS 的攻击步骤：\n\n1. 攻击者构造出特殊的 `URL`，其中包含恶意代码。\n2. 用户打开带有恶意代码的 `URL` 时，网站服务端将恶意代码从 `URL` 中取出，拼接在 HTML 中返回给浏览器。\n3. 用户浏览器接收到响应后解析执行，混在其中的恶意代码也被执行。\n4. 恶意代码窃取用户数据并发送到攻击者的网站，或者冒充用户的行为，调用目标网站接口执行攻击者指定的操作。\n\n反射型 XSS 漏洞常见于通过 `URL` 传递参数的功能，如网站搜索、跳转等。由于需要用户主动打开恶意的 `URL` 才能生效，攻击者往往会结合多种手段诱导用户点击。\n\nPOST 的内容也可以触发反射型 XSS，只不过其触发条件比较苛刻（需要构造表单提交页面，并引导用户点击），所以非常少见。\n\n> 查看反射型攻击示例\n\n请戳： https://github.com/YvetteLau/Blog/tree/master/Security\n\n根据 `README.md` 的提示进行操作(真实情况下是需要诱导用户点击的，上述代码仅是用作演示)。\n\n注意`Chrome` 和 `Safari` 能够检测到 `url` 上的xss攻击，将网页拦截掉，但是其它浏览器不行，如`Firefox`\n\n如果不希望被前端拿到cookie，后端可以设置 `httpOnly` (不过这不是 `XSS攻击` 的解决方案，只能降低受损范围)\n\n> 如何防范反射型XSS攻击\n\n**对字符串进行编码。**\n\n对url的查询参数进行转义后再输出到页面。\n\n```\napp.get(\'/welcome\', function(req, res) {\n    //对查询参数进行编码，避免反射型 XSS攻击\n    res.send(`${encodeURIComponent(req.query.type)}`); \n});\n```\n\n> ### 1.2 DOM 型 XSS\n\nDOM 型 XSS 攻击，实际上就是前端 `JavaScript` 代码不够严谨，把不可信的内容插入到了页面。在使用 `.innerHTML`、`.outerHTML`、`.appendChild`、`document.write()`等API时要特别小心，不要把不可信的数据作为 HTML 插到页面上，尽量使用 `.innerText`、`.textContent`、`.setAttribute()` 等。\n\n> DOM 型 XSS 的攻击步骤：\n\n1. 攻击者构造出特殊数据，其中包含恶意代码。\n2. 用户浏览器执行了恶意代码。\n3. 恶意代码窃取用户数据并发送到攻击者的网站，或者冒充用户的行为，调用目标网站接口执行攻击者指定的操作。\n\n> 如何防范 DOM 型 XSS 攻击\n\n防范 DOM 型 XSS 攻击的核心就是对输入内容进行转义(DOM 中的内联事件监听器和链接跳转都能把字符串作为代码运行，需要对其内容进行检查)。\n\n1.对于`url`链接(例如图片的`src`属性)，那么直接使用 `encodeURIComponent` 来转义。\n\n2.非`url`，我们可以这样进行编码：\n\n```\nfunction encodeHtml(str) {\n    return str.replace(/\"/g, \'&quot;\')\n            .replace(/\'/g, \'&apos;\')\n            .replace(/</g, \'&lt;\')\n            .replace(/>/g, \'&gt;\');\n}\n```\n\nDOM 型 XSS 攻击中，取出和执行恶意代码由浏览器端完成，属于前端 JavaScript 自身的安全漏洞。\n\n> 查看DOM型XSS攻击示例(根据readme提示查看)\n\n请戳： https://github.com/YvetteLau/Blog/tree/master/Security\n\n> ### 1.3 存储型XSS\n\n恶意脚本永久存储在目标服务器上。当浏览器请求数据时，脚本从服务器传回并执行，影响范围比反射型和DOM型XSS更大。存储型XSS攻击的原因仍然是没有做好数据过滤：前端提交数据至服务端时，没有做好过滤；服务端在接受到数据时，在存储之前，没有做过滤；前端从服务端请求到数据，没有过滤输出。\n\n> 存储型 XSS 的攻击步骤：\n\n1. 攻击者将恶意代码提交到目标网站的数据库中。\n2. 用户打开目标网站时，网站服务端将恶意代码从数据库取出，拼接在 HTML 中返回给浏览器。\n3. 用户浏览器接收到响应后解析执行，混在其中的恶意代码也被执行。\n4. 恶意代码窃取用户数据并发送到攻击者的网站，或者冒充用户的行为，调用目标网站接口执行攻击者指定的操作。\n\n这种攻击常见于带有用户保存数据的网站功能，如论坛发帖、商品评论、用户私信等。\n\n> 如何防范存储型XSS攻击：\n\n1. 前端数据传递给服务器之前，先转义/过滤(防范不了抓包修改数据的情况)\n2. 服务器接收到数据，在存储到数据库之前，进行转义/过滤\n3. 前端接收到服务器传递过来的数据，在展示到页面前，先进行转义/过滤\n\n> 查看存储型XSS攻击示例(根据Readme提示查看)\n\n请戳： https://github.com/YvetteLau/Blog/tree/master/Security\n\n**除了谨慎的转义，我们还需要其他一些手段来防范XSS攻击:**\n\n**1.Content Security Policy**\n\n在服务端使用 HTTP的 `Content-Security-Policy` 头部来指定策略，或者在前端设置 `meta` 标签。\n\n例如下面的配置只允许加载同域下的资源：\n\n```\nContent-Security-Policy: default-src \'self\'\n<meta http-equiv=\"Content-Security-Policy\" content=\"form-action \'self\';\">\n```\n\n前端和服务端设置 CSP 的效果相同，但是`meta`无法使用`report`\n\n> 更多的设置可以查看 [Content-Security-Policy\n> ](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Security-Policy__by_cnvoid)\n\n严格的 CSP 在 XSS 的防范中可以起到以下的作用：\n\n1. 禁止加载外域代码，防止复杂的攻击逻辑。\n2. 禁止外域提交，网站被攻击后，用户的数据不会泄露到外域。\n3. 禁止内联脚本执行（规则较严格，目前发现 GitHub 使用）。\n4. 禁止未授权的脚本执行（新特性，Google Map 移动版在使用）。\n5. 合理使用上报可以及时发现 XSS，利于尽快修复问题。\n\n**2.输入内容长度控制**\n\n对于不受信任的输入，都应该限定一个合理的长度。虽然无法完全防止 XSS 发生，但可以增加 XSS 攻击的难度。\n\n**3.输入内容限制**\n\n对于部分输入，可以限定不能包含特殊字符或者仅能输入数字等。\n\n**4.其他安全措施**\n\n- HTTP-only Cookie: 禁止 JavaScript 读取某些敏感 Cookie，攻击者完成 XSS 注入后也无法窃取此 Cookie。\n- 验证码：防止脚本冒充用户提交危险操作。\n\n> ### 1.5 XSS 检测\n\n读到这儿，相信大家已经知道了什么是XSS攻击，XSS攻击的类型，以及如何去防范XSS攻击。但是有一个非常重要的问题是：我们如何去检测XSS攻击，怎么知道自己的页面是否存在XSS漏洞？\n\n很多大公司，都有专门的安全部门负责这个工作，但是如果没有安全部门，作为开发者本身，该如何去检测呢？\n\n1.使用通用 XSS 攻击字串手动检测 XSS 漏洞\n\n如: ```jaVasCript:/*-/*`/*\\`/*\'/*\"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//\\x3csVg/\\x3e```\n\n能够检测到存在于 HTML 属性、HTML 文字内容、HTML 注释、跳转链接、内联 JavaScript 字符串、内联 CSS 样式表等多种上下文中的 XSS 漏洞，也能检测 eval()、setTimeout()、setInterval()、Function()、innerHTML、document.write() 等 DOM 型 XSS 漏洞，并且能绕过一些 XSS 过滤器。\n\n`<img src=1 onerror=alert(1)>`\n\n2.使用第三方工具进行扫描（详见最后一个章节）\n------\n\n## 2. CSRF\n\nCSRF（Cross-site request forgery）跨站请求伪造：攻击者诱导受害者进入第三方网站，在第三方网站中，向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证，绕过后台的用户验证，达到冒充用户对被攻击的网站执行某项操作的目的。\n\n> 典型的CSRF攻击流程：\n\n1. 受害者登录A站点，并保留了登录凭证（Cookie）。\n2. 攻击者诱导受害者访问了站点B。\n3. 站点B向站点A发送了一个请求，浏览器会默认携带站点A的Cookie信息。\n4. 站点A接收到请求后，对请求进行验证，并确认是受害者的凭证，误以为是无辜的受害者发送的请求。\n5. 站点A以受害者的名义执行了站点B的请求。\n6. 攻击完成，攻击者在受害者不知情的情况下，冒充受害者完成了攻击。\n\n> CSRF的特点\n\n1.攻击通常在第三方网站发起，如图上的站点B，站点A无法防止攻击发生。\n\n2.攻击利用受害者在被攻击网站的登录凭证，冒充受害者提交操作；并不会去获取cookie信息(cookie有同源策略)\n\n3.跨站请求可以用各种方式：图片URL、超链接、CORS、Form提交等等(来源不明的链接，不要点击)\n\n> 运行代码，更直观了解一下\n\n用户 loki 银行存款 10W。\n\n用户 yvette 银行存款 1000。\n\n我们来看看 yvette 如何通过 `CSRF` 攻击，将 loki 的钱偷偷转到自己的账户中，并根据提示，查看如何去防御CSRF攻击。\n\n#### CSRF 攻击防御\n\n**1. 添加验证码(体验不好)**\n\n验证码能够防御CSRF攻击，但是我们不可能每一次交互都需要验证码，否则用户的体验会非常差，但是我们可以在转账，交易等操作时，增加验证码，确保我们的账户安全。\n\n**2. 判断请求的来源：检测Referer(并不安全，Referer可以被更改)**\n\n```\n`Referer` 可以作为一种辅助手段，来判断请求的来源是否是安全的，但是鉴于 `Referer` 本身是可以被修改的，因为不能仅依赖于  `Referer`\n```\n\n**3. 使用Token(主流)**\n\n```\nCSRF攻击之所以能够成功，是因为服务器误把攻击者发送的请求当成了用户自己的请求。那么我们可以要求所有的用户请求都携带一个CSRF攻击者无法获取到的Token。服务器通过校验请求是否携带正确的Token，来把正常的请求和攻击的请求区分开。跟验证码类似，只是用户无感知。\n\n- 服务端给用户生成一个token，加密后传递给用户\n- 用户在提交请求时，需要携带这个token\n- 服务端验证token是否正确\n```\n\n**4. Samesite Cookie属性**\n\n为了从源头上解决这个问题，Google起草了一份草案来改进HTTP协议，为Set-Cookie响应头新增Samesite属性，它用来标明这个 Cookie是个“同站 Cookie”，同站Cookie只能作为第一方Cookie，不能作为第三方Cookie，Samesite 有两个属性值，分别是 Strict 和 Lax。\n\n部署简单，并能有效防御CSRF攻击，但是存在兼容性问题。\n\n**Samesite=Strict**\n\n`Samesite=Strict` 被称为是严格模式,表明这个 Cookie 在任何情况都不可能作为第三方的 Cookie，有能力阻止所有CSRF攻击。此时，我们在B站点下发起对A站点的任何请求，A站点的 Cookie 都不会包含在cookie请求头中。\n\n```\n**Samesite=Lax**\n\n`Samesite=Lax` 被称为是宽松模式，与 Strict 相比，放宽了限制，允许发送安全 HTTP 方法带上 Cookie，如 `Get` / `OPTIONS` 、`HEAD` 请求.\n\n但是不安全 HTTP 方法，如： `POST`, `PUT`, `DELETE` 请求时，不能作为第三方链接的 Cookie\n```\n\n为了更好的防御CSRF攻击，我们可以组合使用以上防御手段。\n\n## 3. 点击劫持\n\n点击劫持是指在一个Web页面中隐藏了一个透明的iframe，用外层假页面诱导用户点击，实际上是在隐藏的frame上触发了点击事件进行一些用户不知情的操作。\n\n#### 典型点击劫持攻击流程\n\n1. 攻击者构建了一个非常有吸引力的网页【不知道哪些内容对你们来说有吸引力，我就不写页面了，偷个懒】\n2. 将被攻击的页面放置在当前页面的 `iframe` 中\n3. 使用样式将 iframe 叠加到非常有吸引力内容的上方\n4. 将iframe设置为100%透明\n5. 你被诱导点击了网页内容，你以为你点击的是***，而实际上，你成功被攻击了。\n\n#### 点击劫持防御\n\n**1. frame busting**\n\nFrame busting\n\n```\nif ( top.location != window.location ){\n    top.location = window.location\n}\n```\n\n需要注意的是: HTML5中iframe的 `sandbox` 属性、IE中iframe的`security` 属性等，都可以限制iframe页面中的JavaScript脚本执行，从而可以使得 frame busting 失效。\n\n**2. X-Frame-Options**\n\nX-FRAME-OPTIONS是微软提出的一个http头，专门用来防御利用iframe嵌套的点击劫持攻击。并且在IE8、Firefox3.6、Chrome4以上的版本均能很好的支持。\n\n可以设置为以下值:\n\n- DENY: 拒绝任何域加载\n- SAMEORIGIN: 允许同源域下加载\n- ALLOW-FROM: 可以定义允许frame加载的页面地址\n\n## 4. URL跳转漏洞\n\nURL 跳转漏洞是指后台服务器在告知浏览器跳转时，未对客户端传入的重定向地址进行合法性校验，导致用户浏览器跳转到钓鱼页面的一种漏洞。\n\nURL跳转一般有以下几种实现方式\n\n1. Header头跳转\n2. Javascript跳转\n3. META标签跳转\n\n> #### URL跳转漏洞防御\n\n之所以会出现跳转 URL 漏洞，就是因为服务端没有对客户端传递的跳转地址进行合法性校验，所以，预防这种攻击的方式，就是对客户端传递过来的跳转 URL 进行校验。\n\n**1.referer的限制**\n\n如果确定传递URL参数进入的来源，我们可以通过该方式实现安全限制，保证该URL的有效性，避免恶意用户自己生成跳转链接\n\n**2.加入有效性验证Token**\n\n保证所有生成的链接都是来自于可信域，通过在生成的链接里加入用户不可控的token对生成的链接进行校验，可以避免用户生成自己的恶意链接从而被利用。\n\n### 安全扫描工具\n\n上面我们介绍了几种常见的前端安全漏洞，也了解一些防范措施，那么我们如何发现自己网站的安全问题呢？没有安全部门的公司可以考虑下面几款开源扫码工具：\n\n#### 1. [Arachni](https://github.com/Arachni/arachni)\n\nArachni是基于Ruby的开源，功能全面，高性能的漏洞扫描框架，Arachni提供简单快捷的扫描方式，只需要输入目标网站的网址即可开始扫描。它可以通过分析在扫描过程中获得的信息，来评估漏洞识别的准确性和避免误判。\n\nArachni默认集成大量的检测工具，可以实施 代码注入、CSRF、文件包含检测、SQL注入、命令行注入、路径遍历等各种攻击。\n\n同时，它还提供了各种插件，可以实现表单爆破、HTTP爆破、防火墙探测等功能。\n\n针对大型网站，该工具支持会话保持、浏览器集群、快照等功能，帮助用户更好实施渗透测试。\n\n#### 2. [Mozilla HTTP Observatory](https://github.com/mozilla/http-observatory/)\n\nMozilla HTTP Observatory，是Mozilla最近发布的一款名为Observatory的网站安全分析工具，意在鼓励开发者和系统管理员增强自己网站的安全配置。用法非常简单：输入网站URL，即可访问并分析网站HTTP标头，随后可针对网站安全性提供数字形式的分数和字母代表的安全级别。\n\n> 检查的主要范围包括：\n\n- Cookie\n- 跨源资源共享（CORS）\n- 内容安全策略（CSP）\n- HTTP公钥固定（Public Key Pinning）\n- HTTP严格安全传输（HSTS）状态\n- 是否存在HTTP到HTTPs的自动重定向\n- 子资源完整性（Subresource Integrity）\n- X-Frame-Options\n- X-XSS-Protection\n\n#### 3. [w3af](https://github.com/andresriancho/w3af)\n\nW3af是一个基于Python的Web应用安全扫描器。可帮助开发人员，有助于开发人员和测试人员识别Web应用程序中的漏洞。\n\n扫描器能够识别200多个漏洞，包括跨站点脚本、SQL注入和操作系统命令。\n\n\n参考文章:\n\n[1] https://github.com/0xsobky/HackVault/wiki/Unleashing-an-Ultimate-XSS-Polyglot\n\n[2] https://tech.meituan.com/2018/10/11/fe-security-csrf.html\n\n[3] https://zhuanlan.zhihu.com/p/25486768?group_id=820705780520079360\n\n[4] https://mp.weixin.qq.com/s/up35Zd8EmEfDVnvxOX9EmA\n\n[5] https://juejin.im/post/5b4e0c936fb9a04fcf59cb79\n\n[6] https://juejin.im/post/5c8a33dcf265da2dc538fc7d\n\n[7] https://github.com/OWASP/CheatSheetSeries\n\n编写本文，虽然花费了很多时间，但是在这个过程中，我也学习到了很多知识，谢谢各位小伙伴愿意花费宝贵的时间阅读本文，如果本文给了您一点帮助或者是启发，请不要吝啬你的赞和Star，您的肯定是我前进的最大动力。https://github.com/YvetteLau/Blog', '0', 'http://localhost:8080/image/1608866201195_791987780216422400.jpg', '0', '8', '2020-12-25 11:16:46', '', '随着互联网的发展，各种Web应用变得越来越复杂，满足了用户的各种需求的同时，各种网络安全问题也接踵而至。作为前端工程师的我们也逃不开这个问题，今天一起看一看Web前端有哪些安全问题以及我们如何去检测和防范这些问题。', '0');
INSERT INTO `post` VALUES ('792038075155873792', '测试1', '780837408789757952', '787413269127102464', '# 测试1\n\n# 测试2\n\n![21.jpg](http://localhost:8080/image/1608878157364_792037928023883776.jpg)', '0', 'http://localhost:8080/image/1608878185252_792038044868804608.jpg', '0', '2', '2020-12-25 14:36:32', 'hello', '测试1测试1测试1测试1测试1', '0');

-- ----------------------------
-- Table structure for setting
-- ----------------------------
DROP TABLE IF EXISTS `setting`;
CREATE TABLE `setting` (
  `key` varchar(200) NOT NULL,
  `value` text NOT NULL,
  PRIMARY KEY (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of setting
-- ----------------------------
INSERT INTO `setting` VALUES ('manage_account_init_state', '1');
INSERT INTO `setting` VALUES ('setting_init', '1');
INSERT INTO `setting` VALUES ('web_site_record', '浙ICP备20019719号-1');
INSERT INTO `setting` VALUES ('web_site_up_time', '2020-12-08');
INSERT INTO `setting` VALUES ('web_statement', '[\"真诚且有趣有用的内容分享，在这里更受欢迎\"]');

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `uid` varchar(20) NOT NULL COMMENT '用户ID',
  `account` varchar(255) DEFAULT NULL COMMENT '账号',
  `password` varchar(255) DEFAULT NULL COMMENT '密码',
  `role` varchar(1) DEFAULT '0' COMMENT '角色 1 管理员 0 普通用户',
  `name` varchar(255) DEFAULT NULL COMMENT '用户名',
  `sign` varchar(255) DEFAULT NULL COMMENT '用户签名',
  `avatar` varchar(1024) DEFAULT 'https://i.loli.net/2020/05/07/YeHwMu3GkX276vp.png' COMMENT '头像',
  `status` varchar(1) DEFAULT '0' COMMENT '0 正常 1 禁用',
  PRIMARY KEY (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('780837408789757952', 'nilbrains', '$2a$10$S5EXB8JO4TPySlPvKduJFuOcMp79wxLbFLRcoGLnCGWHHLHC0PXha', '0', 'NilBrains', '没有Bug的程序是不完美的，是的呢~~~', 'http://localhost:8080/image/1608813552234_791766954342875136.png', '0');
INSERT INTO `user` VALUES ('787427861714698240', 'hello', '$2a$10$NL772ps0e7XwxbC8WzEmLe8DUyERIL1Pc9QwHosju6UsvaDUy7vE.', '1', 'hello', '这个人很懒，没有签名~~', 'https://i.loli.net/2020/05/07/YeHwMu3GkX276vp.png', '0');
INSERT INTO `user` VALUES ('790234089197142016', 'user', '$2a$10$XdoCPZrgACtRpbe8HmyDye5GqT7Vc2PD31NBmw3xyQRFJmCdYbRG6', '1', 'user', '这个人很懒，没有签名~~', 'https://i.loli.net/2020/05/07/YeHwMu3GkX276vp.png', '0');
INSERT INTO `user` VALUES ('792033330324307968', 'admin', '$2a$10$WX2WR/n11rf9WWecJq7jJu35ITiMuHBxZRvaL3bQVu7bJ0zLheRey', '1', 'admin', '这个人很懒，没有签名~~', 'https://i.loli.net/2020/05/07/YeHwMu3GkX276vp.png', '0');
INSERT INTO `user` VALUES ('792037574905430016', 'nihao', '$2a$10$hndz5jE665i8tAqqt8NsbuIWwSOfbdYEhIsKEk.keFg7gPEzKUQPu', '1', 'nihao', '这个人很懒，没有签名~~', 'https://i.loli.net/2020/05/07/YeHwMu3GkX276vp.png', '0');
INSERT INTO `user` VALUES ('807255994903035904', 'nihaoya', '$2a$10$4N8xNFxpVF03aE0sHz.ba.JWNkTXuLRTW4VzLOJ3Bb9Bk2TpOUpEm', '1', 'nihaoya', '这个人很懒，没有签名~~', 'https://i.loli.net/2020/05/07/YeHwMu3GkX276vp.png', '0');

-- ----------------------------
-- Table structure for userinfo
-- ----------------------------
DROP TABLE IF EXISTS `userinfo`;
CREATE TABLE `userinfo` (
  `id` varchar(20) NOT NULL,
  `uid` varchar(20) DEFAULT NULL,
  `days` int(11) DEFAULT '0' COMMENT '签到天数',
  `sign_day` date DEFAULT NULL COMMENT '签到日期',
  `pyb` int(11) DEFAULT '0' COMMENT '积分',
  `rate` varchar(255) DEFAULT '0' COMMENT '等级 0 1 2 3 4 5 6',
  `integral` int(11) DEFAULT '0' COMMENT '积分',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of userinfo
-- ----------------------------
INSERT INTO `userinfo` VALUES ('787399432835432448', '780837408789757952', '6', '2021-02-05', '8', '2', '1124');
INSERT INTO `userinfo` VALUES ('787427862125740032', '787427861714698240', '2', '2020-12-25', '4', '1', '32');
INSERT INTO `userinfo` VALUES ('790234089754984448', '790234089197142016', '0', null, '2', '0', '4');
INSERT INTO `userinfo` VALUES ('792033330789875712', '792033330324307968', '0', null, '2', '0', '2');
INSERT INTO `userinfo` VALUES ('792037576453128192', '792037574905430016', '0', null, '2', '0', '2');
INSERT INTO `userinfo` VALUES ('807255995356020736', '807255994903035904', '0', null, '2', '0', '2');
