first commit

This commit is contained in:
ronger 2022-09-24 23:20:40 +08:00
commit 6e6286dd80
346 changed files with 313270 additions and 0 deletions

36
.gitignore vendored Normal file
View File

@ -0,0 +1,36 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**
!**/src/test/**
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
/.mvn/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
### VS Code ###
.vscode/
### lucene ###
index
userDic

0
Dockerfile Normal file
View File

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 RYMCU
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.

105
README.md Normal file
View File

@ -0,0 +1,105 @@
# forest
![forest](src/main/resources/static/logo_size.jpg)
下一代的知识社区系统,为未来而建
## 💡 简介
forest[ˈfôrəst]n.森林)是一款现代化的知识社区项目,使用 SpringBoot + Shiro + MyBatis + JWT + Redis 实现。
与 [nubula](https://github.com/rymcu/nubula) [ˈnebyələ]n.星云)一起食用,让我们一起探索知识社区的未来。
[forest 食用手册](UserManual.md)
## ⚡ 动机
在 2019 年的某一天,受到 [Hugh](https://rymcu.com/user/RYMCU-J) 的邀请, 构建一个开源嵌入式知识学习交流平台。因此就有了 forest 这个项目。 forest
在很多方面受到了 [Symphony](https://github.com/88250/symphony) 的启发,并尝试着在 [Symphony](https://github.com/88250/symphony)
和 [B3log 思想](https://ld246.com/article/1546941897596) 的基础上进一步探索。
## ✨ 特性
- 内容编辑器
- MarkdownGFM
- emoji
- 上传文件
- 图片
- 文件
- 单独渲染 MP3 文件
- 单独渲染视频文件
- 剪切板处理
- 粘贴内容处理为 Markdown
- 粘贴图片自动重新上传
- 数学公式LaTeX、流程图支持
- 工具栏
- 表情
- 粗体
- 斜体
- 引用
- 无序列表
- 有序列表
- 链接
- 上传
- 预览
- 全屏
- 编辑模式
- 传统的 Markdown 分屏编辑预览
- 保留 Markdown 标记符的即时渲染
- 类富文本编辑器的所见即所得
- 注册
- 用户名
- 邮箱
- 验证码
- 登录
- 账户(用户名/邮箱)
- 密码
- 忘记密码
- 邮箱
- 邮箱验证
- 发帖
- 帖子类型
- 普通帖子
- 标题
- 正文
- 内容编辑器
- 标签
- 使用已有(选择、自动完成)或创建
- 默认“待分类”
- 发布后
- 可更新
- 可删除
- 回帖
- 内容编辑器
- 回复(回复针对回帖)
- 货币
- 货币规则
- 浏览贴子
- 编辑自己的帖子
- 发布时间/浏览数/标签
- 分享
- 微信
- 分享链接(带用户标识)
## 报告缺陷
> 社区系统可能存在一些潜在的缺陷,大家如果有空的话可以帮助我们一起解决。
如果你在使用社区时发现了如下类型的问题,请回帖进行反馈,并附上 bug 截图以及操作步骤:
* **功能性缺陷**:例如发布文章失败、创建作品集失败等
* **安全性漏洞**:例如 XSS/CSRF、盗用用户信息等
## 功能建议
欢迎对社区提出功能特性方面的建议,我们一起讨论,如果有可能我们会尽快实现。
在提功能建议前可以先看一下 [计划表](https://rymcu.com/article/29) ,避免重复提议
## 鸣谢
- 感谢 `JetBrains` 对本项目的帮助,为作者提供了开源许可版 `JetBrains` 全家桶
![JetBrains](src/main/resources/static/jb_beam.svg)
## ⭐ Star 历史
[![Stargazers over time](https://starchart.cc/rymcu/forest.svg)](https://starchart.cc/rymcu/forest)

85
UserManual.md Normal file
View File

@ -0,0 +1,85 @@
# forest 食用手册
感谢使用 forest以下是本项目的开发手册
## 开发环境搭建
### ide
本项目使用 `JetBrains IntelliJ IDEA UItimate` 作为编辑器进行开发
### java 环境
本项目在 `java se 8` 环境下进行开发
### 数据库
- `redis`
- `mysql`
## 其他
- 本项目使用了 `Lombok`,所以你还需在你的编辑器上安装 `Lombok` 插件
- 本项目使用 `maven` 作为依赖管理工具
## 初始化数据库
![forest-sql](https://static.rymcu.com/article/1650261394563.png)
执行 `resources/static` 目录下的 `forest.sql` 文件进行数据库初始化操作
## 配置文件说明
### 数据库配置
```yaml
datasource:
url: jdbc:mysql://localhost:3306/forest?characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true
username: root
password: # 数据库密码
driver-class-name: com.mysql.cj.jdbc.Driver
```
- `characterEncoding` 指定处理字符的解码和编码的格式
- `serverTimezone` 指定时区
- `allowMultiQueries` 允许多行 `sql` 一起执行
### redis 配置
```yaml
redis:
host: 127.0.0.1
port: 6379
password: # redis 密码
database: 1
timeout: 3000
jedis:
pool:
max-active: 8
max-wait: 1
max-idle: 500
min-idle: 0
```
### 邮箱服务配置
```yaml
mail:
host: smtp.163.com # 网站发送邮件邮箱服务 host
port: 465
username: # 邮箱
password: # 密码
```
用户注册及找回密码时使用,本项目使用的是网易邮箱,其他邮箱可根据官方教程配置
### 系统资源路径配置
```yaml
resource:
domain: http://yourdomain.com # 网站域名,本地测试时填写前端项目访问地址即可
file-path: http://yourdomain.com # 上传文件前缀域名,本地测试时填写前端项目访问地址即可
pic-path: /yoursrc/xx/nebula/static # 上传文件存储地址,本地测试时填写前端项目路径下的 static 目录即可
```
### 百度相关配置
```yaml
baidu:
data:
site: https://yourdomain.com # 百度搜索(SEO)绑定网站域名
token: xxxx
ai:
appId: xxx # 百度AI-文字识别 应用 appId
appKey: xxxx # 百度AI-文字识别 应用 appKey
secretKey: xxxx # 百度AI-文字识别 应用 secretKey
```
## 常见问题
**Q: 找不到数据库配置Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.**
A: 检查是否配置了资源文件目录
![1636569760e18471dd2c74bedac5756e5fff537df.png](https://static.rymcu.com/article/1650261657433.png)

179
forest.iml Normal file
View File

@ -0,0 +1,179 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="Spring" name="Spring">
<configuration />
</facet>
<facet type="web" name="Web">
<configuration>
<webroots>
<root url="file://$MODULE_DIR$/src/main/webapp" relative="/" />
</webroots>
<sourceRoots>
<root url="file://$MODULE_DIR$/src/main/java" />
<root url="file://$MODULE_DIR$/src/main/resources" />
</sourceRoots>
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-data-redis:2.7.2" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter:2.7.2" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot:2.7.2" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-logging:2.7.2" level="project" />
<orderEntry type="library" name="Maven: ch.qos.logback:logback-classic:1.2.11" level="project" />
<orderEntry type="library" name="Maven: ch.qos.logback:logback-core:1.2.11" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:jul-to-slf4j:1.7.36" level="project" />
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-redis:2.7.2" level="project" />
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-keyvalue:2.7.2" level="project" />
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-commons:2.7.2" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-tx:5.3.22" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-oxm:5.3.22" level="project" />
<orderEntry type="library" name="Maven: io.lettuce:lettuce-core:6.1.9.RELEASE" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-common:4.1.79.Final" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-handler:4.1.79.Final" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-resolver:4.1.79.Final" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-buffer:4.1.79.Final" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-transport-native-unix-common:4.1.79.Final" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-codec:4.1.79.Final" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty-transport:4.1.79.Final" level="project" />
<orderEntry type="library" name="Maven: io.projectreactor:reactor-core:3.4.21" level="project" />
<orderEntry type="library" name="Maven: org.reactivestreams:reactive-streams:1.0.4" level="project" />
<orderEntry type="library" name="Maven: org.yaml:snakeyaml:1.32" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-mail:2.7.2" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-context-support:5.3.22" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-beans:5.3.22" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-context:5.3.22" level="project" />
<orderEntry type="library" name="Maven: com.sun.mail:jakarta.mail:1.6.7" level="project" />
<orderEntry type="library" name="Maven: com.sun.activation:jakarta.activation:1.2.2" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-web:2.7.2" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-json:2.7.2" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.3" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.3" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.module:jackson-module-parameter-names:2.13.3" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-web:5.3.22" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-webmvc:5.3.22" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-expression:5.3.22" level="project" />
<orderEntry type="library" name="Maven: org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-jdbc:2.7.2" level="project" />
<orderEntry type="library" name="Maven: com.zaxxer:HikariCP:4.0.3" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-jdbc:5.3.22" level="project" />
<orderEntry type="library" name="Maven: org.mybatis.spring.boot:mybatis-spring-boot-autoconfigure:2.2.2" level="project" />
<orderEntry type="library" name="Maven: org.mybatis:mybatis:3.5.9" level="project" />
<orderEntry type="library" name="Maven: org.mybatis:mybatis-spring:2.0.7" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: mysql:mysql-connector-java:8.0.30" level="project" />
<orderEntry type="library" name="Maven: org.projectlombok:lombok:1.18.24" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.springframework.boot:spring-boot-starter-tomcat:2.7.2" level="project" />
<orderEntry type="library" name="Maven: jakarta.annotation:jakarta.annotation-api:1.3.5" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.tomcat.embed:tomcat-embed-core:9.0.65" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.tomcat.embed:tomcat-embed-el:9.0.65" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.apache.tomcat.embed:tomcat-embed-websocket:9.0.65" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-starter-test:2.7.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-test:2.7.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-test-autoconfigure:2.7.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: com.jayway.jsonpath:json-path:2.7.0" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: jakarta.xml.bind:jakarta.xml.bind-api:2.3.3" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: jakarta.activation:jakarta.activation-api:1.2.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.assertj:assertj-core:3.22.0" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest:2.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter:5.8.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter-api:5.8.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.opentest4j:opentest4j:1.2.0" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.platform:junit-platform-commons:1.8.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.apiguardian:apiguardian-api:1.1.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter-params:5.8.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter-engine:5.8.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.platform:junit-platform-engine:1.8.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.mockito:mockito-core:4.5.1" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: net.bytebuddy:byte-buddy:1.12.12" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: net.bytebuddy:byte-buddy-agent:1.12.12" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.objenesis:objenesis:3.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.mockito:mockito-junit-jupiter:4.5.1" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.skyscreamer:jsonassert:1.5.1" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-core:5.3.22" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-jcl:5.3.22" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.springframework:spring-test:5.3.22" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.xmlunit:xmlunit-core:2.9.0" level="project" />
<orderEntry type="library" name="Maven: net.minidev:json-smart:2.4.8" level="project" />
<orderEntry type="library" name="Maven: net.minidev:accessors-smart:2.4.8" level="project" />
<orderEntry type="library" name="Maven: org.ow2.asm:asm:9.1" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-aop:2.7.2" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-aop:5.3.22" level="project" />
<orderEntry type="library" name="Maven: org.aspectj:aspectjweaver:1.9.7" level="project" />
<orderEntry type="library" name="Maven: tk.mybatis:mapper:4.2.1" level="project" />
<orderEntry type="library" name="Maven: javax.persistence:persistence-api:1.0" level="project" />
<orderEntry type="library" name="Maven: com.github.pagehelper:pagehelper:5.3.2" level="project" />
<orderEntry type="library" name="Maven: com.github.jsqlparser:jsqlparser:4.5" level="project" />
<orderEntry type="library" name="Maven: com.alibaba:fastjson:2.0.14" level="project" />
<orderEntry type="library" name="Maven: com.alibaba.fastjson2:fastjson2-extension:2.0.14" level="project" />
<orderEntry type="library" name="Maven: com.alibaba.fastjson2:fastjson2:2.0.14" level="project" />
<orderEntry type="library" name="Maven: org.apache.shiro:shiro-spring:1.9.1" level="project" />
<orderEntry type="library" name="Maven: org.apache.shiro:shiro-core:1.9.1" level="project" />
<orderEntry type="library" name="Maven: org.apache.shiro:shiro-lang:1.9.1" level="project" />
<orderEntry type="library" name="Maven: org.apache.shiro:shiro-cache:1.9.1" level="project" />
<orderEntry type="library" name="Maven: org.apache.shiro:shiro-crypto-hash:1.9.1" level="project" />
<orderEntry type="library" name="Maven: org.apache.shiro:shiro-crypto-core:1.9.1" level="project" />
<orderEntry type="library" name="Maven: org.apache.shiro:shiro-crypto-cipher:1.9.1" level="project" />
<orderEntry type="library" name="Maven: org.apache.shiro:shiro-config-core:1.9.1" level="project" />
<orderEntry type="library" name="Maven: org.apache.shiro:shiro-config-ogdl:1.9.1" level="project" />
<orderEntry type="library" name="Maven: commons-beanutils:commons-beanutils:1.9.4" level="project" />
<orderEntry type="library" name="Maven: org.apache.shiro:shiro-event:1.9.1" level="project" />
<orderEntry type="library" name="Maven: org.apache.shiro:shiro-web:1.9.1" level="project" />
<orderEntry type="library" name="Maven: org.owasp.encoder:encoder:1.2.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-collections4:4.4" level="project" />
<orderEntry type="library" name="Maven: commons-lang:commons-lang:2.6" level="project" />
<orderEntry type="library" name="Maven: commons-io:commons-io:2.11.0" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-text:1.9" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.12.0" level="project" />
<orderEntry type="library" name="Maven: commons-codec:commons-codec:1.15" level="project" />
<orderEntry type="library" name="Maven: io.jsonwebtoken:jjwt:0.9.1" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.13.3" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.13.3" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.13.3" level="project" />
<orderEntry type="library" name="Maven: com.alibaba:druid-spring-boot-starter:1.2.13-SNSAPSHOT" level="project" />
<orderEntry type="library" name="Maven: com.alibaba:druid:1.2.13-SNSAPSHOT" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.36" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-autoconfigure:2.7.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-to-slf4j:2.19.0" level="project" />
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-api:2.19.0" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-configuration-processor:2.7.2" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-thymeleaf:2.7.2" level="project" />
<orderEntry type="library" name="Maven: org.thymeleaf:thymeleaf-spring5:3.0.15.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.thymeleaf:thymeleaf:3.0.15.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.attoparser:attoparser:2.0.5.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.unbescape:unbescape:1.1.6.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.thymeleaf.extras:thymeleaf-extras-java8time:3.0.4.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-websocket:2.7.2" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-messaging:5.3.22" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-websocket:5.3.22" level="project" />
<orderEntry type="library" name="Maven: org.jodd:jodd-http:6.3.0" level="project" />
<orderEntry type="library" name="Maven: org.jodd:jodd-util:6.1.0" level="project" />
<orderEntry type="library" name="Maven: com.github.jedis-lock:jedis-lock:1.0.0" level="project" />
<orderEntry type="library" name="Maven: redis.clients:jedis:3.8.0" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-pool2:2.11.1" level="project" />
<orderEntry type="library" name="Maven: org.apache.lucene:lucene-queryparser:8.11.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.lucene:lucene-core:8.11.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.lucene:lucene-queries:8.11.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.lucene:lucene-sandbox:8.11.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.lucene:lucene-analyzers-common:8.11.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.lucene:lucene-highlighter:8.11.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.lucene:lucene-memory:8.11.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.lucene:lucene-suggest:8.11.2" level="project" />
<orderEntry type="library" name="Maven: cn.hutool:hutool-core:5.8.7" level="project" />
<orderEntry type="library" name="Maven: cn.hutool:hutool-http:5.8.7" level="project" />
<orderEntry type="library" name="Maven: com.warrenstrange:googleauth:1.5.0" level="project" />
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpclient:4.5.13" level="project" />
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpcore:4.4.15" level="project" />
</component>
</module>

322
mvnw vendored Normal file
View File

@ -0,0 +1,322 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven2 Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ]; then
if [ -f /etc/mavenrc ]; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ]; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false
darwin=false
mingw=false
case "$(uname)" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true ;;
Darwin*)
darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="$(/usr/libexec/java_home)"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ]; then
if [ -r /etc/gentoo-release ]; then
JAVA_HOME=$(java-config --jre-home)
fi
fi
if [ -z "$M2_HOME" ]; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ]; do
ls=$(ls -ld "$PRG")
link=$(expr "$ls" : '.*-> \(.*\)$')
if expr "$link" : '/.*' >/dev/null; then
PRG="$link"
else
PRG="$(dirname "$PRG")/$link"
fi
done
saveddir=$(pwd)
M2_HOME=$(dirname "$PRG")/..
# make it fully qualified
M2_HOME=$(cd "$M2_HOME" && pwd)
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=$(cygpath --unix "$M2_HOME")
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
[ -n "$CLASSPATH" ] &&
CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw; then
[ -n "$M2_HOME" ] &&
M2_HOME="$( (
cd "$M2_HOME"
pwd
))"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="$( (
cd "$JAVA_HOME"
pwd
))"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="$(which javac)"
if [ -n "$javaExecutable" ] && ! [ "$(expr \"$javaExecutable\" : '\([^ ]*\)')" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=$(which readlink)
if [ ! $(expr "$readLink" : '\([^ ]*\)') = "no" ]; then
if $darwin; then
javaHome="$(dirname \"$javaExecutable\")"
javaExecutable="$(cd \"$javaHome\" && pwd -P)/javac"
else
javaExecutable="$(readlink -f \"$javaExecutable\")"
fi
javaHome="$(dirname \"$javaExecutable\")"
javaHome=$(expr "$javaHome" : '\(.*\)/bin')
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ]; then
if [ -n "$JAVA_HOME" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="$(which java)"
fi
fi
if [ ! -x "$JAVACMD" ]; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ]; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]; then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ]; do
if [ -d "$wdir"/.mvn ]; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=$(
cd "$wdir/.."
pwd
)
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' <"$1")"
fi
}
BASE_DIR=$(find_maven_basedir "$(pwd)")
if [ -z "$BASE_DIR" ]; then
exit 1
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar"
fi
while IFS="=" read key value; do
case "$key" in wrapperUrl)
jarUrl="$value"
break
;;
esac
done <"$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
fi
if command -v wget >/dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
elif command -v curl >/dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=$(cygpath --path --windows "$javaClass")
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=$(cygpath --path --windows "$M2_HOME")
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
[ -n "$CLASSPATH" ] &&
CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

182
mvnw.cmd vendored Normal file
View File

@ -0,0 +1,182 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven2 Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%

286
pom.xml Normal file
View File

@ -0,0 +1,286 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.rymcu</groupId>
<artifactId>forest</artifactId>
<version>0.0.1</version>
<packaging>war</packaging>
<name>forest</name>
<description>forest(森林) —— 一款现代化的知识社区后台项目,使用 SpringBoot + Shrio + MyBatis + JWT + Redis 实现。</description>
<properties>
<java.version>1.8</java.version>
<lucene.version>8.11.2</lucene.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.32</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
<exclusion>
<groupId>com.vaadin.external.google</groupId>
<artifactId>android-json</artifactId>
</exclusion>
<exclusion>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- https://mvnrepository.com/artifact/net.minidev/json-smart -->
<dependency>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
<version>2.4.8</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 通用Mapper插件 -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>4.2.1</version>
</dependency>
<!-- pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.2</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.14</version>
</dependency>
<!-- shiro权限控制框架 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.9.1</version>
<exclusions>
<exclusion>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
<!--apache相关依赖-->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.9</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- 阿里巴巴连接池Druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.13-SNSAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>2.19.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.jodd</groupId>
<artifactId>jodd-http</artifactId>
<version>6.3.0</version>
</dependency>
<dependency>
<groupId>com.github.jedis-lock</groupId>
<artifactId>jedis-lock</artifactId>
<version>1.0.0</version>
</dependency>
<!-- lucene -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>${lucene.version}</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>${lucene.version}</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-highlighter</artifactId>
<version>${lucene.version}</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-suggest</artifactId>
<version>${lucene.version}</version>
</dependency>
<!-- hutool 核心工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.8.7</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
<version>5.8.7</version>
</dependency>
<dependency>
<groupId>com.warrenstrange</groupId>
<artifactId>googleauth</artifactId>
<version>1.5.0</version>
<exclusions>
<exclusion>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<!-- 程序编译时默认不会把 src/main/java 目录下文件添加到 jar 包中,需手动添加 -->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
<include>**/**/*.xml</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.5.RELEASE</version>
</plugin>
</plugins>
<finalName>forest</finalName>
</build>
</project>

View File

@ -0,0 +1,18 @@
package com.rymcu.forest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
/**
* @author ronger
*/
@EnableAsync
@SpringBootApplication
public class ForestApplication {
public static void main(String[] args) {
SpringApplication.run(ForestApplication.class, args);
}
}

