提交 d358591d 编写于 作者: 智布道's avatar 智布道 👁

1. springboot -> 2.0

2. 新增博客迁移功能,支持csdn、iteye和慕课网平台
3. 其他详情参考readme.md和update.md
上级 87a7b4eb
# 注:本项目Github版已不更新,获取最新版本请点击:[Gitee](https://gitee.com/yadong.zhang/DBlog)
# DBlog简介
DBlog是一款简洁美观、自适应的Java博客系统。使用springboot开发,前端使用Bootstrap。支持移动端自适应,配有完备的前台和后台管理功能。
**网站预览**
[https://www.zhyd.me](https://www.zhyd.me)
**开源地址**
1. [Gitee](https://gitee.com/yadong.zhang/DBlog)
2. [Github](https://github.com/zhangyd-c/DBlog)
## 写在前面的话
**DBlog** 是一款简洁美观、自适应的Java博客系统。使用springboot开发,前端使用Bootstrap。支持移动端自适应,配有完备的前台和后台管理功能。
![JDK](https://img.shields.io/badge/JDK-1.8-green.svg)
![Maven](https://img.shields.io/badge/Maven-3.3.1-green.svg)
![MySQL](https://img.shields.io/badge/MySQL-5.6.4-green.svg)
![Redis](https://img.shields.io/badge/Redis-3.0.503-green.svg)
[![license](https://img.shields.io/badge/license-MIT-yellow.svg)](https://gitee.com/yadong.zhang/DBlog/blob/master/LICENSE)
----
# 博客新增“文章搬运工”功能(20180824)
该功能可以将其他平台(目前支持imooc、csdn和iteye)的文章同步到本地服务器。点击下图查看视频示例
[![输入图片说明](https://images.gitee.com/uploads/images/2018/0824/161625_250da176_784199.png "屏幕截图.png")](https://gitee.com/yadong.zhang/static/raw/master/dblog/DBlog-%E6%96%87%E7%AB%A0%E6%90%AC%E8%BF%90%E5%B7%A5%E7%A4%BA%E4%BE%8B.webm)
----
# 重要声明
1. 本项目配有相关[帮助文档](https://gitee.com/yadong.zhang/DBlog/wikis)。文档中包括**基本的项目说明****shiro标签使用****七牛云配置**和一些常见的**异常问题解决方案**。使用时碰到问题请**优先**查阅【[帮助文档](https://gitee.com/yadong.zhang/DBlog/wikis)】。因为现在好多朋友问的问题大部分都写在【[帮助文档](https://gitee.com/yadong.zhang/DBlog/wikis)】中了,希望各位朋友能自己翻阅下相关资料,高效提问,避免重复问题!
2. **提问题**前请优先阅读【[如何向开源社区提问题](https://github.com/seajs/seajs/issues/545)】&【[提问的智慧](http://www.dianbo.org/9238/stone/tiwendezhihui.htm)
3. **提问题**时请优先选择[Gitee Issues](https://gitee.com/yadong.zhang/DBlog/issues)(方便问题追踪和一对一解决),其次[我的博客-留言板](https://www.zhyd.me/guestbook),再次[QQ群190886500](http://shang.qq.com/wpa/qunwpa?idkey=9f986e9b33b1de953e1ef9a96cdeec990affd0ac7855e00ff103514de2027b60)(QQ群消息较多,提问请注意节奏、时机),最次加我QQ好友直接提问(不推荐)
4. 本项目唯一官网:[https://www.zhyd.me](https://www.zhyd.me)
5. 本项目开源地址:[Gitee](https://gitee.com/yadong.zhang/DBlog) 注: **Github上的项目已不准备更新** ,因此版本较老,请Github用户移步至[Gitee](https://gitee.com/yadong.zhang/DBlog)
6. 本项目修改记录,详情请移步[这里](https://gitee.com/yadong.zhang/DBlog/blob/master/update.md)
7. 如果你想贡献代码,请先阅读[这篇文章](https://gitee.com/yadong.zhang/DBlog/blob/master/contribution.md)
----
# 写在前面的话
ps: 虽然我知道,大部分人都是来了**直接下载源代码**后就潇洒的离开,并且只有等到下次**突然想到**“我天~~我得去看看DBlog这烂项目更新新功能了吗”的时候才会重新来到这儿,即使你重新来过,我估计你也只有两个选择:
发现更新代码了 --> 下载源码后重复上面的步骤
......@@ -19,18 +35,24 @@ ps: 虽然我知道,大部分人都是来了**直接下载源代码**后就潇
虽然我知道现实就是如此的残酷,但我还是要以我萤虫之力对各位到来的同仁发出一声诚挚的嘶吼:
**如果喜欢,请多多分享!!多多Star!!fork可以,但还是请star一下!!**
**如果喜欢,请多多分享!!多多Star!!**
----
### 已经在用DBlog的网站 ( 排名按照留言先后顺序 )
- [张亚东博客](https://www.zhyd.me)
- [攻城狮不是猫博客](http://www.jsdblog.com)
- [刘辉辉的博客](http://www.axxo.top)
- [乐赚淘](http://www.lezhuantao.com)
- [时光号](http://www.shiguanghao.cn/)
- [大杂烩 | 好好网](https://www.haohaowang.top)
- 更多待续...
烦请各位使用DBlog已经跑起来自己的博客的朋友,能留下你的网址(没别的意思,只是看看有多少人而已) - [点这儿](https://gitee.com/yadong.zhang/DBlog/issues/ILIAQ)
### 开发环境
| 工具 | 版本或描述 |
| ----- | -------------------- |
| OS | Windows 7 |
| JDK | 1.7+ |
| IDE | IntelliJ IDEA 2017.3 |
| Maven | 3.3.1 |
| MySQL | 5.6.4 |
----
### 模块划分
......@@ -43,7 +65,7 @@ ps: 虽然我知道,大部分人都是来了**直接下载源代码**后就潇
### 技术栈
- Springboot 1.5.9
- Springboot 2.0.1
- Apache Shiro 1.2.2
- Logback
- Redis
......@@ -59,101 +81,83 @@ ps: 虽然我知道,大部分人都是来了**直接下载源代码**后就潇
- Qiniu
- ...
### 功能简介
- 支持wangEditor和Markdown两种富文本编辑器,可以自行选择
- 在线申请友情链接,无需站长手动配置,只需申请方添加完站长的连接后自行申请即可
- 支持将文件提交到百度站长收录平台,加快百度引擎的收录
- 自研评论系统
- 后台配备完善的权限管理
- 自带robots、sitemap等seo模板,实现自动生成robots和sitemap
- 集成七牛云,实现文件云存储
- 系统配置支持快速配置。可通过后台手动修改诸如域名信息、SEO优化、赞赏码、七牛云以及更新维护通知等。
- 管理员可向在线的用户发送实时消息(需用户授权 - 基于websocket实现,具体参考[DBlog建站之Websocket的使用](https://www.zhyd.me/article/111)
### 使用方法
1. 使用IDE导入本项目
2. 新建数据库`CREATE DATABASE dblog;`
3. 导入数据库`docs/db/dblog.sql`
4. 修改(`resources/application.yml`)配置文件
1. 数据库链接属性(可搜索`datasource`或定位到L.19)
2. redis配置(可搜索`redis`或定位到L.69)
3. mail配置(可搜索`mail`或定位到L.89)
4.[七牛云](http://qiniu.com)】配置(见sys_config表中qiniu_*开头的字段)
注:因为系统存在redis缓存,如果是第一次使用,可以直接修改sys_config表内容,如果不是第一次用,建议使用admin项目中的`系统配置`页面修改相关配置内容
4. 修改配置文件
1. 数据库链接属性(`resources/application-{env}.yml`配置文件中搜索`datasource`或定位到L.7)
2. redis配置(`resources/application.yml`配置文件中搜索`redis`或定位到L.65)
3. mail配置(`resources/application-{env}.yml`配置文件中搜索`mail`或定位到L.14)
4.[七牛云](https://portal.qiniu.com/signup?code=3l8yx2v0f21ci)】配置(见sys_config表中qiniu_*开头的字段)
注:因为系统存在redis缓存,如果是第一次使用,可以直接修改sys_config表内容,如果不是第一次用,建议使用admin项目中的`系统配置`页面修改相关配置内容
5. 运行项目(三种方式)
1. 项目根目录下执行`mvn -X clean package -Dmaven.test.skip=true`编译打包,然后执行`java -jar target/blog-web.jar`
1. 项目根目录下执行`mvn -X clean package -Dmaven.test.skip=true -Ptest`编译打包(注:-Ptest中的test为环境标识),然后cd到blog-web目录下执行`java -jar target/blog-web.jar`
2. 项目根目录下执行`mvn springboot:run`
3. 直接运行`BlogWebApplication.java`
6. 浏览器访问`http://127.0.0.1:8443`
**后台户**
**后台默认账户**
_超级管理员_: 账号:root 密码:123456 (本地测试使用这个账号,admin没设置权限)
_超级管理员_(超级管理员): 账号:root 密码:123456
_普通管理员_: 账号:admin 密码:123456
_普通管理员_(普通管理员,无权限): 账号:admin 密码:123456
_评论审核管理员_: 账号:comment-admin 密码:123456
_评论审核管理员_(只有评论审核的权限): 账号:comment-admin 密码:123456
注:后台用户的创建,尽可能做到**权限最小化**
更多详情,请参考【[Wiki](https://gitee.com/yadong.zhang/DBlog/wikis)
### 更新日志
2018-05-25
**修改功能:**
1. 修复后台标签等分页失败的问题
2. 修复前台自动申请友链失败的问题
3. 其他一些问题
2018-05-22
**修改功能:**
1. 完善shiro权限(数据库、页面)。注:需要重新执行下`sys_resources``sys_role_resources`两张表的`insert`语句
2. redis配置默认不含密码(鉴于大多数朋友的redis都没有密码做此修改,不过本人 **强烈建议**设置下密码)
你能看到这儿已经很不容易了,剩下的自己先摸索摸索吧,实在不行,加QQ群[190886500](http://shang.qq.com/wpa/qunwpa?idkey=9f986e9b33b1de953e1ef9a96cdeec990affd0ac7855e00ff103514de2027b60),进群可以选择性的备注:~~欧巴群主我爱你~~(咳咳,鉴于部分群友的抗议,该备注就不用了),麻烦大家换成:`我猜群主一定很帅`
2018-05-18
**修复bug:**
### 后续扩展
- [x] 1. 结合websocket+notification实现管理员向在线用户实时发送消息通知(隐藏掉浏览器的情况下一样会弹出,类似windowx桌面弹窗,需要用户授权)
- [ ] 2. 页面缓存
- [ ] 3. 数据统计
- [ ] 4. cc防护
...
1. web端自动申请友链后不显示的问题
2. config表修改后不能实时刷新的问题
**增加功能:**
1. 网站赞赏码
2. 百度推送功能(链接提交到百度站长平台)
**修改功能:**
1. 百度api的ak和百度推送的token以及七牛云的配置改为通过config表管理
3. admin模块菜单通过标签实时获取
3. 弹窗工具类js结构调整
### 图片预览
你能看到这儿已经很不容易了,剩下的自己先摸索摸索吧,实在不行,加QQ群[190886500](http://shang.qq.com/wpa/qunwpa?idkey=9f986e9b33b1de953e1ef9a96cdeec990affd0ac7855e00ff103514de2027b60),进群可以选择性的备注:~~欧巴群主我爱你~~(咳咳,鉴于部分群友的抗议,该备注就不用了),麻烦大家换成:`我猜群主一定很帅`
#### 前台页面
### 图片预览
![PC-首页](https://gitee.com/uploads/images/2018/0627/161851_3eefd129_784199.png?v=1.0 "PC-首页")
![手机端](https://gitee.com/uploads/images/2018/0627/163121_6b6c551e_784199.png "手机端")
**前台页面**
![PC-首页](https://gitee.com/yadong.zhang/DBlog/raw/master/docs/img/pc-index.png?v=1.0)
![PC-文章详情页](https://gitee.com/yadong.zhang/DBlog/raw/master/docs/img/pc-detail.png?v=1.0)
![手机](https://gitee.com/yadong.zhang/DBlog/raw/master/docs/img/m.png?v=1.0)
**后台页面**
![首页](https://gitee.com/yadong.zhang/DBlog/raw/master/docs/img/admin-index.png)
![菜单](https://gitee.com/yadong.zhang/DBlog/raw/master/docs/img/admin-menu.png)
![文章列表](https://gitee.com/yadong.zhang/DBlog/raw/master/docs/img/admin-articles.png)
![发表文章](https://gitee.com/yadong.zhang/DBlog/raw/master/docs/img/admin-article2.png)
![角色列表](https://gitee.com/yadong.zhang/DBlog/raw/master/docs/img/admin-role.png)
![角色分配](https://gitee.com/yadong.zhang/DBlog/raw/master/docs/img/admin-role2.png)
#### 后台页面
![PC-后台首页](https://gitee.com/uploads/images/2018/0627/162136_01f40c01_784199.png?v=1.0 "PC-后台首页")
![PC-文章列表页](https://gitee.com/uploads/images/2018/0627/162222_001e3342_784199.png?v=1.0 "PC-文章列表页")
![PC-发布文章页-markdown版](https://gitee.com/uploads/images/2018/0627/162317_5ea6c8d5_784199.png "PC-发布文章页-markdown版")
![PC-文章发布页-html版](https://gitee.com/uploads/images/2018/0627/162442_c98194c4_784199.png "PC-文章发布页-html版.png")
### 生命不息,折腾不止! 更多信息,请关注:
### 生命不息,折腾不止! 更多信息,请关注:
1. [我的博客](https://www.zhyd.me)
2. [我的微博](http://weibo.com/211230415)
3. [我的头条号](http://www.toutiao.com/c/user/3286958681/)
4. [我的mooc](http://www.imooc.com/u/1175248/articles)
4. [我的imooc](http://www.imooc.com/u/1175248/articles)
### 有任何问题可以
- [给我留言](https://www.zhyd.me/guestbook)
### 特别感谢
1. 广大的开源爱好者
2. 无私的网友
### 开源协议
[MIT](https://gitee.com/yadong.zhang/DBlog/blob/master/LICENSE)
[![license](https://img.shields.io/badge/license-MIT-yellow.svg)](https://gitee.com/yadong.zhang/DBlog/blob/master/LICENSE)
\ No newline at end of file
......@@ -3,9 +3,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zyd</groupId>
<artifactId>blog-admin</artifactId>
<version>0.0.1-SNAPSHOT</version>
<artifactId>blog-admin</artifactId>
<packaging>jar</packaging>
<name>blog-admin</name>
......@@ -14,36 +12,105 @@
<parent>
<groupId>com.zyd</groupId>
<artifactId>blog</artifactId>
<version>0.0.1-SNAPSHOT</version>
<version>2.0.1.Beta</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<groupId>com.zyd</groupId>
<artifactId>blog-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>com.zyd</groupId>
<artifactId>blog-core</artifactId>
<version>0.0.1-SNAPSHOT</version>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 解决@xx@无法解析的问题 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<delimiters>
<delimiter>@</delimiter>
</delimiters>
<useDefaultDelimiters>false</useDefaultDelimiters>
</configuration>
</plugin>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>yuicompressor-maven-plugin</artifactId>
<executions>
<execution>
<phase>process-resources</phase>
<goals>
<goal>compress</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- 读取js,css文件采用UTF-8编码 -->
<encoding>UTF-8</encoding>
<!-- 不显示js可能的错误 -->
<jswarn>false</jswarn>
<!-- 若存在已压缩的文件,会先对比源文件是否有改动 有改动便压缩,无改动就不压缩 -->
<force>false</force>
<!-- 在指定的列号后插入新行 -->
<linebreakpos>-1</linebreakpos>
<!-- 压缩之前先执行聚合文件操作 -->
<preProcessAggregates>true</preProcessAggregates>
<!-- 压缩后保存的文件后缀:true=无后缀 ,false=有后缀-min -->
<nosuffix>true</nosuffix>
<!-- 源目录,即需压缩的根目录 -->
<sourceDirectory>src/main/resources/static/assets</sourceDirectory>
<!-- 目标输出目录 -->
<outputDirectory>target/classes/static/assets</outputDirectory>
<!-- 压缩js和css文件 -->
<includes>
<include>**/*.js</include>
<include>**/*.css</include>
</includes>
<!-- 以下目录和文件不会被压缩 -->
<excludes>
<exclude>**/*.min.js</exclude>
<exclude>**/*.min.css</exclude>
<exclude>**/jquery-form.js</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.zyd.blog.BlogAdminApplication</mainClass>
<layout>JAR</layout>
<!--构建完整可执行程序,可以直接运行-->
<executable>true</executable>
</configuration>
</plugin>
</plugins>
<!-- 打包的时候放开 -->
<!--<resources>
<resource>
<filtering>true</filtering>
<directory>src/main/resources</directory>
<includes>
<include>static/assets/images/**</include>
&lt;!&ndash;<include>%regex[static/assets/js/(jquery-form|wangEditor\.min)\.js]</include>&ndash;&gt;
<include>static/assets/js/jquery-form.js</include>
<include>static/assets/js/wangEditor.min.js</include>
<include>static/assets/css/jquery-confirm.min.css</include>
<include>templates/**</include>
<include>*.yml</include>
<include>*.txt</include>
<include>*.xml</include>
</includes>
</resource>
</resources>-->
</build>
......
......@@ -21,18 +21,17 @@ package com.zyd.blog.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
import org.springframework.boot.autoconfigure.web.ErrorController;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
......@@ -43,8 +42,6 @@ import java.util.Map;
* 重写BasicErrorController,主要负责系统的异常页面的处理以及错误信息的显示
* <p/>
* 此处指需要记录
* @see org.springframework.boot.autoconfigure.web.BasicErrorController
* @see org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration
* <p/>
* 要注意,这个类里面的代码一定不能有异常或者潜在异常发生,否则可能会让程序陷入死循环。
* <p/>
......@@ -78,18 +75,18 @@ public class ErrorPagesController implements ErrorController {
}
@RequestMapping("/404")
public ModelAndView errorHtml404(HttpServletRequest request, HttpServletResponse response) {
public ModelAndView errorHtml404(HttpServletRequest request, HttpServletResponse response, WebRequest webRequest) {
response.setStatus(HttpStatus.NOT_FOUND.value());
Map<String, Object> model = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML));
Map<String, Object> model = getErrorAttributes(webRequest, isIncludeStackTrace(request, MediaType.TEXT_HTML));
return new ModelAndView("error/404", model);
}
@RequestMapping("/403")
public ModelAndView errorHtml403(HttpServletRequest request, HttpServletResponse response) {
public ModelAndView errorHtml403(HttpServletRequest request, HttpServletResponse response, WebRequest webRequest) {
response.setStatus(HttpStatus.FORBIDDEN.value());
// 404拦截规则,如果是静态文件发生的404则不记录到DB
Map<String, Object> model = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML));
Map<String, Object> model = getErrorAttributes(webRequest, isIncludeStackTrace(request, MediaType.TEXT_HTML));
if (!String.valueOf(model.get("path")).contains(".")) {
model.put("status", HttpStatus.FORBIDDEN.value());
}
......@@ -97,23 +94,23 @@ public class ErrorPagesController implements ErrorController {
}
@RequestMapping("/400")
public ModelAndView errorHtml400(HttpServletRequest request, HttpServletResponse response) {
public ModelAndView errorHtml400(HttpServletRequest request, HttpServletResponse response, WebRequest webRequest) {
response.setStatus(HttpStatus.BAD_REQUEST.value());
Map<String, Object> model = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML));
Map<String, Object> model = getErrorAttributes(webRequest, isIncludeStackTrace(request, MediaType.TEXT_HTML));
return new ModelAndView("error/400", model);
}
@RequestMapping("/401")
public ModelAndView errorHtml401(HttpServletRequest request, HttpServletResponse response) {
public ModelAndView errorHtml401(HttpServletRequest request, HttpServletResponse response, WebRequest webRequest) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
Map<String, Object> model = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML));
Map<String, Object> model = getErrorAttributes(webRequest, isIncludeStackTrace(request, MediaType.TEXT_HTML));
return new ModelAndView("error/401", model);
}
@RequestMapping("/500")
public ModelAndView errorHtml500(HttpServletRequest request, HttpServletResponse response) {
public ModelAndView errorHtml500(HttpServletRequest request, HttpServletResponse response, WebRequest webRequest) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
Map<String, Object> model = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML));
Map<String, Object> model = getErrorAttributes(webRequest, isIncludeStackTrace(request, MediaType.TEXT_HTML));
return new ModelAndView("error/500", model);
}
......@@ -139,15 +136,13 @@ public class ErrorPagesController implements ErrorController {
/**
* 获取错误的信息
*
* @param request
* @param webRequest
* @param includeStackTrace
* @return
*/
private Map<String, Object> getErrorAttributes(HttpServletRequest request,
private Map<String, Object> getErrorAttributes(WebRequest webRequest,
boolean includeStackTrace) {
RequestAttributes requestAttributes = new ServletRequestAttributes(request);
return this.errorAttributes.getErrorAttributes(requestAttributes,
includeStackTrace);
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
}
/**
......
......@@ -21,13 +21,13 @@ package com.zyd.blog.controller;
import com.zyd.blog.business.consts.CommonConst;
import com.zyd.blog.business.enums.ResponseStatus;
import com.zyd.blog.framework.exception.ZhydArticleException;
import com.zyd.blog.framework.exception.ZhydCommentException;
import com.zyd.blog.framework.exception.ZhydFileException;
import com.zyd.blog.framework.exception.ZhydLinkException;
import com.zyd.blog.framework.exception.*;
import com.zyd.blog.framework.object.ResponseVO;
import com.zyd.blog.util.ResultUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AccountException;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
......@@ -48,13 +48,27 @@ import java.lang.reflect.UndeclaredThrowableException;
@ControllerAdvice
public class ExceptionHandleController {
/**
* Shiro权限认证异常
*
* @param e
* @return
*/
@ExceptionHandler(value = {UnauthorizedException.class, AccountException.class})
@ResponseBody
public ResponseVO unauthorizedExceptionHandle(Throwable e) {
e.printStackTrace(); // 打印异常栈
return ResultUtil.error(HttpStatus.UNAUTHORIZED.value(), e.getLocalizedMessage());
}
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ResponseVO handle(Throwable e) {
if (e instanceof ZhydArticleException
|| e instanceof ZhydCommentException
|| e instanceof ZhydFileException
|| e instanceof ZhydLinkException) {
|| e instanceof ZhydLinkException
|| e instanceof ZhydException) {
return ResultUtil.error(e.getMessage());
}
if (e instanceof UndeclaredThrowableException) {
......
......@@ -20,6 +20,8 @@
package com.zyd.blog.controller;
import com.zyd.blog.business.annotation.BussinessLog;
import com.zyd.blog.business.entity.UserPwd;
import com.zyd.blog.business.service.SysUserService;
import com.zyd.blog.framework.object.ResponseVO;
import com.zyd.blog.framework.property.AppProperties;
import com.zyd.blog.util.ResultUtil;
......@@ -32,6 +34,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
......@@ -55,14 +59,12 @@ public class PassportController {
@Autowired
private AppProperties config;
@Autowired
private SysUserService userService;
@BussinessLog("进入登录页面")
@GetMapping("/login")
public ModelAndView login(Model model) {
Subject subject = SecurityUtils.getSubject();
if (subject.isAuthenticated()||subject.isRemembered()){
return ResultUtil.redirect("/index");
}
model.addAttribute("enableKaptcha", config.getEnableKaptcha());
return ResultUtil.view("/login");
}
......@@ -99,6 +101,22 @@ public class PassportController {
}
}
/**
* 修改密码
*
* @return
*/
@PostMapping("/updatePwd")
@ResponseBody
public ResponseVO updatePwd(@Validated UserPwd userPwd, BindingResult bindingResult) throws Exception {
if (bindingResult.hasErrors()) {
return ResultUtil.error(bindingResult.getFieldError().getDefaultMessage());
}
boolean result = userService.updatePwd(userPwd);
SessionUtil.removeAllSession();
return ResultUtil.success(result ? "密码已修改成功,请重新登录" : "密码修改失败");
}
/**
* 使用权限管理工具进行用户的退出,跳出登录,给出提示信息
*
......
......@@ -28,8 +28,17 @@ package com.zyd.blog.controller;
* @date 2018/4/24 14:37
* @since 1.0
*/
import com.zyd.blog.business.annotation.BussinessLog;
import com.zyd.blog.business.entity.Article;
import com.zyd.blog.business.service.BizArticleService;
import com.zyd.blog.core.websocket.server.ZydWebsocketServer;
import com.zyd.blog.util.ResultUtil;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.authz.annotation.RequiresUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
......@@ -48,122 +57,166 @@ import org.springframework.web.servlet.ModelAndView;
@Controller
public class RenderController {
@BussinessLog("进入首页")
@GetMapping("")
public ModelAndView home() {
return ResultUtil.view("index");
}
@Autowired
private BizArticleService articleService;
@Autowired
private ZydWebsocketServer websocketServer;
@RequiresAuthentication
@BussinessLog("进入首页")
@GetMapping("/index")
public ModelAndView index() {
@GetMapping(value = {""})
public ModelAndView home() {
return ResultUtil.view("index");
}
@RequiresPermissions("users")
@BussinessLog("进入用户列表页")
@GetMapping("/users")
public ModelAndView user() {
return ResultUtil.view("user/list");
}
@RequiresPermissions("resources")
@BussinessLog("进入资源列表页")
@GetMapping("/resources")
public ModelAndView resources() {
return ResultUtil.view("resources/list");
}
@RequiresPermissions("roles")
@BussinessLog("进入角色列表页")
@GetMapping("/roles")
public ModelAndView roles() {
return ResultUtil.view("role/list");
}
@RequiresPermissions("articles")
@BussinessLog("进入文章列表页")
@GetMapping("/articles")
public ModelAndView articles() {
return ResultUtil.view("article/list");
}
@BussinessLog("进入发表文章页")
@RequiresPermissions("article:publish")
@BussinessLog(value = "发表文章页[html]")
@GetMapping("/article/publish")
public ModelAndView publish() {
return ResultUtil.view("article/publish");
}
@BussinessLog("进入发表文章页")
@RequiresPermissions("article:publish")
@BussinessLog(value = "发表文章页[markdown]")
@GetMapping("/article/publishMd")
public ModelAndView publishMd() {
return ResultUtil.view("article/publish-md");
}
@RequiresPermissions("article:publish")
@BussinessLog(value = "修改文章页[id={1}]")
@GetMapping("/article/update/{id}")
public ModelAndView edit(@PathVariable("id") Long id, Model model) {
model.addAttribute("id", id);
Article article = articleService.getByPrimaryKey(id);
if(article.getIsMarkdown()){
return ResultUtil.view("article/publish-md");
}
return ResultUtil.view("article/publish");
}
@RequiresPermissions("types")
@BussinessLog("进入分类列表页")
@GetMapping("/article/types")
public ModelAndView types() {
return ResultUtil.view("article/types");
}
@RequiresPermissions("tags")
@BussinessLog("进入标签列表页")
@GetMapping("/article/tags")
public ModelAndView tags() {
return ResultUtil.view("article/tags");
}
@RequiresPermissions("links")
@BussinessLog("进入链接页")
@GetMapping("/links")
public ModelAndView links() {
return ResultUtil.view("link/list");
}
@RequiresPermissions("comments")
@BussinessLog("进入评论页")
@GetMapping("/comments")
public ModelAndView comments() {
return ResultUtil.view("comment/list");
}
@RequiresPermissions("notices")
@BussinessLog("进入系统通知页")
@GetMapping("/notices")
public ModelAndView notices() {
return ResultUtil.view("notice/list");
}
@RequiresRoles("role:root")
@BussinessLog("进入系统配置页")
@GetMapping("/config")
public ModelAndView config() {
return ResultUtil.view("config");
}
@RequiresPermissions("templates")
@BussinessLog("进入模板管理页")
@GetMapping("/templates")
public ModelAndView templates() {
return ResultUtil.view("template/list");
}
@RequiresPermissions("updateLogs")
@BussinessLog("进入更新记录管理页")
@GetMapping("/updates")
public ModelAndView updates() {
return ResultUtil.view("update/list");
}
@RequiresPermissions("plays")
@BussinessLog("进入歌单管理页")
@GetMapping("/plays")
public ModelAndView plays() {
return ResultUtil.view("play/list");
}
@RequiresPermissions("sysWebpage")
@BussinessLog("进入静态页面管理页")
@GetMapping("/sysWebpage")
public ModelAndView sysWebpage() {
return ResultUtil.view("sysWebpage/list");
}
@RequiresPermissions("icons")
@GetMapping("/icons")
public ModelAndView icons(Model model) {
return ResultUtil.view("icons");
}
@RequiresPermissions("shiro")
@GetMapping("/shiro")
public ModelAndView shiro(Model model) {
return ResultUtil.view("shiro");
}
@RequiresPermissions("notice")
@BussinessLog("进入通知管理页")
@GetMapping("/notice")
public ModelAndView notice(Model model) {
model.addAttribute("online", websocketServer.getOnlineUserCount());
return ResultUtil.view("notification");
}
@RequiresUser
@BussinessLog("进入搬运工页面")
@GetMapping("/remover")
public ModelAndView remover(Model model) {
return ResultUtil.view("remover/list");
}
}
......@@ -19,11 +19,16 @@
*/
package com.zyd.blog.controller;
import com.zyd.blog.business.entity.Config;
import com.zyd.blog.business.enums.QiniuUploadType;
import com.zyd.blog.business.service.BizArticleService;
import com.zyd.blog.business.service.SysConfigService;
import com.zyd.blog.core.websocket.server.ZydWebsocketServer;
import com.zyd.blog.core.websocket.util.WebSocketUtil;
import com.zyd.blog.framework.object.ResponseVO;
import com.zyd.blog.util.FileUtil;
import com.zyd.blog.util.ResultUtil;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
......@@ -31,6 +36,10 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
/**
* 其他api性质的接口
*
......@@ -46,6 +55,10 @@ public class RestApiController {
@Autowired
private BizArticleService articleService;
@Autowired
private SysConfigService configService;
@Autowired
private ZydWebsocketServer websocketServer;
/**
* 上传文件到七牛云
......@@ -53,19 +66,45 @@ public class RestApiController {
* @param file
* @return
*/
@RequiresPermissions("article:publish")
@PostMapping("/upload2Qiniu")
public ResponseVO upload2Qiniu(@RequestParam("file") MultipartFile file) {
String filePath = FileUtil.uploadToQiniu(file, QiniuUploadType.SIMPLE, false);
return ResultUtil.success("图片上传成功", filePath);
}
@RequiresPermissions("article:publish")
@PostMapping("/upload2QiniuForMd")
public Object upload2QiniuForMd(@RequestParam("file") MultipartFile file) {
String filePath = FileUtil.uploadToQiniu(file, QiniuUploadType.SIMPLE, false);
Config config = configService.get();
Map<String, Object> resultMap = new HashMap<>(3);
resultMap.put("success", 1);
resultMap.put("message", "上传成功");
resultMap.put("filename", config.getQiuniuBasePath() + filePath + "-pw");
return resultMap;
}
/**
* 发布文章选择图片时获取素材库
*
* @return
*/
@RequiresPermissions("article:publish")
@PostMapping("/material")
public ResponseVO material() {
return ResultUtil.success("", articleService.listMaterial());
}
/**
* 发送消息通知
*
* @return
*/
@RequiresPermissions("notice")
@PostMapping("/notice")
public ResponseVO notice(String msg) throws UnsupportedEncodingException {
WebSocketUtil.sendNotificationMsg(msg, websocketServer.getOnlineUsers());
return ResultUtil.success("消息发送成功", articleService.listMaterial());
}
}
......@@ -23,6 +23,7 @@ import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.PageInfo;
import com.zyd.blog.business.entity.Article;
import com.zyd.blog.business.entity.Config;
import com.zyd.blog.business.enums.ArticleStatusEnum;
import com.zyd.blog.business.enums.BaiduPushTypeEnum;
import com.zyd.blog.business.enums.ResponseStatus;
import com.zyd.blog.business.service.BizArticleService;
......@@ -34,6 +35,7 @@ import com.zyd.blog.framework.object.ResponseVO;
import com.zyd.blog.util.ResultUtil;
import com.zyd.blog.util.UrlBuildUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
......@@ -67,6 +69,7 @@ public class RestArticleController {
return ResultUtil.tablePage(pageInfo);
}
@RequiresPermissions(value = {"article:batchDelete", "article:delete"}, logical = Logical.OR)
@PostMapping(value = "/remove")
public ResponseVO remove(Long[] ids) {
if (null == ids) {
......@@ -78,23 +81,27 @@ public class RestArticleController {
return ResultUtil.success("成功删除 [" + ids.length + "] 篇文章");
}
@RequiresPermissions("article:get")
@PostMapping("/get/{id}")
public ResponseVO get(@PathVariable Long id) {
return ResultUtil.success(null, this.articleService.getByPrimaryKey(id));
}
@RequiresPermissions(value = {"article:edit", "article:publish"}, logical = Logical.OR)
@PostMapping("/save")
public ResponseVO edit(Article article, Long[] tags, MultipartFile file) {
articleService.publish(article, tags, file);
return ResultUtil.success(ResponseStatus.SUCCESS);
}
@RequiresPermissions(value = {"article:top", "article:recommend"}, logical = Logical.OR)
@PostMapping("/update/{type}")
public ResponseVO update(@PathVariable("type") String type, Long id) {
articleService.updateTopOrRecommendedById(type, id);
return ResultUtil.success(ResponseStatus.SUCCESS);
}
@RequiresPermissions(value = {"article:batchPush", "article:push"}, logical = Logical.OR)
@PostMapping(value = "/pushToBaidu/{type}")
public ResponseVO pushToBaidu(@PathVariable("type") BaiduPushTypeEnum type, Long[] ids) {
if (null == ids) {
......@@ -129,4 +136,14 @@ public class RestArticleController {
}
return ResultUtil.success(null, result);
}
@RequiresPermissions(value = {"article:publish"}, logical = Logical.OR)
@PostMapping(value = "/batchPublish")
public ResponseVO batchPublish(Long[] ids) {
if (null == ids) {
return ResultUtil.error(500, "请至少选择一条记录");
}
articleService.batchUpdateStatus(ids, true);
return ResultUtil.success("批量发布完成");
}
}
......@@ -21,22 +21,20 @@ package com.zyd.blog.controller;
import com.github.pagehelper.PageInfo;
import com.zyd.blog.business.entity.Comment;
import com.zyd.blog.business.entity.Config;
import com.zyd.blog.business.entity.User;
import com.zyd.blog.business.enums.CommentStatusEnum;
import com.zyd.blog.business.enums.ResponseStatus;
import com.zyd.blog.business.enums.TemplateKeyEnum;
import com.zyd.blog.business.service.BizCommentService;
import com.zyd.blog.business.service.MailService;
import com.zyd.blog.business.service.SysConfigService;
import com.zyd.blog.business.vo.CommentConditionVO;
import com.zyd.blog.framework.exception.ZhydCommentException;
import com.zyd.blog.framework.object.PageResult;
import com.zyd.blog.framework.object.ResponseVO;
import com.zyd.blog.util.ResultUtil;
import com.zyd.blog.util.SessionUtil;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
......@@ -57,8 +55,6 @@ public class RestCommentController {
@Autowired
private BizCommentService commentService;
@Autowired
private SysConfigService configService;
@Autowired
private MailService mailService;
@RequiresPermissions("comments")
......@@ -68,25 +64,18 @@ public class RestCommentController {
return ResultUtil.tablePage(pageInfo);
}
@RequiresPermissions("comment:reply")
@PostMapping(value = "/reply")
public ResponseVO reply(Comment comment) {
try {
Config config = configService.get();
User user = SessionUtil.getUser();
comment.setQq(user.getQq());
comment.setEmail(user.getEmail());
comment.setNickname(user.getNickname());
comment.setAvatar(user.getAvatar());
comment.setUrl(config.getSiteUrl());
comment.setUserId(user.getId());
comment.setStatus(CommentStatusEnum.APPROVED.toString());
commentService.comment(comment);
} catch (ZhydCommentException e) {
commentService.commentForAdmin(comment);
} catch (ZhydCommentException e){
return ResultUtil.error(e.getMessage());
}
return ResultUtil.success("成功");
}
@RequiresPermissions(value = {"comment:batchDelete", "comment:delete"}, logical = Logical.OR)
@PostMapping(value = "/remove")
public ResponseVO remove(Long[] ids) {
if (null == ids) {
......@@ -98,11 +87,13 @@ public class RestCommentController {
return ResultUtil.success("成功删除 [" + ids.length + "] 条评论");
}
@RequiresPermissions("comments")
@PostMapping("/get/{id}")
public ResponseVO get(@PathVariable Long id) {
return ResultUtil.success(null, this.commentService.getByPrimaryKey(id));
}
@RequiresPermissions("comments")
@PostMapping("/edit")
public ResponseVO edit(Comment comment) {
try {
......@@ -120,11 +111,16 @@ public class RestCommentController {
* @param comment
* @return
*/
@RequiresPermissions("comment:audit")
@PostMapping("/audit")
public ResponseVO audit(Comment comment, Boolean sendEmail) {
public ResponseVO audit(Comment comment, String contentText, Boolean sendEmail) {
try {
commentService.updateSelective(comment);
if (null != sendEmail && sendEmail) {
if(!StringUtils.isEmpty(contentText)){
comment.setContent(contentText);
commentService.commentForAdmin(comment);
}
if(null != sendEmail && sendEmail){
Comment commentDB = commentService.getByPrimaryKey(comment.getId());
mailService.send(commentDB, TemplateKeyEnum.TM_COMMENT_AUDIT, true);
}
......@@ -141,6 +137,7 @@ public class RestCommentController {
* @param comment
* @return
*/
@RequiresUser
@PostMapping("/listVerifying")
public ResponseVO listVerifying(Comment comment) {
return ResultUtil.success(null, commentService.listVerifying(10));
......
......@@ -25,6 +25,7 @@ import com.zyd.blog.business.service.SysConfigService;
import com.zyd.blog.framework.object.ResponseVO;
import com.zyd.blog.util.FileUtil;
import com.zyd.blog.util.ResultUtil;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
......@@ -47,11 +48,13 @@ public class RestConfigController {
@Autowired
private SysConfigService sysConfigService;
@RequiresRoles("role:root")
@PostMapping("/get")
public ResponseVO get() {
return ResultUtil.success(null, sysConfigService.get());
}
@RequiresRoles("role:root")
@PostMapping("/edit")
public ResponseVO edit(Config config,
@RequestParam(required = false) MultipartFile wxPraiseCodeFile,
......
......@@ -30,6 +30,8 @@ import com.zyd.blog.business.vo.LinkConditionVO;
import com.zyd.blog.framework.object.PageResult;
import com.zyd.blog.framework.object.ResponseVO;
import com.zyd.blog.util.ResultUtil;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
......@@ -53,12 +55,14 @@ public class RestLinkController {
@Autowired
private MailService mailService;
@RequiresPermissions("links")
@PostMapping("/list")
public PageResult list(LinkConditionVO vo) {
PageInfo<Link> pageInfo = linkService.findPageBreakByCondition(vo);
return ResultUtil.tablePage(pageInfo);
}
@RequiresPermissions("link:add")
@PostMapping(value = "/add")
public ResponseVO add(Link link) {
link.setSource(LinkSourceEnum.ADMIN);
......@@ -67,6 +71,7 @@ public class RestLinkController {
return ResultUtil.success("成功");
}
@RequiresPermissions(value = {"link:batchDelete", "link:delete"}, logical = Logical.OR)
@PostMapping(value = "/remove")
public ResponseVO remove(Long[] ids) {
if (null == ids) {
......@@ -78,11 +83,13 @@ public class RestLinkController {
return ResultUtil.success("成功删除 [" + ids.length + "] 个友情链接");
}
@RequiresPermissions("link:get")
@PostMapping("/get/{id}")
public ResponseVO get(@PathVariable Long id) {
return ResultUtil.success(null, this.linkService.getByPrimaryKey(id));
}
@RequiresPermissions("link:edit")
@PostMapping("/edit")
public ResponseVO edit(Link link) {
try {
......
......@@ -30,6 +30,8 @@ import com.zyd.blog.framework.object.PageResult;
import com.zyd.blog.framework.object.ResponseVO;
import com.zyd.blog.util.ResultUtil;
import com.zyd.blog.util.SessionUtil;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
......@@ -51,12 +53,14 @@ public class RestNoticeController {
@Autowired
private SysNoticeService noticeService;
@RequiresPermissions("notices")
@PostMapping("/list")
public PageResult list(NoticeConditionVO vo) {
PageInfo<Notice> pageInfo = noticeService.findPageBreakByCondition(vo);
return ResultUtil.tablePage(pageInfo);
}
@RequiresPermissions("notice:add")
@PostMapping(value = "/add")
public ResponseVO add(Notice notice) {
User user = SessionUtil.getUser();
......@@ -67,6 +71,7 @@ public class RestNoticeController {
return ResultUtil.success("系统通知添加成功");
}
@RequiresPermissions(value = {"notice:batchDelete", "notice:delete"}, logical = Logical.OR)
@PostMapping(value = "/remove")
public ResponseVO remove(Long[] ids) {
if (null == ids) {
......@@ -78,11 +83,13 @@ public class RestNoticeController {
return ResultUtil.success("成功删除 [" + ids.length + "] 个系统通知");
}
@RequiresPermissions("notice:get")
@PostMapping("/get/{id}")
public ResponseVO get(@PathVariable Long id) {
return ResultUtil.success(null, this.noticeService.getByPrimaryKey(id));
}
@RequiresPermissions("notice:edit")
@PostMapping("/edit")
public ResponseVO edit(Notice notice) {
try {
......@@ -94,6 +101,7 @@ public class RestNoticeController {
return ResultUtil.success(ResponseStatus.SUCCESS);
}
@RequiresPermissions("notice:release")
@PostMapping("/release/{id}")
public ResponseVO release(@PathVariable Long id) {
try {
......@@ -108,6 +116,7 @@ public class RestNoticeController {
return ResultUtil.success("该通知已发布,可去前台页面查看效果!");
}
@RequiresPermissions("notice:withdraw")
@PostMapping("/withdraw/{id}")
public ResponseVO withdraw(@PathVariable Long id) {
try {
......
/**
* MIT License
* Copyright (c) 2018 yadong.zhang
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.zyd.blog.controller;
import com.zyd.blog.business.service.RemoverService;
import com.zyd.blog.spider.model.BaseModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Remover:搬运工(英语渣渣,实在想不出好玩的名字了)
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2018/8/14 14:37
* @since 1.0
*/
@RestController
@RequestMapping("/remover")
public class RestRemoverController {
@Autowired
private RemoverService removerService;
@PostMapping("/run")
@ResponseBody
public void run(Long typeId, BaseModel model, HttpServletResponse response) throws IOException, InterruptedException {
removerService.run(typeId, model, response.getWriter());
}
}
......@@ -28,6 +28,8 @@ import com.zyd.blog.core.shiro.ShiroService;
import com.zyd.blog.framework.object.PageResult;
import com.zyd.blog.framework.object.ResponseVO;
import com.zyd.blog.util.ResultUtil;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
......@@ -54,17 +56,20 @@ public class RestResourcesController {
@Autowired
private ShiroService shiroService;
@RequiresPermissions("resources")
@PostMapping("/list")
public PageResult getAll(ResourceConditionVO vo) {
PageInfo<Resources> pageInfo = resourcesService.findPageBreakByCondition(vo);
return ResultUtil.tablePage(pageInfo);
}
@RequiresPermissions("role:allotResource")
@PostMapping("/resourcesWithSelected")
public ResponseVO<List<Resources>> resourcesWithSelected(Long rid) {
return ResultUtil.success(null, resourcesService.queryResourcesListWithSelected(rid));
}
@RequiresPermissions("resource:add")
@PostMapping(value = "/add")
public ResponseVO add(Resources resources) {
resourcesService.insert(resources);
......@@ -73,6 +78,7 @@ public class RestResourcesController {
return ResultUtil.success("成功");
}
@RequiresPermissions(value = {"resource:batchDelete", "resource:delete"}, logical = Logical.OR)
@PostMapping(value = "/remove")
public ResponseVO remove(Long[] ids) {
if (null == ids) {
......@@ -87,11 +93,13 @@ public class RestResourcesController {
return ResultUtil.success("成功删除 [" + ids.length + "] 个资源");
}
@RequiresPermissions("resource:get")
@PostMapping("/get/{id}")
public ResponseVO get(@PathVariable Long id) {
return ResultUtil.success(null, this.resourcesService.getByPrimaryKey(id));
}
@RequiresPermissions("resource:edit")
@PostMapping("/edit")
public ResponseVO edit(Resources resources) {
try {
......
......@@ -29,6 +29,8 @@ import com.zyd.blog.core.shiro.ShiroService;
import com.zyd.blog.framework.object.PageResult;
import com.zyd.blog.framework.object.ResponseVO;
import com.zyd.blog.util.ResultUtil;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
......@@ -57,17 +59,20 @@ public class RestRoleController {
@Autowired
private ShiroService shiroService;
@RequiresPermissions("roles")
@PostMapping("/list")
public PageResult getAll(RoleConditionVO vo) {
PageInfo<Role> pageInfo = roleService.findPageBreakByCondition(vo);
return ResultUtil.tablePage(pageInfo);
}
@RequiresPermissions("user:allotRole")
@PostMapping("/rolesWithSelected")
public ResponseVO<List<Role>> rolesWithSelected(Integer uid) {
return ResultUtil.success(null, roleService.queryRoleListWithSelected(uid));
}
@RequiresPermissions("role:allotResource")
@PostMapping("/saveRoleResources")
public ResponseVO saveRoleResources(Long roleId, String resourcesId) {
if (StringUtils.isEmpty(roleId)) {
......@@ -79,12 +84,14 @@ public class RestRoleController {
return ResultUtil.success("成功");
}
@RequiresPermissions("role:add")
@PostMapping(value = "/add")
public ResponseVO add(Role role) {
roleService.insert(role);
return ResultUtil.success("成功");
}
@RequiresPermissions(value = {"role:batchDelete", "role:delete"}, logical = Logical.OR)
@PostMapping(value = "/remove")
public ResponseVO remove(Long[] ids) {
if (null == ids) {
......@@ -97,11 +104,13 @@ public class RestRoleController {
return ResultUtil.success("成功删除 [" + ids.length + "] 个角色");
}
@RequiresPermissions("role:get")
@PostMapping("/get/{id}")
public ResponseVO get(@PathVariable Long id) {
return ResultUtil.success(null, this.roleService.getByPrimaryKey(id));
}
@RequiresPermissions("role:edit")
@PostMapping("/edit")
public ResponseVO edit(Role role) {
try {
......
......@@ -27,6 +27,8 @@ import com.zyd.blog.business.vo.TagsConditionVO;
import com.zyd.blog.framework.object.PageResult;
import com.zyd.blog.framework.object.ResponseVO;
import com.zyd.blog.util.ResultUtil;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
......@@ -48,18 +50,21 @@ public class RestTagController {
@Autowired
private BizTagsService tagsService;
@RequiresPermissions("tags")
@PostMapping("/list")
public PageResult list(TagsConditionVO vo) {
PageInfo<Tags> pageInfo = tagsService.findPageBreakByCondition(vo);
return ResultUtil.tablePage(pageInfo);
}
@RequiresPermissions("tag:add")
@PostMapping(value = "/add")
public ResponseVO add(Tags tags) {
tagsService.insert(tags);
return ResultUtil.success("标签添加成功!新标签 - " + tags.getName());
}
@RequiresPermissions(value = {"tag:batchDelete", "tag:delete"}, logical = Logical.OR)
@PostMapping(value = "/remove")
public ResponseVO remove(Long[] ids) {
if (null == ids) {
......@@ -71,11 +76,13 @@ public class RestTagController {
return ResultUtil.success("成功删除 [" + ids.length + "] 个标签");
}
@RequiresPermissions("tag:get")
@PostMapping("/get/{id}")
public ResponseVO get(@PathVariable Long id) {
return ResultUtil.success(null, this.tagsService.getByPrimaryKey(id));
}
@RequiresPermissions("tag:edit")
@PostMapping("/edit")
public ResponseVO edit(Tags tags) {
try {
......
......@@ -27,6 +27,8 @@ import com.zyd.blog.business.vo.TemplateConditionVO;
import com.zyd.blog.framework.object.PageResult;
import com.zyd.blog.framework.object.ResponseVO;
import com.zyd.blog.util.ResultUtil;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
......@@ -48,18 +50,21 @@ public class RestTemplateController {
@Autowired
private SysTemplateService templateService;
@RequiresPermissions("templates")
@PostMapping("/list")
public PageResult list(TemplateConditionVO vo) {
PageInfo<Template> pageInfo = templateService.findPageBreakByCondition(vo);
return ResultUtil.tablePage(pageInfo);
}
@RequiresPermissions("template:add")
@PostMapping(value = "/add")
public ResponseVO add(Template template) {
templateService.insert(template);
return ResultUtil.success("成功");
}
@RequiresPermissions(value = {"template:batchDelete", "template:delete"}, logical = Logical.OR)
@PostMapping(value = "/remove")
public ResponseVO remove(Long[] ids) {
if (null == ids) {
......@@ -71,11 +76,13 @@ public class RestTemplateController {
return ResultUtil.success("成功删除 [" + ids.length + "] 个模板");
}
@RequiresPermissions("template:get")
@PostMapping("/get/{id}")
public ResponseVO get(@PathVariable Long id) {
return ResultUtil.success(null, this.templateService.getByPrimaryKey(id));
}
@RequiresPermissions("template:edit")
@PostMapping("/edit")
public ResponseVO edit(Template template) {
try {
......
......@@ -28,6 +28,8 @@ import com.zyd.blog.business.vo.TypeConditionVO;
import com.zyd.blog.framework.object.PageResult;
import com.zyd.blog.framework.object.ResponseVO;
import com.zyd.blog.util.ResultUtil;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
......@@ -49,28 +51,21 @@ public class RestTypeController {
@Autowired
private BizTypeService typeService;
@RequiresPermissions("types")
@PostMapping("/list")
public PageResult list(TypeConditionVO vo) {
PageInfo<Type> pageInfo = typeService.findPageBreakByCondition(vo);
return ResultUtil.tablePage(pageInfo);
}
@PostMapping("/listNodes")
public PageResult listNodes(TypeConditionVO vo) {
PageHelper.startPage(vo.getPageNumber() - 1, vo.getPageSize());
if (vo.getPid() == null) {
return ResultUtil.tablePage(null);
}
PageInfo<Type> pageInfo = typeService.findPageBreakByCondition(vo);
return ResultUtil.tablePage(pageInfo);
}
@RequiresPermissions("type:add")
@PostMapping(value = "/add")
public ResponseVO add(Type type) {
typeService.insert(type);
return ResultUtil.success("文章类型添加成功!新类型 - " + type.getName());
}
@RequiresPermissions(value = {"type:batchDelete", "type:delete"}, logical = Logical.OR)
@PostMapping(value = "/remove")
public ResponseVO remove(Long[] ids) {
if (null == ids) {
......@@ -82,11 +77,13 @@ public class RestTypeController {
return ResultUtil.success("成功删除 [" + ids.length + "] 个文章类型");
}
@RequiresPermissions("type:get")
@PostMapping("/get/{id}")
public ResponseVO get(@PathVariable Long id) {
return ResultUtil.success(null, this.typeService.getByPrimaryKey(id));
}
@RequiresPermissions("type:edit")
@PostMapping("/edit")
public ResponseVO edit(Type type) {
try {
......@@ -100,7 +97,12 @@ public class RestTypeController {
@PostMapping("/listAll")
public ResponseVO listType() {
return ResultUtil.success(null, typeService.listAll());
return ResultUtil.success(null, typeService.listTypeForMenu());
}
@PostMapping("/listParent")
public ResponseVO listParent() {
return ResultUtil.success(null, typeService.listParent());
}
}
......@@ -27,6 +27,8 @@ import com.zyd.blog.business.vo.UpdateRecordeConditionVO;
import com.zyd.blog.framework.object.PageResult;
import com.zyd.blog.framework.object.ResponseVO;
import com.zyd.blog.util.ResultUtil;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
......@@ -48,18 +50,21 @@ public class RestUpdateController {
@Autowired
private SysUpdateRecordeService updateRecordeService;
@RequiresPermissions("updateLogs")
@PostMapping("/list")
public PageResult list(UpdateRecordeConditionVO vo) {
PageInfo<UpdateRecorde> pageInfo = updateRecordeService.findPageBreakByCondition(vo);
return ResultUtil.tablePage(pageInfo);
}
@RequiresPermissions("updateLog:add")
@PostMapping(value = "/add")
public ResponseVO add(UpdateRecorde updateRecorde) {
updateRecordeService.insert(updateRecorde);
return ResultUtil.success("成功");
}
@RequiresPermissions(value = {"updateLog:batchDelete", "updateLog:delete"}, logical = Logical.OR)
@PostMapping(value = "/remove")
public ResponseVO remove(Long[] ids) {
if (null == ids) {
......@@ -71,11 +76,13 @@ public class RestUpdateController {
return ResultUtil.success("成功删除 [" + ids.length + "] 个更新记录");
}
@RequiresPermissions("updateLog:get")
@PostMapping("/get/{id}")
public ResponseVO get(@PathVariable Long id) {
return ResultUtil.success(null, this.updateRecordeService.getByPrimaryKey(id));
}
@RequiresPermissions("updateLog:edit")
@PostMapping("/edit")
public ResponseVO edit(UpdateRecorde updateRecorde) {
try {
......
......@@ -29,6 +29,8 @@ import com.zyd.blog.framework.object.PageResult;
import com.zyd.blog.framework.object.ResponseVO;
import com.zyd.blog.util.PasswordUtil;
import com.zyd.blog.util.ResultUtil;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
......@@ -53,6 +55,7 @@ public class RestUserController {
@Autowired
private SysUserRoleService userRoleService;
@RequiresPermissions("users")
@PostMapping("/list")
public PageResult list(UserConditionVO vo) {
PageInfo<User> pageInfo = userService.findPageBreakByCondition(vo);
......@@ -68,6 +71,7 @@ public class RestUserController {
* 此处获取的参数的角色id是以 “,” 分隔的字符串
* @return
*/
@RequiresPermissions("user:allotRole")
@PostMapping("/saveUserRoles")
public ResponseVO saveUserRoles(Long userId, String roleIds) {
if (StringUtils.isEmpty(userId)) {
......@@ -77,11 +81,12 @@ public class RestUserController {
return ResultUtil.success("成功");
}
@RequiresPermissions("user:add")
@PostMapping(value = "/add")
public ResponseVO add(User user) {
User u = userService.getByUserName(user.getUsername());
if (u != null) {
return ResultUtil.error("该用户名[" + user.getUsername() + "]已存在!请更改用户名");
return ResultUtil.error("该用户名["+user.getUsername()+"]已存在!请更改用户名");
}
try {
user.setPassword(PasswordUtil.encrypt(user.getPassword(), user.getUsername()));
......@@ -93,6 +98,7 @@ public class RestUserController {
}
}
@RequiresPermissions(value = {"user:batchDelete", "user:delete"}, logical = Logical.OR)
@PostMapping(value = "/remove")
public ResponseVO remove(Long[] ids) {
if (null == ids) {
......@@ -105,11 +111,13 @@ public class RestUserController {
return ResultUtil.success("成功删除 [" + ids.length + "] 个用户");
}
@RequiresPermissions("user:get")
@PostMapping("/get/{id}")
public ResponseVO get(@PathVariable Long id) {
return ResultUtil.success(null, this.userService.getByPrimaryKey(id));
}
@RequiresPermissions("user:edit")
@PostMapping("/edit")
public ResponseVO edit(User user) {
try {
......
......@@ -23,6 +23,8 @@ import com.zyd.blog.core.shiro.ShiroService;
import com.zyd.blog.core.shiro.credentials.RetryLimitCredentialsMatcher;
import com.zyd.blog.core.shiro.realm.ShiroRealm;
import com.zyd.blog.framework.property.RedisProperties;
import com.zyd.blog.framework.property.ShiroProperties;
import com.zyd.blog.framework.redis.CustomRedisManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
......@@ -38,6 +40,7 @@ import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
......@@ -62,12 +65,28 @@ public class ShiroConfig {
private ShiroService shiroService;
@Autowired
private RedisProperties redisProperties;
@Autowired
private ShiroProperties shiroProperties;
@Bean(name = "lifecycleBeanPostProcessor")
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 修复UnavailableSecurityManagerException(详见issues#IK7C3)
*
* @param securityManager
* @return
*/
@Bean
public MethodInvokingFactoryBean methodInvokingFactoryBean(SecurityManager securityManager) {
MethodInvokingFactoryBean bean = new MethodInvokingFactoryBean();
bean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
bean.setArguments(securityManager);
return bean;
}
/**
* ShiroFilterFactoryBean 处理拦截资源文件问题。
* 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在
......@@ -83,11 +102,11 @@ public class ShiroConfig {
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/passport/login/");
shiroFilterFactoryBean.setLoginUrl(shiroProperties.getLoginUrl());
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
shiroFilterFactoryBean.setSuccessUrl(shiroProperties.getSuccessUrl());
// 未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/error/403");
shiroFilterFactoryBean.setUnauthorizedUrl(shiroProperties.getUnauthorizedUrl());
// 配置数据库中的resource
Map<String, String> filterChainDefinitionMap = shiroService.loadFilterChainDefinitions();
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
......@@ -157,11 +176,12 @@ public class ShiroConfig {
* @return
*/
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
CustomRedisManager redisManager = new CustomRedisManager();
redisManager.setHost(redisProperties.getHost());
redisManager.setPort(redisProperties.getPort());
redisManager.setExpire(1800);
redisManager.setTimeout(redisProperties.getTimeout());
redisManager.setDatabase(redisProperties.getDatabase());
redisManager.setExpire(redisProperties.getExpire());
redisManager.setTimeout(redisProperties.getTimeout().getNano()*1000);
redisManager.setPassword(redisProperties.getPassword());
return redisManager;
}
......@@ -207,10 +227,10 @@ public class ShiroConfig {
* @return
*/
public SimpleCookie rememberMeCookie() {
//这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
// 这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
//<!-- 记住我cookie生效时间30天 ,单位秒;-->
simpleCookie.setMaxAge(2592000);
// 记住我cookie生效时间30天 ,单位秒。 注释掉,默认永久不过期 2018-07-15
simpleCookie.setMaxAge(redisProperties.getExpire());
return simpleCookie;
}
......
/**
* MIT License
* Copyright (c) 2018 yadong.zhang
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.zyd.blog.core.config;
import com.zyd.blog.core.interceptor.RememberAuthenticationInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2018/7/15 15:03
* @since 1.0
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private RememberAuthenticationInterceptor rememberAuthenticationInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(rememberAuthenticationInterceptor)
.excludePathPatterns("/passport/**", "/error/**", "/assets/**", "/getKaptcha/**", "favicon.ico")
.addPathPatterns("/**");
}
}
/**
* MIT License
* Copyright (c) 2018 yadong.zhang
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.zyd.blog.core.interceptor;
import com.zyd.blog.business.consts.SessionConst;
import com.zyd.blog.business.entity.User;
import com.zyd.blog.business.service.SysUserService;
import com.zyd.blog.util.PasswordUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2018/7/15 15:24
* @since 1.0
*/
@Slf4j
@Component
public class RememberAuthenticationInterceptor implements HandlerInterceptor {
@Autowired
private SysUserService userService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Subject subject = SecurityUtils.getSubject();
if (subject.isAuthenticated()) {
return true;
}
Session session = subject.getSession(true);
if (session.getAttribute(SessionConst.USER_SESSION_KEY) != null) {
return true;
}
if(!subject.isRemembered()) {
log.warn("未设置“记住我”,跳转到登录页...");
response.sendRedirect(request.getContextPath() + "/passport/login");
return false;
}
try {
Long userId = Long.parseLong(subject.getPrincipal().toString());
User user = userService.getByPrimaryKey(userId);
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), PasswordUtil.decrypt(user.getPassword(), user.getUsername()), true);
subject.login(token);
session.setAttribute(SessionConst.USER_SESSION_KEY, user);
log.info("[{}] - 已自动登录", user.getUsername());
} catch (Exception e) {
log.error("自动登录失败", e);
response.sendRedirect(request.getContextPath() + "/passport/login");
return false;
}
return true;
}
}
......@@ -68,7 +68,7 @@ public class ShiroService {
/*
配置访问权限
- anon:所有url都都可以匿名访问
- authc: 需要认证才能进行访问
- authc: 需要认证才能进行访问(此处指所有非匿名的路径都需要登陆才能访问)
- user:配置记住我或认证通过可以访问
*/
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
......@@ -76,6 +76,7 @@ public class ShiroService {
filterChainDefinitionMap.put("/passport/logout", "logout");
filterChainDefinitionMap.put("/passport/login", "anon");
filterChainDefinitionMap.put("/passport/signin", "anon");
filterChainDefinitionMap.put("/websocket", "anon");
filterChainDefinitionMap.put("/favicon.ico", "anon");
filterChainDefinitionMap.put("/error", "anon");
filterChainDefinitionMap.put("/assets/**", "anon");
......@@ -90,7 +91,8 @@ public class ShiroService {
filterChainDefinitionMap.put(resources.getUrl(), permission);
}
}
filterChainDefinitionMap.put("/**", "authc");
// 本博客中并不存在什么特别关键的操作,所以直接使用user认证。如果有朋友是参考本博客的shiro开发其他安全功能(比如支付等)时,建议针对这类操作使用authc权限 by yadong.zhang
filterChainDefinitionMap.put("/**", "user");
return filterChainDefinitionMap;
}
......
......@@ -23,6 +23,7 @@ import com.zyd.blog.business.entity.Resources;
import com.zyd.blog.business.entity.Role;
import com.zyd.blog.business.entity.User;
import com.zyd.blog.business.enums.UserStatusEnum;
import com.zyd.blog.business.enums.UserTypeEnum;
import com.zyd.blog.business.service.SysResourcesService;
import com.zyd.blog.business.service.SysRoleService;
import com.zyd.blog.business.service.SysUserService;
......@@ -37,7 +38,10 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Shiro-密码输入错误的状态下重试次数的匹配管理
......@@ -98,14 +102,27 @@ public class ShiroRealm extends AuthorizingRealm {
}
// 赋予权限
List<Resources> resourcesList = resourcesService.listByUserId(userId);
List<Resources> resourcesList = null;
User user = userService.getByPrimaryKey(userId);
if (null == user) {
return info;
}
// ROOT用户默认拥有所有权限
if (UserTypeEnum.ROOT.toString().equalsIgnoreCase(user.getUserType())) {
resourcesList = resourcesService.listAll();
} else {
resourcesList = resourcesService.listByUserId(userId);
}
if (!CollectionUtils.isEmpty(resourcesList)) {
Set<String> permissionSet = new HashSet<>();
for (Resources resources : resourcesList) {
String permission = null;
if (!StringUtils.isEmpty(permission = resources.getPermission())) {
info.addStringPermission(permission);
permissionSet.addAll(Arrays.asList(permission.trim().split(",")));
}
}
info.setStringPermissions(permissionSet);
}
return info;
}
......
......@@ -17,7 +17,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.zyd.blog.core.config;
package com.zyd.blog.core.websocket.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
......@@ -34,6 +34,11 @@ import org.springframework.web.socket.server.standard.ServerEndpointExporter;
*/
@Configuration
public class WebSocketConfig {
/**
* ServerEndpointExporter会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
......
......@@ -17,8 +17,9 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.zyd.blog.core.websocket;
package com.zyd.blog.core.websocket.server;
import com.zyd.blog.core.websocket.util.WebSocketUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
......@@ -40,16 +41,16 @@ import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@ServerEndpoint(value = "/websocket")
@Component
public class ZydWebSocket {
public class ZydWebsocketServer {
/**
* 初始在线人数
*/
private static AtomicInteger onlineCount = new AtomicInteger(0);
/**
* 线程安全的socket集合
*/
private static CopyOnWriteArraySet<Session> webSocketSet = new CopyOnWriteArraySet<>();
/**
* 初始在线人数
*/
private static AtomicInteger onlineCount = new AtomicInteger(0);
/**
* 连接建立成功调用的方法
......@@ -57,9 +58,10 @@ public class ZydWebSocket {
@OnOpen
public void onOpen(Session session) {
webSocketSet.add(session);
onlineCount.incrementAndGet();
log.info("有链接加入,当前在线人数为: {}", getOnlineCount());
WebSocketUtil.broadcast(getOnlineCount(), webSocketSet);
int count = onlineCount.incrementAndGet();
log.info("有链接加入,当前在线人数为: {}", count);
WebSocketUtil.sendOnlineMsg(Integer.toString(count), webSocketSet);
}
/**
......@@ -67,9 +69,9 @@ public class ZydWebSocket {
*/
@OnClose
public void onClose() {
onlineCount.decrementAndGet();
log.info("有链接关闭,当前在线人数为: {}", getOnlineCount());
WebSocketUtil.broadcast(getOnlineCount(), webSocketSet);
int count = onlineCount.decrementAndGet();
log.info("有链接关闭,当前在线人数为: {}", count);
WebSocketUtil.sendOnlineMsg(Integer.toString(count), webSocketSet);
}
/**
......@@ -81,10 +83,23 @@ public class ZydWebSocket {
@OnMessage
public void onMessage(String message, Session session) {
log.info("{}来自客户端的消息:{}", session.getId(), message);
WebSocketUtil.sendMessage(message, session);
}
private String getOnlineCount() {
return Integer.toString(onlineCount.get());
/**
* 获取在线用户数量
*
* @return
*/
public int getOnlineUserCount() {
return onlineCount.get();
}
/**
* 获取在线用户的会话信息
*
* @return
*/
public CopyOnWriteArraySet<Session> getOnlineUsers() {
return webSocketSet;
}
}
......@@ -17,12 +17,16 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.zyd.blog.core.websocket;
package com.zyd.blog.core.websocket.util;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.Charsets;
import org.springframework.util.CollectionUtils;
import javax.websocket.Session;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Set;
/**
......@@ -37,10 +41,47 @@ import java.util.Set;
@Slf4j
public class WebSocketUtil {
private static final String ONLINE_MSG_KEY = "online";
private static final String NOTIFICATION_MSG_KEY = "notification";
private WebSocketUtil() {
// 私有化构造方法,禁止new
}
/**
* 根据消息类型,生成发送到客户端的最终消息内容
*
* @param type
* 消息类型
* @param content
* 消息正文
* @return
*/
private static String generateMsg(String type, String content) {
return String.format("{\"fun\": \"%s\", \"msg\":\"%s\"}", type, content);
}
/**
* 发送在线用户的消息
*
* @param msg
* @param sessionSet
*/
public static void sendOnlineMsg(String msg, Set<Session> sessionSet) {
broadcast(generateMsg(ONLINE_MSG_KEY, msg), sessionSet);
}
/**
* 发送通知的消息
*
* @param msg
* @param sessionSet
*/
public static void sendNotificationMsg(String msg, Set<Session> sessionSet) throws UnsupportedEncodingException {
// 为了防止消息中存在特殊字符(比如换行符)等造成前台解析错误,此处编码一次。前台对应的需要解码
broadcast(generateMsg(NOTIFICATION_MSG_KEY, URLEncoder.encode(msg, Charsets.UTF_8.displayName())), sessionSet);
}
/**
* 向客户端发送消息
*
......@@ -50,7 +91,7 @@ public class WebSocketUtil {
* 客户端session
* @throws IOException
*/
public static void sendMessage(String message, Session session) {
private static void sendMessage(String message, Session session) {
try {
session.getAsyncRemote().sendText(message);
} catch (Exception e) {
......@@ -67,10 +108,13 @@ public class WebSocketUtil {
* 客户端session列表
* @throws IOException
*/
public static void broadcast(String message, Set<Session> sessionSet) {
private static void broadcast(String message, Set<Session> sessionSet) {
if (CollectionUtils.isEmpty(sessionSet)) {
return;
}
// 多线程群发
for (Session entry : sessionSet) {
if (entry.isOpen()) {
if (null != entry && entry.isOpen()) {
sendMessage(message, entry);
} else {
sessionSet.remove(entry);
......
# Server settings
server:
tomcat:
basedir: /var/tmp/website-blog-admin
# SPRING PROFILES
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/dblog?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: root
# 指定默认MimeMessage的编码,默认为: UTF-8
mail:
default-encoding: UTF-8
# 指定SMTP server使用的协议,默认为: smtp
protocol: smtp
# 指定SMTP server host.
host: xxx.xxx.xxx
port: 465
# 指定SMTP server的用户名.
username: xxx@xxx.xxx
# 指定SMTP server登陆密码:
password: xxx
# 指定是否在启动时测试邮件服务器连接,默认为false
test-connection: false
properties:
mail.smtp.auth: true
# 腾讯企业邮箱 下两个配置必须!!!
mail.smtp.ssl.enable: true
mail.smtp.socketFactory.class: javax.net.ssl.SSLSocketFactory
mail.smtp.socketFactory.port: 465
mail.smtp.starttls.enable: true
mail.smtp.starttls.required: true
mail.smtp.connectiontimeout: 50000
mail.smtp.timeout: 30000
mail.smtp.writetimeout: 50000
# logging settings
logging:
path: /var/tmp/website-blog-admin
####################################自定义配置##########################################
app:
# 是否启用kaptcha验证码
enableKaptcha: false
####################################自定义配置##########################################
\ No newline at end of file
# Server settings
server:
tomcat:
basedir: /var/tmp/website-blog-admin
# SPRING PROFILES
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/dblog?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: ZhangYad0ng
# 指定默认MimeMessage的编码,默认为: UTF-8
mail:
default-encoding: UTF-8
# 指定SMTP server使用的协议,默认为: smtp
protocol: smtp
# 指定SMTP server host.
host: smtp.zhyd.me
port: 465
# 指定SMTP server的用户名.
username: admin@zhyd.me
# 指定SMTP server登陆密码:
password: qwer1234!@#$
# 指定是否在启动时测试邮件服务器连接,默认为false
test-connection: false
properties:
mail.smtp.auth: true
# 腾讯企业邮箱 下两个配置必须!!!
mail.smtp.ssl.enable: true
mail.smtp.socketFactory.class: javax.net.ssl.SSLSocketFactory
mail.smtp.socketFactory.port: 465
mail.smtp.starttls.enable: true
mail.smtp.starttls.required: true
mail.smtp.connectiontimeout: 50000
mail.smtp.timeout: 30000
mail.smtp.writetimeout: 50000
# logging settings
logging:
path: /var/tmp/website-blog-admin
####################################自定义配置##########################################
app:
# 是否启用kaptcha验证码
enableKaptcha: false
# shiro配置项
shiro:
loginUrl: "/passport/login/"
successUrl: "/"
unauthorizedUrl: "/error/403"
####################################自定义配置##########################################
\ No newline at end of file
......@@ -13,15 +13,10 @@ server:
protocol-header: X-Forwarded-Proto
port-header: X-Forwarded-Port
uri-encoding: UTF-8
basedir: /var/tmp/website-app
# SPRING PROFILES
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/dblog?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false
username: root
password: root
profiles:
active: '@profileActive@'
application:
name: blog-admin
freemarker:
......@@ -43,10 +38,11 @@ spring:
default_encoding: UTF-8
classic_compatible: true
# HTTP ENCODING
http:
servlet:
multipart:
max-file-size: 2MB
max-request-size: 10MB
http:
encoding:
enabled: true
charset: UTF-8
......@@ -67,50 +63,29 @@ spring:
store-type: redis
# Redis数据库索引(默认为0)
redis:
database: 1
database: 5
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接池最大连接数(使用负值表示没有限制)
pool:
maxActive: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
maxWait: -1
# 连接池中的最大空闲连接
maxIdle: 8
# 连接池中的最小空闲连接
minIdle: 0
password: qwe!@#123
jedis:
pool:
# 连接池最大连接数(使用负值表示没有限制)
max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 8
# 连接池中的最小空闲连接
min-idle: 0
# 连接超时时间(毫秒)
timeout: 0
# 指定默认MimeMessage的编码,默认为: UTF-8
mail:
default-encoding: UTF-8
# 指定SMTP server使用的协议,默认为: smtp
protocol: smtp
# 指定SMTP server host.
host: xxx.xxx.xxx
port: 465
# 指定SMTP server的用户名.
username: xxx@xxx.xxx
# 指定SMTP server登陆密码:
password: xxx
# 指定是否在启动时测试邮件服务器连接,默认为false
test-connection: false
properties:
mail.smtp.auth: true
# 腾讯企业邮箱 下两个配置必须!!!
mail.smtp.ssl.enable: true
mail.smtp.socketFactory.class: javax.net.ssl.SSLSocketFactory
mail.smtp.socketFactory.port: 465
mail.smtp.starttls.enable: true
mail.smtp.starttls.required: true
mail.smtp.connectiontimeout: 50000
mail.smtp.timeout: 30000
mail.smtp.writetimeout: 50000
timeout: 0ms
# 默认的数据过期时间,主要用于shiro权限管理
expire: 2592000
banner:
charset: UTF-8
# MyBatis
mybatis:
type-aliases-package: com.zyd.blog.persistence.beans
......@@ -126,11 +101,4 @@ pagehelper:
helper-dialect: mysql
reasonable: true
support-methods-arguments: true
params: count=countSql
banner:
charset: UTF-8
####################################自定义配置##########################################
app:
# 是否启用kaptcha验证码
enableKaptcha: false
####################################自定义配置##########################################
\ No newline at end of file
params: count=countSql
\ No newline at end of file
${AnsiColor.BLUE}
======================================================================================================================================================
欢迎使用 DBlog博客系统 - Powered By https://www.zhyd.me
唯一官方开源地址(Github已不维护):https://gitee.com/yadong.zhang/DBlog
唯一官方QQ群:190886500
;'*¨'`·- ., ‘ ,. - · - ., ' ,. ' , ·. ,.-·~·., ‘ ,.-·^*ª'` ·,
\`:·-,. , '` ·. ' ,·'´,.-, ,. -., `';,' / ';\ / ·'´,.-·-., `,'‚ .·´ ,·'´:¯'`·, '\‘
'\:/ ;\:'`:·, '`·, ' \::\.'´ ;'\::::;:' ,·':\' ,' ,'::'\ / .'´\:::::::'\ '\ ° ,´ ,'\:::::::::\,.·\'
; ;'::\;::::'; ;\ '\:'; ;:;:·'´,.·'´\::::'; ,' ;:::';' ,·' ,'::::\:;:-·-:'; ';\‚ / /:::\;·'´¯'`·;\:::\°
; ,':::; `·:;; ,':'\' ,.·' ,.·:'´:::::::'\;·´ '; ,':::;' ;. ';:::;´ ,' ,':'\‚ ; ;:::;' '\;:·´
; ;:::; ,·' ,·':::; '·, ,.`' ·- :;:;·'´ ; ,':::;' ' '; ;::; ,'´ .'´\::';‚ '; ;::/ ,·´¯'; °
; ;:::;' ,.'´,·´:::::; ; ';:\:`*·, '`·, ° ,' ,'::;' '; ':;: ,.·´,.·´::::\;'° '; '·;' ,.·´, ;'\
':,·:;::-·´,.·´\:::::;´' ; ;:;:'-·'´ ,.·':\ ; ';_:,.-·´';\‘ \·, `*´,.·'´::::::;·´ \'·. `'´,.·:´'; ;::\'
\::;. -·´:::::;\;·´ ,·', ,. -~:*'´\:::::'\‘ ', _,.-·'´:\:\‘ \\:¯::\:::::::;:·´ '\::\¯::::::::'; ;::'; ‘
\;'\::::::::;·´' \:\`'´\:::::::::'\;:·'´ \¨:::::::::::\'; `\:::::\;::·'´ ° `·:\:::;:·´';.·´\::;'
`\;::-·´ '\;\:::\;: -~*´‘ '\;::_;:-·'´‘ ¯ ¯ \::::\;'‚
' '¨ ‘ '\:·´'
当前SpringBoot版本 :: ${spring-boot.version}
======================================================================================================================================================
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--定义日志文件的存储路径-->
<!--<property name="LOG_HOME" value="/var/tmp/website-blog-admin"/>-->
<springProperty scope="context" name="logdir" source="logging.path"/>
<!-- 控制台 appender -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%class:%line] %-5level - %msg%n</pattern>
</encoder>
</appender>
<!--按天生成日志-->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<Prudent>true</Prudent>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>
${logdir}/%d{yyyy-MM-dd}/%d{yyyy-MM-dd}.log
</FileNamePattern>
</rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>
%d{yyyy-MM-dd HH:mm:ss} -%msg%n
</Pattern>
</layout>
</appender>
<logger name="org.springframework.core.env" level="ERROR"/>
<logger name="us.codecraft.webmagic.downloader" level="WARN"/>
<!-- 测试环境+开发环境,日志级别为INFO且不写日志文件 -->
<springProfile name="test,dev">
<logger name="com.zyd.blog" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</springProfile>
<!-- 生产环境. 日志级别为WARN且写日志文件-->
<springProfile name="prod">
<logger name="com.zyd.blog" additivity="false">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE" />
</logger>
<root level="WARN">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE" />
</root>
</springProfile>
</configuration>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 控制台 appender -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%class:%line] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<!-- 出错日志 appender -->
<appender name="ERROR_OUT" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>/home/logs/admin/zhyd/error.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 按天回滚 daily -->
<!-- log.dir 在maven profile里配置 -->
<fileNamePattern>/home/logs/www/zhyd/error-%d{yyyy-MM-dd}.log
</fileNamePattern>
<!-- 日志最大的历史 7天 -->
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%class:%line] %-5level %logger - %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter"><!-- 只打印错误日志 -->
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!--<logger name="org.mybatis" level="DEBUG" />
<logger name="java.sql.Connection" level="DEBUG" />
<logger name="java.sql.Statement" level="DEBUG" />
<logger name="java.sql.PreparedStatement" level="DEBUG" />
<logger name="java.sql.ResultSet" level="DEBUG" />
<logger name="backend" level="DEBUG"/>-->
<logger name="org.springframework.core.env" level="ERROR"/>
<logger name="com.zyd.blog" level="INFO"/>
<!--&lt;!&ndash;日志打印的包的范围,及分类日志文件存储 &ndash;&gt;-->
<!--<logger name="com.blog" additivity="false">-->
<!--<level value="ERROR"/>-->
<!--<appender-ref ref="STDOUT"/>-->
<!--<appender-ref ref="ERROR"/>-->
<!--</logger>-->
<!--控制台打印资源加载信息 -->
<root level="ERROR">
<appender-ref ref="STDOUT"/>
<appender-ref ref="ERROR_OUT"/>
</root>
</configuration>
\ No newline at end of file
......@@ -4790,10 +4790,10 @@ ul, menu, dir {
.breadcrumb {
width: 100%;
padding: 10px 17px;
padding: 0;
display: inline-block;
background: #fff;
border: 1px solid #E6E9ED;
/* background: #fff; */
/* border: 1px solid #E6E9ED; */
-webkit-column-break-inside: avoid;
-moz-column-break-inside: avoid;
column-break-inside: avoid;
......
/*jslint newcap: true */
/*global inlineAttachment: false */
/**
* CodeMirror version for inlineAttachment
*
* Call inlineAttachment.attach(editor) to attach to a codemirror instance
*/
(function() {
'use strict';
var codeMirrorEditor = function(instance) {
if (!instance.getWrapperElement) {
throw "Invalid CodeMirror object given";
}
this.codeMirror = instance;
};
codeMirrorEditor.prototype.getValue = function() {
return this.codeMirror.getValue();
};
codeMirrorEditor.prototype.insertValue = function(val) {
this.codeMirror.replaceSelection(val);
};
codeMirrorEditor.prototype.setValue = function(val) {
var cursor = this.codeMirror.getCursor();
this.codeMirror.setValue(val);
this.codeMirror.setCursor(cursor);
};
/**
* Attach InlineAttachment to CodeMirror
*
* @param {CodeMirror} codeMirror
*/
codeMirrorEditor.attach = function(codeMirror, options) {
options = options || {};
var editor = new codeMirrorEditor(codeMirror),
inlineattach = new inlineAttachment(options, editor),
el = codeMirror.getWrapperElement();
el.addEventListener('paste', function(e) {
inlineattach.onPaste(e);
}, false);
codeMirror.setOption('onDragEvent', function(data, e) {
if (e.type === "drop") {
e.stopPropagation();
e.preventDefault();
return inlineattach.onDrop(e);
}
});
};
inlineAttachment.editors.codemirror3 = codeMirrorEditor;
var codeMirrorEditor4 = function(instance) {
codeMirrorEditor.call(this, instance);
};
codeMirrorEditor4.attach = function(codeMirror, options) {
options = options || {};
var editor = new codeMirrorEditor(codeMirror),
inlineattach = new inlineAttachment(options, editor),
el = codeMirror.getWrapperElement();
el.addEventListener('paste', function(e) {
inlineattach.onPaste(e);
}, false);
codeMirror.on('drop', function(data, e) {
if (inlineattach.onDrop(e)) {
e.stopPropagation();
e.preventDefault();
return true;
} else {
return false;
}
});
};
inlineAttachment.editors.codemirror4 = codeMirrorEditor4;
})();
\ No newline at end of file
/*jslint newcap: true */
/*global XMLHttpRequest: false, FormData: false */
/*
* Inline Text Attachment
*
* Author: Roy van Kaathoven
* Contact: ik@royvankaathoven.nl
*/
(function(document, window) {
'use strict';
var inlineAttachment = function(options, instance) {
this.settings = inlineAttachment.util.merge(options, inlineAttachment.defaults);
this.editor = instance;
this.filenameTag = '{filename}';
this.lastValue = null;
};
/**
* Will holds the available editors
*
* @type {Object}
*/
inlineAttachment.editors = {};
/**
* Utility functions
*/
inlineAttachment.util = {
/**
* Simple function to merge the given objects
*
* @param {Object[]} object Multiple object parameters
* @returns {Object}
*/
merge: function() {
var result = {};
for (var i = arguments.length - 1; i >= 0; i--) {
var obj = arguments[i];
for (var k in obj) {
if (obj.hasOwnProperty(k)) {
result[k] = obj[k];
}
}
}
return result;
},
/**
* Append a line of text at the bottom, ensuring there aren't unnecessary newlines
*
* @param {String} appended Current content
* @param {String} previous Value which should be appended after the current content
*/
appendInItsOwnLine: function(previous, appended) {
return (previous + "\n\n[[D]]" + appended)
.replace(/(\n{2,})\[\[D\]\]/, "\n\n")
.replace(/^(\n*)/, "");
},
/**
* Inserts the given value at the current cursor position of the textarea element
*
* @param {HtmlElement} el
* @param {String} value Text which will be inserted at the cursor position
*/
insertTextAtCursor: function(el, text) {
var scrollPos = el.scrollTop,
strPos = 0,
browser = false,
range;
if ((el.selectionStart || el.selectionStart === '0')) {
browser = "ff";
} else if (document.selection) {
browser = "ie";
}
if (browser === "ie") {
el.focus();
range = document.selection.createRange();
range.moveStart('character', -el.value.length);
strPos = range.text.length;
} else if (browser === "ff") {
strPos = el.selectionStart;
}
var front = (el.value).substring(0, strPos);
var back = (el.value).substring(strPos, el.value.length);
el.value = front + text + back;
strPos = strPos + text.length;
if (browser === "ie") {
el.focus();
range = document.selection.createRange();
range.moveStart('character', -el.value.length);
range.moveStart('character', strPos);
range.moveEnd('character', 0);
range.select();
} else if (browser === "ff") {
el.selectionStart = strPos;
el.selectionEnd = strPos;
el.focus();
}
el.scrollTop = scrollPos;
}
};
/**
* Default configuration options
*
* @type {Object}
*/
inlineAttachment.defaults = {
/**
* URL where the file will be send
*/
uploadUrl: 'upload_attachment.php',
/**
* Which method will be used to send the file to the upload URL
*/
uploadMethod: 'POST',
/**
* Name in which the file will be placed
*/
uploadFieldName: 'file',
/**
* Extension which will be used when a file extension could not
* be detected
*/
defaultExtension: 'png',
/**
* JSON field which refers to the uploaded file URL
*/
jsonFieldName: 'filename',
/**
* Allowed MIME types
*/
allowedTypes: [
'image/jpeg',
'image/png',
'image/jpg',
'image/gif'
],
/**
* Text which will be inserted when dropping or pasting a file.
* Acts as a placeholder which will be replaced when the file is done with uploading
*/
progressText: '![Uploading file...]()',
/**
* When a file has successfully been uploaded the progressText
* will be replaced by the urlText, the {filename} tag will be replaced
* by the filename that has been returned by the server
*/
urlText: "![file]({filename})",
/**
* Text which will be used when uploading has failed
*/
errorText: "Error uploading file",
/**
* Extra parameters which will be send when uploading a file
*/
extraParams: {},
/**
* Extra headers which will be send when uploading a file
*/
extraHeaders: {},
/**
* Before the file is send
*/
beforeFileUpload: function() {
return true;
},
/**
* Triggers when a file is dropped or pasted
*/
onFileReceived: function() {},
/**
* Custom upload handler
*
* @return {Boolean} when false is returned it will prevent default upload behavior
*/
onFileUploadResponse: function() {
return true;
},
/**
* Custom error handler. Runs after removing the placeholder text and before the alert().
* Return false from this function to prevent the alert dialog.
*
* @return {Boolean} when false is returned it will prevent default error behavior
*/
onFileUploadError: function() {
return true;
},
/**
* When a file has succesfully been uploaded
*/
onFileUploaded: function() {}
};
/**
* Uploads the blob
*
* @param {Blob} file blob data received from event.dataTransfer object
* @return {XMLHttpRequest} request object which sends the file
*/
inlineAttachment.prototype.uploadFile = function(file) {
var me = this,
formData = new FormData(),
xhr = new XMLHttpRequest(),
settings = this.settings,
extension = settings.defaultExtension || settings.defualtExtension;
if (typeof settings.setupFormData === 'function') {
settings.setupFormData(formData, file);
}
// Attach the file. If coming from clipboard, add a default filename (only works in Chrome for now)
// http://stackoverflow.com/questions/6664967/how-to-give-a-blob-uploaded-as-formdata-a-file-name
if (file.name) {
var fileNameMatches = file.name.match(/\.(.+)$/);
if (fileNameMatches) {
extension = fileNameMatches[1];
}
}
var remoteFilename = "image-" + Date.now() + "." + extension;
if (typeof settings.remoteFilename === 'function') {
remoteFilename = settings.remoteFilename(file);
}
formData.append(settings.uploadFieldName, file, remoteFilename);
// Append the extra parameters to the formdata
if (typeof settings.extraParams === "object") {
for (var key in settings.extraParams) {
if (settings.extraParams.hasOwnProperty(key)) {
formData.append(key, settings.extraParams[key]);
}
}
}
xhr.open('POST', settings.uploadUrl);
// Add any available extra headers
if (typeof settings.extraHeaders === "object") {
for (var header in settings.extraHeaders) {
if (settings.extraHeaders.hasOwnProperty(header)) {
xhr.setRequestHeader(header, settings.extraHeaders[header]);
}
}
}
xhr.onload = function() {
// If HTTP status is OK or Created
if (xhr.status === 200 || xhr.status === 201) {
me.onFileUploadResponse(xhr);
} else {
me.onFileUploadError(xhr);
}
};
if (settings.beforeFileUpload(xhr) !== false) {
xhr.send(formData);
}
return xhr;
};
/**
* Returns if the given file is allowed to handle
*
* @param {File} clipboard data file
*/
inlineAttachment.prototype.isFileAllowed = function(file) {
if (file.kind === 'string') { return false; }
if (this.settings.allowedTypes.indexOf('*') === 0){
return true;
} else {
return this.settings.allowedTypes.indexOf(file.type) >= 0;
}
};
/**
* Handles upload response
*
* @param {XMLHttpRequest} xhr
* @return {Void}
*/
inlineAttachment.prototype.onFileUploadResponse = function(xhr) {
if (this.settings.onFileUploadResponse.call(this, xhr) !== false) {
var result = JSON.parse(xhr.responseText),
filename = result[this.settings.jsonFieldName];
if (result && filename) {
var newValue;
if (typeof this.settings.urlText === 'function') {
newValue = this.settings.urlText.call(this, filename, result);
} else {
newValue = this.settings.urlText.replace(this.filenameTag, filename);
}
var text = this.editor.getValue().replace(this.lastValue, newValue);
this.editor.setValue(text);
this.settings.onFileUploaded.call(this, filename);
}
}
};
/**
* Called when a file has failed to upload
*
* @param {XMLHttpRequest} xhr
* @return {Void}
*/
inlineAttachment.prototype.onFileUploadError = function(xhr) {
if (this.settings.onFileUploadError.call(this, xhr) !== false) {
var text = this.editor.getValue().replace(this.lastValue, "");
this.editor.setValue(text);
}
};
/**
* Called when a file has been inserted, either by drop or paste
*
* @param {File} file
* @return {Void}
*/
inlineAttachment.prototype.onFileInserted = function(file) {
if (this.settings.onFileReceived.call(this, file) !== false) {
this.lastValue = this.settings.progressText;
this.editor.insertValue(this.lastValue);
}
};
/**
* Called when a paste event occured
* @param {Event} e
* @return {Boolean} if the event was handled
*/
inlineAttachment.prototype.onPaste = function(e) {
var result = false,
clipboardData = e.clipboardData,
items;
if (typeof clipboardData === "object") {
items = clipboardData.items || clipboardData.files || [];
for (var i = 0; i < items.length; i++) {
var item = items[i];
if (this.isFileAllowed(item)) {
result = true;
this.onFileInserted(item.getAsFile());
this.uploadFile(item.getAsFile());
}
}
}
if (result) { e.preventDefault(); }
return result;
};
/**
* Called when a drop event occures
* @param {Event} e
* @return {Boolean} if the event was handled
*/
inlineAttachment.prototype.onDrop = function(e) {
var result = false;
for (var i = 0; i < e.dataTransfer.files.length; i++) {
var file = e.dataTransfer.files[i];
if (this.isFileAllowed(file)) {
result = true;
this.onFileInserted(file);
this.uploadFile(file);
}
}
return result;
};
window.inlineAttachment = inlineAttachment;
})(document, window);
/*
* SmartWizard 3.3.1 plugin
* jQuery Wizard control Plugin
* by Dipu
*
* Refactored and extended:
* https://github.com/mstratman/jQuery-Smart-Wizard
*
* Original URLs:
* http://www.techlaboratory.net
* http://tech-laboratory.blogspot.com
*/
function SmartWizard(target, options) {
this.target = target;
this.options = options;
this.curStepIdx = options.selected;
this.steps = $(target).children("ul").children("li").children("a"); // Get all anchors
this.contentWidth = 0;
this.msgBox = $('<div class="msgBox"><div class="content"></div><a href="#" class="close">X</a></div>');
this.elmStepContainer = $('<div></div>').addClass("stepContainer");
this.loader = $('<div>Loading</div>').addClass("loader");
this.buttons = {
next : $('<a>'+options.labelNext+'</a>').attr("href","#").addClass("buttonNext"),
previous : $('<a>'+options.labelPrevious+'</a>').attr("href","#").addClass("buttonPrevious"),
finish : $('<a>'+options.labelFinish+'</a>').attr("href","#").addClass("buttonFinish")
};
/*
* Private functions
*/
var _init = function($this) {
var elmActionBar = $('<div></div>').addClass("actionBar");
elmActionBar.append($this.msgBox);
$('.close',$this.msgBox).click(function() {
$this.msgBox.fadeOut("normal");
return false;
});
var allDivs = $this.target.children('div');
$this.target.children('ul').addClass("anchor");
allDivs.addClass("content");
// highlight steps with errors
if($this.options.errorSteps && $this.options.errorSteps.length>0){
$.each($this.options.errorSteps, function(i, n){
$this.setError({ stepnum: n, iserror:true });
});
}
$this.elmStepContainer.append(allDivs);
elmActionBar.append($this.loader);
$this.target.append($this.elmStepContainer);
elmActionBar.append($this.buttons.finish)
.append($this.buttons.next)
.append($this.buttons.previous);
$this.target.append(elmActionBar);
this.contentWidth = $this.elmStepContainer.width();
$($this.buttons.next).click(function() {
$this.goForward();
return false;
});
$($this.buttons.previous).click(function() {
$this.goBackward();
return false;
});
$($this.buttons.finish).click(function() {
if(!$(this).hasClass('buttonDisabled')){
if($.isFunction($this.options.onFinish)) {
var context = { fromStep: $this.curStepIdx + 1 };
if(!$this.options.onFinish.call(this,$($this.steps), context)){
return false;
}
}else{
var frm = $this.target.parents('form');
if(frm && frm.length){
frm.submit();
}
}
}
return false;
});
$($this.steps).bind("click", function(e){
if($this.steps.index(this) == $this.curStepIdx){
return false;
}
var nextStepIdx = $this.steps.index(this);
var isDone = $this.steps.eq(nextStepIdx).attr("isDone") - 0;
if(isDone == 1){
_loadContent($this, nextStepIdx);
}
return false;
});
// Enable keyboard navigation
// by zhyd . 2018-06-09 10:54 禁用掉键盘左右键切换tab的功能。PS.该功能严重影响到了文章的编辑!
/*if($this.options.keyNavigation){
$(document).keyup(function(e){
if(e.which==39){ // Right Arrow
$this.goForward();
}else if(e.which==37){ // Left Arrow
$this.goBackward();
}
});
}*/
// Prepare the steps
_prepareSteps($this);
// Show the first slected step
_loadContent($this, $this.curStepIdx);
};
var _prepareSteps = function($this) {
if(! $this.options.enableAllSteps){
$($this.steps, $this.target).removeClass("selected").removeClass("done").addClass("disabled");
$($this.steps, $this.target).attr("isDone",0);
}else{
$($this.steps, $this.target).removeClass("selected").removeClass("disabled").addClass("done");
$($this.steps, $this.target).attr("isDone",1);
}
$($this.steps, $this.target).each(function(i){
$($(this).attr("href").replace(/^.+#/, '#'), $this.target).hide();
$(this).attr("rel",i+1);
});
};
var _step = function ($this, selStep) {
return $(
$(selStep, $this.target).attr("href").replace(/^.+#/, '#'),
$this.target
);
};
var _loadContent = function($this, stepIdx) {
var selStep = $this.steps.eq(stepIdx);
var ajaxurl = $this.options.contentURL;
var ajaxurl_data = $this.options.contentURLData;
var hasContent = selStep.data('hasContent');
var stepNum = stepIdx+1;
if (ajaxurl && ajaxurl.length>0) {
if ($this.options.contentCache && hasContent) {
_showStep($this, stepIdx);
} else {
var ajax_args = {
url: ajaxurl,
type: "POST",
data: ({step_number : stepNum}),
dataType: "text",
beforeSend: function(){
$this.loader.show();
},
error: function(){
$this.loader.hide();
},
success: function(res){
$this.loader.hide();
if(res && res.length>0){
selStep.data('hasContent',true);
_step($this, selStep).html(res);
_showStep($this, stepIdx);
}
}
};
if (ajaxurl_data) {
ajax_args = $.extend(ajax_args, ajaxurl_data(stepNum));
}
$.ajax(ajax_args);
}
}else{
_showStep($this,stepIdx);
}
};
var _showStep = function($this, stepIdx) {
var selStep = $this.steps.eq(stepIdx);
var curStep = $this.steps.eq($this.curStepIdx);
if(stepIdx != $this.curStepIdx){
if($.isFunction($this.options.onLeaveStep)) {
var context = { fromStep: $this.curStepIdx+1, toStep: stepIdx+1 };
if (! $this.options.onLeaveStep.call($this,$(curStep), context)){
return false;
}
}
}
// zyd 取消动态加载stepContainer高度
// $this.elmStepContainer.height(_step($this, selStep).outerHeight());
var prevCurStepIdx = $this.curStepIdx;
$this.curStepIdx = stepIdx;
if ($this.options.transitionEffect == 'slide'){
_step($this, curStep).slideUp("fast",function(e){
_step($this, selStep).slideDown("fast");
_setupStep($this,curStep,selStep);
});
} else if ($this.options.transitionEffect == 'fade'){
_step($this, curStep).fadeOut("fast",function(e){
_step($this, selStep).fadeIn("fast");
_setupStep($this,curStep,selStep);
});
} else if ($this.options.transitionEffect == 'slideleft'){
var nextElmLeft = 0;
var nextElmLeft1 = null;
var nextElmLeft = null;
var curElementLeft = 0;
if(stepIdx > prevCurStepIdx){
nextElmLeft1 = $this.contentWidth + 10;
nextElmLeft2 = 0;
curElementLeft = 0 - _step($this, curStep).outerWidth();
} else {
nextElmLeft1 = 0 - _step($this, selStep).outerWidth() + 20;
nextElmLeft2 = 0;
curElementLeft = 10 + _step($this, curStep).outerWidth();
}
if (stepIdx == prevCurStepIdx) {
nextElmLeft1 = $($(selStep, $this.target).attr("href"), $this.target).outerWidth() + 20;
nextElmLeft2 = 0;
curElementLeft = 0 - $($(curStep, $this.target).attr("href"), $this.target).outerWidth();
} else {
$($(curStep, $this.target).attr("href"), $this.target).animate({left:curElementLeft},"fast",function(e){
$($(curStep, $this.target).attr("href"), $this.target).hide();
});
}
_step($this, selStep).css("left",nextElmLeft1).show().animate({left:nextElmLeft2},"fast",function(e){
_setupStep($this,curStep,selStep);
});
} else {
_step($this, curStep).hide();
_step($this, selStep).show();
_setupStep($this,curStep,selStep);
}
return true;
};
var _setupStep = function($this, curStep, selStep) {
$(curStep, $this.target).removeClass("selected");
$(curStep, $this.target).addClass("done");
$(selStep, $this.target).removeClass("disabled");
$(selStep, $this.target).removeClass("done");
$(selStep, $this.target).addClass("selected");
$(selStep, $this.target).attr("isDone",1);
_adjustButton($this);
if($.isFunction($this.options.onShowStep)) {
var context = { fromStep: parseInt($(curStep).attr('rel')), toStep: parseInt($(selStep).attr('rel')) };
if(! $this.options.onShowStep.call(this,$(selStep),context)){
return false;
}
}
if ($this.options.noForwardJumping) {
// +2 == +1 (for index to step num) +1 (for next step)
for (var i = $this.curStepIdx + 2; i <= $this.steps.length; i++) {
$this.disableStep(i);
}
}
};
var _adjustButton = function($this) {
if (! $this.options.cycleSteps){
if (0 >= $this.curStepIdx) {
$($this.buttons.previous).addClass("buttonDisabled");
if ($this.options.hideButtonsOnDisabled) {
$($this.buttons.previous).hide();
}
}else{
$($this.buttons.previous).removeClass("buttonDisabled");
if ($this.options.hideButtonsOnDisabled) {
$($this.buttons.previous).show();
}
}
if (($this.steps.length-1) <= $this.curStepIdx){
$($this.buttons.next).addClass("buttonDisabled");
if ($this.options.hideButtonsOnDisabled) {
$($this.buttons.next).hide();
}
}else{
$($this.buttons.next).removeClass("buttonDisabled");
if ($this.options.hideButtonsOnDisabled) {
$($this.buttons.next).show();
}
}
}
// Finish Button
if (! $this.steps.hasClass('disabled') || $this.options.enableFinishButton){
$($this.buttons.finish).removeClass("buttonDisabled");
if ($this.options.hideButtonsOnDisabled) {
$($this.buttons.finish).show();
}
}else{
$($this.buttons.finish).addClass("buttonDisabled");
if ($this.options.hideButtonsOnDisabled) {
$($this.buttons.finish).hide();
}
}
};
/*
* Public methods
*/
SmartWizard.prototype.goForward = function(){
var nextStepIdx = this.curStepIdx + 1;
if (this.steps.length <= nextStepIdx){
if (! this.options.cycleSteps){
return false;
}
nextStepIdx = 0;
}
_loadContent(this, nextStepIdx);
};
SmartWizard.prototype.goBackward = function(){
var nextStepIdx = this.curStepIdx-1;
if (0 > nextStepIdx){
if (! this.options.cycleSteps){
return false;
}
nextStepIdx = this.steps.length - 1;
}
_loadContent(this, nextStepIdx);
};
SmartWizard.prototype.goToStep = function(stepNum){
var stepIdx = stepNum - 1;
if (stepIdx >= 0 && stepIdx < this.steps.length) {
_loadContent(this, stepIdx);
}
};
SmartWizard.prototype.enableStep = function(stepNum) {
var stepIdx = stepNum - 1;
if (stepIdx == this.curStepIdx || stepIdx < 0 || stepIdx >= this.steps.length) {
return false;
}
var step = this.steps.eq(stepIdx);
$(step, this.target).attr("isDone",1);
$(step, this.target).removeClass("disabled").removeClass("selected").addClass("done");
}
SmartWizard.prototype.disableStep = function(stepNum) {
var stepIdx = stepNum - 1;
if (stepIdx == this.curStepIdx || stepIdx < 0 || stepIdx >= this.steps.length) {
return false;
}
var step = this.steps.eq(stepIdx);
$(step, this.target).attr("isDone",0);
$(step, this.target).removeClass("done").removeClass("selected").addClass("disabled");
}
SmartWizard.prototype.currentStep = function() {
return this.curStepIdx + 1;
}
SmartWizard.prototype.showMessage = function (msg) {
$('.content', this.msgBox).html(msg);
this.msgBox.show();
}
SmartWizard.prototype.hideMessage = function () {
this.msgBox.fadeOut("normal");
}
SmartWizard.prototype.showError = function(stepnum) {
this.setError(stepnum, true);
}
SmartWizard.prototype.hideError = function(stepnum) {
this.setError(stepnum, false);
}
SmartWizard.prototype.setError = function(stepnum,iserror) {
if (typeof stepnum == "object") {
iserror = stepnum.iserror;
stepnum = stepnum.stepnum;
}
if (iserror){
$(this.steps.eq(stepnum-1), this.target).addClass('error')
}else{
$(this.steps.eq(stepnum-1), this.target).removeClass("error");
}
}
SmartWizard.prototype.fixHeight = function(){
var height = 0;
var selStep = this.steps.eq(this.curStepIdx);
var stepContainer = _step(this, selStep);
stepContainer.children().each(function() {
height += $(this).outerHeight();
});
// These values (5 and 20) are experimentally chosen.
stepContainer.height(height + 5);
this.elmStepContainer.height(height + 20);
}
_init(this);
};
(function($){
$.fn.smartWizard = function(method) {
var args = arguments;
var rv = undefined;
var allObjs = this.each(function() {
var wiz = $(this).data('smartWizard');
if (typeof method == 'object' || ! method || ! wiz) {
var options = $.extend({}, $.fn.smartWizard.defaults, method || {});
if (! wiz) {
wiz = new SmartWizard($(this), options);
$(this).data('smartWizard', wiz);
}
} else {
if (typeof SmartWizard.prototype[method] == "function") {
rv = SmartWizard.prototype[method].apply(wiz, Array.prototype.slice.call(args, 1));
return rv;
} else {
$.error('Method ' + method + ' does not exist on jQuery.smartWizard');
}
}
});
if (rv === undefined) {
return allObjs;
} else {
return rv;
}
};
// Default Properties and Events
$.fn.smartWizard.defaults = {
selected: 0, // Selected Step, 0 = first step
keyNavigation: true, // Enable/Disable key navigation(left and right keys are used if enabled)
enableAllSteps: false,
transitionEffect: 'fade', // Effect on navigation, none/fade/slide/slideleft
contentURL:null, // content url, Enables Ajax content loading
contentCache:true, // cache step contents, if false content is fetched always from ajax url
cycleSteps: false, // cycle step navigation
enableFinishButton: false, // make finish button enabled always
hideButtonsOnDisabled: false, // when the previous/next/finish buttons are disabled, hide them instead?
errorSteps:[], // Array Steps with errors
labelNext:'Next',
labelPrevious:'Previous',
labelFinish:'Finish',
noForwardJumping: false,
onLeaveStep: null, // triggers when leaving a step
onShowStep: null, // triggers when showing a step
onFinish: null // triggers when Finish button is clicked
};
})(jQuery);
......@@ -29,14 +29,16 @@
* @date 2018-04-25
* @since 1.0
*/
var editor = null, simplemde = null;
var zhyd = window.zhyd || {
initSidebar: function () {
var a = function () {
$RIGHT_COL.css("min-height", $(window).height());
var a = $BODY.outerHeight(),
b = $BODY.hasClass("footer_fixed") ? -10 : $FOOTER.height(),
c = $LEFT_COL.eq(1).height() + $SIDEBAR_FOOTER.height(),
d = a < c ? c : a;
b = $BODY.hasClass("footer_fixed") ? -10 : $FOOTER.height(),
c = $LEFT_COL.eq(1).height() + $SIDEBAR_FOOTER.height(),
d = a < c ? c : a;
d -= $NAV_MENU.height() + b, $RIGHT_COL.css("min-height", d)
};
$SIDEBAR_MENU.find("a").on("click", function (b) {
......@@ -77,15 +79,217 @@ var zhyd = window.zhyd || {
var b = !0;
return validator.checkAll($(this)) || (b = !1), b && this.submit(), !1
}));
},
initHelloMsg: function () {
var $helloMsg = $("#hello_msg");
var now = new Date();
var nowHours = now.getHours();
$helloMsg.html((nowHours >= 0 && nowHours <= 5) ? "凌晨好" : (nowHours > 5 && nowHours <= 9) ? "早上好" : ((nowHours > 9 && nowHours <= 12) ? "上午好" : ((nowHours > 12 && nowHours <= 13) ? "中午好" : ((nowHours > 13 && nowHours <= 18) ? "下午好" : "晚上好"))));
},
initWangEditor: function (options) {
// 全屏插件
window.wangEditor.fullscreen = {
init: function(editorSelector) {
$(editorSelector + " .w-e-toolbar").append('<div class="w-e-menu"><a class="_wangEditor_btn_fullscreen" href="###" onclick="window.wangEditor.fullscreen.toggleFullscreen(\'' + editorSelector + '\')" data-toggle="tooltip" data-placement="bottom" title data-original-title="全屏编辑"><i class="fa fa-expand"></i></a></div>')
},
toggleFullscreen: function(editorSelector) {
$(editorSelector).toggleClass('fullscreen-editor');
var $a = $(editorSelector + ' ._wangEditor_btn_fullscreen');
var $i = $a.find("i:first-child");
if ($i.hasClass("fa-expand")) {
$a.attr("data-original-title", "退出全屏");
$i.removeClass("fa-expand").addClass("fa-compress")
} else {
$a.attr("data-original-title", "全屏编辑");
$i.removeClass("fa-compress").addClass("fa-expand")
}
}
};
var $op = $.extend({
id: "wangEditor",
contentId: "content",
uploadUrl: "",
uploadFileName: "file"
}, options);
var E = window.wangEditor;
editor = new E('#' + $op.id);
// 通过 url 参数配置 debug 模式。url 中带有 wangeditor_debug_mode=1 才会开启 debug 模式
editor.customConfig.debug = location.href.indexOf('wangeditor_debug_mode=1') > 0;
// 关闭粘贴样式的过滤
editor.customConfig.pasteFilterStyle = false;
editor.customConfig.zIndex = 100;
var $content = $('#' + $op.contentId);
editor.customConfig.onchange = function (html) {
// 监控变化,同步更新到 textarea
$content.val(html);
};
if ($op.uploadUrl) {
// 上传图片到服务器
editor.customConfig.uploadImgServer = $op.uploadUrl;
editor.customConfig.uploadFileName = 'file';
// 将图片大小限制为 5M
editor.customConfig.uploadImgMaxSize = 5 * 1024 * 1024;
editor.customConfig.customAlert = function (info) {
// info 是需要提示的内容
$.alert.error(info);
};
editor.customConfig.uploadImgHooks = {
error: function (xhr, editor) {
$.alert.error("图片上传出错");
},
timeout: function (xhr, editor) {
$.alert.error("请求超时");
},
customInsert: function (insertImg, result, editor) {
// 图片上传并返回结果,自定义插入图片的事件(而不是编辑器自动插入图片!!!)
// insertImg 是插入图片的函数,editor 是编辑器对象,result 是服务器端返回的结果
console.log('customInsert:' + insertImg, result, editor);
if (result.status == 200) {
console.log(result.data);
var imgFullPath = appConfig.qiniuPath + result.data + appConfig.qiniuImgStyle;
editor.txt.append('<img src="' + imgFullPath + '" alt="" style="width: 95%;max-width: 100%;height: auto;border-radius: 6px;"/>');
// 解决上传完图片如果未进行其他操作,则不会触发编辑器的“change”事件,导致实际文章内容中缺少最后上传的图片文件 2018-07-13
$content.val(editor.txt.html());
} else {
$.alert.error(result.message);
}
}
};
}
editor.create();
E.fullscreen.init('#' + $op.id);
// 修改编辑器大小
editor.$textContainerElem.css('max-height', '115px').css('height', '100%');
},
initMdEditor: function (options) {
var $op = $.extend({
id: "mdEditor",
uniqueId: "mdEditor_1",
uploadUrl: ""
}, options);
// js实现aop切面编程,实时保存文章内容
Function.prototype.after = function (afterfn) {
var __self = this;
//保存原函数的引用
return function () {
//返回包含了原函数和新函数的"代理"函数
afterfn.apply(this, arguments);//(1)
//执行新函数,且保证this不被劫持,新函数接受的参数
//也会被原封不动地传入原函数,新函数在原函数之前执行
return __self.apply(this, arguments);//(2)
//执行原函数并返回原函数的执行结果
//并且保证this不被劫持
}
};
var showMsg = function () {
var $div = $('<div></div>');
$div.css({
'position': 'absolute',
'right': '10px',
'top': 0,
'padding': '5px',
'font-size': '12px',
'color': '#ccc',
'opacity': 0
});
$div.html("自动保存完成");
$div.appendTo($(".CodeMirror"));
$div.animate({opacity: 1}, 1000, function () {
$div.animate({opacity: 0}, 1000, function () {
$(this).remove();
})
})
};
SimpleMDE.prototype.autosave = SimpleMDE.prototype.autosave.after(showMsg);
// Powered by https://github.com/sparksuite/simplemde-markdown-editor
simplemde = new SimpleMDE({
// textarea的DOM对象
element: document.getElementById($op.id),
// 自动下载FontAwesome,设为false为不下载(如果设为false则必须手动引入)
autoDownloadFontAwesome: false,
// 自动聚焦输入框
autofocus: true,
// 是否自动保存正在编写的文本
autosave: {
// 启用自动保存功能
enabled: true,
// 自动保存的间隔,以毫秒为单位。默认为10000(10s)
delay: 15000,
// 唯一的字符串标识符(保证每个SimpleMDE编辑器的uniqueId唯一)
uniqueId: $op.uniqueId,
msg: "自动保存成功了"
},
placeholder: "请输入文本内容",
// 如果设置为true,则会出现JS警报窗口,询问链接或图像URL(插入图片或链接弹窗)。默认为false
promptURLs: true,
renderingConfig: {
// 如果设置为true,将使用highlight.js高亮显示。默认为false
codeSyntaxHighlighting: true
},
showIcons: ["code", "table", "clean-block", "horizontal-rule"],
tabSize: 4,
// 编辑器底部的状态栏
status: true,
status: ["autosave", "lines", "words", "cursor"], // Optional usage
status: ["autosave", "lines", "words", "cursor", {
className: "keystrokes",
defaultValue: function (el) {
this.keystrokes = 0;
el.innerHTML = "0 Keystrokes";
},
onUpdate: function (el) {
el.innerHTML = ++this.keystrokes + " Keystrokes";
}
}]
});
var $fullscreen = $(".editor-toolbar a.fa-arrows-alt, .editor-toolbar a.fa-columns");
$fullscreen.click(function () {
var $this = $(this);
if ($fullscreen.hasClass("active")) {
$(".CodeMirror, .CodeMirror-scroll").css('max-height', 'none');
} else {
$(".CodeMirror, .CodeMirror-scroll").css('max-height', '200px');
}
});
if ($op.uploadUrl) {
inlineAttachment.editors.codemirror4.attach(simplemde.codemirror, {
uploadUrl: $op.uploadUrl
});
}
$(".editor-preview-side").addClass("markdown-body");
},
/**
* 下拉框组件, 支持自动填充option
*/
combox: {
init: function () {
$('select[target=combox]').each(function (e) {
var $this = $(this);
var url = $this.data("url");
if (!url) {
return false;
}
var method = $this.data("method") || "get";
$.ajax({
url: url,
type: method,
success: function (json) {
if (json && json.status == 200) {
var optionTpl = '<option value="">请选择</option>{{#data}}<option value="{{id}}">{{name}}</option>{{/data}}';
var html = Mustache.render(optionTpl, json);
$this.html(html);
}
}
});
})
}
}
};
function countChecked() {
"all" === checkState && $(".bulk_action input[name='table_records']").iCheck("check"), "none" === checkState && $(".bulk_action input[name='table_records']").iCheck("uncheck");
var a = $(".bulk_action input[name='table_records']:checked").length;
a ? ($(".column-title").hide(), $(".bulk-actions").show(), $(".action-cnt").html(a + " Records Selected")) : ($(".column-title").show(), $(".bulk-actions").hide())
}
function gd(a, b, c) {
return new Date(a, b - 1, c).getTime()
}
......@@ -99,7 +303,7 @@ function gd(a, b, c) {
}
var f = this,
g = arguments;
g = arguments;
d ? clearTimeout(d) : c && a.apply(f, g), d = setTimeout(h, b || 100)
}
};
......@@ -109,22 +313,22 @@ function gd(a, b, c) {
}(jQuery, "smartresize");
var CURRENT_URL = window.location.href.split("#")[0].split("?")[0],
$BODY = $("body"),
$MENU_TOGGLE = $("#menu_toggle"),
$SIDEBAR_MENU = $("#sidebar-menu"),
$SIDEBAR_FOOTER = $(".sidebar-footer"),
$LEFT_COL = $(".left_col"),
$RIGHT_COL = $(".right_col"),
$NAV_MENU = $(".nav_menu"),
$FOOTER = $("footer"),
randNum = function () {
return Math.floor(21 * Math.random()) + 20
};
$BODY = $("body"),
$MENU_TOGGLE = $("#menu_toggle"),
$SIDEBAR_MENU = $("#sidebar-menu"),
$SIDEBAR_FOOTER = $(".sidebar-footer"),
$LEFT_COL = $(".left_col"),
$RIGHT_COL = $(".right_col"),
$NAV_MENU = $(".nav_menu"),
$FOOTER = $("footer"),
randNum = function () {
return Math.floor(21 * Math.random()) + 20
};
$(document).ready(function () {
$(".collapse-link").on("click", function () {
var a = $(this).closest(".x_panel"),
b = $(this).find("i"),
c = a.find(".x_content");
b = $(this).find("i"),
c = a.find(".x_content");
a.attr("style") ? c.slideToggle(200, function () {
a.removeAttr("style")
}) : (c.slideToggle(200), a.css("height", "auto")), b.toggleClass("fa-chevron-up fa-chevron-down")
......@@ -151,57 +355,31 @@ $(document).ready(function () {
radioClass: 'iradio_square-green',
increaseArea: '20%' // optional
});
}), $("table input").on("ifChecked", function () {
checkState = "", $(this).parent().parent().parent().addClass("selected"), countChecked()
}), $("table input").on("ifUnchecked", function () {
checkState = "", $(this).parent().parent().parent().removeClass("selected"), countChecked()
});
var checkState = "";
$(".bulk_action input").on("ifChecked", function () {
checkState = "", $(this).parent().parent().parent().addClass("selected"), countChecked()
}), $(".bulk_action input").on("ifUnchecked", function () {
checkState = "", $(this).parent().parent().parent().removeClass("selected"), countChecked()
}), $(".bulk_action input#check-all").on("ifChecked", function () {
checkState = "all", countChecked()
}), $(".bulk_action input#check-all").on("ifUnchecked", function () {
checkState = "none", countChecked()
}), $(document).ready(function () {
$(".expand").on("click", function () {
$(this).next().slideToggle(200), $expand = $(this).find(">:first-child"), "+" == $expand.text() ? $expand.text("-") : $expand.text("+")
})
}), "undefined" != typeof NProgress && ($(document).ready(function () {
"undefined" != typeof NProgress && ($(document).ready(function () {
NProgress.start()
}), $(window).load(function () {
NProgress.done()
}));
var originalLeave = $.fn.popover.Constructor.prototype.leave;
$.fn.popover.Constructor.prototype.leave = function (a) {
var c, d,
b = a instanceof this.constructor ? a : $(a.currentTarget)[this.type](this.getDelegateOptions()).data("bs." + this.type);
originalLeave.call(this, a), a.currentTarget && (c = $(a.currentTarget).siblings(".popover"), d = b.timeout, c.one("mouseenter", function () {
clearTimeout(d), c.one("mouseleave", function () {
$.fn.popover.Constructor.prototype.leave.call(b, b)
})
}))
}, $("body").popover({
selector: "[data-popover]",
trigger: "click hover",
delay: {
show: 50,
hide: 400
}
}), $(document).ready(function () {
$(document).ready(function () {
// 工具提示
$('[data-toggle="tooltip"]').tooltip();
// 图片预览
$(".showImage").fancybox();
/* 自定义下拉 div */
$(".custom-dropdown").on("click", function () {
var a = $(this).closest(".custom-panel"),
b = $(this).find("i"),
c = a.find(".custom-container");
b = $(this).find("i"),
c = a.find(".custom-container");
a.attr("style") ? c.slideToggle(200, function () {
a.removeAttr("style")
}) : (c.slideToggle(200), a.css("height", "auto")), b.toggleClass("fa-angle-double-up fa-angle-double-down")
});
$(".showContent").click(function () {
$(this).toggleClass('fa-plus-square fa-minus-square');
$(".disable-content").slideToggle(400);
});
zhyd.initDaterangepicker();
zhyd.initValidator();
zhyd.initSidebar();
......@@ -243,6 +421,29 @@ $.fn.popover.Constructor.prototype.leave = function (a) {
*/
$(".uploadPreview").each(function () {
var $this = $(this);
$this.uploadPreview({ imgContainer: $this.data("preview-container") });
})
$this.uploadPreview({imgContainer: $this.data("preview-container")});
});
zhyd.initHelloMsg();
$("#updPassBtn").click(function () {
var $form = $("#updPassForm");
if (validator.checkAll($form)) {
$form.ajaxSubmit({
type: "POST",
url: '/passport/updatePwd',
success: function (json) {
$.alert.ajaxSuccess(json);
if (json.status == 200) {
setTimeout(function () {
window.location.reload();
}, 2000);
}
},
error: $.alert.ajaxError
});
}
});
zhyd.combox.init();
});
\ No newline at end of file
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @date 2018/6/7 10:48
* @since 1.0
*/
var $publishForm = $("#publishForm");
// 加载所有分类
function loadType(){
$.ajax({
type: "post",
url: "/type/listAll",
success: function (json) {
$.alert.ajaxSuccess(json);
var data = '';
if(data = json.data){
var tpl = '<option value="">选择分类</option>{{#data}}<option value="{{id}}">{{name}}</option>{{#nodes}}<option value="{{id}}"> -- {{name}}</option>{{/nodes}}{{/data}}';
var html = Mustache.render(tpl, json);
$("select#typeId").html(html);
}
$("#refressType").removeClass("fa-spin");
$.alert.showSuccessMessage("分类加载完成!");
},
error: $.alert.ajaxError
});
}
// 加载所有标签
function loadTag() {
$.ajax({
type: "post",
url: "/tag/listAll",
success: function (json) {
$.alert.ajaxSuccess(json);
var data = '';
if (data = json.data) {
var tagHtml = '';
for (var i = 0, len = data.length; i < len; i++) {
var tag = data[i];
tagHtml += '<li>'
+ '<input type="checkbox" class="square ignore" name="tags" value="' + tag.id + '"> ' + tag.name
+ '</li>';
}
$("#tag-list").html(tagHtml);
$("input[type=checkbox], input[type=radio]").iCheck({
checkboxClass: 'icheckbox_square-green',
radioClass: 'iradio_square-green',
increaseArea: '20%' // optional
});
}
$("#refressTag").removeClass("fa-spin");
$.alert.showSuccessMessage("标签加载完成!");
},
error: $.alert.ajaxError
});
}
$("#refressType").click(function () {
$(this).addClass("fa-spin");
loadType();
});
$("#refressTag").click(function () {
$(this).addClass("fa-spin");
loadTag();
});
loadTag();
loadType();
if(articleId){
setTimeout(function () {
$.ajax({
type: "post",
url: "/article/get/" + articleId,
success: function (json) {
$.alert.ajaxSuccess(json);
var info = json.data;
// 标签
var tags = info.tags;
for(var i = 0, len = tags.length; i < len ; i ++){
var tag = tags[i];
$("input[name=tags][value=" + tag.id + "]").iCheck('check');
}
if($('input[name=original]')){
$('input[name=original]').iCheck(info.original ? 'check' : 'uncheck');
}
if($('#comment')){
$('#comment').iCheck(info.comment ? 'check' : 'uncheck');
}
if(info['coverImage']){
$(".coverImage").attr('src', appConfig.qiniuPath + info['coverImage']);
}
var contentMd = info['contentMd'];
if(contentMd){
$("#contentMd").val(contentMd);
if(simplemde){
simplemde.value(contentMd);
}
}
var contentHtml = info['content'];
if(contentHtml){
$("#content").val(contentHtml);
if(editor){
editor.txt.html(contentHtml);
}
}
$publishForm.find("input[type!=checkbox], select, textarea").each(function () {
clearText($(this), this.type, info);
});
},
error: $.alert.ajaxError
});
}, 1000);
}
// 点击保存
$(".publishBtn").click(function () {
if(validator.checkAll($publishForm)) {
$publishForm.ajaxSubmit({
type: "post",
url: "/article/save",
success: function (json) {
$.alert.ajaxSuccessConfirm(json, function () {
window.location.href = '/articles';
});
},
error: $.alert.ajaxError
});
}
});
var loadImg = false;
// 选择图片
$("#file-upload-btn").click(function () {
$("#chooseImg").modal('show');
if(!loadImg){
// 加载素材库
$.ajax({
type: "post",
url: "/api/material",
success: function (json) {
$.alert.ajaxSuccess(json);
loadImg = true;
json.qiniuPath = appConfig.qiniuPath;
var $box = $(".list-material");
var tpl = '{{#data}}<li data-imgUrl="{{.}}"><div class="col-md-55"><img class="lazy-img" data-original="{{qiniuPath}}{{.}}" alt="image"></div></li>{{/data}}{{^data}}<li>素材库为空</li>{{/data}}';
var html = Mustache.render(tpl, json);
$box.html(html);
$box.find("li").click(function () {
$box.find("li").each(function () {
$(this).removeClass("active");
});
var $this = $(this);
$this.toggleClass("active");
if($this.hasClass("active")){
var imgUrl = $this.attr("data-imgUrl");
$("#cover-img-input").val(imgUrl);
$(".preview img.coverImage").attr("src", appConfig.qiniuPath + imgUrl);
}
});
$("img.lazy-img").lazyload({
placeholder : appConfig.staticPath + "/img/loading.gif",
effect: "fadeIn",
threshold: 100
});
$("img.lazy-img").trigger("sporty");
},
error: $.alert.ajaxError
});
}
});
// 选择图片
$("#file-btn").click(function () {
var $this = $(this);
$("#cover-img-file").click();
});
$("input[name=file]").uploadPreview({ imgContainer: ".preview", width: 190, height: 200 });
......@@ -36,12 +36,12 @@
init: function (options) {
$.tableUtil._option = options;
// console.log(options.url);
$('#tablelist').bootstrapTable({
$('#tablelist').bootstrapTable('destroy').bootstrapTable({
url: options.url,
method: 'post', //请求方式(*)
toolbar: '#toolbar', //工具按钮用哪个容器
striped: true, //是否显示行间隔色
cache: true, //是否使用缓存,默认为true,所以一般情况下需要设置一下这个属性(*)
cache: false, //是否使用缓存,默认为true,所以一般情况下需要设置一下这个属性(*)
contentType: "application/x-www-form-urlencoded", // 发送到服务器的数据编码类型, application/x-www-form-urlencoded为了实现post方式提交
sortable: false, //是否启用排序
sortOrder: "asc", //排序方式
......@@ -88,12 +88,14 @@
return false;
}
},
onExpandRow: options.onExpandRow,
rowStyle: options.rowStyle || function (row, index){return {};},
columns: options.columns
});
},
queryParams: function (params) {
params = $.extend({}, params);
params.keywords = params.searchText;
return params;
},
refresh: function () {
......@@ -221,12 +223,15 @@ function clearText($this, type, info){
var thisValue = info[thisName];
if (type == 'radio') {
$this.iCheck(((thisValue && 1 == $this.val()) || (!thisValue && 0 == $this.val())) ? 'check' : 'uncheck')
} else if (type == 'checkbox') {
$this.iCheck((thisValue || thisValue == 1) ? 'check' : 'uncheck');
} else {
if (thisValue && thisName != 'password') {
$this.val(thisValue);
} else if (type.startsWith('select')) {
if(thisValue == 'true' || thisValue == true) {
thisValue = 1;
} else if(thisValue == 'false' || thisValue == false) {
thisValue = 0;
}
$this.val(thisValue);
} else {
$this.val(thisValue);
}
} else {
if (type === 'radio' || type === 'checkbox') {
......@@ -255,6 +260,5 @@ function getSelectedId() {
* @returns {*|jQuery}
*/
function getSelectedObj() {
var selectedJson = $("#tablelist").bootstrapTable('getAllSelections');
return selectedJson;
return $("#tablelist").bootstrapTable('getAllSelections');
}
......@@ -36,7 +36,7 @@
$.extend({
alert: {
info: function (content, delayTime, callback) {
info: function (content, callback, delayTime) {
delayTime = delayTime ? "confirm|" + delayTime : "confirm|3000";
$.jqAlert({
title: '友情提示',
......@@ -46,7 +46,7 @@
confirm: callback
});
},
error: function (content, delayTime, callback) {
error: function (content, callback, delayTime) {
delayTime = delayTime ? "confirm|" + delayTime : "confirm|3000";
$.jqAlert({
title: '警告',
......@@ -61,7 +61,7 @@
$.jqConfirm({
confirmButtonClass: 'btn-success',
cancelButtonClass: 'btn-default',
title: '友情提示',
title: '确认提示',
content: content,
autoClose: delayTime,
confirmButton: '确定',
......@@ -70,10 +70,10 @@
cancel: cancelCallback
});
},
ajaxSuccessConfirm: function (json, callback) {
ajaxSuccessConfirm: function (json, callback, cancelCallback) {
if (json.status == 200) {
if(json.message){
$.alert.confirm(json.message, callback);
$.alert.confirm(json.message, callback, cancelCallback);
}
} else {
if(json.message){
......@@ -134,6 +134,18 @@
});
$.extend({
tool: {
cache: function (key, value) {
if(!value){
return localStorage.getItem(key);
}
localStorage.setItem(key, value);
return false;
},
delCache: function (key) {
if(this.cache(key)){
localStorage.removeItem(key);
}
},
isEmpty: function (value) {
if (value == null || this.trim(value) == "") {
return true;
......@@ -192,6 +204,94 @@
}
});
})(jQuery);
/**
* 扩展String方法
*/
$.extend(String.prototype, {
trim: function () {
return this.replace(/(^\s*)|(\s*$)|\r|\n/g, "");
},
startsWith: function (pattern) {
return this.indexOf(pattern) === 0;
},
endsWith: function (pattern) {
var d = this.length - pattern.length;
return d >= 0 && this.lastIndexOf(pattern) === d;
},
replaceSuffix: function (index) {
return this.replace(/\[[0-9]+\]/, '[' + index + ']').replace('#index#', index);
},
getRequestURI: function () {
var indexOf = this.indexOf("?");
return (indexOf == -1) ? this : this.substr(0, indexOf);
},
getParams: function (encode) {
var params = {},
indexOf = this.indexOf("?");
if (indexOf != -1) {
var str = this.substr(indexOf + 1),
strs = str.split("&");
for (var i = 0; i < strs.length; i++) {
var item = strs[i].split("=");
var val = encode ? item[1].encodeParam() : item[1];
params[item[0]] = item.length > 1 ? val : '';
}
}
return params;
},
encodeParam: function () {
return encodeURIComponent(this);
},
replaceAll: function (os, ns) {
return this.replace(new RegExp(os, "gm"), ns);
},
skipChar: function (ch) {
if (!this || this.length === 0) {
return '';
}
if (this.charAt(0) === ch) {
return this.substring(1).skipChar(ch);
}
return this;
},
isPositiveInteger: function () {
return (new RegExp(/^[1-9]\d*$/).test(this));
},
isInteger: function () {
return (new RegExp(/^\d+$/).test(this));
},
isNumber: function (value, element) {
return (new RegExp(/^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/).test(this));
},
isValidPwd: function () {
return (new RegExp(/^([_]|[a-zA-Z0-9]){6,32}$/).test(this));
},
isValidMail: function () {
return (new RegExp(/^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/).test(this.trim()));
},
isSpaces: function () {
for (var i = 0; i < this.length; i += 1) {
var ch = this.charAt(i);
if (ch != ' ' && ch != "\n" && ch != "\t" && ch != "\r") {
return false;
}
}
return true;
},
isMobile: function () {
return (new RegExp(/(^[0-9]{11,11}$)/).test(this));
},
isUrl: function () {
return (new RegExp(/^[a-zA-z]+:\/\/([a-zA-Z0-9\-\.]+)([-\w .\/?%&=:]*)$/).test(this));
},
isExternalUrl: function () {
return this.isUrl() && this.indexOf("://" + document.domain) == -1;
},
parseCurrency: function (num) {
var numberValue = parseFloat(this);
return parseFloat(numberValue.toFixed(num || 2));
}
});
/**
* Created by yadong.zhang on 2017-03-19.
......
<#include "/layout/header.ftl"/>
<#include "/include/macros.ftl">
<@header></@header>
<div class="">
<div class="clearfix"></div>
<div class="row">
......@@ -12,13 +13,19 @@
<div class="<#--table-responsive-->">
<div class="btn-group hidden-xs" id="toolbar">
<@shiro.hasPermission name="article:publish">
<a class="btn btn-default" title="发布文章" href="/article/publish"> <i class="fa fa-plus"></i> 发布文章 </a>
<a class="btn btn-default" title="发表文章" href="/article/publishMd"> <i class="fa fa-pencil"></i> 发表文章 </a>
</@shiro.hasPermission>
<@shiro.hasPermission name="article:batchDelete">
<button id="btn_delete_ids" type="button" class="btn btn-default" title="删除选中">
<i class="fa fa-trash-o"></i> 批量删除
</button>
</@shiro.hasPermission>
<#-- 由草稿状态批量修改为已发布状态 -->
<@shiro.hasPermission name="article:publish">
<button id="btn_update_status" type="button" class="btn btn-default" title="批量发布">
<i class="fa fa-trash-o"></i> 批量发布
</button>
</@shiro.hasPermission>
<@shiro.hasPermission name="article:batchPush">
<button id="btn_push_ids" type="button" class="btn btn-info" title="批量推送">
<i class="fa fa-send-o"></i> 批量推送到百度
......@@ -33,7 +40,7 @@
</div>
</div>
</div>
<#include "/layout/footer.ftl"/>
<@footer>
<script>
/**
* 操作按钮
......@@ -44,12 +51,14 @@
*/
function operateFormatter(code, row, index) {
var trId = row.id;
var recommended = row.recommended ? '<i class="fa fa-thumbs-o-down"></i>取消推荐' : '<i class="fa fa-thumbs-o-up"></i>推荐';
var top = row.top ? '<i class="fa fa-arrow-circle-down"></i>取消置顶' : '<i class="fa fa-arrow-circle-up"></i>置顶';
var operateBtn = [
'<@shiro.hasPermission name="article:push"><a class="btn btn-xs btn-info btn-push" title="推送" data-id="' + trId + '"><i class="fa fa-send-o"></i>推送</a></@shiro.hasPermission>',
'<@shiro.hasPermission name="article:top"><a class="btn btn-xs btn-success btn-top" data-id="' + trId + '"><i class="fa fa-arrow-circle-up"></i>置顶</a></@shiro.hasPermission>',
'<@shiro.hasPermission name="article:recommend"><a class="btn btn-xs btn-success btn-recommend" data-id="' + trId + '"><i class="fa fa-thumbs-o-up"></i>推荐</a></@shiro.hasPermission>',
'<@shiro.hasPermission name="article:edit"><a class="btn btn-xs btn-primary" href="/article/update/' + trId + '"><i class="fa fa-edit"></i>编辑</a></@shiro.hasPermission>',
'<@shiro.hasPermission name="article:delete"><a class="btn btn-xs btn-danger btn-remove" data-id="' + trId + '"><i class="fa fa-trash-o"></i>删除</a></@shiro.hasPermission>'
'<@shiro.hasPermission name="article:delete"><a class="btn btn-xs btn-danger btn-remove" data-id="' + trId + '"><i class="fa fa-trash-o"></i>删除</a></@shiro.hasPermission>',
'<@shiro.hasPermission name="article:top"><a class="btn btn-xs btn-success btn-top" data-id="' + trId + '">' + top + '</a></@shiro.hasPermission>',
'<@shiro.hasPermission name="article:recommend"><a class="btn btn-xs btn-success btn-recommend" data-id="' + trId + '">' + recommended + '</a></@shiro.hasPermission>'
];
return operateBtn.join('');
}
......@@ -63,47 +72,25 @@
columns: [
{
checkbox: true
}, {
field: 'id',
title: 'ID',
editable: false
}, {
field: 'title',
title: '标题',
width: '200px',
editable: false,
formatter: function (code, row, index) {
var title = code;
title = title.length > 10 ? (title.substr(0, 10) + '...') : title;
var id = row.id;
var original= row.original ? "原创" : "转载";
return '<strong>['+original+']</strong> <a href="' + appConfig.wwwPath + '/article/' + id + '" target="_blank">' + code + '</a>';
}
}, {
field: 'status',
title: '状态',
width: '60px',
editable: false,
formatter: function (code) {
return code ? '已发布' : '<span style="color: red;font-weight: 700">草稿</span>';
}
}, {
field: 'recommended',
title: '推荐',
width: '60px',
editable: false,
formatter: function (code) {
return code ? '<span style="color: #26B99A;font-weight: 700">是</span>' : '否';
}
}, {
field: 'top',
title: '置顶',
width: '60px',
editable: false,
formatter: function (code) {
return code ? '<span style="color: #26B99A;font-weight: 700">是</span>' : '否';
// var original= row.original ? "原创" : "转载";
// return '<strong>['+original+']</strong> <a href="' + appConfig.wwwPath + '/article/' + id + '" target="_blank">' + code + '</a>';
var status = row.status ? '<span class="label label-success">已发布</span>' : '<span class="label label-danger">草稿</span>';
return status + '<a href="' + appConfig.wwwPath + '/article/' + id + '" target="_blank" title="' + code + '">' + title + '</a>';
}
}, {
field: 'type',
title: '分类',
width: '80px',
editable: false,
formatter: function (code) {
var type = code;
......@@ -112,7 +99,7 @@
}, {
field: 'tags',
title: '标签',
width: '200px',
width: '140px',
editable: false,
formatter: function (code) {
var tags = code;
......@@ -120,23 +107,46 @@
if (tags) {
for (var i = 0, len = tags.length; i < len; i++) {
var tag = tags[i];
tagHtml += '<a class="btn btn-default btn-xs" href="' + appConfig.wwwPath + '/tag/' + tag.id + '" target="_blank"> ' + tag.name + '</a> ';
tagHtml += ' <a class="" href="' + appConfig.wwwPath + '/tag/' + tag.id + '" target="_blank"> ' + tag.name + '</a> |';
}
}
return tagHtml;
return tagHtml.substr(0, tagHtml.length - 1);
}
}, {
field: 'comment',
title: '评论',
width: '50px',
editable: false,
formatter: function (code) {
return code ? '<span class="label label-success">开启</span>' : '<span class="label label-danger">关闭</span>';
}
}, {
field: 'createTime',
title: '发布时间',
editable: false,
width: '145px',
width: '100px',
formatter: function (code) {
return new Date(code).format("yyyy-MM-dd hh:mm:ss")
}
}, {
field: 'lookCount',
title: '浏览',
editable: false,
width: '50px'
}, {
field: 'commentCount',
title: '评论',
editable: false,
width: '50px'
}, {
field: 'loveCount',
title: '喜欢',
editable: false,
width: '50px'
}, {
field: 'operate',
title: '操作',
width: '250px',
width: '200px',
formatter: operateFormatter //自定义方法,添加操作按钮
}
]
......@@ -192,12 +202,37 @@
$("#btn_push_ids").click(function () {
var selectedId = getSelectedId();
if (!selectedId || selectedId == '[]' || selectedId.length == 0) {
$.alert.alertErrorMsg("请至少选择一条记录");
$.alert.error("请至少选择一条记录");
return;
}
push(selectedId);
});
/**
* 批量修改状态
*/
$("#btn_update_status").click(function () {
var selectedId = getSelectedId();
if (!selectedId || selectedId == '[]' || selectedId.length == 0) {
$.alert.error("请至少选择一条记录");
return;
}
$.alert.confirm("确定批量发布?发布完成后用户可见", function () {
$.ajax({
type: "post",
url: "/article/batchPublish",
traditional: true,
data: {'ids': selectedId},
success: function (json) {
$.alert.ajaxSuccess(json);
},
error: $.alert.ajaxError
});
}, function () {
}, 5000);
});
function push(ids) {
$.alert.confirm("确定推送到百度站长平台?", function () {
$.ajax({
......@@ -234,4 +269,5 @@
}, 5000);
}
});
</script>
\ No newline at end of file
</script>
</@footer>
\ No newline at end of file
<#include "/include/macros.ftl">
<@header>
<link href="https://cdn.bootcss.com/simplemde/1.11.2/simplemde.min.css" rel="stylesheet">
<link href="https://cdn.bootcss.com/github-markdown-css/2.10.0/github-markdown.min.css" rel="stylesheet">
<link href="https://cdn.bootcss.com/highlight.js/9.12.0/styles/github.min.css" rel="stylesheet">
<style>
.CodeMirror, .CodeMirror-scroll {
min-height: 130px;
max-height: 200px;
}
.CodeMirror .cm-spell-error:not(.cm-url):not(.cm-comment):not(.cm-tag):not(.cm-word) {
background: none;
}
</style>
</@header>
<div class="clearfix"></div>
<div class="row">
<div class="col-md-12 col-sm-12 col-xs-12">
<ol class="breadcrumb">
<li><a href="/">首页</a></li>
<li><a href="/articles">文章列表</a></li>
<li class="active">发布文章-Markdown编辑器</li>
</ol>
<div class="x_panel">
<div class="x_title">
<h2>发布文章 <small>Markdown编辑器,使用 <a href="/article/publish">HTML编辑器</a></small></h2>
<div class="clearfix"></div>
</div>
<div class="x_content">
<div id="wizard" class="form_wizard wizard_horizontal">
<ul class="wizard_steps">
<li>
<a href="#step-1">
<span class="step_no">1</span>
<span class="step_descr"> <small><i class="fa fa-pencil"></i> 写文章</small> </span>
</a>
</li>
<li>
<a href="#step-2">
<span class="step_no">2</span>
<span class="step_descr"> <small><i class="fa fa-info"></i> 完善配置</small> </span>
</a>
</li>
<li>
<a href="#step-3">
<span class="step_no">3</span>
<span class="step_descr"> <small><i class="fa fa-paw"></i> 设置SEO相关</small> </span>
</a>
</li>
<li>
<a href="#step-4">
<span class="step_no">4</span>
<span class="step_descr"> <small><i class="fa fa-paper-plane"></i> 发布</small> </span>
</a>
</li>
</ul>
<form id="publishForm" class="form-horizontal form-label-left" novalidate>
<input type="hidden" name="id">
<input type="hidden" name="isMarkdown" value="1">
<div id="step-1">
<div class="ln_solid"></div>
<div class="custom-panel">
<div class="custom-container">
<div class="item form-group">
<label class="control-label col-md-1 col-sm-1 col-xs-1" for="title"></label>
<div class="col-md-9 col-sm-9 col-xs-9">
<input type="text" class="form-control col-md-7 col-xs-12" name="title" id="title" required="required" placeholder="请输入标题"/>
</div>
</div>
<div class="item form-group">
<label class="control-label col-md-1 col-sm-1 col-xs-1" for="contentMd"></label>
<div class="col-md-10 col-sm-10 col-xs-10">
<textarea class="form-control col-md-7 col-xs-12" id="content" name="content" style="display: none" required="required"></textarea>
<textarea class="form-control col-md-7 col-xs-12 valid" id="contentMd" name="contentMd" style="display: none" required="required"></textarea>
</div>
</div>
</div>
</div>
</div>
<div id="step-2">
<div class="ln_solid"></div>
<div class="custom-panel">
<div class="custom-container">
<div class="item form-group">
<label class="control-label col-md-2 col-sm-2 col-xs-2" for="title">文章类型 <span class="required">*</span></label>
<div class="col-md-2 col-sm-2 col-xs-2">
<select class="form-control" name="original" required="required" id="original" style="width: 120px;">
<option value="1">原创</option>
<option value="0">转载</option>
</select>
</div>
<div class="col-md-3 col-sm-3 col-xs-3">
<select class="form-control" name="typeId" required="required" id="typeId" style="width: 120px;"></select>
</div>
</div>
<div class="item form-group">
<label class="control-label col-md-2 col-sm-2 col-xs-2" for="title">文章封面 <span class="required">*</span></label>
<div class="col-md-3 col-sm-3 col-xs-3">
<div class="choose-local-img">
<button type="button" class="btn btn-success" id="file-upload-btn">上传图片</button>
<input id="cover-img-file" type="file" name="file" required="required" style="display: none">
<input id="cover-img-input" type="hidden" name="coverImage">
<div class="preview">
<img class="coverImage" src="" alt="">
</div>
</div>
</div>
</div>
<div class="item form-group">
<label class="control-label col-md-2 col-sm-2 col-xs-2" for="mobile">文章标签 <span class="required">*</span></label>
<div class="col-md-10 col-sm-10 col-xs-10" >
<div class="h5"><i class="fa fa-plus-square showContent pointer"> 点击查看</i> <i class="fa fa-refresh fa-1x pointer" id="refressTag"> 刷新</i></div>
<div class="disable-content" style="display: none;">
<ul class="list-unstyled list-inline" id="tag-list" style="line-height: 30px;"></ul>
</div>
</div>
</div>
<div class="item form-group">
<label class="control-label col-md-2 col-sm-2 col-xs-2" for="status">文章状态 <span class="required">*</span></label>
<div class="col-md-10 col-sm-10 col-xs-10">
<ul class="list-unstyled list-inline">
<li>
<input type="radio" class="square" checked name="status" value="1"> 发布
</li>
<li>
<input type="radio" class="square" name="status" value="0"> 草稿
</li>
</ul>
</div>
</div>
<div class="item form-group">
<label class="control-label col-md-2 col-sm-2 col-xs-2" for="comment">开启评论 <span class="required">*</span></label>
<div class="col-md-10 col-sm-10 col-xs-10">
<input type="checkbox" class="square" name="comment" id="comment">
</div>
</div>
</div>
</div>
</div>
<div id="step-3">
<div class="ln_solid"></div>
<div class="custom-panel">
<div class="custom-container">
<div class="item form-group">
<label class="control-label col-md-2 col-sm-2 col-xs-2" for="password">摘要 <span class="required">*</span></label>
<div class="col-md-9 col-sm-9 col-xs-9">
<textarea class="form-control col-md-7 col-xs-12" id="description" name="description" required="required"></textarea>
</div>
</div>
<div class="item form-group">
<label class="control-label col-md-2 col-sm-2 col-xs-2" for="password">关键词 <span class="required">*</span></label>
<div class="col-md-9 col-sm-9 col-xs-9">
<textarea class="form-control col-md-7 col-xs-12" id="keywords" name="keywords" required="required"></textarea>
</div>
</div>
</div>
</div>
</div>
<div id="step-4">
<div class="ln_solid"></div>
<div class="custom-panel">
<div class="custom-container" style="text-align: center">
点击【发布文章】按钮
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<!--上传图片弹框-->
<div class="modal fade" id="chooseImg" tabindex="-1" role="dialog" aria-labelledby="addroleLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="addroleLabel">选择图片</h4>
</div>
<div class="modal-body">
<div class="" role="tabpanel" data-example-id="togglable-tabs">
<ul id="myTab" class="nav nav-tabs bar_tabs" role="tablist">
<li role="presentation" class="active">
<a href="#tab_content1" id="home-tab" role="tab" data-toggle="tab" aria-expanded="true">素材库</a>
</li>
<li role="presentation" class="">
<a href="#tab_content2" role="tab" id="profile-tab" data-toggle="tab" aria-expanded="false">本地图片</a>
</li>
</ul>
<div id="myTabContent" class="tab-content">
<div role="tabpanel" class="tab-pane fade active in material-box" id="tab_content1" aria-labelledby="home-tab">
<ul class="list-unstyled list-material">
</ul>
</div>
<div role="tabpanel" class="tab-pane fade" id="tab_content2" aria-labelledby="profile-tab">
<button type="button" class="btn btn-success" id="file-btn">选择图片</button>
<div class="preview" class="fa-2x">
<img class="coverImage" src="" alt="">
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal"><i class="fa fa-close"> 关闭</i></button>
<button type="button" class="btn btn-success" data-dismiss="modal"><i class="fa fa-hand-o-up"> 确定</i></button>
</div>
</div>
</div>
</div>
<!--上传图片弹框-->
<@footer>
<script type="text/javascript" src="https://cdn.bootcss.com/highlight.js/9.12.0/highlight.min.js"></script>
<script type="text/javascript" src="https://cdn.bootcss.com/simplemde/1.11.2/simplemde.min.js"></script>
<script type="text/javascript" src="/assets/js/inline-attachment.js"></script>
<script type="text/javascript" src="/assets/js/codemirror.inline-attachment.js"></script>
<script src="/assets/js/jquery.smartWizard.js"></script>
<script>
var op = {
id: "contentMd",
uniqueId: "mdEditor_1",
uploadUrl: "/api/upload2QiniuForMd"
};
zhyd.initMdEditor(op);
articleId = '${id}';
</script>
<script src="/assets/js/zhyd.publish-article.js"></script>
<script>
console.log('init_SmartWizard');
$('#wizard').smartWizard({
labelNext:'<i class="fa fa-angle-right"></i> 下一步',
labelPrevious:'<i class="fa fa-angle-left"></i> 上一步',
labelFinish:'<i class="fa fa-pencil"></i> 发布文章',
// Events
onLeaveStep: leaveAStepCallback,
onFinish: onFinishCallback,
buttonOrder: ['next', 'prev', 'finish']
});
$('.buttonNext').addClass('btn btn-info');
$('.buttonPrevious').addClass('btn btn-primary');
$('.buttonFinish').addClass('btn btn-success');
function leaveAStepCallback(obj, context){
var from = context.fromStep;
var to = context.toStep;
if(from < to){
return validateSteps(from); // return false to stay on step and true to continue navigation
}
return true;
}
function onFinishCallback(objs, context){
if(validateAllSteps(context.fromStep)){
$("#publishForm").ajaxSubmit({
type: "post",
url: "/article/save",
success: function (json) {
$.tool.delCache("smde_" + op.uniqueId);
$.alert.ajaxSuccessConfirm(json, function () {
window.location.href = '/articles';
}, function () {
window.location.href = '/articles';
});
},
error: $.alert.ajaxError
});
}
}
function validateSteps(stepNum){
if(stepNum == 1){
$("#contentMd").val(simplemde.value());
$("#content").val(simplemde.markdown(simplemde.value()));
}
var $step = $("#step-" + stepNum);
return validator.checkAll($step);
}
function validateAllSteps(stepnumber){
for(var i = 1; i <= stepnumber; i ++){
if(!validateSteps(i)){
$.alert.error("第" + i + "部分未完成校验!");
return false;
}
}
return true;
}
</script>
</@footer>
\ No newline at end of file
<#include "/layout/header.ftl"/>
<#include "/include/macros.ftl">
<@header>
</@header>
<div class="">
<div class="clearfix"></div>
<div class="row">
<div class="col-md-12 col-sm-12 col-xs-12">
<ol class="breadcrumb">
<li><a href="/">首页</a></li>
<li class="active">发布文章</li>
<li><a href="/articles">文章列表</a></li>
<li class="active">发布文章-HTML编辑器</li>
</ol>
<div class="x_panel">
<div class="x_title">
<h2>发布文章 <small></small></h2>
<h2>发布文章 <small>HTML编辑器,使用 <a href="/article/publishMd">Markdown编辑器</a></small></h2>
<div class="clearfix"></div>
</div>
<div class="x_content">
<form id="publishForm" class="form-horizontal form-label-left" novalidate>
<input type="hidden" name="isMarkdown" value="0">
<input type="hidden" name="id">
<div class="item form-group">
<label class="control-label col-md-1 col-sm-1 col-xs-12" for="title">标题 <span class="required">*</span></label>
......@@ -60,7 +65,7 @@
<div class="item form-group">
<label class="control-label col-md-1 col-sm-1 col-xs-12" for="mobile"><i class="fa fa-refresh fa-fw fa-1x pointer" id="refressTag"></i>标签 <span class="required">*</span></label>
<div class="col-md-11 col-sm-11 col-xs-12">
<ul class="list-unstyled list-inline" id="tag-list">
<ul class="list-unstyled list-inline" id="tag-list" style="line-height: 30px;">
</ul>
</div>
</div>
......@@ -68,23 +73,17 @@
<label class="control-label col-md-1 col-sm-1 col-xs-12" for="mobile">状态 <span class="required">*</span></label>
<div class="col-md-11 col-sm-11 col-xs-12">
<ul class="list-unstyled list-inline">
<li>
<div class="radio">
<label>
<input type="radio" class="square" checked name="status" value="1"> 发布
</label>
</div>
</li>
<li>
<div class="radio">
<label>
<input type="radio" class="square" name="status" value="0"> 草稿
</label>
</div>
</li>
<li><input type="radio" class="square" checked name="status" value="1"> 发布</li>
<li><input type="radio" class="square" name="status" value="0"> 草稿</li>
</ul>
</div>
</div>
<div class="item form-group">
<label class="control-label col-md-1 col-sm-1 col-xs-12" for="comment">开启评论 <span class="required">*</span></label>
<div class="col-md-11 col-sm-11 col-xs-12">
<input type="checkbox" class="square" name="comment" id="comment">
</div>
</div>
<div class="custom-panel">
<h2 class="x_title custom-dropdown">其他录入项 <small> - 方便SEO收录</small> <i class="pull-right fa fa-angle-double-up"></i></h2>
<div class="custom-container">
......@@ -105,8 +104,8 @@
<div class="ln_solid"></div>
<div class="form-group">
<div class="col-md-10 col-sm-10 col-xs-12">
<button type="button" class="btn btn-success publishBtn">保存</button>
<button type="reset" class="btn btn-primary">重置</button>
<button type="button" class="btn btn-success publishBtn"><i class="fa fa-pencil"> 发布文章</i></button>
<button type="reset" class="btn btn-primary"><i class="fa fa-undo"> 重置</i></button>
</div>
</div>
</form>
......@@ -115,43 +114,6 @@
</div>
</div>
</div>
<#--<style type="text/css">
.img-tool{
width: 200px;
}
.img-tool tools{
opacity: 1;
transform: translateY(0);
transition: all .2s ease-in-out;
color: #fff;
text-align: center;
font-size: 17px;
padding: 3px;
background: rgba(0, 0, 0, .35);
/* margin: 72px 0 0; */
position: relative;
}
.img-tool tools a{
display: inline-block;
color: #FFF;
font-size: 18px;
font-weight: 400;
padding: 0 4px;
}
</style>-->
<#--<div>
<img src="http://ofndwaoqp.bkt.clouddn.com/flyat%2Farticle%2F1516606560576.jpg-pw" alt="">
<div class="img-tool">
<div class="mask">
<div class="tools tools-bottom">
<a href="javascript:void(0)"><i class="fa fa-link"></i></a>
<a href="javascript:void(0)"<i class="fa fa-pencil"></i></a>
<a href="javascript:void(0)"><i class="fa fa-times"></i></a>
</div>
</div>
</div>
</div>-->
<!--上传图片弹框-->
<div class="modal fade" id="chooseImg" tabindex="-1" role="dialog" aria-labelledby="addroleLabel">
<div class="modal-dialog" role="document">
......@@ -185,256 +147,31 @@
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
<button type="button" class="btn btn-success" data-dismiss="modal">确定</button>
<button type="button" class="btn btn-default" data-dismiss="modal"><i class="fa fa-close"> 关闭</i></button>
<button type="button" class="btn btn-success" data-dismiss="modal"><i class="fa fa-hand-o-up"> 确定</i></button>
</div>
</div>
</div>
</div>
<!--上传图片弹框-->
<#include "/layout/footer.ftl"/>
<script>
$(function () {
setTimeout(function () {
$('.network-img-checkbox').on('ifChanged', function(event){
$(".choose-network-img").toggleClass("hide");
$(".choose-local-img").toggleClass("hide");
});
},1000);
var E = window.wangEditor;
var editor = new E('#editor');
// debug模式下,有 JS 错误会以throw Error方式提示出来
editor.customConfig.debug = true;
// 关闭粘贴样式的过滤
editor.customConfig.pasteFilterStyle = false;
// 插入网络图片的回调
editor.customConfig.linkImgCallback = function(url) {
console.log(url) // url 即插入图片的地址
};
editor.customConfig.zIndex = 100;
var $content = $('#content');
editor.customConfig.onchange = function (html) {
// 监控变化,同步更新到 textarea
$content.val(filterXSS(html))
console.log(html);
};
// 下面两个配置,使用其中一个即可显示“上传图片”的tab。但是两者不要同时使用!!!
// editor.customConfig.uploadImgShowBase64 = true // 使用 base64 保存图片
// 上传图片到服务器
editor.customConfig.uploadImgServer = '/api/upload2Qiniu';
editor.customConfig.uploadFileName = 'file';
// 将图片大小限制为 5M
editor.customConfig.uploadImgMaxSize = 5 * 1024 * 1024;
editor.customConfig.customAlert = function (info) {
// info 是需要提示的内容
$.alert.error(info);
}
editor.customConfig.uploadImgHooks = {
error: function (xhr, editor) {
$.alert.error("图片上传出错");
},
timeout: function (xhr, editor) {
$.alert.error("请求超时");
},
customInsert: function (insertImg, result, editor) {
// 图片上传并返回结果,自定义插入图片的事件(而不是编辑器自动插入图片!!!)
// insertImg 是插入图片的函数,editor 是编辑器对象,result 是服务器端返回的结果
console.log('customInsert:' + insertImg, result, editor);
if(result.status == 200){
console.log(result.data);
var imgFullPath = appConfig.qiniuPath + result.data + appConfig.qiniuImgStyle;
// editor.txt.append(' <a href="' + imgFullPath + '" class="showImage" title="" rel="external nofollow"><img src="' + imgFullPath + '" class="img-responsive img-rounded" alt="" style="width: 95%;"/></a>');
editor.txt.append('<img src="' + imgFullPath + '" alt="" style="width: 95%;max-width: 100%;height: auto;border-radius: 6px;"/>');
} else {
$.alert.error(result.message);
}
}
};
editor.create();
E.fullscreen.init('#editor');
<@footer>
<script>
articleId = '${id}';
$(function () {
if(!articleId) {
$.alert.confirm("当前已支持markdown编辑器,去试试?", function () {
window.location.href="/article/publishMd";
}, function () {
// 加载所有分类
function loadType(){
$.ajax({
type: "post",
url: "/type/listAll",
success: function (json) {
$.alert.ajaxSuccess(json);
var data = '';
if(data = json.data){
var typeOptions = '';
for(var i = 0, len = data.length; i < len; i ++){
var type = data[i];
typeOptions += '<option value="' + type.id + '">' + type.name + '</option>';
}
$("select#typeId").html(typeOptions);
}
$("#refressType").removeClass("fa-spin");
$.alert.showSuccessMessage("分类加载完成!");
},
error: $.alert.ajaxError
});
}
// 加载所有标签
function loadTag() {
$.ajax({
type: "post",
url: "/tag/listAll",
success: function (json) {
$.alert.ajaxSuccess(json);
var data = '';
if (data = json.data) {
var tagHtml = '';
for (var i = 0, len = data.length; i < len; i++) {
var tag = data[i];
tagHtml += '<li>'
+ ' <div class="checkbox">'
+ '<label>'
+ '<input type="checkbox" class="square" name="tags" value="' + tag.id + '"> ' + tag.name
+ ' </label>'
+ ' </div>'
+ '</li>';
}
$("#tag-list").html(tagHtml);
$("input[type=checkbox], input[type=radio]").iCheck({
checkboxClass: 'icheckbox_square-green',
radioClass: 'iradio_square-green',
increaseArea: '20%' // optional
});
}
$("#refressTag").removeClass("fa-spin");
$.alert.showSuccessMessage("标签加载完成!");
},
error: $.alert.ajaxError
});
}
$("#refressType").click(function () {
$(this).addClass("fa-spin");
loadType();
});
$("#refressTag").click(function () {
$(this).addClass("fa-spin");
loadTag();
});
loadTag();
loadType();
var articleId = '${id}';
if(articleId){
setTimeout(function () {
$.ajax({
type: "post",
url: "/article/get/" + articleId,
success: function (json) {
$.alert.ajaxSuccess(json);
var info = json.data;
// 标签
var tags = info.tags;
for(var i = 0, len = tags.length; i < len ; i ++){
var tag = tags[i];
$("input[name=tags][value=" + tag.id + "]").iCheck('check');
}
$('input[name=original]').iCheck(info.original ? 'check' : 'uncheck');
$("#publishForm input[type!=checkbox], #publishForm select, #publishForm textarea").each(function () {
var $this = $(this);
var $div = $this.parents(".item");
if ($div.hasClass("bad")) {
$div.toggleClass("bad");
$div.find("div.alert").remove();
}
if(info['coverImage']){
$(".coverImage").attr('src', appConfig.qiniuPath + info['coverImage']);
}
var type = this.type;
var thisName = $this.attr("name");
var thisValue = info[thisName];
if(thisName == 'content'){
$this.val(thisValue);
editor.txt.html(thisValue);
return;
}
if (type == 'radio') {
$this.iCheck(((thisValue && 1 == $this.val()) || (!thisValue && 0 == $this.val())) ? 'check' : 'uncheck')
} else {
if (thisValue && thisName != 'password') {
$this.val(thisValue);
}
}
});
},
error: $.alert.ajaxError
});
}, 100);
}
// 点击保存
$(".publishBtn").click(function () {
if(validator.checkAll($("#publishForm"))) {
$("#publishForm").ajaxSubmit({
type: "post",
url: "/article/save",
success: function (json) {
$.alert.ajaxSuccessConfirm(json, function () {
window.location.href = '/articles';
});
},
error: $.alert.ajaxError
});
}
zhyd.initWangEditor({
id: "editor",
contentId: "content",
uploadUrl: "/api/upload2Qiniu",
uploadFileName: "file"
});
});
var loadImg = false;
// 选择图片
$("#file-upload-btn").click(function () {
$("#chooseImg").modal('show');
if(!loadImg){
// 加载素材库
$.ajax({
type: "post",
url: "/api/material",
success: function (json) {
$.alert.ajaxSuccess(json);
loadImg = true;
json.qiniuPath = appConfig.qiniuPath;
var $box = $(".list-material");
var tpl = '{{#data}}<li data-imgUrl="{{.}}"><div class="col-md-55"><img class="lazy-img" data-original="{{qiniuPath}}{{.}}" alt="image"></div></li>{{/data}}{{^data}}<li>素材库为空</li>{{/data}}';
var html = Mustache.render(tpl, json);
$box.html(html);
$box.find("li").click(function () {
$box.find("li").each(function () {
$(this).removeClass("active");
});
var $this = $(this);
$this.toggleClass("active");
if($this.hasClass("active")){
var imgUrl = $this.attr("data-imgUrl");
console.log(imgUrl);
$("#cover-img-input").val(imgUrl);
$(".preview img.coverImage").attr("src", appConfig.qiniuPath + imgUrl);
}
});
$("img.lazy-img").lazyload({
placeholder : appConfig.staticPath + "/img/loading.gif",
effect: "fadeIn",
threshold: 100
});
$("img.lazy-img").trigger("sporty");
},
error: $.alert.ajaxError
});
}
});
// 选择图片
$("#file-btn").click(function () {
var $this = $(this);
$("#cover-img-file").click();
});
$("input[name=file]").uploadPreview({ imgContainer: ".preview", width: 190, height: 200 });
});
</script>
\ No newline at end of file
</script>
<script src="/assets/js/zhyd.publish-article.js"></script>
</@footer>
<#include "/layout/header.ftl"/>
<#include "/include/macros.ftl">
<@header></@header>
<div class="clearfix"></div>
<div class="row">
<div class="col-md-12 col-sm-12 col-xs-12">
......@@ -32,7 +33,7 @@
</div>
</div>
</div>
<#include "/layout/footer.ftl"/>
<@footer></@footer>
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册