View File

@ -0,0 +1,13 @@
package com.rymcu.forest;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(ForestApplication.class);
}
}

View File

@ -0,0 +1,50 @@
package com.rymcu.forest.answer;
import com.alibaba.fastjson.JSONObject;
import com.rymcu.forest.core.result.GlobalResult;
import com.rymcu.forest.core.service.log.annotation.TransactionLogger;
import com.rymcu.forest.dto.AnswerDTO;
import com.rymcu.forest.entity.User;
import com.rymcu.forest.enumerate.TransactionEnum;
import com.rymcu.forest.util.HttpUtils;
import com.rymcu.forest.util.UserUtils;
import com.rymcu.forest.web.api.exception.BaseApiException;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* @author ronger
*/
@RestController
@RequestMapping("/api/v1/answer")
public class AnswerController {
private final static String ANSWER_API_URL = "http://1.116.175.112:8089/question";
@GetMapping("/today")
public GlobalResult today() throws BaseApiException {
User user = UserUtils.getCurrentUserByToken();
String result = HttpUtils.sendGet(ANSWER_API_URL + "/record/" + user.getIdUser() );
return JSONObject.parseObject(result, GlobalResult.class);
}
@PostMapping("/answer")
@TransactionLogger(transactionType = TransactionEnum.Answer)
public GlobalResult answer(@RequestBody AnswerDTO answerDTO) throws BaseApiException {
User user = UserUtils.getCurrentUserByToken();
Map params = new HashMap<>(3);
params.put("userId", user.getIdUser());
params.put("answer", answerDTO.getAnswer());
params.put("subjectQuestionId", answerDTO.getIdSubjectQuestion());
String result = HttpUtils.sendPost(ANSWER_API_URL + "/answer/everyday", params);
return JSONObject.parseObject(result, GlobalResult.class);
}
@GetMapping("/get-answer")
public GlobalResult getAnswer(Integer idSubjectQuestion) {
String result = HttpUtils.sendGet(ANSWER_API_URL + "/show-answer/" + idSubjectQuestion );
return JSONObject.parseObject(result, GlobalResult.class);
}
}

View File

@ -0,0 +1,153 @@
package com.rymcu.forest.config;
import com.alibaba.fastjson.support.spring.FastJsonJsonView;
import com.rymcu.forest.core.exception.BusinessException;
import com.rymcu.forest.core.exception.ServiceException;
import com.rymcu.forest.core.exception.TransactionException;
import com.rymcu.forest.core.result.GlobalResult;
import com.rymcu.forest.core.result.ResultCode;
import com.rymcu.forest.enumerate.TransactionCode;
import com.rymcu.forest.web.api.exception.BaseApiException;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* 全局异常处理器
*
* @author ronger
*/
@RestControllerAdvice
public class BaseExceptionHandler {
private final Logger logger = LoggerFactory.getLogger(BaseExceptionHandler.class);
@SuppressWarnings("Duplicates")
@ExceptionHandler(Exception.class)
public Object errorHandler(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
if (isAjax(request)) {
GlobalResult result = new GlobalResult();
if (ex instanceof BaseApiException) {
result.setCode(((BaseApiException) ex).getCode());
result.setMessage(((BaseApiException) ex).getExtraMessage());
logger.info(result.getMessage());
} else if (ex instanceof UnauthenticatedException) {
result.setCode(1000001);
result.setMessage("token错误");
logger.info("token错误");
} else if (ex instanceof UnauthorizedException) {
result.setCode(1000002);
result.setMessage("用户无权限");
logger.info("用户无权限");
} else if (ex instanceof ServiceException) {
//业务失败的异常账号或密码错误
result.setCode(((ServiceException) ex).getCode());
result.setMessage(ex.getMessage());
logger.info(ex.getMessage());
} else if (ex instanceof NoHandlerFoundException) {
result.setCode(ResultCode.NOT_FOUND.getCode());
result.setMessage(ResultCode.NOT_FOUND.getMessage());
} else if (ex instanceof ServletException) {
result.setCode(ResultCode.FAIL.getCode());
result.setMessage(ex.getMessage());
} else if (ex instanceof BusinessException) {
result.setCode(ResultCode.FAIL.getCode());
result.setMessage(ex.getMessage());
} else if (ex instanceof TransactionException) {
result.setCode(TransactionCode.InsufficientBalance.getCode());
result.setMessage(ex.getMessage());
} else {
//系统内部异常,不返回给客户端,内部记录错误日志
result.setCode(ResultCode.INTERNAL_SERVER_ERROR.getCode());
String message;
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
message = String.format("接口 [%s] 出现异常,方法:%s.%s异常摘要%s",
request.getRequestURI(),
handlerMethod.getBean().getClass().getName(),
handlerMethod.getMethod().getName(),
ex.getMessage());
} else {
message = ex.getMessage();
}
result.setMessage("操作失败");
logger.error(message, ex);
}
result.setSuccess(false);
return result;
} else {
ModelAndView mv = new ModelAndView();
FastJsonJsonView view = new FastJsonJsonView();
Map<String, Object> attributes = new HashMap(2);
if (ex instanceof BaseApiException) {
attributes.put("code", ((BaseApiException) ex).getCode());
attributes.put("message", ((BaseApiException) ex).getExtraMessage());
} else if (ex instanceof UnauthenticatedException) {
attributes.put("code", "1000001");
attributes.put("message", "token错误");
} else if (ex instanceof UnauthorizedException) {
attributes.put("code", "1000002");
attributes.put("message", "用户无权限");
} else if (ex instanceof ServiceException) {
//业务失败的异常账号或密码错误
attributes.put("code", ((ServiceException) ex).getCode());
attributes.put("message", ex.getMessage());
logger.info(ex.getMessage());
} else if (ex instanceof NoHandlerFoundException) {
attributes.put("code", ResultCode.NOT_FOUND.getCode());
attributes.put("message", ResultCode.NOT_FOUND.getMessage());
} else if (ex instanceof ServletException) {
attributes.put("code", ResultCode.FAIL.getCode());
attributes.put("message", ex.getMessage());
} else if (ex instanceof BusinessException) {
attributes.put("code", ResultCode.FAIL.getCode());
attributes.put("message", ex.getMessage());
} else if (ex instanceof TransactionException) {
attributes.put("code", TransactionCode.InsufficientBalance.getCode());
attributes.put("message", ex.getMessage());
} else {
//系统内部异常,不返回给客户端,内部记录错误日志
attributes.put("code", ResultCode.INTERNAL_SERVER_ERROR.getCode());
String message;
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
message = String.format("接口 [%s] 出现异常,方法:%s.%s异常摘要%s",
request.getRequestURI(),
handlerMethod.getBean().getClass().getName(),
handlerMethod.getMethod().getName(),
ex.getMessage());
} else {
message = ex.getMessage();
}
logger.error(message, ex);
attributes.put("message", "操作失败");
}
attributes.put("success", false);
view.setAttributesMap(attributes);
mv.setView(view);
return mv;
}
}
private boolean isAjax(HttpServletRequest request) {
String requestedWith = request.getHeader("x-requested-with");
if (requestedWith != null && "XMLHttpRequest".equalsIgnoreCase(requestedWith)) {
return true;
}
String contentType = request.getContentType();
return StringUtils.isNotBlank(contentType) && contentType.contains("application/json");
}
}

View File

@ -0,0 +1,39 @@
package com.rymcu.forest.config;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.util.StringUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
/**
* @author ronger
*/
public class BaseSessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "Authorization";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
public BaseSessionManager() {
super();
}
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
// 如果请求头中有 Authorization 则其值为 sessionId
if (!StringUtils.isEmpty(id)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
} else {
// 否则按默认规则从 cookie sessionId
return super.getSessionId(request, response);
}
}
}

View File

@ -0,0 +1,100 @@
package com.rymcu.forest.config;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.mgt.FilterChainManager;
import org.apache.shiro.web.filter.mgt.FilterChainResolver;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.apache.shiro.web.mgt.WebSecurityManager;
import org.apache.shiro.web.servlet.AbstractShiroFilter;
import org.springframework.beans.factory.BeanInitializationException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
/**
* @author ronger
* Shiro静态资源配置
* */
public class BaseShiroFilterFactoryBean extends ShiroFilterFactoryBean {
private Set<String> ignoreExt;
public BaseShiroFilterFactoryBean() {
super();
ignoreExt = new HashSet<>();
ignoreExt.add(".svg");
ignoreExt.add(".jpg");
ignoreExt.add(".png");
ignoreExt.add(".gif");
ignoreExt.add(".bmp");
ignoreExt.add(".js");
ignoreExt.add(".css");
}
@Override
protected AbstractShiroFilter createInstance() throws Exception {
SecurityManager securityManager = getSecurityManager();
if (securityManager == null) {
String msg = "SecurityManager property must be set.";
throw new BeanInitializationException(msg);
}
if (!(securityManager instanceof WebSecurityManager)) {
String msg = "The security manager does not implement the WebSecurityManager interface.";
throw new BeanInitializationException(msg);
}
FilterChainManager manager = createFilterChainManager();
PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
chainResolver.setFilterChainManager(manager);
return new BaseSpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}
private final class BaseSpringShiroFilter extends AbstractShiroFilter {
protected BaseSpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
super();
if (webSecurityManager == null) {
throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
}
setSecurityManager(webSecurityManager);
if (resolver != null) {
setFilterChainResolver(resolver);
}
}
@Override
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain chain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
String str = request.getRequestURI().toLowerCase();
// 因为ShiroFilter 拦截所有请求在上面我们配置了urlPattern * 当然你也可以在那里精确的添加要处理的路径这样就不需要这个类了而在每次请求里面都做了session的读取和更新访问时间等操作这样在集群部署session共享的情况下数量级的加大了处理量负载
// 所以我们这里将一些能忽略的请求忽略掉
// 当然如果你的集群系统使用了动静分离处理静态资料的请求不会到Filter这个层面便可以忽略
boolean flag = true;
int idx = 0;
if(( idx = str.indexOf(".")) > 0){
str = str.substring(idx);
if(ignoreExt.contains(str.toLowerCase())) {
flag = false;
}
}
if(flag){
super.doFilterInternal(servletRequest, servletResponse, chain);
}else{
chain.doFilter(servletRequest, servletResponse);
}
}
}
}

View File

@ -0,0 +1,158 @@
package com.rymcu.forest.config;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.rymcu.forest.core.constant.ShiroConstants;
import com.rymcu.forest.core.exception.CaptchaException;
import com.rymcu.forest.entity.Permission;
import com.rymcu.forest.entity.Role;
import com.rymcu.forest.entity.User;
import com.rymcu.forest.service.PermissionService;
import com.rymcu.forest.service.RoleService;
import com.rymcu.forest.service.UserService;
import com.rymcu.forest.util.Encodes;
import com.rymcu.forest.util.Utils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.exceptions.TooManyResultsException;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.List;
/**
* @author ronger
* @since 2018/05/28 11:00
* 自定义权限匹配和账号密码匹配
*/
public class BaseShiroRealm extends AuthorizingRealm {
@Resource
private RoleService roleService;
@Resource
private PermissionService permissionService;
@Resource
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Principal principal = (Principal) principals.getPrimaryPrincipal();
User user = new User();
user.setIdUser(principal.getId());
try {
List<Role> roles = roleService.selectRoleByUser(user);
for (Role role : roles) {
if (StringUtils.isNotBlank(role.getInputCode())) {
authorizationInfo.addRole(role.getInputCode());
}
}
List<Permission> permissions = permissionService.selectPermissionByUser(user);
for (Permission perm : permissions) {
if (perm.getPermissionCategory() != null) {
authorizationInfo.addStringPermission(perm.getPermissionCategory());
}
}
// 添加用户权限
authorizationInfo.addStringPermission("user");
} catch (Exception e) {
e.printStackTrace();
}
return authorizationInfo;
}
/**
* 认证回调函数, 登录时调用主要是用来进行身份认证的也就是说验证用户输入的账号和密码是否正确
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
//获取用户的输入的账号.
String username = token.getUsername();
User user = null;
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (!org.springframework.util.StringUtils.isEmpty(attributes.getRequest().getAttribute(ShiroConstants.CURRENT_CAPTCHA))) {
throw new CaptchaException();
}
try {
user = userService.findByAccount(username);
} catch (TooManyResultsException e) {
e.printStackTrace();
}
if (user == null) {
return null;
}
// 账户冻结(是否允许登陆)
if (!"0".equals(user.getStatus())) {
throw new LockedAccountException();
}
byte[] salt = Encodes.decodeHex(user.getPassword().substring(0, 16));
return new SimpleAuthenticationInfo(new Principal(user, token.isMobileLogin()),
user.getPassword().substring(16), ByteSource.Util.bytes(salt), getName());
}
/**
* 授权用户信息
*/
public static class Principal implements Serializable {
private static final long serialVersionUID = 1L;
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long id; // 编号
private String account; // 登录名
private String name; // 姓名
private boolean mobileLogin; // 是否手机登录
// private Map<String, Object> cacheMap;
public Principal(User user, boolean mobileLogin) {
this.id = user.getIdUser();
this.account = user.getAccount();
this.name = user.getNickname();
this.mobileLogin = mobileLogin;
}
public Long getId() {
return id;
}
public String getAccount() {
return account;
}
public String getName() {
return name;
}
public boolean isMobileLogin() {
return mobileLogin;
}
/**
* 获取SESSIONID
*/
public String getSessionid() {
try {
return (String) Utils.getSession().getId();
} catch (Exception e) {
return "";
}
}
@Override
public String toString() {
return id.toString();
}
}
}

View File

@ -0,0 +1,69 @@
package com.rymcu.forest.config;
import com.github.pagehelper.PageInterceptor;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import tk.mybatis.spring.mapper.MapperScannerConfigurer;
import javax.sql.DataSource;
import java.util.Properties;
import static com.rymcu.forest.core.constant.ProjectConstant.*;
/**
* Mybatis & Mapper & PageHelper 配置
* @author ronger
*/
@Configuration
public class MybatisConfigurer {
@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setTypeAliasesPackage(MODEL_PACKAGE);
//配置分页插件详情请查阅官方文档
PageInterceptor pageHelper = new PageInterceptor();
Properties properties = new Properties();
properties.setProperty("helperDialect", "mysql");
properties.setProperty("pageSizeZero", "true");
properties.setProperty("reasonable", "true");
properties.setProperty("supportMethodsArguments", "true");
properties.setProperty("rowBoundsWithCount", "true");
pageHelper.setProperties(properties);
//添加插件
factory.setPlugins(pageHelper);
//添加XML目录
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
factory.setMapperLocations(resolver.getResources("classpath:mapper/**/*.xml"));
return factory.getObject();
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactoryBean");
mapperScannerConfigurer.setBasePackage(MAPPER_PACKAGE);
//配置通用Mapper详情请查阅官方文档
Properties properties = new Properties();
properties.setProperty("mappers", MAPPER_INTERFACE_REFERENCE);
properties.setProperty("notEmpty", "false");
properties.setProperty("IDENTITY", "JDBC");
mapperScannerConfigurer.setProperties(properties);
return mapperScannerConfigurer;
}
}

View File

@ -0,0 +1,54 @@
package com.rymcu.forest.config;
import com.rymcu.forest.entity.User;
import com.rymcu.forest.jwt.def.JwtConstants;
import com.rymcu.forest.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* Created on 2021/10/9 9:25.
*
* @author ronger
* @email ronger-x@outlook.com
* @packageName com.rymcu.forest.config
*/
@Slf4j
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
@Resource
private UserService userService;
@Autowired
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
/**
* 针对 redis 数据失效事件进行数据处理
*
* @param message key
* @param pattern pattern
*/
@Override
public void onMessage(Message message, byte[] pattern) {
// 获取到失效的 key
String expiredKey = message.toString();
if (expiredKey.contains(JwtConstants.LAST_ONLINE)) {
String email = expiredKey.replace(JwtConstants.LAST_ONLINE, "");
log.info("拿到过期的数据:{}", expiredKey);
log.info("处理后的数据:{}", email);
userService.updateLastOnlineTimeByEmail(email);
}
super.onMessage(message, pattern);
}
}

View File

@ -0,0 +1,24 @@
package com.rymcu.forest.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
/**
* Created on 2021/10/9 9:23.
*
* @author ronger
* @email ronger-x@outlook.com
* @packageName com.rymcu.forest.config
*/
@Configuration
public class RedisListenerConfig {
@Bean
public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
return container;
}
}

View File

@ -0,0 +1,124 @@
package com.rymcu.forest.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Protocol;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocketFactory;
/**
* @author <a href="https://github.com/007gzs">007</a>
*/
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties extends JedisPoolConfig {
private String host = Protocol.DEFAULT_HOST;
private int port = Protocol.DEFAULT_PORT;
private String password;
private int database = 1;
private int connectionTimeout = Protocol.DEFAULT_TIMEOUT;
private int soTimeout = Protocol.DEFAULT_TIMEOUT;
private String clientName;
private boolean ssl;
private SSLSocketFactory sslSocketFactory;
private SSLParameters sslParameters;
private HostnameVerifier hostnameVerifier;
public boolean isSsl() {
return ssl;
}
public void setSsl(boolean ssl) {
this.ssl = ssl;
}
public SSLSocketFactory getSslSocketFactory() {
return sslSocketFactory;
}
public void setSslSocketFactory(SSLSocketFactory sslSocketFactory) {
this.sslSocketFactory = sslSocketFactory;
}
public SSLParameters getSslParameters() {
return sslParameters;
}
public void setSslParameters(SSLParameters sslParameters) {
this.sslParameters = sslParameters;
}
public HostnameVerifier getHostnameVerifier() {
return hostnameVerifier;
}
public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
this.hostnameVerifier = hostnameVerifier;
}
public String getHost() {
return host;
}
public void setHost(String host) {
if (host == null || "".equals(host)) {
host = Protocol.DEFAULT_HOST;
}
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
if ("".equals(password)) {
password = null;
}
this.password = password;
}
public int getDatabase() {
return database;
}
public void setDatabase(int database) {
this.database = database;
}
public String getClientName() {
return clientName;
}
public void setClientName(String clientName) {
if ("".equals(clientName)) {
clientName = null;
}
this.clientName = clientName;
}
public int getConnectionTimeout() {
return connectionTimeout;
}
public void setConnectionTimeout(int connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}
public int getSoTimeout() {
return soTimeout;
}
public void setSoTimeout(int soTimeout) {
this.soTimeout = soTimeout;
}
}

View File

@ -0,0 +1,172 @@
package com.rymcu.forest.config;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author ronger
*/
@Configuration
@ConfigurationProperties(prefix = "redis.shiro")
public class ShiroConfig implements EnvironmentAware {
private Environment env;
@Override
public void setEnvironment(Environment environment) {
this.env=environment;
}
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new BaseShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
//注意过滤器配置顺序 不能颠倒
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了登出后跳转配置的loginUrl
filterChainDefinitionMap.put("/logout", "logout");
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/druid/**", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/fonts/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/uploadFile/**", "anon");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/api/**", "anon");
filterChainDefinitionMap.put("/ws/**", "anon");
filterChainDefinitionMap.put("/wss/**", "anon");
filterChainDefinitionMap.put("/wx/**", "anon");
filterChainDefinitionMap.put("/**", "auth");
//配置shiro默认登录界面地址前后端分离中登录界面跳转应由前端路由控制后台仅返回json数据
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
//未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
Map<String, Filter> filtersMap = new LinkedHashMap<>();
filtersMap.put("auth", baseFormAuthenticationFilter());
shiroFilterFactoryBean.setFilters(filtersMap);
filterChainDefinitionMap.put("/**", "auth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 凭证匹配器
* 由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
*
*
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashAlgorithmName("SHA-1");
// 散列的次数比如散列两次相当于 md5(md5(""));
hashedCredentialsMatcher.setHashIterations(1024);
return hashedCredentialsMatcher;
}
@Bean
public BaseShiroRealm baseShiroRealm() {
BaseShiroRealm shiroRealm = new BaseShiroRealm();
shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return shiroRealm;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(baseShiroRealm());
// 自定义session管理 使用redis
securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* 自定义sessionManager
* */
@Bean
public SessionManager sessionManager() {
BaseSessionManager sessionManager = new BaseSessionManager();
sessionManager.setSessionDAO(new MemorySessionDAO());
sessionManager.setSessionIdUrlRewritingEnabled(false);
sessionManager.setGlobalSessionTimeout(21600000L);
return sessionManager;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* Shiro生命周期处理器
* @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
return new LifecycleBeanPostProcessor();
}
/**
* 开启Shiro的注解(@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
*
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
public FormAuthenticationFilter baseFormAuthenticationFilter(){
FormAuthenticationFilter formAuthenticationFilter = new ShiroLoginFilter();
return formAuthenticationFilter;
}
@Bean
public FilterRegistrationBean someFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
FormAuthenticationFilter baseFormAuthenticationFilter = new ShiroLoginFilter();
registration.setFilter(baseFormAuthenticationFilter);
registration.setEnabled(false);
return registration;
}
}

View File

@ -0,0 +1,75 @@
package com.rymcu.forest.config;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.rymcu.forest.core.result.GlobalResultGenerator;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* @author wanwh
* @date 2019/1/24 0024
*/
public class ShiroLoginFilter extends FormAuthenticationFilter {
private static final Logger log = LoggerFactory.getLogger(FormAuthenticationFilter.class);
/**
* 在访问controller前判断是否登录返回json不进行重定向
* @param request
* @param response
* @return true-继续往下执行false-该filter过滤器已经处理不继续执行其他过滤器
* @throws Exception
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
if (this.isLoginRequest(request, response)) {
if (this.isLoginSubmission(request, response)) {
if (log.isTraceEnabled()) {
log.trace("Login submission detected. Attempting to execute login.");
}
return this.executeLogin(request, response);
} else {
if (log.isTraceEnabled()) {
log.trace("Login page view.");
}
return true;
}
}else if(isAjax((HttpServletRequest) request)){
httpServletResponse.setContentType("application/json");
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setHeader("sessionstatus", "timeOut");
httpServletResponse.addHeader("loginPath", this.getLoginUrl());
httpServletResponse.getWriter().write(JSONObject.toJSONString(GlobalResultGenerator.genErrorResult("未登录或已登录超时,请重新登录"), SerializerFeature.PrettyFormat));
return false;
}else {
if (log.isTraceEnabled()) {
log.trace("Attempting to access a path which requires authentication. Forwarding to the Authentication url [" + this.getLoginUrl() + "]");
}
this.saveRequestAndRedirectToLogin(request, response);
return false;
}
}
private boolean isAjax(HttpServletRequest request) {
String requestedWith = request.getHeader("x-requested-with");
if (requestedWith != null && requestedWith.equalsIgnoreCase("XMLHttpRequest")) {
return true;
} else {
return false;
}
}
}

View File

@ -0,0 +1,37 @@
package com.rymcu.forest.config;
/**
* 用户和密码包含验证码令牌类
* @author ronger
*/
public class UsernamePasswordToken extends org.apache.shiro.authc.UsernamePasswordToken {
private static final long serialVersionUID = 1L;
private String captcha;
private boolean mobileLogin;
public UsernamePasswordToken() {
super();
}
public UsernamePasswordToken(String username, char[] password,
boolean rememberMe, String host, String captcha, boolean mobileLogin) {
super(username, password, rememberMe, host);
this.captcha = captcha;
this.mobileLogin = mobileLogin;
}
public String getCaptcha() {
return captcha;
}
public void setCaptcha(String captcha) {
this.captcha = captcha;
}
public boolean isMobileLogin() {
return mobileLogin;
}
}

View File

@ -0,0 +1,87 @@
package com.rymcu.forest.config;
import com.rymcu.forest.util.Utils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Enumeration;
/**
* WebLogAspect 实现Web层的日志切面
*
* @author suwen
* @date 2020/12/22 9:16 上午
*/
@Aspect
@Component
public class WebLogAspect {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
ThreadLocal<Long> startTime = new ThreadLocal<Long>();
/**
* 定义一个切入点. 解释下
*
* <p>~ 第一个 * 代表任意修饰符及任意返回值. ~ 第二个 * 任意包名 ~ 第三个 * 代表任意方法.
*
* <p>~ 第四个 * 定义在web包或者子包 ~ 第五个 * 任意方法 ~ .. 匹配任意数量的参数. execution(*
* xiao.ze.demo.service.impl.*.*(..))
*/
@Pointcut("execution(* com.rymcu.forest.*.api.*.*.*(..))")
public void webLog() {
}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) {
startTime.set(System.currentTimeMillis());
// 接收到请求记录请求内容
logger.info("WebLogAspect.doBefore()");
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 记录下请求内容
logger.info("URL : " + request.getRequestURL().toString());
logger.info("HTTP_METHOD : " + request.getMethod());
logger.info("IP : " + Utils.getIpAddress(request));
logger.info(
"CLASS_METHOD : "
+ joinPoint.getSignature().getDeclaringTypeName()
+ "."
+ joinPoint.getSignature().getName());
logger.info("ARGS : " + Arrays.toString(joinPoint.getArgs())
.replaceAll("(?<=password).*?(?=(nickname|$))", "=****, ")
.replaceAll("(?<=password).*?(?=(\\)|$))", "=****)]")
.replaceAll("(?<=password).*?(?=(code|$))", "=****, "));
// 获取所有参数方法一
Enumeration<String> enu = request.getParameterNames();
while (enu.hasMoreElements()) {
String paraName = enu.nextElement();
if ("password".equals(paraName)) {
continue;
}
logger.info(paraName + ": " + request.getParameter(paraName));
}
}
@AfterReturning("webLog()")
public void doAfterReturning(JoinPoint joinPoint) {
// 处理完请求返回内容
logger.info("WebLogAspect.doAfterReturning()");
logger.info("耗时(毫秒) : " + (System.currentTimeMillis() - startTime.get()));
}
}

View File

@ -0,0 +1,98 @@
package com.rymcu.forest.config;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.rymcu.forest.jwt.aop.RestAuthTokenInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.ResourceUtils;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
/**
* Spring MVC 配置
*
* @author ronger
*/
@Configuration
public class WebMvcConfigurer extends WebMvcConfigurationSupport {
private final Logger logger = LoggerFactory.getLogger(WebMvcConfigurer.class);
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
FastJsonConfig config = new FastJsonConfig();
// 保留空的字段
config.setSerializerFeatures(SerializerFeature.WriteMapNullValue,
//String null -> ""
SerializerFeature.WriteNullStringAsEmpty);
// SerializerFeature.WriteNullNumberAsZero);//Number null -> 0
//关闭循环引用
config.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect);
converter.setFastJsonConfig(config);
converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON));
converter.setDefaultCharset(Charset.forName("UTF-8"));
converters.add(0, converter);
}
/**
* 解决跨域问题
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns(CorsConfiguration.ALL)
.allowCredentials(true)
.allowedMethods("GET", "POST", "DELETE", "PUT", "PATCH");
}
@Bean
public RestAuthTokenInterceptor restAuthTokenInterceptor() {
return new RestAuthTokenInterceptor();
}
/**
* 添加拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(restAuthTokenInterceptor()).addPathPatterns("/api/**")
.excludePathPatterns("/api/v1/console/**", "/api/v1/article/articles/**", "/api/v1/article/detail/**"
, "/api/v1/topic/**", "/api/v1/user/**", "/api/v1/article/*/comments", "/api/v1/rule/currency/**", "/api/v1/lucene/**", "/api/v1/open-data/**");
}
/**
* 访问静态资源
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
/**
* SpringBoot自动配置本身并不会把/swagger-ui.html
* 这个路径映射到对应的目录META-INF/resources/下面
* 采用WebMvcConfigurerAdapter将swagger的静态文件进行发布;
*/
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
//将所有/static/** 访问都映射到classpath:/static/ 目录下
registry.addResourceHandler("/static/**").addResourceLocations(ResourceUtils.CLASSPATH_URL_PREFIX + "/static/");
super.addResourceHandlers(registry);
}
}

View File

@ -0,0 +1,40 @@
package com.rymcu.forest.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
/**
* @author ronger
*/
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketStompConfig implements WebSocketMessageBrokerConfigurer {
/**
* 注册stomp端点
* @param registry
*/
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 允许使用socketJs方式访问 即可通过http://IP:PORT/ws来和服务端websocket连接
registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS();
registry.addEndpoint("/wss").setAllowedOrigins("*").withSockJS();
}
/**
* 配置信息代理
* @param registry
*/
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// 订阅Broker名称 user点对点 topic广播即群发
registry.enableSimpleBroker("/topic", "/user");
// 全局(客户端)使用的消息前缀
registry.setApplicationDestinationPrefixes("/app");
// 点对点使用的前缀 无需配置 默认/user
registry.setUserDestinationPrefix("/user");
}
}

View File

@ -0,0 +1,19 @@
package com.rymcu.forest.core.constant;
/**
* 消息通知类型
* @author ronger
*/
public class NotificationConstant {
public static String Article = "0";
public static String Follow = "1";
public static String Comment = "2";
public static String PostArticle = "3";
public static String UpdateArticle = "4";
}

View File

@ -0,0 +1,26 @@
package com.rymcu.forest.core.constant;
/**
* 项目常量
* @author ronger
*/
public final class ProjectConstant {
/**当前环境*/
public static final String ENV = "dev";
/**项目基础包名称,根据自己公司的项目修改*/
public static final String BASE_PACKAGE = "com.rymcu.forest";
/**DTO所在包*/
public static final String DTO_PACKAGE = BASE_PACKAGE + ".dto";
/**Model所在包*/
public static final String MODEL_PACKAGE = BASE_PACKAGE + ".entity";
/**Mapper所在包*/
public static final String MAPPER_PACKAGE = BASE_PACKAGE + ".mapper";
/**Service所在包*/
public static final String SERVICE_PACKAGE = BASE_PACKAGE + ".service";
/**ServiceImpl所在包*/
public static final String SERVICE_IMPL_PACKAGE = SERVICE_PACKAGE + ".impl";
/**Controller所在包*/
public static final String CONTROLLER_PACKAGE = BASE_PACKAGE + ".web";
/**Mapper插件基础接口的完全限定名*/
public static final String MAPPER_INTERFACE_REFERENCE = BASE_PACKAGE + ".core.mapper.Mapper";
}

View File

@ -0,0 +1,63 @@
package com.rymcu.forest.core.constant;
/**
* Shiro通用常量
*
* @author ronger
*/
public interface ShiroConstants {
/**
* 当前登录的用户
*/
public static final String CURRENT_USER = "currentUser";
/**
* 用户名
*/
public static final String CURRENT_USERNAME = "username";
/**
* 消息key
*/
public static String MESSAGE = "message";
/**
* 错误key
*/
public static String ERROR = "errorMsg";
/**
* 编码格式
*/
public static String ENCODING = "UTF-8";
/**
* 当前在线会话
*/
public String ONLINE_SESSION = "online_session";
/**
* 验证码key
*/
public static final String CURRENT_CAPTCHA = "captcha";
/**
* 验证码开关
*/
public static final String CURRENT_ENABLED = "captchaEnabled";
/**
* 验证码开关
*/
public static final String CURRENT_TYPE = "captchaType";
/**
* 验证码
*/
public static final String CURRENT_VALIDATE_CODE = "validateCode";
/**
* 验证码错误
*/
public static final String CAPTCHA_ERROR = "captchaError";
}

View File

@ -0,0 +1,28 @@
package com.rymcu.forest.core.exception;
import org.apache.shiro.authc.AccountException;
/**
* Created on 2022/8/25 19:27.
*
* @author ronger
* @email ronger-x@outlook.com
*/
public class AccountExistsException extends AccountException {
private static final long serialVersionUID = 3206734387536223284L;
public AccountExistsException() {
}
public AccountExistsException(String message) {
super(message);
}
public AccountExistsException(String message, Throwable cause) {
super(message, cause);
}
public AccountExistsException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,28 @@
package com.rymcu.forest.core.exception;
/**
* @author KKould
*/
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 3206744387536223284L;
public BusinessException() {
}
public BusinessException(String message) {
super(message);
}
public BusinessException(String message, Throwable cause) {
super(message, cause);
}
public BusinessException(Throwable cause) {
super(cause);
}
public BusinessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@ -0,0 +1,18 @@
package com.rymcu.forest.core.exception;
import org.apache.shiro.authc.AuthenticationException;
/**
* 验证码错误异常类
*
* @author ronger
*/
public class CaptchaException extends AuthenticationException
{
private static final long serialVersionUID = 1L;
public CaptchaException()
{
super("验证码不正确");
}
}

View File

@ -0,0 +1,27 @@
package com.rymcu.forest.core.exception;
/**
* @author KKould
*/
public class ContentNotExistException extends BusinessException {
private static final long serialVersionUID = 3206734387536223284L;
public ContentNotExistException() {
}
public ContentNotExistException(String message) {
super(message);
}
public ContentNotExistException(String message, Throwable cause) {
super(message, cause);
}
public ContentNotExistException(Throwable cause) {
super(cause);
}
public ContentNotExistException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@ -0,0 +1,32 @@
package com.rymcu.forest.core.exception;
/**
* Created on 2022/8/25 19:11.
*
* @author ronger
* @email ronger-x@outlook.com
*/
public class NicknameOccupyException extends BusinessException {
private static final long serialVersionUID = 3206744387536223284L;
public NicknameOccupyException() {
}
public NicknameOccupyException(String message) {
super(message);
}
public NicknameOccupyException(String message, Throwable cause) {
super(message, cause);
}
public NicknameOccupyException(Throwable cause) {
super(cause);
}
public NicknameOccupyException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@ -0,0 +1,48 @@
package com.rymcu.forest.core.exception;
import com.rymcu.forest.core.result.ResultCode;
/**
* 服务业务异常如 账号或密码错误 该异常只做INFO级别的日志记录 @see WebMvcConfigurer
* @author ronger
*/
public class ServiceException extends Exception {
private int code;
private String extraMessage;
public ServiceException(ResultCode resultCode) {
super(resultCode.getMessage());
this.code=resultCode.getCode();
}
public ServiceException(String message, Throwable cause) {
}
public ServiceException(int code, String message, String extraMessage, Throwable cause){
super(message,cause);
this.code=code;
this.extraMessage=extraMessage;
}
public ServiceException(ResultCode resultCode, String extraMessage){
this(resultCode.getCode(),resultCode.getMessage(),extraMessage,null);
}
public ServiceException(String extraMessage){
this(ResultCode.INVALID_PARAM,extraMessage);
}
public int getCode() {
return code;
}
public String getExtraMessage() {
return extraMessage;
}
}

View File

@ -0,0 +1,22 @@
package com.rymcu.forest.core.exception;
import com.rymcu.forest.enumerate.TransactionCode;
/**
* @author ronger
*/
public class TransactionException extends BusinessException {
private int code;
private String message;
public TransactionException(TransactionCode transactionCode) {
super(transactionCode.getMessage());
this.code = transactionCode.getCode();
}
public int getCode() {
return code;
}
}

View File

@ -0,0 +1,29 @@
package com.rymcu.forest.core.exception;
/**
* @author KKould
*/
public class UltraViresException extends BusinessException {
private static final long serialVersionUID = 3206744387536228284L;
public UltraViresException() {
super();
}
public UltraViresException(String message) {
super(message);
}
public UltraViresException(String message, Throwable cause) {
super(message, cause);
}
public UltraViresException(Throwable cause) {
super(cause);
}
protected UltraViresException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@ -0,0 +1,17 @@
package com.rymcu.forest.core.mapper;
import tk.mybatis.mapper.common.BaseMapper;
import tk.mybatis.mapper.common.ConditionMapper;
import tk.mybatis.mapper.common.IdsMapper;
import tk.mybatis.mapper.common.special.InsertListMapper;
/**
* 定制版MyBatis Mapper插件接口如需其他接口参考官方文档自行添加
*/
public interface Mapper<T>
extends
BaseMapper<T>,
ConditionMapper<T>,
IdsMapper<T>,
InsertListMapper<T> {
}

View File

@ -0,0 +1,21 @@
package com.rymcu.forest.core.mapper;
import java.util.List;
public interface TreeMapper<T> extends Mapper<T> {
/**
* 找到所有子节点
* @param entity
* @return
*/
public List<T> findByParentIdsLike(T entity);
/**
* 更新所有父节点字段
* @param entity
* @return
*/
public int updateParentIds(T entity);
}

View File

@ -0,0 +1,19 @@
package com.rymcu.forest.core.result;
import lombok.Data;
@Data
public class GlobalResult<T> {
private boolean success = false;
private T data;
private int code;
private String message;
public GlobalResult() {
}
public static <T> GlobalResult<T> newInstance() {
return new GlobalResult();
}
}

View File

@ -0,0 +1,80 @@
package com.rymcu.forest.core.result;
import com.rymcu.forest.util.ErrorCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class GlobalResultGenerator {
private static final Logger LOGGER = LoggerFactory.getLogger(GlobalResultGenerator.class);
/**
* normal
* @param success
* @param data
* @param message
* @param <T>
* @return
*/
public static <T> GlobalResult<T> genResult(boolean success, T data, String message) {
GlobalResult<T> result = GlobalResult.newInstance();
result.setSuccess(success);
result.setData(data);
result.setMessage(message);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("generate rest result:{}", result);
}
return result;
}
/**
* success
* @param data
* @param <T>
* @return
*/
public static <T> GlobalResult<T> genSuccessResult(T data) {
return genResult(true, data, null);
}
/**
* error message
* @param message error message
* @param <T>
* @return
*/
public static <T> GlobalResult<T> genErrorResult(String message) {
return genResult(false, null, message);
}
/**
* error
* @param error error enum
* @param <T>
* @return
*/
public static <T> GlobalResult<T> genErrorResult(ErrorCode error) {
return genErrorResult(error.getMessage());
}
/**
* success no message
* @return
*/
public static GlobalResult genSuccessResult() {
return genSuccessResult(null);
}
/**
* success
* @param <T>
* @return
*/
public static <T> GlobalResult<T> genSuccessResult(String message) {
return genResult(true, null, message);
}
}

View File

@ -0,0 +1,18 @@
package com.rymcu.forest.core.result;
import lombok.Getter;
@Getter
public enum GlobalResultMessage {
SUCCESS("操作成功!"),
FAIL("操作失败!"),
SEND_FAIL("发送失败,请稍后再试!"),
SEND_SUCCESS("验证码已发送至邮箱!");
private String message;
GlobalResultMessage(String message){
this.message = message;
}
}

View File

@ -0,0 +1,37 @@
package com.rymcu.forest.core.result;
/**
* 响应码枚举参考HTTP状态码的语义
*
* @author ronger
*/
public enum ResultCode {
// 成功
SUCCESS(1, "SUCCESS"),
// 失败
FAIL(400, "访问失败"),
// 未认证签名错误
UNAUTHORIZED(401, "签名错误"),
// 接口不存在
NOT_FOUND(404, "此接口不存在"),
// 服务器内部错误
INTERNAL_SERVER_ERROR(500, "系统繁忙,请稍后再试"),
// 参数错误
INVALID_PARAM(10000, "参数错误");
private int code;
private String message;
ResultCode(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}

View File

@ -0,0 +1,90 @@
package com.rymcu.forest.core.service;
import com.rymcu.forest.core.exception.ServiceException;
import com.rymcu.forest.core.mapper.Mapper;
import org.apache.ibatis.exceptions.TooManyResultsException;
import org.springframework.beans.factory.annotation.Autowired;
import tk.mybatis.mapper.entity.Condition;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.List;
/**
* 基于通用MyBatis Mapper插件的Service接口的实现
*
* @author ronger
*/
public abstract class AbstractService<T> implements Service<T> {
@Autowired
protected Mapper<T> mapper;
/**
* 当前泛型真实类型的Class
*/
private Class<T> modelClass;
public AbstractService() {
ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
modelClass = (Class<T>) pt.getActualTypeArguments()[0];
}
@Override
public void save(T model) {
mapper.insertSelective(model);
}
@Override
public void save(List<T> models) {
mapper.insertList(models);
}
@Override
public void deleteById(String id) {
mapper.deleteByPrimaryKey(id);
}
@Override
public void deleteByIds(String ids) {
mapper.deleteByIds(ids);
}
@Override
public void update(T model) {
mapper.updateByPrimaryKeySelective(model);
}
@Override
public T findById(String id) {
return mapper.selectByPrimaryKey(id);
}
@Override
public T findBy(String fieldName, Object value) throws TooManyResultsException, ServiceException {
try {
T model = modelClass.newInstance();
Field field = modelClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(model, value);
return mapper.selectOne(model);
} catch (ReflectiveOperationException e) {
throw new ServiceException(e.getMessage(), e);
}
}
@Override
public List<T> findByIds(String ids) {
return mapper.selectByIds(ids);
}
@Override
public List<T> findByCondition(Condition condition) {
return mapper.selectByCondition(condition);
}
@Override
public List<T> findAll() {
return mapper.selectAll();
}
}

View File

@ -0,0 +1,92 @@
package com.rymcu.forest.core.service;
import com.rymcu.forest.core.exception.ServiceException;
import org.apache.ibatis.exceptions.TooManyResultsException;
import tk.mybatis.mapper.entity.Condition;
import java.util.List;
/**
* Service 基础接口其他Service 接口 请继承该接口
*
* @author ronger
*/
public interface Service<T> {
/**
* 持久化
*
* @param model
*/
void save(T model);
/**
* 批量持久化
*
* @param models
*/
void save(List<T> models);
/**
* 通过主鍵刪除
*
* @param id
*/
void deleteById(String id);
/**
* 批量刪除 egids -> 1,2,3,4
*
* @param ids
*/
void deleteByIds(String ids);
/**
* 更新
*
* @param model
*/
void update(T model);
/**
* 通过ID查找
*
* @param id
* @return
*/
T findById(String id);
/**
* 通过Model中某个成员变量名称非数据表中column的名称查找,value需符合unique约束
*
* @param fieldName
* @param value
* @return
* @throws TooManyResultsException
* @throws ServiceException
*/
T findBy(String fieldName, Object value) throws TooManyResultsException, ServiceException;
/**
* 通过多个ID查找//egids -> 1,2,3,4
*
* @param ids
* @return
*/
List<T> findByIds(String ids);
/**
* 根据条件查找
*
* @param condition
* @return
*/
List<T> findByCondition(Condition condition);
/**
* 获取所有
*
* @return
*/
List<T> findAll();
}

View File

@ -0,0 +1,94 @@
package com.rymcu.forest.core.service.log;
import com.rymcu.forest.core.result.GlobalResult;
import com.rymcu.forest.core.service.log.annotation.TransactionLogger;
import com.rymcu.forest.entity.TransactionRecord;
import com.rymcu.forest.entity.User;
import com.rymcu.forest.enumerate.TransactionEnum;
import com.rymcu.forest.service.TransactionRecordService;
import com.rymcu.forest.util.UserUtils;
import com.rymcu.forest.web.api.exception.BaseApiException;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Objects;
/**
* @author ronger
*
*/
@Aspect
@Component
public class TransactionAspect {
Logger logger = LoggerFactory.getLogger(TransactionAspect.class);
@Resource
private TransactionRecordService transactionRecordService;
@Pointcut("@annotation(com.rymcu.forest.core.service.log.annotation.TransactionLogger)")
public void pointCut() {}
/**
* 保存交易操作日志
*
* @param joinPoint 连接点
* @return 方法执行结果
* @throws Throwable 调用出错
*/
@AfterReturning(value = "pointCut()", returning="obj")
public void save(JoinPoint joinPoint, Object obj) throws Exception {
logger.info("保存交易记录 start ...");
/**
* 解析Log注解
*/
String methodName = joinPoint.getSignature().getName();
Method method = currentMethod(joinPoint, methodName);
TransactionLogger log = method.getAnnotation(TransactionLogger.class);
if (Objects.nonNull(log)) {
User user = UserUtils.getCurrentUserByToken();
GlobalResult globalResult = (GlobalResult) obj;
if (globalResult.isSuccess()) {
if (TransactionEnum.Answer.equals(log.transactionType())) {
if (globalResult.getData().equals(true)) {
transactionRecordService.bankTransfer(user.getIdUser(), TransactionEnum.CorrectAnswer);
} else {
transactionRecordService.bankTransfer(user.getIdUser(), TransactionEnum.Answer);
}
}
}
}
logger.info("保存交易记录 end ...");
}
/**
* 获取当前执行的方法
*
* @param joinPoint 连接点
* @param methodName 方法名称
* @return 方法
*/
private Method currentMethod(JoinPoint joinPoint, String methodName) {
/**
* 获取目标类的所有方法找到当前要执行的方法
*/
Method[] methods = joinPoint.getTarget().getClass().getMethods();
Method resultMethod = null;
for (Method method : methods) {
if (method.getName().equals(methodName)) {
resultMethod = method;
break;
}
}
return resultMethod;
}
}

View File

@ -0,0 +1,114 @@
package com.rymcu.forest.core.service.log;
import com.rymcu.forest.core.service.log.constant.LoggerConstant;
import com.rymcu.forest.dto.TokenUser;
import com.rymcu.forest.entity.Visit;
import com.rymcu.forest.jwt.def.JwtConstants;
import com.rymcu.forest.service.ArticleService;
import com.rymcu.forest.service.VisitService;
import com.rymcu.forest.util.UserUtils;
import com.rymcu.forest.util.Utils;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.HandlerMapping;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
/**
* 浏览
*
* @author ronger
*/
@Aspect
@Component
public class VisitAspect {
Logger logger = LoggerFactory.getLogger(VisitAspect.class);
@Resource
private ArticleService articleService;
@Resource
private VisitService visitService;
@Pointcut("@annotation(com.rymcu.forest.core.service.log.annotation.VisitLogger)")
public void pointCut() {
}
/**
* 保存系统操作日志
*
* @param joinPoint 连接点
* @return 方法执行结果
* @throws Throwable 调用出错
*/
@AfterReturning(value = "pointCut()", returning = "obj")
public void save(JoinPoint joinPoint, Object obj) {
logger.info("保存访问记录 start ...");
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String ip = Utils.getIpAddress(request);
String url = request.getRequestURL().toString();
String ua = request.getHeader("user-agent");
String referer = request.getHeader("Referer");
String fingerprint = request.getHeader("fingerprint");
Visit visit = new Visit();
visit.setVisitUrl(url);
visit.setVisitIp(ip);
visit.setVisitUa(ua);
visit.setVisitCity("");
visit.setVisitDeviceId(fingerprint);
visit.setVisitRefererUrl(referer);
visit.setCreatedTime(new Date());
String authHeader = request.getHeader(JwtConstants.AUTHORIZATION);
if (StringUtils.isNotBlank(authHeader)) {
TokenUser tokenUser = UserUtils.getTokenUser(authHeader);
if (Objects.nonNull(tokenUser)) {
visit.setVisitUserId(tokenUser.getIdUser());
}
}
visitService.save(visit);
String methodName = joinPoint.getSignature().getName();
Map params = getParams(request);
if (params.isEmpty()) {
params = (Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
} else {
params.putAll((Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE));
}
switch (methodName) {
case LoggerConstant.ARTICLE:
String param = String.valueOf(params.get("id"));
if (StringUtils.isBlank(param) || "undefined".equals(param) || "null".equals(param)) {
break;
}
Long id = Long.parseLong(param);
articleService.incrementArticleViewCount(id);
break;
default:
break;
}
logger.info("保存访问记录 end ...");
}
private Map<String, String> getParams(HttpServletRequest request) {
Map<String, String> paramsMap = new HashMap<>(10);
Enumeration<String> paraNames = request.getParameterNames();
while (paraNames.hasMoreElements()) {
String key = paraNames.nextElement();
if ("password".equals(key)) {
continue;
}
paramsMap.put(key, request.getParameter(key));
}
return paramsMap;
}
}

View File

@ -0,0 +1,16 @@
package com.rymcu.forest.core.service.log.annotation;
import com.rymcu.forest.enumerate.TransactionEnum;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* @author ronger
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface TransactionLogger {
TransactionEnum transactionType();
}

View File

@ -0,0 +1,12 @@
package com.rymcu.forest.core.service.log.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* 浏览记录器
* @author ronger
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface VisitLogger {
}

View File

@ -0,0 +1,12 @@
package com.rymcu.forest.core.service.log.constant;
/**
* @author ronger
*/
public class LoggerConstant {
public final static String ARTICLE = "article";
public final static String ARTICLES = "articles";
}

View File

@ -0,0 +1,75 @@
package com.rymcu.forest.core.service.redis;
import com.rymcu.forest.entity.BaseDO;
import java.util.List;
/**
* redis 中取得的结果
* Created by liwei on 2017/2/7.
*/
public class RedisResult<T> extends BaseDO {
/**
* redis中是否存在
*/
private boolean exist = false;
/**
* redis中取得的数据
*/
private T result;
/**
* redis中取得的List数据
*/
private List<T> listResult;
/**
* redis中的key是否存在true:表示redis中存在Key,但对应的值为空值标记
*/
private boolean keyExists = false;
/**
* redis中key 对应在对象值
*/
private T resultObj;
public boolean isExist() {
return exist;
}
public void setExist(boolean exist) {
this.exist = exist;
}
public T getResult() {
return result;
}
public void setResult(T result) {
this.result = result;
}
public List<T> getListResult() {
return listResult;
}
public void setListResult(List<T> listResult) {
this.listResult = listResult;
}
public void setKeyExists(boolean keyExists) {
this.keyExists = keyExists;
}
public boolean isKeyExists() {
return keyExists;
}
public T getResultObj() {
return resultObj;
}
public void setResultObj(T resultObj) {
this.resultObj = resultObj;
}
}

View File

@ -0,0 +1,264 @@
package com.rymcu.forest.core.service.redis;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Set;
/**
* Redis 服务接口
* @author Jimersy Lee
* 2017-09-18 14:58:21
*/
public interface RedisService {
/**
* NX: 当且仅当缓存中特定的key不存在时设定数据
*/
String NXXX_SET_IF_NOT_EXISTS = "nx";
/**
* XX: 当且仅当缓存中特定的key存在时设定数据
*/
String NXXX_SET_IF_EXISTS = "xx";
/**
* EX:缓存失效的时间单位
*/
String EXPX_SECONDS = "ex";
/**
* EX:缓存失效的时间单位毫秒
*/
String EXPX_MILLISECOND = "px";
/**
* 默认过期时间 3600()
*/
int DEFAULT_EXPIRE_TIME = 3600;
ObjectMapper om = new ObjectMapper();
/**
* 空白占位符
*/
String BLANK_CONTENT = "__BLANK__";
/**
* 初始化操作
*/
void init();
void destroy();
// /**
// * 从连接池里取连接用完连接后必须销毁
// *
// * @return
// */
// Jedis getResource();
// /**
// * 用完后销毁连接必须
// *
// * @param jedis
// */
// void destroyResource(Jedis jedis);
/**
* 根据key取数据
*
* @param key
* @return
*/
String get(String key);
/**
* 根据key取对象数据不支持Collection数据类型
*
* @param key
* @param clazz
* @return
*/
<T> T get(String key, Class<T> clazz);
/**
* 根据key取对象数据不支持Collection数据类型
*
* @param key
* @param clazz
* @param <T>
* @return
*/
<T> RedisResult<T> getResult(String key, Class<T> clazz);
/**
* 根据key取 Collection 对象数据
*
* @param key
* @param elementClazz 集合元素类型
* @param <T>
* @return
*/
<T> RedisResult<T> getListResult(String key, Class<T> elementClazz);
/**
* 写入/修改 缓存内容
*
* @param key
* @param obj
* @return
*/
String set(String key, Object obj);
/**
* 写入/修改 缓存内容
*
* @param key
* @param value
* @return
*/
String set(String key, String value);
/**
* 写入/修改 缓存内容(无论key是否存在均会更新key对应的值)
*
* @param key
* @param obj
* @param expireTime 缓存内容过期时间 单位 若expireTime小于0 则表示该内容不过期
* @return
*/
String set(String key, Object obj, int expireTime);
/**
* 写入/修改 缓存内容(无论key是否存在均会更新key对应的值)
*
* @param key
* @param value
* @param expireTime 缓存内容过期时间 单位 若expireTime小于0 则表示该内容不过期
* @return
*/
String set(String key, String value, int expireTime);
/**
* 写入/修改 缓存内容
*
* @param key
* @param value
* @param nxxx 缓存写入值模式 详见 {@link RedisService#NXXX_SET_IF_EXISTS}, {@link RedisService#NXXX_SET_IF_NOT_EXISTS}
* @param expx 缓存超时时间单位 详见{@link RedisService#EXPX_SECONDS}, {@link RedisService#EXPX_MILLISECOND}
* @param expiredTime 缓存存活时长必须 大于0
* @return
*/
String set(String key, String value, String nxxx, String expx, long expiredTime);
/**
* 仅当redis中不含对应的key时设定缓存内容
*
* @param key
* @param value
* @param expiredTime 缓存内容过期时间 单位 若expireTime小于0 则表示该内容不过期
* @return
*/
String setnx(String key, String value, long expiredTime);
/**
* 仅当redis中含有对应的key时修改缓存内容
*
* @param key
* @param value
* @param expiredTime 缓存内容过期时间 单位 若expireTime小于0 则表示该内容不过期
* @return
*/
String setxx(String key, String value, long expiredTime);
/**
* 根据key删除缓存,
*
* @param keys
* @return
*/
Long delete(String... keys);
/**
* 判断对应的key是否存在
*
* @param key
* @return
*/
boolean exists(String key);
/**
* redis 加法运算
*
* @param key
* @param value
* @return 运算结果
*/
Long incrBy(String key, long value);
/**
* 设定redis 对应的key的剩余存活时间
*
* @param key
* @param seconds
*/
void setTTL(String key, int seconds);
/**
* 根据通配符表达式查询key值的set通配符仅支持*
*
* @param pattern ke6*abc等
* @return
*/
Set<String> keys(String pattern);
/**
* 将对象转为json字符串若对象为null则返回 {@link RedisService#BLANK_CONTENT}
*
* @param object
* @return
*/
String toJsonString(Object object);
/**
* json序列化对象
*
* @param value
* @return 返回序列化后的字符串若value为null则返回 {@link RedisService#BLANK_CONTENT}
*/
String makeSerializedString(Object value);
/**
* 写入/修改 缓存内容(无论key是否存在均会更新key对应的值)
*
* @param cacheName
* @param key
* @param value
* @return
*/
String put(String cacheName, String key, Object value);
/**
* 写入/修改 缓存内容(无论key是否存在均会更新key对应的值)
*
* @param cacheName
* @param key
* @param value
* @param expireTime 缓存 内容过期时间 单位 若expireTime小于0 则表示该内容不过期
* @return
*/
String put(String cacheName, String key, Object value, int expireTime);
Object get(String cacheName, String key);
}

View File

@ -0,0 +1,213 @@
package com.rymcu.forest.core.service.redis.impl;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
/**
* Redis Key 辅助类
*/
@Component
@Lazy(false)
public class RedisKeyHelper {
/**
* 应用级别前缀
*/
private static String SYS_PREFIX = "PROJECT_";
/**
* 用户信息前缀
*/
private static String UIC_PREFIX = SYS_PREFIX + "UIC_";
/**
* 商品中心信息前缀
*/
private static String IC_PREFIX = SYS_PREFIX + "IC_";
/**
* 交易中心信息前缀
*/
private static String TC_PREFIX = SYS_PREFIX + "TC_";
/**
* 优惠中心信息前缀
*/
private static String PROM_PREFIX = SYS_PREFIX + "PROM_";
/**
* 会员中心信息前缀
*/
private static String MMB_PREFIX = SYS_PREFIX + "MMB_";
/**
* 分布式互斥锁前缀
*/
private static String LOCK_PREFIX = SYS_PREFIX + "LOCK_";
/**
* 单次登录的默认有效时长单位
*/
public static final int DEFAULT_LOGIN_TIMEOUT = 3600 * 24 * 7;
/**
* 签到记录的默认有效时长单位
*/
public static final int MMB_SIGN_TIMEOUT = 3600 * 12;
/**
* redis中加息券信息的保存时间单位
*/
public static final int PROM_IRC_TIMEOUT = 60 * 3;
/**
* redis中借款人信息默认有效效时间单位
*/
public static final int BORROWER_EXPIRE_TIMEOUT = 60 * 3;//redis缓存失效时间
//商品中心KEY配置==begin========================================================================================
/**
* 商品资产标前缀
*/
public static final String IC_ITEM_PREFIX = IC_PREFIX + "ITEM_";
/**
* 商品资产标列表前缀
*/
public static final String IC_ITEM_LIST_PREFIX = IC_ITEM_PREFIX + "LIST_";
/**
* 商品已投金额或份数前缀
*/
public static final String IC_ITEM_INVESTED_AMOUNT_PREFIX = IC_PREFIX + "INTESTED_AMT_";
public static final String IC_ITEM_DEAL_CREDITOR_PREFIX = IC_PREFIX + "DEAL_CREDITOR_";
//商品中心KEY配置==end==========================================================================================
//优惠中心KEY配置==begin========================================================================================
/**
* 加息券信息前缀
*/
public static String PROM_IRC_PREFIX = PROM_PREFIX + "IRC_";
/**
* 优惠配置信息前缀
*/
public static String PROM_CONFIG_PREFIX = PROM_PREFIX + "CONFIG_";
/**
* 红包列表信息前缀
*/
public static final String PROM_COUPON_LIST_PREFIX = PROM_PREFIX + "COUPON_LIST_";
//优惠中心KEY配置==end==========================================================================================
//交易中心KEY配置==========================================================================================
public static final String TC_TRANS_ACCOUNT_PREFIX = TC_PREFIX + "ACC_";
public static final String TC_TRANS_CURRENT_DEAL_CONFIG_PREFIX = TC_PREFIX + "CURRENT_DEAL_CONFIG_";
public static final String TC_TRANS_EXPERIENCE_MONEY_CONFIG_PREFIX = TC_PREFIX + "EXPERIENCE_MONEY_CONFIG_";
public static final String TC_TRANS_CURRENT_DEAL_LOAD_PREFIX = TC_PREFIX + "CURRENT_DEAL_LOAD_";
public static final String TC_TRANS_CONST_DEAL_ORDER_LIST_PREFIX = TC_PREFIX + "CURRENT_CONST_DEAL_ORDER_LIST_";
/**
* 流水号redis key前缀
*/
public static final String TC_TRANS_SEQ_PREFIX = TC_PREFIX + "SEQ_";
//交易中心KEY配置==end==========================================================================================
//用户中心KEY配置==========================================================================================
/**
* redis中登录用户token的key前缀
*/
public static String LOGIN_TOKEN_KEY_PREFIX = UIC_PREFIX + "LOGIN_TOKEN_";
/**
* redis中登录用户USERID的key前缀
*/
public static String LOGIN_UID_KEY_PREFIX = UIC_PREFIX + "LOGIN_UID_";
/**
* 用户信息前缀手机号
*/
public static String UIC_MOBILE_PREFIX = UIC_PREFIX + "MOB_";
/**
* 用户角色前缀
*/
public static String UIC_ROLE_PREFIX = UIC_PREFIX + "B_R_";
public static String UIC_ROLE_CANCEL_SUFFIX = UIC_PREFIX + "CNL_";
//用户中心KEY配置==end==========================================================================================
/**
* 构建分布式锁的key
*
* @param clazz
* @param key
* @return
*/
public String makeLockKey(Class clazz, String key) {
return buildKeyString(LOCK_PREFIX, clazz.getSimpleName(), key);
}
/**
* 构建分布式锁的key
*
* @param key
* @return
*/
public String makeLockKey(String key) {
return buildKeyString(LOCK_PREFIX, key);
}
/**
* 构造商品信息 redis key
*
* @param itemId
* @return
*/
public String makeIcItemKey(long itemId) {
return buildKeyString(IC_ITEM_PREFIX, itemId);
}
/**
* 构造商品信息列表 redis key
*
* @param pageSize
* @param pageNo
* @return
*/
public String makeIcItemListKey(int pageSize, int pageNo) {
return buildKeyString(IC_ITEM_LIST_PREFIX, pageSize, pageNo);
}
/**
* 构造资产标已投金额前缀
*
* @param itemId
* @return
*/
public String makeInvestedAmountKey(long itemId) {
return buildKeyString(IC_ITEM_INVESTED_AMOUNT_PREFIX, itemId);
}
/**
* 构造交易中心流水号前缀
*
* @param flag
* @return
*/
public String makeSeqKey(String flag) {
return buildKeyString(TC_TRANS_SEQ_PREFIX, flag);
}
public static String buildKeyString(Object... objs) {
if (objs == null || objs.length == 0) {
return "";
}
StringBuilder builder = new StringBuilder();
boolean isFirst = true;
for (Object obj : objs) {
if (isFirst) {
isFirst = false;
} else {
builder.append("_");
}
if (obj instanceof Class) {
builder.append(((Class) obj).getName());
} else {
builder.append(obj);
}
}
return builder.toString();
}
}

View File

@ -0,0 +1,577 @@
package com.rymcu.forest.core.service.redis.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.rymcu.forest.config.RedisProperties;
import com.rymcu.forest.core.service.redis.RedisResult;
import com.rymcu.forest.core.service.redis.RedisService;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;
/**
* Redis 服务接口实现类
*
* @author liwei
* 16/10/30 下午5:28
*/
@Component("redisService")
@EnableConfigurationProperties({RedisProperties.class})
public class RedisServiceImpl implements RedisService {
private static final Logger logger = LoggerFactory.getLogger(RedisServiceImpl.class);
private static JedisPool pool = null;
@Resource
private RedisProperties redisProperties;
/**
* 初始化操作
*/
@Override
@PostConstruct
public void init() {
if (pool != null) {
return;
}
pool = getJedisPool();
}
private JedisPool getJedisPool() {
if (pool == null) {
synchronized (RedisServiceImpl.class) {
if (pool == null) {
pool = new JedisPool(redisProperties, redisProperties.getHost(),
redisProperties.getPort(), redisProperties.getConnectionTimeout(),
redisProperties.getSoTimeout(), redisProperties.getPassword(),
redisProperties.getDatabase(), redisProperties.getClientName(),
redisProperties.isSsl(), redisProperties.getSslSocketFactory(),
redisProperties.getSslParameters(), redisProperties.getHostnameVerifier());
}
}
}
return pool;
}
@Override
@PreDestroy
public void destroy() {
try {
if (pool != null) {
pool.destroy();
}
} catch (Exception e) {
//do nothing
}
}
/**
* 从连接池里取连接用完连接后必须销毁
*
* @return
*/
private Jedis getResource() {
return pool.getResource();
}
/**
* 用完后销毁连接必须
*
* @param jedis
*/
private void destroyResource(Jedis jedis) {
if (jedis == null) {
return;
}
jedis.close();
}
/**
* 根据key取数据
*
* @param key
* @return
*/
@Override
public String get(String key) {
if (StringUtils.isBlank(key)) {
logger.warn("Params key is blank!");
return StringUtils.EMPTY;
}
Jedis jedis = this.getResource();
try {
return jedis.get(key);
} finally {
this.destroyResource(jedis);
}
}
/**
* 根据key取对象数据不支持Collection数据类型
*
* @param key
* @param clazz
* @return
*/
@Override
public <T> T get(String key, Class<T> clazz) {
if (StringUtils.isBlank(key)) {
logger.warn("Params key is blank!");
return null;
}
if (clazz == null) {
logger.warn("Params clazz is null!");
return null;
}
String value = get(key);
if (StringUtils.isBlank(value) || StringUtils.equalsIgnoreCase(value, BLANK_CONTENT)) {
return null;
}
T obj = null;
try {
obj = om.readValue(value, clazz);
} catch (IOException e) {
logger.error("Can not unserialize obj to [{}] with string [{}]", clazz.getName(), value);
}
return obj;
}
/**
* 写入/修改 缓存内容(无论key是否存在均会更新key对应的值)
*
* @param key
* @param obj
* @param expireTime 缓存 内容过期时间 单位 若expireTime小于0 则表示该内容不过期
* @return
*/
@Override
public String set(String key, Object obj, int expireTime) {
String value = RedisService.BLANK_CONTENT;
if (obj != null) {
try {
value = RedisService.om.writeValueAsString(obj);
} catch (IOException e) {
logger.error("Can not write object to redis:" + obj.toString(), e);
}
}
return set(key, value, expireTime);
}
/**
* 写入/修改 缓存内容(无论key是否存在均会更新key对应的值)
*
* @param key
* @param value
* @param expireTime 缓存 内容过期时间 单位 若expireTime小于0 则表示该内容不过期
* @return
*/
@Override
public String set(String key, String value, int expireTime) {
if (StringUtils.isBlank(key)) {
logger.warn("Params key is blank!");
return null;
}
if (value == null) {
logger.warn("Params value is null!");
return null;
}
Jedis jedis = this.getResource();
try {
String result = jedis.set(key, value);
if (expireTime > 0) {
jedis.expire(key, expireTime);
}
return result;
} finally {
this.destroyResource(jedis);
}
}
/**
* 根据key取对象数据不支持Collection数据类型
*
* @param key
* @param clazz
* @return
*/
@Override
public <T> RedisResult<T> getResult(String key, Class<T> clazz) {
if (StringUtils.isBlank(key)) {
logger.warn("Params key is blank!");
return null;
}
if (clazz == null) {
logger.warn("Params clazz is null!");
return null;
}
RedisResult<T> redisResult = new RedisResult<T>();
String value = get(key);
if (StringUtils.isBlank(value)) {
redisResult.setExist(false);
return redisResult;
}
//到此步则表明redis中存在key
redisResult.setExist(true);
if (StringUtils.equalsIgnoreCase(value, BLANK_CONTENT)) {
return redisResult;
}
T obj = null;
try {
obj = om.readValue(value, clazz);
redisResult.setResult(obj);
} catch (IOException e) {
logger.error("Can not unserialize obj to [{}] with string [{}]", clazz.getName(), value);
//到此步直接视为无值
redisResult.setExist(false);
}
return redisResult;
}
/**
* 根据key取 Collection 对象数据
*
* @param key
* @param elementClazz 集合元素类型
* @return
*/
@Override
public <T> RedisResult<T> getListResult(String key, Class<T> elementClazz) {
if (StringUtils.isBlank(key)) {
logger.warn("Params key is blank!");
return null;
}
if (elementClazz == null) {
logger.warn("Params elementClazz is null!");
return null;
}
RedisResult<T> redisResult = new RedisResult<T>();
String value = get(key);
if (StringUtils.isBlank(value)) {
redisResult.setExist(false);
return redisResult;
}
//到此步则表明redis中存在key
redisResult.setExist(true);
if (StringUtils.equalsIgnoreCase(value, BLANK_CONTENT)) {
return redisResult;
}
List<T> list = null;
try {
list = om.readValue(value, getCollectionType(List.class, elementClazz));
redisResult.setListResult(list);
} catch (IOException e) {
logger.error("Can not unserialize list to [{}] with string [{}]", elementClazz.getName(), value);
//到此步直接视为无值
redisResult.setExist(false);
}
return redisResult;
}
/**
* 写入/修改 缓存内容
*
* @param key
* @param obj
* @return
*/
@Override
public String set(String key, Object obj) {
String value = RedisService.BLANK_CONTENT;
if (obj != null) {
try {
value = RedisService.om.writeValueAsString(obj);
} catch (IOException e) {
logger.error("Can not write object to redis:" + obj.toString(), e);
}
}
return set(key, value);
}
private static <T> JavaType getCollectionType(Class<? extends Collection> collectionClazz,
Class<T> elementClazz) {
return om.getTypeFactory().constructCollectionType(collectionClazz, elementClazz);
}
/**
* 写入/修改 缓存内容(默认有过期时间 1小时)
*
* @param key
* @param value
* @return
*/
@Override
public String set(String key, String value) {
return this.set(key, value, DEFAULT_EXPIRE_TIME);
}
/**
* 写入/修改 缓存内容
*
* @param key
* @param value
* @param nxxx 缓存写入值模式 详见 {@link RedisService#NXXX_SET_IF_EXISTS}, {@link RedisService#NXXX_SET_IF_NOT_EXISTS}
* @param expx 缓存超时时间单位 详见{@link RedisService#EXPX_SECONDS}, {@link RedisService#EXPX_MILLISECOND}
* @param expiredTime 缓存存活时长必须 大于0
* @return
*/
@Override
public String set(String key, String value, String nxxx, String expx, long expiredTime) {
if (StringUtils.isBlank(key)) {
logger.warn("Params key is blank!");
return null;
}
if (value == null) {
logger.warn("Params value is null!");
return null;
}
Jedis jedis = this.getResource();
try {
return jedis.set(key, value);
} finally {
this.destroyResource(jedis);
}
}
/**
* 仅当redis中不含对应的key时设定缓存内容
*
* @param key
* @param value
* @param expiredTime 缓存内容过期时间 单位 expireTime必须大于0
* @return
*/
@Override
public String setnx(String key, String value, long expiredTime) {
return this.set(key, value, NXXX_SET_IF_NOT_EXISTS, EXPX_SECONDS, expiredTime);
}
/**
* 仅当redis中含有对应的key时修改缓存内容
*
* @param key
* @param value
* @param expiredTime 缓存内容过期时间 单位 expireTime必须大于0
* @return
*/
@Override
public String setxx(String key, String value, long expiredTime) {
return this.set(key, value, NXXX_SET_IF_EXISTS, EXPX_SECONDS, expiredTime);
}
/**
* 根据key删除缓存
*
* @param keys
* @return
*/
@Override
public Long delete(String... keys) {
if (keys == null || keys.length == 0) {
logger.warn("Params keys is null or 0 length!");
return -1L;
}
Jedis jedis = this.getResource();
try {
return jedis.del(keys);
} finally {
this.destroyResource(jedis);
}
}
/**
* 判断对应的key是否存在
*
* @param key
* @return
*/
@Override
public boolean exists(String key) {
if (StringUtils.isBlank(key)) {
//不接受空值
return false;
}
Jedis jedis = this.getResource();
try {
return jedis.exists(key);
} finally {
this.destroyResource(jedis);
}
}
/**
* redis 加法运算
*
* @param key
* @param value
* @return
*/
@Override
public Long incrBy(String key, long value) {
if (StringUtils.isBlank(key)) {
logger.warn("Params key is blank!");
return null;
}
Jedis jedis = this.getResource();
try {
return jedis.incrBy(key, value);
} finally {
this.destroyResource(jedis);
}
}
/**
* 设定redis 对应的key的剩余存活时间
*
* @param key
* @param seconds
*/
@Override
public void setTTL(String key, int seconds) {
if (seconds < 0) {
return;
}
if (StringUtils.isBlank(key)) {
logger.warn("Params key is blank!");
return;
}
Jedis jedis = this.getResource();
try {
jedis.expire(key, seconds);
} finally {
this.destroyResource(jedis);
}
}
/**
* 根据通配符表达式查询key值的set通配符仅支持*
*
* @param pattern ke6*abc等
* @return
*/
@Override
public Set<String> keys(String pattern) {
if (StringUtils.isBlank(pattern)) {
logger.warn("Params pattern is blank!");
return Collections.emptySet();
}
Jedis jedis = this.getResource();
try {
return jedis.keys(pattern);
} finally {
this.destroyResource(jedis);
}
}
/**
* 将对象转为json字符串若对象为null则返回 {@link RedisService#BLANK_CONTENT}
*
* @param object
* @return
*/
@Override
public String toJsonString(Object object) {
if (object == null) {
return BLANK_CONTENT;
}
if ((object instanceof Collection) && CollectionUtils.isEmpty((Collection) object)) {
return BLANK_CONTENT;
}
if ((object instanceof Map) && CollectionUtils.isEmpty((Map) object)) {
return BLANK_CONTENT;
}
try {
return om.writeValueAsString(object);
} catch (IOException e) {
return null;
}
}
@Override
public String makeSerializedString(Object value) {
if (value == null) {
return BLANK_CONTENT;
}
if ((value instanceof Collection) && ((Collection) value).size() == 0) {
return BLANK_CONTENT;
}
if ((value instanceof Map) && ((Map) value).size() == 0) {
return BLANK_CONTENT;
}
return JSON.toJSONString(value);
}
@Override
public String put(String cacheName, String key, Object value) {
String result = get(cacheName);
Map map = new HashMap();
if (StringUtils.isNotBlank(result)){
map = JSON.parseObject(result, new TypeReference<Map>() {});
}
map.put(key,value);
return set(cacheName,map);
}
@Override
public String put(String cacheName, String key, Object value, int expireTime) {
String result = get(cacheName);
Map map = new HashMap();
if (StringUtils.isNotBlank(result)){
map = JSON.parseObject(result, new TypeReference<Map>() {});
}
map.put(key,value);
return set(cacheName,map,expireTime);
}
@Override
public Object get(String cacheName, String key){
String result = get(cacheName);
if (StringUtils.isNotBlank(result)){
Map map = JSON.parseObject(result, new TypeReference<Map>() {});
return map.get(key);
}
return null;
}
}

View File

@ -0,0 +1,198 @@
package com.rymcu.forest.core.service.security;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.rymcu.forest.core.service.security.annotation.AuthorshipInterceptor;
import com.rymcu.forest.dto.TokenUser;
import com.rymcu.forest.entity.Article;
import com.rymcu.forest.entity.Portfolio;
import com.rymcu.forest.enumerate.Module;
import com.rymcu.forest.jwt.def.JwtConstants;
import com.rymcu.forest.mapper.UserMapper;
import com.rymcu.forest.service.ArticleService;
import com.rymcu.forest.service.PortfolioService;
import com.rymcu.forest.util.UserUtils;
import com.rymcu.forest.web.api.exception.BaseApiException;
import com.rymcu.forest.web.api.exception.ErrorCode;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.HandlerMapping;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 检查用户修改信息权限
*
* @author ronger
*/
@Aspect
@Component
public class AuthorshipAspect {
Logger logger = LoggerFactory.getLogger(AuthorshipAspect.class);
@Pointcut("@annotation(com.rymcu.forest.core.service.security.annotation.AuthorshipInterceptor)")
public void authorshipPointCut() {
}
@Resource
private ArticleService articleService;
@Resource
private PortfolioService portfolioService;
@Resource
private UserMapper userMapper;
/**
* 检查用户修改信息权限
*
* @param joinPoint 连接点
* @return 方法执行结果
* @throws Throwable 调用出错
*/
@Before(value = "authorshipPointCut()")
public void doBefore(JoinPoint joinPoint) throws BaseApiException {
logger.info("检查作者身份 start ...");
String methodName = joinPoint.getSignature().getName();
Method method = currentMethod(joinPoint, methodName);
AuthorshipInterceptor log = method.getAnnotation(AuthorshipInterceptor.class);
if (Objects.nonNull(log)) {
boolean isArticle = true;
if (Module.PORTFOLIO.equals(log.moduleName())) {
isArticle = false;
}
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
String idArticle;
Long idAuthor = 0l;
if (isAjax(request)) {
Object[] objects = joinPoint.getArgs();
JSONObject jsonObject;
if (objects[0] instanceof Integer) {
jsonObject = new JSONObject();
if (isArticle) {
jsonObject.put("idArticle", objects[0].toString());
} else {
jsonObject.put("idPortfolio", objects[0].toString());
}
} else {
jsonObject = JSONObject.parseObject(JSON.toJSONString(objects[0]));
}
if (Objects.nonNull(jsonObject)) {
if (isArticle) {
idArticle = jsonObject.getString("idArticle");
Article article = articleService.findById(idArticle);
if (Objects.nonNull(article)) {
idAuthor = article.getArticleAuthorId();
}
} else {
idArticle = jsonObject.getString("idPortfolio");
Portfolio portfolio = portfolioService.findById(idArticle);
if (Objects.nonNull(portfolio)) {
idAuthor = portfolio.getPortfolioAuthorId();
}
}
}
} else {
Map params = getParams(request);
if (params.isEmpty()) {
params = (Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
} else {
params.putAll((Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE));
}
if (isArticle) {
idArticle = (String) params.get("idArticle");
Article article = articleService.findById(idArticle);
if (Objects.nonNull(article)) {
idAuthor = article.getArticleAuthorId();
}
} else {
idArticle = (String) params.get("idPortfolio");
Portfolio portfolio = portfolioService.findById(idArticle);
if (Objects.nonNull(portfolio)) {
idAuthor = portfolio.getPortfolioAuthorId();
}
}
}
if (idAuthor > 0) {
String authHeader = request.getHeader(JwtConstants.AUTHORIZATION);
if (StringUtils.isNotBlank(authHeader)) {
TokenUser tokenUser = UserUtils.getTokenUser(authHeader);
if (Objects.nonNull(tokenUser)) {
if (!idAuthor.equals(tokenUser.getIdUser())) {
boolean hasPermission = false;
if (Module.ARTICLE_TAG.equals(log.moduleName())) {
// 判断管理员权限
hasPermission = userMapper.hasAdminPermission(tokenUser.getAccount());
}
if (!hasPermission) {
throw new BaseApiException(ErrorCode.ACCESS_DENIED);
}
}
} else {
throw new BaseApiException(ErrorCode.ACCESS_DENIED);
}
}
} else {
throw new BaseApiException(ErrorCode.ACCESS_DENIED);
}
}
logger.info("检查作者身份 end ...");
}
/**
* 获取当前执行的方法
*
* @param joinPoint 连接点
* @param methodName 方法名称
* @return 方法
*/
private Method currentMethod(JoinPoint joinPoint, String methodName) {
/**
* 获取目标类的所有方法找到当前要执行的方法
*/
Method[] methods = joinPoint.getTarget().getClass().getMethods();
Method resultMethod = null;
for (Method method : methods) {
if (method.getName().equals(methodName)) {
resultMethod = method;
break;
}
}
return resultMethod;
}
private Map<String, String> getParams(HttpServletRequest request) {
Map<String, String> paramsMap = new HashMap<>(10);
Enumeration<String> paraNames = request.getParameterNames();
while (paraNames.hasMoreElements()) {
String key = paraNames.nextElement();
if ("password".equals(key)) {
continue;
}
paramsMap.put(key, request.getParameter(key));
}
return paramsMap;
}
private boolean isAjax(HttpServletRequest request) {
String requestedWith = request.getHeader("x-requested-with");
if (requestedWith != null && "XMLHttpRequest".equalsIgnoreCase(requestedWith)) {
return true;
}
String contentType = request.getContentType();
return StringUtils.isNotBlank(contentType) && contentType.contains("application/json");
}
}

View File

@ -0,0 +1,114 @@
package com.rymcu.forest.core.service.security;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.rymcu.forest.dto.TokenUser;
import com.rymcu.forest.jwt.def.JwtConstants;
import com.rymcu.forest.util.UserUtils;
import com.rymcu.forest.web.api.exception.BaseApiException;
import com.rymcu.forest.web.api.exception.ErrorCode;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.HandlerMapping;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 检查用户修改信息权限
*
* @author ronger
*/
@Aspect
@Component
public class SecurityAspect {
Logger logger = LoggerFactory.getLogger(SecurityAspect.class);
@Pointcut("@annotation(com.rymcu.forest.core.service.security.annotation.SecurityInterceptor)")
public void securityPointCut() {
}
/**
* 检查用户修改信息权限
*
* @param joinPoint 连接点
* @return 方法执行结果
* @throws Throwable 调用出错
*/
@Before(value = "securityPointCut()")
public void doBefore(JoinPoint joinPoint) throws BaseApiException {
logger.info("检查用户修改信息权限 start ...");
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String idUser = "";
if (isAjax(request)) {
Object[] objects = joinPoint.getArgs();
JSONObject jsonObject;
if (objects[0] instanceof Integer) {
idUser = objects[0].toString();
} else {
jsonObject = JSONObject.parseObject(JSON.toJSONString(objects[0]));
if (Objects.nonNull(jsonObject)) {
idUser = jsonObject.getString("idUser");
}
}
} else {
Map params = getParams(request);
if (params.isEmpty()) {
params = (Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
} else {
params.putAll((Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE));
}
idUser = (String) params.get("idUser");
}
if (Objects.nonNull(idUser)) {
String authHeader = request.getHeader(JwtConstants.AUTHORIZATION);
if (StringUtils.isNotBlank(authHeader)) {
TokenUser tokenUser = UserUtils.getTokenUser(authHeader);
if (Objects.nonNull(tokenUser)) {
if (!idUser.equals(tokenUser.getIdUser().toString())) {
throw new BaseApiException(ErrorCode.ACCESS_DENIED);
}
} else {
throw new BaseApiException(ErrorCode.ACCESS_DENIED);
}
}
} else {
throw new BaseApiException(ErrorCode.ACCESS_DENIED);
}
logger.info("检查用户修改信息权限 end ...");
}
private Map<String, String> getParams(HttpServletRequest request) {
Map<String, String> paramsMap = new HashMap<>(10);
Enumeration<String> paraNames = request.getParameterNames();
while (paraNames.hasMoreElements()) {
String key = paraNames.nextElement();
if ("password".equals(key)) {
continue;
}
paramsMap.put(key, request.getParameter(key));
}
return paramsMap;
}
private boolean isAjax(HttpServletRequest request) {
String requestedWith = request.getHeader("x-requested-with");
if (requestedWith != null && "XMLHttpRequest".equalsIgnoreCase(requestedWith)) {
return true;
}
String contentType = request.getContentType();
return StringUtils.isNotBlank(contentType) && contentType.contains("application/json");
}
}

View File

@ -0,0 +1,21 @@
package com.rymcu.forest.core.service.security.annotation;
import com.rymcu.forest.enumerate.Module;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created on 2022/1/5 19:46.
*
* @author ronger
* @email ronger-x@outlook.com
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthorshipInterceptor {
Module moduleName();
}

View File

@ -0,0 +1,12 @@
package com.rymcu.forest.core.service.security.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* 安全拦截器
* @author ronger
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface SecurityInterceptor {
}

View File

@ -0,0 +1,17 @@
package com.rymcu.forest.dto;
import lombok.Data;
/**
* @author ronger
*/
@Data
public class AnswerDTO {
private Integer idSubjectQuestion;
private String answer;
private Integer idUser;
}

View File

@ -0,0 +1,73 @@
package com.rymcu.forest.dto;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
import java.util.List;
/**
* @author ronger
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ArticleDTO {
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idArticle;
/** 文章标题 */
private String articleTitle;
/** 文章缩略图 */
private String articleThumbnailUrl;
/** 文章作者id */
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long articleAuthorId;
/** 文章作者 */
private String articleAuthorName;
/** 文章作者头像 */
private String articleAuthorAvatarUrl;
/** 文章类型 */
private String articleType;
/** 文章标签 */
private String articleTags;
/** 浏览总数 */
private Integer articleViewCount;
/** 预览内容 */
private String articlePreviewContent;
/** 文章内容 */
private String articleContent;
/** 文章内容html */
private String articleContentHtml;
/** 评论总数 */
private Integer articleCommentCount;
/** 过去时长 */
private String timeAgo;
/** 文章永久链接 */
private String articlePermalink;
/** 站内链接 */
private String articleLink;
/** 文章状态 */
private String articleStatus;
/** 更新时间 */
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date updatedTime;
private Author articleAuthor;
private List<ArticleTagDTO> tags;
private List<PortfolioArticleDTO> portfolios;
private Integer sortNo;
/** 0:非优选1优选;0 */
private String articlePerfect;
/** 点赞总数 */
private Integer articleThumbsUpCount;
/** 赞赏总数 */
private Integer articleSponsorCount;
}

View File

@ -0,0 +1,17 @@
package com.rymcu.forest.dto;
import lombok.Data;
/**
* @author ronger
*/
@Data
public class ArticleSearchDTO {
private String searchText;
private String topicUri;
private String tag;
}

View File

@ -0,0 +1,35 @@
package com.rymcu.forest.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author ronger
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ArticleTagDTO {
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idArticleTag;
private Integer idTag;
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idArticle;
private String tagTitle;
private String tagUri;
private String tagDescription;
private String tagIconPath;
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long tagAuthorId;
}

View File

@ -0,0 +1,29 @@
package com.rymcu.forest.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author ronger
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Author {
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idUser;
private String userNickname;
private String userAccount;
private String userAvatarURL;
private String userArticleCount;
}

View File

@ -0,0 +1,35 @@
package com.rymcu.forest.dto;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
* @author ronger
*/
@Data
public class BankAccountDTO {
private Integer idBankAccount;
/** 所属银行 */
private Integer idBank;
/** 所属银行名称 */
private String bankName;
/** 银行账户 */
private String bankAccount;
/** 账户余额 */
private BigDecimal accountBalance;
/** 账户所有者 */
private Integer accountOwner;
/** 账户所有者姓名 */
private String accountOwnerName;
/** 创建时间 */
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date createdTime;
private List<TransactionRecordDTO> transactionRecords;
}

View File

@ -0,0 +1,17 @@
package com.rymcu.forest.dto;
import lombok.Data;
/**
* @author ronger
*/
@Data
public class BankAccountSearchDTO {
/** 所属银行名称 */
private String bankName;
/** 银行账户 */
private String bankAccount;
/** 账户所有者姓名 */
private String accountOwnerName;
}

View File

@ -0,0 +1,33 @@
package com.rymcu.forest.dto;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* @author ronger
*/
@Data
public class BankDTO {
private Integer idBank;
/** 银行名称 */
private String bankName;
/** 银行负责人 */
private Integer bankOwner;
/** 银行负责人 */
private String bankOwnerName;
/** 银行账户 */
private String bankAccount;
/** 账户余额 */
private BigDecimal accountBalance;
/** 银行描述 */
private String bankDescription;
/** 创建人 */
private Integer createdBy;
/** 创建时间 */
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date createdTime;
}

View File

@ -0,0 +1,19 @@
package com.rymcu.forest.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/**
* @author ronger
*/
@Data
public class ChangeEmailDTO {
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idUser;
private String email;
private String code;
}

View File

@ -0,0 +1,47 @@
package com.rymcu.forest.dto;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import java.util.Date;
/**
* @author ronger
*/
@Data
public class CommentDTO {
private Integer idComment;
/** 评论内容 */
private String commentContent;
/** 作者 id */
private Integer commentAuthorId;
/** 文章 id */
private Integer commentArticleId;
/** 锚点 url */
private String commentSharpUrl;
/** 父评论 id */
private Integer commentOriginalCommentId;
/** 父评论作者头像 */
private String commentOriginalAuthorThumbnailURL;
/** 父评论作者昵称 */
private String commentOriginalAuthorNickname;
/** 父评论作者昵称 */
private String commentOriginalContent;
/** 状态 */
private String commentStatus;
/** 0公开回帖1匿名回帖 */
private String commentAnonymous;
/** 回帖计数 */
private Integer commentReplyCount;
/** 0所有人可见1仅楼主和自己可见 */
private String commentVisible;
/** 创建时间 */
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date createdTime;
private Author commenter;
private String timeAgo;
private String articleTitle;
}

View File

@ -0,0 +1,13 @@
package com.rymcu.forest.dto;
import lombok.Data;
/**
* @author ronger
*/
@Data
public class ForgetPasswordDTO {
private String code;
private String password;
}

View File

@ -0,0 +1,17 @@
package com.rymcu.forest.dto;
import lombok.Data;
import java.io.Serializable;
/**
* @author ronger
*/
@Data
public class LabelModel implements Serializable {
private String label;
private String value;
}

View File

@ -0,0 +1,15 @@
package com.rymcu.forest.dto;
import lombok.Data;
/**
* @author ronger
*/
@Data
public class LinkToImageUrlDTO {
private String url;
private Integer type;
}

View File

@ -0,0 +1,30 @@
package com.rymcu.forest.dto;
import lombok.Data;
/**
* @author ronger
*/
@Data
public class MenuDTO {
private String id;
private String parentId;
private String parentName;
private String name;
private Long sort;
private String href;
private String menuType;
private String permission;
private String remarks;
private String status;
}

View File

@ -0,0 +1,24 @@
package com.rymcu.forest.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.rymcu.forest.entity.Notification;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author ronger
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class NotificationDTO extends Notification {
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idNotification;
private String dataTitle;
private String dataUrl;
private Author author;
}

View File

@ -0,0 +1,31 @@
package com.rymcu.forest.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.List;
/**
* @author ronger
*/
@Data
public class PortfolioArticleDTO {
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long id;
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idPortfolio;
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idArticle;
private String headImgUrl;
private String portfolioTitle;
private Integer sortNo;
private List<ArticleDTO> articles;
}

View File

@ -0,0 +1,38 @@
package com.rymcu.forest.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
/**
* @author ronger
*/
@Data
public class PortfolioDTO {
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idPortfolio;
/** 作品集头像 */
private String headImgUrl;
/** 作品集作者 */
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long portfolioAuthorId;
/** 作品集作者 */
private String portfolioAuthorName;
/** 作品集作者头像 */
private String portfolioAuthorAvatarUrl;
/** 作品集名称 */
private String portfolioTitle;
/** 作品集介绍 */
private String portfolioDescription;
/** 更新时间 */
private Date updatedTime;
/** 过去时长 */
private String timeAgo;
private Author portfolioAuthor;
private Integer articleNumber;
}

View File

@ -0,0 +1,23 @@
package com.rymcu.forest.dto;
import com.rymcu.forest.entity.Product;
import lombok.Data;
/**
* Created on 2022/6/21 9:38.
*
* @author ronger
* @email ronger-x@outlook.com
* @packageName com.rymcu.forest.dto
*/
@Data
public class ProductDTO extends Product {
/**
* 文章内容
*/
private String productContent;
/**
* 文章内容html
*/
private String productContentHtml;
}

View File

@ -0,0 +1,19 @@
package com.rymcu.forest.dto;
import lombok.Data;
@Data
public class RoleDTO {
// id
private String id;
// 名称
private String name;
// 英文名称
private String inputCode;
// 角色授权菜单ids
private String menuIds;
}

View File

@ -0,0 +1,17 @@
package com.rymcu.forest.dto;
import lombok.Data;
/**
* @author ronger
*/
@Data
public class SearchModel {
private String label;
private String value;
private String type;
}

View File

@ -0,0 +1,27 @@
package com.rymcu.forest.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/**
* @author ronger
*/
@Data
public class TokenUser {
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idUser;
private String account;
private String nickname;
private String token;
private String avatarType;
private String avatarUrl;
private Integer weights;
}

View File

@ -0,0 +1,36 @@
package com.rymcu.forest.dto;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* @author ronger
*/
@Data
public class TransactionRecordDTO {
private Integer idTransactionRecord;
/** 交易流水号 */
private String transactionNo;
/** 款项 */
private String funds;
/** 交易发起方 */
private String formBankAccount;
/** 交易发起方 */
private BankAccountDTO formBankAccountInfo;
/** 交易收款方 */
private String toBankAccount;
/** 交易收款方 */
private BankAccountDTO toBankAccountInfo;
/** 交易金额 */
private BigDecimal money;
/** 交易类型 */
private String transactionType;
/** 交易时间 */
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date transactionTime;
}

View File

@ -0,0 +1,17 @@
package com.rymcu.forest.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/**
* @author ronger
*/
@Data
public class UpdatePasswordDTO {
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idUser;
private String password;
}

View File

@ -0,0 +1,26 @@
package com.rymcu.forest.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/**
* @author ronger
*/
@Data
public class UserDTO {
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idUser;
private String account;
private String avatarType;
private String avatarUrl;
private String nickname;
private String signature;
private String bgImgUrl;
}

View File

@ -0,0 +1,53 @@
package com.rymcu.forest.dto;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import javax.persistence.Column;
import java.io.Serializable;
import java.util.Date;
/**
* @author ronger
*/
@Data
public class UserInfoDTO implements Serializable {
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idUser;
private String account;
private String avatarType;
private String avatarUrl;
private String nickname;
private String email;
private String phone;
private String status;
private String roleIds;
private String sex;
private String signature;
@JSONField(format = "yyyy-MM-dd HH:mm")
private Date lastLoginTime;
@JSONField(format = "yyyy-MM-dd HH:mm")
private Date lastOnlineTime;
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date createdTime;
private Integer onlineStatus;
private String bgImgUrl;
}

View File

@ -0,0 +1,17 @@
package com.rymcu.forest.dto;
import lombok.Data;
/**
* @author ronger
*/
@Data
public class UserRegisterInfoDTO {
private String email;
private String password;
private String code;
}

View File

@ -0,0 +1,12 @@
package com.rymcu.forest.dto;
import lombok.Data;
/**
* @author ronger
*/
@Data
public class UserSearchDTO {
private String nickname;
}

View File

@ -0,0 +1,22 @@
package com.rymcu.forest.dto.admin;
import lombok.Data;
/**
* @author ronger
*/
@Data
public class Dashboard {
private Integer countUserNum;
private Integer newUserNum;
private Integer countArticleNum;
private Integer newArticleNum;
private Integer countViewNum;
private Integer todayViewNum;
}

View File

@ -0,0 +1,15 @@
package com.rymcu.forest.dto.admin;
import lombok.Data;
/**
* @author ronger
*/
@Data
public class DashboardData {
private String label;
private Integer value;
}

View File

@ -0,0 +1,26 @@
package com.rymcu.forest.dto.admin;
import com.rymcu.forest.dto.Author;
import lombok.Data;
/**
* @author ronger
*/
@Data
public class TagDTO {
private Integer idTag;
private String tagTitle;
private String tagUri;
private String tagDescription;
private String tagIconPath;
private Integer tagAuthorId;
private Author tagAuthor;
}

View File

@ -0,0 +1,29 @@
package com.rymcu.forest.dto.admin;
import lombok.Data;
import java.util.List;
/**
* @author ronger
*/
@Data
public class TopicDTO {
private Integer idTopic;
private String topicTitle;
private String topicUri;
private String topicIconPath;
private String topicDescription;
private String topicStatus;
private Integer topicTagCount;
private List<TagDTO> tags;
}

View File

@ -0,0 +1,17 @@
package com.rymcu.forest.dto.admin;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/**
* @author ronger
*/
@Data
public class TopicTagDTO {
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idTopic;
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idTag;
}

View File

@ -0,0 +1,17 @@
package com.rymcu.forest.dto.admin;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/**
* @author ronger
*/
@Data
public class UserRoleDTO {
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idUser;
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idRole;
}

View File

@ -0,0 +1,17 @@
package com.rymcu.forest.dto.baidu;
import lombok.Data;
import java.math.BigDecimal;
/**
* @author ronger
*/
@Data
public class TagNlpDTO {
private BigDecimal score;
private String tag;
}

View File

@ -0,0 +1,58 @@
package com.rymcu.forest.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Date;
/**
* @author ronger
*/
@Data
@Table(name = "forest_article")
public class Article implements Serializable,Cloneable {
/** 主键 */
@Id
@GeneratedValue(generator = "JDBC")
@Column(name = "id")
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idArticle;
/** 文章标题 */
private String articleTitle;
/** 文章缩略图 */
private String articleThumbnailUrl;
/** 文章作者id */
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long articleAuthorId;
/** 文章类型 */
private String articleType;
/** 文章标签 */
private String articleTags;
/** 浏览总数 */
private Integer articleViewCount;
/** 预览内容 */
private String articlePreviewContent;
/** 评论总数 */
private Integer articleCommentCount;
/** 0:非优选1优选; */
private String articlePerfect;
/** 文章永久链接 */
private String articlePermalink;
/** 站内链接 */
private String articleLink;
/** 创建时间 */
private Date createdTime;
/** 更新时间 */
private Date updatedTime;
/** 文章状态 */
private String articleStatus;
/** 点赞总数 */
private Integer articleThumbsUpCount;
/** 赞赏总数 */
private Integer articleSponsorCount;
}

View File

@ -0,0 +1,33 @@
package com.rymcu.forest.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
/**
* @author ronger
*/
@Data
@Table(name = "forest_article_content")
public class ArticleContent {
@Id
@Column(name = "id")
@GeneratedValue(generator = "JDBC")
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idArticle;
private String articleContent;
private String articleContentHtml;
private Date createdTime;
private Date updatedTime;
}

View File

@ -0,0 +1,41 @@
package com.rymcu.forest.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Date;
/**
* @author ronger
*/
@Data
@Table(name="forest_article_thumbs_up")
public class ArticleThumbsUp implements Serializable, Cloneable {
/**
* 主键
*/
@Id
@Column(name = "id")
@GeneratedValue(generator = "JDBC")
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idArticleThumbsUp;
/**
* 文章表主键
*/
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idArticle;
/**
* 用户表主键
*/
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idUser;
/**
* 点赞时间
*/
private Date thumbsUpTime;
}

View File

@ -0,0 +1,40 @@
package com.rymcu.forest.entity;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
/**
* 银行
* @author ronger
*/
@Table(name = "forest_bank")
@Data
public class Bank {
/** 主键 */
@Id
@Column(name = "id")
@GeneratedValue(generator = "JDBC")
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idBank;
/** 银行名称 */
private String bankName;
/** 银行负责人 */
private Long bankOwner;
/** 银行描述 */
private String bankDescription;
/** 创建人 */
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long createdBy;
/** 创建时间 */
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date createdTime;
}

View File

@ -0,0 +1,42 @@
package com.rymcu.forest.entity;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.math.BigDecimal;
import java.util.Date;
/**
* 银行账户
* @author ronger
*/
@Table(name = "forest_bank_account")
@Data
public class BankAccount {
/** 主键 */
@Id
@Column(name = "id")
@GeneratedValue(generator = "JDBC")
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idBankAccount;
/** 所属银行 */
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idBank;
/** 银行账户 */
private String bankAccount;
/** 账户余额 */
private BigDecimal accountBalance;
/** 账户所有者 */
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long accountOwner;
/** 创建时间 */
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date createdTime;
/** 账户类型 */
private String accountType;
}

View File

@ -0,0 +1,25 @@
package com.rymcu.forest.entity;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.io.Serializable;
/**
* 基础DO类提供toString快方法
*
* @author liwei
* @date 2015/6/16
*/
public class BaseDO implements Serializable {
private static final long serialVersionUID = -1394589131426860408L;
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}

View File

@ -0,0 +1,64 @@
package com.rymcu.forest.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Date;
/**
* @author ronger
*/
@Data
@Table(name="forest_comment")
public class Comment implements Serializable,Cloneable {
/** 主键 */
@Id
@GeneratedValue(generator = "JDBC")
@Column(name = "id")
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long idComment;
/** 评论内容 */
@Column(name = "comment_content")
private String commentContent;
/** 作者 id */
@Column(name = "comment_author_id")
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long commentAuthorId;
/** 文章 id */
@Column(name = "comment_article_id")
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long commentArticleId;
/** 锚点 url */
@Column(name = "comment_sharp_url")
private String commentSharpUrl;
/** 父评论 id */
@Column(name = "comment_original_comment_id")
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long commentOriginalCommentId;
/** 状态 */
@Column(name = "comment_status")
private String commentStatus;
/** 评论 IP */
@Column(name = "comment_ip")
private String commentIP;
/** User-Agent */
@Column(name = "comment_ua")
private String commentUA;
/** 0公开回帖1匿名回帖 */
@Column(name = "comment_anonymous")
private String commentAnonymous;
/** 回帖计数 */
@Column(name = "comment_reply_count")
private Integer commentReplyCount;
/** 0所有人可见1仅楼主和自己可见 */
@Column(name = "comment_visible")
private String commentVisible;
/** 创建时间 */
@Column(name = "created_time")
private Date createdTime;
}

View File

@ -0,0 +1,32 @@
package com.rymcu.forest.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.math.BigDecimal;
import java.util.Date;
/**
* 货币发行记录
* @author ronger
*/
@Table(name = "forest_currency_issue")
@Data
public class CurrencyIssue {
/** 主键 */
@Id
@GeneratedValue(generator = "JDBC")
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long id;
/** 发行数额 */
private BigDecimal issueValue;
/** 发行人 */
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long createdBy;
/** 发行时间 */
private Date createdTime;
}

Some files were not shown because too many files have changed in this diff Show More