Merge remote-tracking branch 'origin/lucene-dev' into lucene-dev

# Conflicts:
#	src/main/java/com/rymcu/forest/web/api/common/CommonApiController.java
This commit is contained in:
ronger 2021-04-29 10:29:37 +08:00
commit 35075e62c0
62 changed files with 1138 additions and 643 deletions

304
README.md
View File

@ -1,5 +1,5 @@
# forest
![forest](src/main/resources/static/logo_size.jpg)
下一代的知识社区系统,为未来而建
## 💡 简介
@ -8,244 +8,72 @@ forest[ˈfôrəst]n.森林)是一款现代化的知识社区项目,使
## ⚡ 动机
在 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) 的基础上进一步探索。
在 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 标记符的即时渲染
类富文本编辑器的所见即所得
注册
用户名
Email
验证码
邮件验证
新手向导
上传头像
关注标签
关注用户
帮助指引
登录
账户(用户名/Email
密码
忘记密码
Email
验证码
发帖
帖子类型
普通帖子
标题
正文
内容编辑器
本地存储
标签
使用已有(选择、自动完成)或创建
过滤
黑白名单
规范化
默认“待分类”
发布后
可更新
可删除
回帖
内容编辑器
本地存储
回复(回复针对回帖)
货币
货币规则
浏览帖子
实时热度
编辑自己的帖子
发布时间/浏览数/回帖数/标签
上一篇/下一篇
分享
微信
QQ
分享链接(带用户标识)
相关帖子
缩略摘要
帖子列表
专题
相关标签
最新
优选
搜索
标签
相关标签
创建者
贡献者
关注/引用/回帖数
所属专题
关注
排序
默认(按发布时间降序)
热议(按回帖数降序)
好评(按评分降序)
最近回帖(按最近回帖时间降序)
首图
实时热度
最新回复
回帖数
创建时间
后台管理
后台首页
数据统计
当前在线
会员在线
最高在线
会员
帖子
专题
标签
回帖
用户管理
按用户名/邮件搜索
添加新用户
用户数据维护
各字段数据
用户状态
正常
未验证
限制登录
封禁
高级更新
用户名
邮箱地址
帖子管理
按 id 搜索帖子
重建所有帖子搜索索引
添加新帖
帖子数据维护
锁定帖子
删除帖子
专题管理
按名称搜索专题
添加专题
专题数据维护
添加/移除相关标签
名称
URI
描述
图标
是否用于导航
状态
CSS
SEO
title
keywords
description
删除专题
标签管理
按名称搜索标签
添加标签
标签数据维护
名称
URI
描述
图标
状态
CSS
SEO
title
keywords
description
角色管理
内建角色
管理员
社区管理员
作者
普通用户
创建角色
功能权限
浏览统计
实时统计
会员
帖子
专题
标签
回帖
数据统计
最近 30 天
历史
新发贴数
新用户数
消息通知
消息数
标记已读
消息类别
收到的回帖
收到的回复
收到的评论
提及我的
关注
我关注的
关注的用户发帖
个人主页
用户统计数据
积分
关注标签
收藏帖子
关注者
关注用户
站点连接
首页
专题、发帖、通知、个人等入口
专题导航列表
对搜索引擎爬虫友好
用户设置
基本信息
昵称
URL
个性签名
个人主页背景图
个人卡片背景图
站点连接
GitHub
微博
QQ
微信
头像
账号
绑定邮箱
更新密码
更新用户名
永久停用账号
钱包
交易记录
帮助
使用入门
基础知识
Markdown 教程
评论
发布评论
```
- 内容编辑器
- MarkdownGFM
- emoji
- 上传文件
- 图片
- 文件
- 单独渲染 MP3 文件
- 单独渲染视频文件
- 剪切板处理
- 粘贴内容处理为 Markdown
- 粘贴图片自动重新上传
- 数学公式LaTeX、流程图支持
- 工具栏
- 表情
- 粗体
- 斜体
- 引用
- 无序列表
- 有序列表
- 链接
- 上传
- 预览
- 全屏
- 编辑模式
- 传统的 Markdown 分屏编辑预览
- 保留 Markdown 标记符的即时渲染
- 类富文本编辑器的所见即所得
- 注册
- 用户名
- 邮箱
- 验证码
- 登录
- 账户(用户名/邮箱)
- 密码
- 忘记密码
- 邮箱
- 邮箱验证
- 发帖
- 帖子类型
- 普通帖子
- 标题
- 正文
- 内容编辑器
- 标签
- 使用已有(选择、自动完成)或创建
- 默认“待分类”
- 发布后
- 可更新
- 可删除
- 回帖
- 内容编辑器
- 回复(回复针对回帖)
- 货币
- 货币规则
- 浏览贴子
- 编辑自己的帖子
- 发布时间/浏览数/标签
- 分享
- 微信
- 分享链接(带用户标识)
## 报告缺陷
@ -261,3 +89,7 @@ forest 在很多方面受到了 [Symphony](https://github.com/88250/symphony)
欢迎对社区提出功能特性方面的建议,我们一起讨论,如果有可能我们会尽快实现。
在提功能建议前可以先看一下 [计划表](https://rymcu.com/article/29) ,避免重复提议
## 鸣谢
- 感谢 `JetBrains` 对本项目的帮助,为作者提供了开源许可版 `JetBrains` 全家桶
![JetBrains](src/main/resources/static/jetbrains.png)

17
pom.xml
View File

@ -89,19 +89,19 @@
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.10</version>
<version>5.2.0</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.67</version>
<version>1.2.76</version>
</dependency>
<!-- shiro权限控制框架 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
<version>1.7.1</version>
</dependency>
<!-- shiro-redis -->
<dependency>
@ -173,7 +173,7 @@
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-open</artifactId>
<version>3.9.0</version>
<version>4.0.0</version>
<exclusions>
<exclusion>
<groupId>commons-codec</groupId>
@ -183,8 +183,17 @@
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</exclusion>
<exclusion>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.16</version>
</dependency>
<dependency>
<groupId>com.github.jedis-lock</groupId>
<artifactId>jedis-lock</artifactId>

View File

@ -3,6 +3,9 @@ package com.rymcu.forest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author ronger
*/
@SpringBootApplication
public class ForestApplication {

View File

@ -2,9 +2,10 @@ package com.rymcu.forest.answer;
import com.alibaba.fastjson.JSONObject;
import com.rymcu.forest.core.result.GlobalResult;
import com.rymcu.forest.core.result.GlobalResultGenerator;
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;
@ -30,6 +31,7 @@ public class AnswerController {
}
@PostMapping("/answer")
@TransactionLogger(transactionType = TransactionEnum.Answer)
public GlobalResult answer(@RequestBody AnswerDTO answerDTO) throws BaseApiException {
User user = UserUtils.getCurrentUserByToken();
Map params = new HashMap<>(3);

View File

@ -2,8 +2,10 @@ package com.rymcu.forest.config;
import com.alibaba.fastjson.support.spring.FastJsonJsonView;
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.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
@ -25,7 +27,7 @@ import java.util.Map;
* 全局异常处理器
*
* @author ronger
* */
*/
@RestControllerAdvice
public class BaseExceptionHandler {
@ -59,6 +61,9 @@ public class BaseExceptionHandler {
} else if (ex instanceof ServletException) {
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());
@ -102,6 +107,9 @@ public class BaseExceptionHandler {
} else if (ex instanceof ServletException) {
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());

View File

@ -29,7 +29,7 @@ import java.util.List;
* @author ronger
* @since 2018/05/28 11:00
* 自定义权限匹配和账号密码匹配
* */
*/
public class BaseShiroRealm extends AuthorizingRealm {
@Resource
private RoleService roleService;
@ -70,7 +70,7 @@ public class BaseShiroRealm extends AuthorizingRealm {
/**
* 认证回调函数, 登录时调用主要是用来进行身份认证的也就是说验证用户输入的账号和密码是否正确
* */
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
@ -90,7 +90,8 @@ public class BaseShiroRealm extends AuthorizingRealm {
if (user == null) {
return null;
}
if (!"0".equals(user.getStatus())) { //账户冻结(是否允许登陆)
// 账户冻结(是否允许登陆)
if (!"0".equals(user.getStatus())) {
throw new LockedAccountException();
}
byte[] salt = Encodes.decodeHex(user.getPassword().substring(0, 16));

View File

@ -45,7 +45,7 @@ public class MybatisConfigurer {
//添加XML目录
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
factory.setMapperLocations(resolver.getResources("classpath:mapper/**/*.xml"));
factory.setTypeHandlersPackage("com.rymcu.forest.util.handlers");
// factory.setTypeHandlersPackage("com.rymcu.forest.util.handlers");
return factory.getObject();
}

View File

@ -55,6 +55,7 @@ public class ShiroConfig implements EnvironmentAware {
filterChainDefinitionMap.put("/api/**", "anon");
filterChainDefinitionMap.put("/ws/**", "anon");
filterChainDefinitionMap.put("/wss/**", "anon");
filterChainDefinitionMap.put("/wx/**", "anon");
filterChainDefinitionMap.put("/**", "auth");
//配置shiro默认登录界面地址前后端分离中登录界面跳转应由前端路由控制后台仅返回json数据

View File

@ -20,6 +20,7 @@ public class WebSocketStompConfig implements WebSocketMessageBrokerConfigurer {
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 允许使用socketJs方式访问 即可通过http://IP:PORT/ws来和服务端websocket连接
registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS();
registry.addEndpoint("/wss").setAllowedOrigins("*").withSockJS();
}
/**
@ -30,7 +31,7 @@ public class WebSocketStompConfig implements WebSocketMessageBrokerConfigurer {
public void configureMessageBroker(MessageBrokerRegistry registry) {
// 订阅Broker名称 user点对点 topic广播即群发
registry.enableSimpleBroker("/user","/public");
registry.enableSimpleBroker("/topic", "/user");
// 全局(客户端)使用的消息前缀
registry.setApplicationDestinationPrefixes("/app");
// 点对点使用的前缀 无需配置 默认/user

View File

@ -3,9 +3,9 @@ package com.rymcu.forest.core.constant;
/**
* Shiro通用常量
*
* @author ronger
*/
public interface ShiroConstants
{
public interface ShiroConstants {
/**
* 当前登录的用户
*/
@ -54,7 +54,7 @@ public interface ShiroConstants
/**
* 验证码
*/
public static final String CURRENT_VALIDATECODE = "validateCode";
public static final String CURRENT_VALIDATE_CODE = "validateCode";
/**
* 验证码错误

View File

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

View File

@ -2,18 +2,22 @@ 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, "参数错误"),
;
// 成功
SUCCESS(1, "SUCCESS"),
// 失败
FAIL(400, "访问失败"),
// 未认证签名错误
UNAUTHORIZED(401, "签名错误"),
// 接口不存在
NOT_FOUND(404, "此接口不存在"),
// 服务器内部错误
INTERNAL_SERVER_ERROR(500, "系统繁忙,请稍后再试"),
// 参数错误
INVALID_PARAM(10000, "参数错误");
private int code;
private String message;

View File

@ -13,13 +13,17 @@ import java.util.List;
/**
* 基于通用MyBatis Mapper插件的Service接口的实现
*
* @author ronger
*/
public abstract class AbstractService<T> implements Service<T> {
@Autowired
protected Mapper<T> mapper;
private Class<T> modelClass; // 当前泛型真实类型的Class
/**
* 当前泛型真实类型的Class
*/
private Class<T> modelClass;
public AbstractService() {
ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();

View File

@ -8,16 +8,85 @@ import java.util.List;
/**
* Service 基础接口其他Service 接口 请继承该接口
*
* @author ronger
*/
public interface Service<T> {
void save(T model);//持久化
void save(List<T> models);//批量持久化
void deleteById(String id);//通过主鍵刪除
void deleteByIds(String ids);//批量刪除 egids -> 1,2,3,4
void update(T model);//更新
T findById(String id);//通过ID查找
T findBy(String fieldName, Object value) throws TooManyResultsException, ServiceException; //通过Model中某个成员变量名称非数据表中column的名称查找,value需符合unique约束
List<T> findByIds(String ids);//通过多个ID查找//egids -> 1,2,3,4
List<T> findByCondition(Condition condition);//根据条件查找
List<T> findAll();//获取所有
/**
* 持久化
*
* @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,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

@ -12,6 +12,8 @@ public class Author {
private String userNickname;
private String userAccount;
private String userAvatarURL;
private String userArticleCount;

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

@ -1,30 +0,0 @@
package com.rymcu.forest.enumerate;
/**
* @author ronger
*/
public enum SponsorEnum {
Article("0", 20);
private String dataType;
private Integer money;
SponsorEnum(String dataType, Integer money) {
this.dataType = dataType;
this.money = money;
}
public String getDataType() {
return this.dataType;
}
public Integer getMoney() {
return this.money;
}
public boolean isArticle() {
return Article.equals(this);
}
}

View File

@ -0,0 +1,28 @@
package com.rymcu.forest.enumerate;
/**
* @author ronger
*/
public enum TransactionCode {
InsufficientBalance(901, "余额不足");
private int code;
private String message;
TransactionCode(int code, String message) {
this.code = code;
this.message = message;
}
public String getMessage() {
return this.message;
}
public int getCode() {
return this.code;
}
}

View File

@ -0,0 +1,45 @@
package com.rymcu.forest.enumerate;
import java.util.Arrays;
/**
* @author ronger
*/
public enum TransactionEnum {
ArticleSponsor("0", 20, "文章赞赏"),
Answer("1", 30, "答题奖励"),
CorrectAnswer("2", 50, "答题奖励");
private String dataType;
private Integer money;
private String description;
TransactionEnum(String dataType, Integer money, String description) {
this.dataType = dataType;
this.money = money;
this.description = description;
}
public static TransactionEnum findTransactionEnum(String dataType) {
return Arrays.stream(TransactionEnum.values()).filter(transactionEnum -> transactionEnum.getDataType().equals(dataType)).findFirst().orElse(TransactionEnum.ArticleSponsor);
}
public String getDataType() {
return this.dataType;
}
public Integer getMoney() {
return this.money;
}
public String getDescription() {
return this.description;
}
public boolean isArticleSponsor() {
return ArticleSponsor.equals(this);
}
}

View File

@ -184,4 +184,10 @@ public interface ArticleMapper extends Mapper<Article> {
* @return
*/
int updatePerfect(@Param("idArticle") Integer idArticle, @Param("articlePerfect") String articlePerfect);
/**
* 删除文章关联文章内容表信息
* @param idArticle
*/
void deleteArticleContent(@Param("idArticle") Integer idArticle);
}

View File

@ -1,6 +1,7 @@
package com.rymcu.forest.mapper;
import com.rymcu.forest.core.mapper.Mapper;
import com.rymcu.forest.dto.NotificationDTO;
import com.rymcu.forest.entity.Notification;
import org.apache.ibatis.annotations.Param;
@ -23,7 +24,7 @@ public interface NotificationMapper extends Mapper<Notification> {
* @param idUser
* @return
*/
List<Notification> selectNotifications(@Param("idUser") Integer idUser);
List<NotificationDTO> selectNotifications(@Param("idUser") Integer idUser);
/**
* 获取消息数据

View File

@ -73,4 +73,10 @@ public interface PortfolioMapper extends Mapper<Portfolio> {
* @return
*/
Integer unbindArticle(@Param("idPortfolio") Integer idPortfolio, @Param("idArticle") Integer idArticle);
/**
* 获取作品集列表数据
* @return
*/
List<Portfolio> selectPortfolios();
}

View File

@ -27,4 +27,12 @@ public interface TransactionRecordMapper extends Mapper<TransactionRecord> {
* @return
*/
List<TransactionRecordDTO> selectTransactionRecords(@Param("bankAccount") String bankAccount);
/**
* 校验今日是否已发放答题奖励
* @param bankAccount
* @param funds
* @return
*/
Boolean existsWithBankAccountAndFunds(@Param("bankAccount") String bankAccount, @Param("funds") String funds);
}

View File

@ -4,9 +4,12 @@ import com.rymcu.forest.core.mapper.Mapper;
import com.rymcu.forest.dto.Author;
import com.rymcu.forest.dto.UserDTO;
import com.rymcu.forest.dto.UserInfoDTO;
import com.rymcu.forest.dto.UserSearchDTO;
import com.rymcu.forest.entity.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @author ronger
*/
@ -36,10 +39,10 @@ public interface UserMapper extends Mapper<User> {
/**
* 根据用户昵称获取用户信息
* @param nickname
* @param account
* @return
*/
UserDTO selectUserDTOByNickname(@Param("nickname") String nickname);
UserDTO selectUserDTOByAccount(@Param("account") String account);
/**
* 修改用户密码
@ -136,4 +139,11 @@ public interface UserMapper extends Mapper<User> {
* @return
*/
Integer updatePasswordById(@Param("idUser") Integer idUser, @Param("password") String password);
/**
* 查询用户数据
* @param searchDTO
* @return
*/
List<User> selectUsers(@Param("searchDTO") UserSearchDTO searchDTO);
}

View File

@ -67,7 +67,7 @@ public interface ArticleService extends Service<Article> {
* @param id
* @return
* */
Map delete(Integer id);
Map delete(Integer id) throws BaseApiException;
/**
* 增量文章浏览数

View File

@ -32,4 +32,10 @@ public interface BankAccountService extends Service<BankAccount> {
* @return
*/
BankAccount findByBankAccount(String bankAccount);
/**
* 查询系统社区银行
* @return
*/
BankAccount findSystemBankAccount();
}

View File

@ -1,5 +1,7 @@
package com.rymcu.forest.service;
import com.rymcu.forest.dto.NotificationDTO;
import javax.mail.MessagingException;
/**
@ -12,6 +14,7 @@ public interface JavaMailService {
* 发送验证码邮件
* @param email 收件人邮箱
* @return 执行结果 0失败1成功
* @throws MessagingException
* */
Integer sendEmailCode(String email) throws MessagingException;
@ -19,6 +22,15 @@ public interface JavaMailService {
* 发送找回密码邮件
* @param email 收件人邮箱
* @return 执行结果 0失败1成功
* @throws MessagingException
* */
Integer sendForgetPasswordEmail(String email) throws MessagingException;
/**
* 发送下消息通知
* @param notification
* @return
* @throws MessagingException
*/
Integer sendNotification(NotificationDTO notification) throws MessagingException;
}

View File

@ -77,4 +77,10 @@ public interface PortfolioService extends Service<Portfolio> {
* @return
*/
Map deletePortfolio(Integer idPortfolio);
/**
* 获取作品集列表数据
* @return
*/
List<Portfolio> findPortfolios();
}

View File

@ -3,6 +3,7 @@ package com.rymcu.forest.service;
import com.rymcu.forest.core.service.Service;
import com.rymcu.forest.dto.TransactionRecordDTO;
import com.rymcu.forest.entity.TransactionRecord;
import com.rymcu.forest.enumerate.TransactionEnum;
import java.math.BigDecimal;
import java.util.List;
@ -30,9 +31,18 @@ public interface TransactionRecordService extends Service<TransactionRecord> {
* 根据用户主键进行交易
* @param toUserId
* @param formUserId
* @param money
* @param transactionType
* @return
* @throws Exception
*/
TransactionRecord transferByUserId(Integer toUserId, Integer formUserId, BigDecimal money) throws Exception;
TransactionRecord userTransfer(Integer toUserId, Integer formUserId, TransactionEnum transactionType) throws Exception;
/**
* 社区银行转账/奖励发放
* @param idUser
* @param transactionType
* @return
* @throws Exception
*/
TransactionRecord bankTransfer(Integer idUser, TransactionEnum transactionType) throws Exception;
}

View File

@ -6,6 +6,7 @@ import com.rymcu.forest.entity.User;
import com.rymcu.forest.entity.UserExtend;
import org.apache.ibatis.exceptions.TooManyResultsException;
import java.util.List;
import java.util.Map;
@ -42,11 +43,11 @@ public interface UserService extends Service<User> {
Map login(String account, String password);
/**
* 通过 nickname 获取用户信息接口
* @param nickname 昵称
* 通过 account 获取用户信息接口
* @param account 昵称
* @return UserDTO
* */
UserDTO findUserDTOByNickname(String nickname);
UserDTO findUserDTOByAccount(String account);
/**
* 找回密码接口
@ -117,10 +118,10 @@ public interface UserService extends Service<User> {
/**
* 获取用户扩展信息
* @param nickname
* @param account
* @return
*/
UserExtend selectUserExtendByNickname(String nickname);
UserExtend selectUserExtendByAccount(String account);
/**
* 更换邮箱
@ -135,4 +136,11 @@ public interface UserService extends Service<User> {
* @return
*/
Map updatePassword(UpdatePasswordDTO updatePasswordDTO);
/**
* 查询用户列表
* @param searchDTO
* @return
*/
List<User> findUsers(UserSearchDTO searchDTO);
}

View File

@ -203,10 +203,15 @@ public class ArticleServiceImpl extends AbstractService<Article> implements Arti
}
if (StringUtils.isNotBlank(articleContentHtml)) {
String previewContent = BaiDuAipUtils.getNewsSummary(newArticle.getArticleTitle(), articleContentHtml, MAX_PREVIEW);
String previewContent;
if (articleContentHtml.length() > MAX_PREVIEW) {
previewContent = BaiDuAipUtils.getNewsSummary(newArticle.getArticleTitle(), articleContentHtml, MAX_PREVIEW);
if (previewContent.length() > MAX_PREVIEW) {
previewContent = previewContent.substring(0, MAX_PREVIEW);
}
} else {
previewContent = Html2TextUtil.getContent(articleContentHtml);
}
newArticle.setArticlePreviewContent(previewContent);
}
articleMapper.updateByPrimaryKeySelective(newArticle);
@ -260,8 +265,18 @@ public class ArticleServiceImpl extends AbstractService<Article> implements Arti
@Override
@Transactional(rollbackFor = Exception.class)
public Map delete(Integer id) {
public Map delete(Integer id) throws BaseApiException {
Map<String, String> map = new HashMap(1);
// 鉴权
User user = UserUtils.getCurrentUserByToken();
Integer roleWeights = userService.findRoleWeightsByUser(user.getIdUser());
if (roleWeights > 2) {
Article article = articleMapper.selectByPrimaryKey(id);
if (!user.getIdUser().equals(article.getArticleAuthorId())) {
map.put("message", "非法访问!");
return map;
}
}
Integer result;
// 判断是否有评论
boolean isHavComment = articleMapper.existsCommentWithPrimaryKey(id);
@ -285,6 +300,8 @@ public class ArticleServiceImpl extends AbstractService<Article> implements Arti
articleMapper.deleteLinkedPortfolioData(id);
// 删除引用标签记录
articleMapper.deleteTagArticle(id);
// 删除文章内容表
articleMapper.deleteArticleContent(id);
}
@Override
@ -398,9 +415,11 @@ public class ArticleServiceImpl extends AbstractService<Article> implements Arti
private Author genAuthor(ArticleDTO article) {
Author author = new Author();
User user = userService.findById(String.valueOf(article.getArticleAuthorId()));
author.setUserNickname(article.getArticleAuthorName());
author.setUserAvatarURL(article.getArticleAuthorAvatarUrl());
author.setIdUser(article.getArticleAuthorId());
author.setUserAccount(user.getAccount());
return author;
}
}

View File

@ -67,6 +67,15 @@ public class BankAccountServiceImpl extends AbstractService<BankAccount> impleme
return bankAccountMapper.selectOne(searchBankAccount);
}
@Override
public BankAccount findSystemBankAccount() {
BankAccount bankAccount = new BankAccount();
bankAccount.setIdBank(1);
bankAccount.setAccountType("1");
bankAccount.setAccountOwner(2);
return bankAccountMapper.selectOne(bankAccount);
}
private String nextBankAccount() {
String bankAccount = "600000001";
String maxBankAccount = bankAccountMapper.selectMaxBankAccount();

View File

@ -1,7 +1,11 @@
package com.rymcu.forest.service.impl;
import com.rymcu.forest.core.constant.NotificationConstant;
import com.rymcu.forest.core.service.redis.RedisService;
import com.rymcu.forest.dto.NotificationDTO;
import com.rymcu.forest.entity.User;
import com.rymcu.forest.service.JavaMailService;
import com.rymcu.forest.service.UserService;
import com.rymcu.forest.util.Utils;
import org.apache.commons.lang.time.StopWatch;
import org.springframework.beans.factory.annotation.Value;
@ -34,6 +38,8 @@ public class JavaMailServiceImpl implements JavaMailService {
private JavaMailSenderImpl mailSender;
@Resource
private RedisService redisService;
@Resource
private UserService userService;
/**
* thymeleaf模板引擎
*/
@ -61,6 +67,43 @@ public class JavaMailServiceImpl implements JavaMailService {
return sendCode(email, 1);
}
@Override
public Integer sendNotification(NotificationDTO notification) throws MessagingException {
Properties props = new Properties();
// 表示SMTP发送邮件需要进行身份验证
props.put("mail.smtp.auth", true);
props.put("mail.smtp.ssl.enable", true);
props.put("mail.smtp.host", SERVER_HOST);
props.put("mail.smtp.port", SERVER_PORT);
// 如果使用ssl则去掉使用25端口的配置进行如下配置,
props.put("mail.smtp.socketFactory.class", "com.rymcu.forest.util.MailSSLSocketFactory");
props.put("mail.smtp.socketFactory.port", SERVER_PORT);
// 发件人的账号填写控制台配置的发信地址,比如xxx@xxx.com
props.put("mail.user", USERNAME);
// 访问SMTP服务时需要提供的密码(在控制台选择发信地址进行设置)
props.put("mail.password", PASSWORD);
mailSender.setJavaMailProperties(props);
User user = userService.findById(String.valueOf(notification.getIdUser()));
if (NotificationConstant.Comment.equals(notification.getDataType())) {
String url = notification.getDataUrl();
String thymeleafTemplatePath = "mail/commentNotification";
Map<String, Object> thymeleafTemplateVariable = new HashMap<String, Object>(4);
thymeleafTemplateVariable.put("user", notification.getAuthor().getUserNickname());
thymeleafTemplateVariable.put("articleTitle", notification.getDataTitle());
thymeleafTemplateVariable.put("content", notification.getDataSummary());
thymeleafTemplateVariable.put("url", url);
sendTemplateEmail(USERNAME,
new String[]{user.getEmail()},
new String[]{},
"【RYMCU】 消息通知",
thymeleafTemplatePath,
thymeleafTemplateVariable);
return 1;
}
return 0;
}
private Integer sendCode(String to, Integer type) throws MessagingException {
Properties props = new Properties();
// 表示SMTP发送邮件需要进行身份验证
@ -92,7 +135,7 @@ public class JavaMailServiceImpl implements JavaMailService {
redisService.set(code, to, 15 * 60);
String thymeleafTemplatePath = "mail/forgetPasswordTemplate";
Map<String, Object> thymeleafTemplateVariable = new HashMap<String, Object>();
Map<String, Object> thymeleafTemplateVariable = new HashMap<String, Object>(1);
thymeleafTemplateVariable.put("url", url);
sendTemplateEmail(USERNAME,
@ -109,18 +152,12 @@ public class JavaMailServiceImpl implements JavaMailService {
/**
* 发送thymeleaf模板邮件
*
* @param deliver
* 发送人邮箱名 javalsj@163.com
* @param receivers
* 收件人可多个收件人 11111@qq.com,2222@163.com
* @param carbonCopys
* 抄送人可多个抄送人 33333@sohu.com
* @param subject
* 邮件主题 您收到一封高大上的邮件请查收
* @param thymeleafTemplatePath
* 邮件模板 mail\mailTemplate.html
* @param thymeleafTemplateVariable
* 邮件模板变量集
* @param deliver 发送人邮箱名 javalsj@163.com
* @param receivers 收件人可多个收件人 11111@qq.com,2222@163.com
* @param carbonCopys 抄送人可多个抄送人 33333@sohu.com
* @param subject 邮件主题 您收到一封高大上的邮件请查收
* @param thymeleafTemplatePath 邮件模板 mail\mailTemplate.html
* @param thymeleafTemplateVariable 邮件模板变量集
*/
public void sendTemplateEmail(String deliver, String[] receivers, String[] carbonCopys, String subject, String thymeleafTemplatePath,
Map<String, Object> thymeleafTemplateVariable) throws MessagingException {
@ -136,22 +173,15 @@ public class JavaMailServiceImpl implements JavaMailService {
/**
* 发送的邮件(支持带附件/html类型的邮件)
*
* @param deliver
* 发送人邮箱名 javalsj@163.com
* @param receivers
* 收件人可多个收件人 11111@qq.com,2222@163.com
* @param carbonCopys
* 抄送人可多个抄送人 3333@sohu.com
* @param subject
* 邮件主题 您收到一封高大上的邮件请查收
* @param text
* 邮件内容 测试邮件逗你玩的 <html><body><img
* @param deliver 发送人邮箱名 javalsj@163.com
* @param receivers 收件人可多个收件人 11111@qq.com,2222@163.com
* @param carbonCopys 抄送人可多个抄送人 3333@sohu.com
* @param subject 邮件主题 您收到一封高大上的邮件请查收
* @param text 邮件内容 测试邮件逗你玩的 <html><body><img
* src=\"cid:attchmentFileName\"></body></html>
* @param attachmentFilePaths
* 附件文件路径
* @param attachmentFilePaths 附件文件路径
* 需要注意的是addInline函数中资源名称attchmentFileName需要与正文中cid:attchmentFileName对应起来
* @throws Exception
* 邮件发送过程中的异常信息
* @throws Exception 邮件发送过程中的异常信息
*/
private void sendMimeMail(String deliver, String[] receivers, String[] carbonCopys, String subject, String text,
boolean isHtml, String[] attachmentFilePaths) throws MessagingException {
@ -186,7 +216,6 @@ public class JavaMailServiceImpl implements JavaMailService {
}
mailSender.send(mimeMessage);
stopWatch.stop();
//logger.info("邮件发送成功, 花费时间{}秒", stopWatch.getStartTime());
}

View File

@ -1,22 +1,16 @@
package com.rymcu.forest.service.impl;
import com.rymcu.forest.core.service.AbstractService;
import com.rymcu.forest.dto.ArticleDTO;
import com.rymcu.forest.dto.Author;
import com.rymcu.forest.dto.NotificationDTO;
import com.rymcu.forest.entity.Comment;
import com.rymcu.forest.entity.Follow;
import com.rymcu.forest.entity.Notification;
import com.rymcu.forest.entity.User;
import com.rymcu.forest.mapper.NotificationMapper;
import com.rymcu.forest.service.*;
import com.rymcu.forest.service.NotificationService;
import com.rymcu.forest.util.BeanCopierUtil;
import org.springframework.beans.factory.annotation.Value;
import com.rymcu.forest.util.NotificationUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@ -28,16 +22,6 @@ public class NotificationServiceImpl extends AbstractService<Notification> imple
@Resource
private NotificationMapper notificationMapper;
@Resource
private ArticleService articleService;
@Resource
private CommentService commentService;
@Resource
private UserService userService;
@Resource
private FollowService followService;
@Value("${resource.domain}")
private String domain;
private final static String unRead = "0";
@ -49,13 +33,12 @@ public class NotificationServiceImpl extends AbstractService<Notification> imple
@Override
public List<NotificationDTO> findNotifications(Integer idUser) {
List<Notification> list = notificationMapper.selectNotifications(idUser);
List<NotificationDTO> notifications = new ArrayList<>();
List<NotificationDTO> list = notificationMapper.selectNotifications(idUser);
list.forEach(notification -> {
NotificationDTO notificationDTO = genNotification(notification);
NotificationDTO notificationDTO = NotificationUtils.genNotification(notification);
// 判断关联数据是否已删除
if (Objects.nonNull(notificationDTO.getAuthor())) {
notifications.add(notificationDTO);
BeanCopierUtil.copy(notificationDTO, notification);
} else {
// 关联数据已删除,且未读
if (unRead.equals(notification.getHasRead())) {
@ -66,88 +49,10 @@ public class NotificationServiceImpl extends AbstractService<Notification> imple
dto.setDataType("-1");
dto.setHasRead("1");
dto.setCreatedTime(notification.getCreatedTime());
notifications.add(dto);
BeanCopierUtil.copy(dto, notification);
}
});
return notifications;
}
private NotificationDTO genNotification(Notification notification) {
NotificationDTO notificationDTO = new NotificationDTO();
BeanCopierUtil.copy(notification, notificationDTO);
ArticleDTO article;
Comment comment;
User user;
Follow follow;
switch (notification.getDataType()) {
case "0":
// 系统公告/帖子
article = articleService.findArticleDTOById(notification.getDataId(), 0);
if (Objects.nonNull(article)) {
notificationDTO.setDataTitle("系统公告");
notificationDTO.setDataUrl(article.getArticlePermalink());
user = userService.findById(article.getArticleAuthorId().toString());
notificationDTO.setAuthor(genAuthor(user));
}
break;
case "1":
// 关注
follow = followService.findById(notification.getDataId().toString());
notificationDTO.setDataTitle("关注提醒");
if (Objects.nonNull(follow)) {
user = userService.findById(follow.getFollowerId().toString());
notificationDTO.setDataUrl(getFollowLink(follow.getFollowingType(), user.getNickname()));
notificationDTO.setAuthor(genAuthor(user));
}
break;
case "2":
// 回帖
comment = commentService.findById(notification.getDataId().toString());
article = articleService.findArticleDTOById(comment.getCommentArticleId(), 0);
if (Objects.nonNull(article)) {
notificationDTO.setDataTitle(article.getArticleTitle());
notificationDTO.setDataUrl(comment.getCommentSharpUrl());
user = userService.findById(comment.getCommentAuthorId().toString());
notificationDTO.setAuthor(genAuthor(user));
}
break;
case "3":
// 关注用户发布文章
case "4":
// 关注文章更新
article = articleService.findArticleDTOById(notification.getDataId(), 0);
if (Objects.nonNull(article)) {
notificationDTO.setDataTitle("关注通知");
notificationDTO.setDataUrl(article.getArticlePermalink());
user = userService.findById(article.getArticleAuthorId().toString());
notificationDTO.setAuthor(genAuthor(user));
}
break;
default:
break;
}
return notificationDTO;
}
private String getFollowLink(String followingType, String id) {
StringBuilder url = new StringBuilder();
url.append(domain);
switch (followingType) {
case "0":
url = url.append("/user/").append(id);
break;
default:
url.append("/notification");
}
return url.toString();
}
private Author genAuthor(User user) {
Author author = new Author();
author.setUserNickname(user.getNickname());
author.setUserAvatarURL(user.getAvatarUrl());
author.setIdUser(user.getIdUser());
return author;
return list;
}
@Override

View File

@ -41,10 +41,7 @@ public class PortfolioServiceImpl extends AbstractService<Portfolio> implements
@Override
public List<PortfolioDTO> findUserPortfoliosByUser(UserDTO userDTO) {
List<PortfolioDTO> list = portfolioMapper.selectUserPortfoliosByIdUser(userDTO.getIdUser());
Author author = new Author();
author.setIdUser(userDTO.getIdUser());
author.setUserAvatarURL(userDTO.getAvatarUrl());
author.setUserNickname(userDTO.getNickname());
Author author = userService.selectAuthor(userDTO.getIdUser());
list.forEach(portfolioDTO -> {
genPortfolioAuthor(portfolioDTO,author);
});
@ -190,6 +187,11 @@ public class PortfolioServiceImpl extends AbstractService<Portfolio> implements
return map;
}
@Override
public List<Portfolio> findPortfolios() {
return portfolioMapper.selectPortfolios();
}
private PortfolioDTO genPortfolioAuthor(PortfolioDTO portfolioDTO, Author author) {
portfolioDTO.setPortfolioAuthorAvatarUrl(author.getUserAvatarURL());
portfolioDTO.setPortfolioAuthorName(author.getUserNickname());

View File

@ -1,11 +1,14 @@
package com.rymcu.forest.service.impl;
import com.rymcu.forest.core.exception.TransactionException;
import com.rymcu.forest.core.service.AbstractService;
import com.rymcu.forest.dto.ArticleDTO;
import com.rymcu.forest.entity.Sponsor;
import com.rymcu.forest.entity.TransactionRecord;
import com.rymcu.forest.entity.User;
import com.rymcu.forest.enumerate.SponsorEnum;
import com.rymcu.forest.enumerate.TransactionCode;
import com.rymcu.forest.enumerate.TransactionEnum;
import com.rymcu.forest.mapper.SponsorMapper;
import com.rymcu.forest.service.ArticleService;
import com.rymcu.forest.service.SponsorService;
@ -39,7 +42,7 @@ public class SponsorServiceImpl extends AbstractService<Sponsor> implements Spon
map.put("success", false);
map.put("message", "数据异常");
} else {
SponsorEnum result = Arrays.stream(SponsorEnum.values()).filter(sponsorEnum -> sponsorEnum.getDataType().equals(sponsor.getDataType())).findFirst().orElse(SponsorEnum.Article);
TransactionEnum result = TransactionEnum.findTransactionEnum(sponsor.getDataType());
BigDecimal money = BigDecimal.valueOf(result.getMoney());
sponsor.setSponsorshipMoney(money);
User user = UserUtils.getCurrentUserByToken();
@ -47,11 +50,11 @@ public class SponsorServiceImpl extends AbstractService<Sponsor> implements Spon
sponsor.setSponsorshipTime(new Date());
sponsorMapper.insertSelective(sponsor);
// 赞赏金额划转
if (result.isArticle()) {
if (result.isArticleSponsor()) {
ArticleDTO articleDTO = articleService.findArticleDTOById(sponsor.getDataId(), 1);
TransactionRecord transactionRecord = transactionRecordService.transferByUserId(articleDTO.getArticleAuthorId(), user.getIdUser(), money);
TransactionRecord transactionRecord = transactionRecordService.userTransfer(articleDTO.getArticleAuthorId(), user.getIdUser(), result);
if (Objects.isNull(transactionRecord.getIdTransactionRecord())) {
throw new Exception("余额不足");
throw new TransactionException(TransactionCode.InsufficientBalance);
}
// 更新文章赞赏数
sponsorMapper.updateArticleSponsorCount(articleDTO.getIdArticle());

View File

@ -1,12 +1,14 @@
package com.rymcu.forest.service.impl;
import com.rymcu.forest.core.exception.ServiceException;
import com.rymcu.forest.core.exception.TransactionException;
import com.rymcu.forest.core.service.AbstractService;
import com.rymcu.forest.core.service.redis.RedisService;
import com.rymcu.forest.dto.BankAccountDTO;
import com.rymcu.forest.dto.TransactionRecordDTO;
import com.rymcu.forest.entity.BankAccount;
import com.rymcu.forest.entity.TransactionRecord;
import com.rymcu.forest.enumerate.TransactionCode;
import com.rymcu.forest.enumerate.TransactionEnum;
import com.rymcu.forest.mapper.TransactionRecordMapper;
import com.rymcu.forest.service.BankAccountService;
import com.rymcu.forest.service.TransactionRecordService;
@ -47,7 +49,7 @@ public class TransactionRecordServiceImpl extends AbstractService<TransactionRec
transactionRecordMapper.insertSelective(transactionRecord);
}
} else {
throw new Exception("余额不足");
throw new TransactionException(TransactionCode.InsufficientBalance);
}
return transactionRecord;
}
@ -58,17 +60,42 @@ public class TransactionRecordServiceImpl extends AbstractService<TransactionRec
}
@Override
public TransactionRecord transferByUserId(Integer toUserId, Integer formUserId, BigDecimal money) throws Exception {
public TransactionRecord userTransfer(Integer toUserId, Integer formUserId, TransactionEnum transactionType) throws Exception {
BankAccountDTO toBankAccount = bankAccountService.findBankAccountByIdUser(toUserId);
BankAccountDTO formBankAccount = bankAccountService.findBankAccountByIdUser(formUserId);
TransactionRecord transactionRecord = new TransactionRecord();
transactionRecord.setToBankAccount(toBankAccount.getBankAccount());
transactionRecord.setFormBankAccount(formBankAccount.getBankAccount());
transactionRecord.setMoney(money);
transactionRecord.setFunds("赞赏");
transactionRecord.setMoney(new BigDecimal(transactionType.getMoney()));
transactionRecord.setFunds(transactionType.getDescription());
return transfer(transactionRecord);
}
@Override
public TransactionRecord bankTransfer(Integer idUser, TransactionEnum transactionType) throws Exception {
BankAccountDTO toBankAccount = bankAccountService.findBankAccountByIdUser(idUser);
Boolean isTrue;
// 校验货币规则
switch (transactionType) {
case Answer:
case CorrectAnswer:
isTrue = transactionRecordMapper.existsWithBankAccountAndFunds(toBankAccount.getBankAccount(), transactionType.getDescription());
break;
default:
isTrue = true;
}
if (isTrue) {
BankAccount formBankAccount = bankAccountService.findSystemBankAccount();
TransactionRecord transactionRecord = new TransactionRecord();
transactionRecord.setToBankAccount(toBankAccount.getBankAccount());
transactionRecord.setFormBankAccount(formBankAccount.getBankAccount());
transactionRecord.setMoney(new BigDecimal(transactionType.getMoney()));
transactionRecord.setFunds(transactionType.getDescription());
return transfer(transactionRecord);
}
return null;
}
private String nextTransactionNo() {
String orderNo = "E";
String key = "orderId";

View File

@ -22,10 +22,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.*;
/**
@ -126,8 +123,8 @@ public class UserServiceImpl extends AbstractService<User> implements UserServic
}
@Override
public UserDTO findUserDTOByNickname(String nickname) {
UserDTO user = userMapper.selectUserDTOByNickname(nickname);
public UserDTO findUserDTOByAccount(String account) {
UserDTO user = userMapper.selectUserDTOByAccount(account);
return user;
}
@ -254,7 +251,7 @@ public class UserServiceImpl extends AbstractService<User> implements UserServic
}
@Override
public UserExtend selectUserExtendByNickname(String nickname) {
public UserExtend selectUserExtendByAccount(String nickname) {
return userExtendMapper.selectUserExtendByNickname(nickname);
}
@ -284,4 +281,9 @@ public class UserServiceImpl extends AbstractService<User> implements UserServic
map.put("message", "更新成功!");
return map;
}
@Override
public List<User> findUsers(UserSearchDTO searchDTO) {
return userMapper.selectUsers(searchDTO);
}
}

View File

@ -1,15 +1,17 @@
package com.rymcu.forest.util;
import com.rymcu.forest.core.constant.NotificationConstant;
import com.rymcu.forest.dto.ArticleDTO;
import com.rymcu.forest.dto.Author;
import com.rymcu.forest.dto.NotificationDTO;
import com.rymcu.forest.entity.Comment;
import com.rymcu.forest.entity.Follow;
import com.rymcu.forest.entity.Notification;
import com.rymcu.forest.entity.User;
import com.rymcu.forest.service.FollowService;
import com.rymcu.forest.service.NotificationService;
import com.rymcu.forest.service.UserService;
import com.rymcu.forest.service.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.*;
/**
@ -19,12 +21,13 @@ import java.util.concurrent.*;
*/
public class NotificationUtils {
@Resource
private static NotificationService notificationService = SpringContextHolder.getBean(NotificationService.class);
@Resource
private static UserService userService = SpringContextHolder.getBean(UserService.class);
@Resource
private static FollowService followService = SpringContextHolder.getBean(FollowService.class);
private static JavaMailService mailService = SpringContextHolder.getBean(JavaMailService.class);
private static ArticleService articleService = SpringContextHolder.getBean(ArticleService.class);
private static CommentService commentService = SpringContextHolder.getBean(CommentService.class);
public static void sendAnnouncement(Integer dataId, String dataType, String dataSummary) {
ExecutorService executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
@ -53,6 +56,11 @@ public class NotificationUtils {
// TODO 记录操作失败数据
}
}
if (NotificationConstant.Comment.equals(dataType)) {
notification = notificationService.findNotification(idUser, dataId, dataType);
NotificationDTO notificationDTO = genNotification(notification);
mailService.sendNotification(notificationDTO);
}
} catch (Exception ex) {
// TODO 记录操作失败数据
ex.printStackTrace();
@ -83,4 +91,82 @@ public class NotificationUtils {
return 0;
}, executor);
}
public static NotificationDTO genNotification(Notification notification) {
NotificationDTO notificationDTO = new NotificationDTO();
BeanCopierUtil.copy(notification, notificationDTO);
ArticleDTO article;
Comment comment;
User user;
Follow follow;
switch (notification.getDataType()) {
case "0":
// 系统公告/帖子
article = articleService.findArticleDTOById(notification.getDataId(), 0);
if (Objects.nonNull(article)) {
notificationDTO.setDataTitle("系统公告");
notificationDTO.setDataUrl(article.getArticlePermalink());
user = userService.findById(article.getArticleAuthorId().toString());
notificationDTO.setAuthor(genAuthor(user));
}
break;
case "1":
// 关注
follow = followService.findById(notification.getDataId().toString());
notificationDTO.setDataTitle("关注提醒");
if (Objects.nonNull(follow)) {
user = userService.findById(follow.getFollowerId().toString());
notificationDTO.setDataUrl(getFollowLink(follow.getFollowingType(), user.getNickname()));
notificationDTO.setAuthor(genAuthor(user));
}
break;
case "2":
// 回帖
comment = commentService.findById(notification.getDataId().toString());
article = articleService.findArticleDTOById(comment.getCommentArticleId(), 0);
if (Objects.nonNull(article)) {
notificationDTO.setDataTitle(article.getArticleTitle());
notificationDTO.setDataUrl(comment.getCommentSharpUrl());
user = userService.findById(comment.getCommentAuthorId().toString());
notificationDTO.setAuthor(genAuthor(user));
}
break;
case "3":
// 关注用户发布文章
case "4":
// 关注文章更新
article = articleService.findArticleDTOById(notification.getDataId(), 0);
if (Objects.nonNull(article)) {
notificationDTO.setDataTitle("关注通知");
notificationDTO.setDataUrl(article.getArticlePermalink());
user = userService.findById(article.getArticleAuthorId().toString());
notificationDTO.setAuthor(genAuthor(user));
}
break;
default:
break;
}
return notificationDTO;
}
private static String getFollowLink(String followingType, String id) {
StringBuilder url = new StringBuilder();
url.append(Utils.getProperty("resource.domain"));
switch (followingType) {
case "0":
url = url.append("/user/").append(id);
break;
default:
url.append("/notification");
}
return url.toString();
}
private static Author genAuthor(User user) {
Author author = new Author();
author.setUserNickname(user.getNickname());
author.setUserAvatarURL(user.getAvatarUrl());
author.setIdUser(user.getIdUser());
return author;
}
}

View File

@ -4,6 +4,7 @@ import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.rymcu.forest.core.result.GlobalResult;
import com.rymcu.forest.core.result.GlobalResultGenerator;
import com.rymcu.forest.dto.UserSearchDTO;
import com.rymcu.forest.dto.admin.TopicTagDTO;
import com.rymcu.forest.dto.admin.UserRoleDTO;
import com.rymcu.forest.entity.*;
@ -18,8 +19,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.util.Comparator.comparing;
/**
* @author ronger
* */
@ -39,11 +38,9 @@ public class AdminController {
private SpecialDayService specialDayService;
@GetMapping("/users")
public GlobalResult<Map<String, Object>> users(@RequestParam(defaultValue = "0") Integer page, @RequestParam(defaultValue = "10") Integer rows){
public GlobalResult<Map<String, Object>> users(@RequestParam(defaultValue = "0") Integer page, @RequestParam(defaultValue = "10") Integer rows, UserSearchDTO searchDTO){
PageHelper.startPage(page, rows);
List<User> list = userService.findAll();
// 按最后登录时间进行倒序排序
list.sort(comparing(User::getLastLoginTime).reversed());
List<User> list = userService.findUsers(searchDTO);
PageInfo<User> pageInfo = new PageInfo<>(list);
Map<String, Object> map = new HashMap<String, Object>(2);
map.put("users", pageInfo.getList());

View File

@ -61,7 +61,7 @@ public class ArticleController {
}
@DeleteMapping("/delete/{id}")
public GlobalResult delete(@PathVariable Integer id) {
public GlobalResult delete(@PathVariable Integer id) throws BaseApiException {
Map map = articleService.delete(id);
return GlobalResultGenerator.genSuccessResult(map);
}

View File

@ -1,7 +1,6 @@
package com.rymcu.forest.web.api.common;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
@ -9,7 +8,7 @@ import org.springframework.messaging.simp.annotation.SendToUser;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.stereotype.Controller;
import java.util.HashMap;
import javax.annotation.Resource;
/**
* @author ronger
@ -17,42 +16,18 @@ import java.util.HashMap;
@Controller
public class WebSocketController {
@Autowired
@Resource
private SimpMessagingTemplate template;
@MessageMapping("/sendMessage")
@SendTo("/public/greetings")
@SendTo("/topic/greening")
public void sendMessage(JSONObject message, StompHeaderAccessor headerAccessor) {
this.template.convertAndSend("/public/greetings",message);
this.template.convertAndSend("/topic/greening", message);
}
@MessageMapping("/message")
@SendToUser("/message")
public void message(JSONObject message){
String type = message.get("type").toString();
HashMap res = (HashMap) message.get("data");
HashMap mine = (HashMap) res.get("mine");
HashMap to = (HashMap) res.get("to");
System.out.println(to.get("type"));
boolean flag = to.get("type").equals("friend")?true:false;
String id = to.get("id").toString();
HashMap map = new HashMap();
map.put("id",mine.get("id"));
map.put("avatar",mine.get("avatar"));
map.put("formid",mine.get("id"));
map.put("username",mine.get("username"));
map.put("type",to.get("type"));
map.put("content",mine.get("content"));
map.put("mine",false);
map.put("cid",0);
map.put("timestamp","");
JSONObject json = new JSONObject();
json.put("type",type);
json.put("data",map);
if(flag){
this.template.convertAndSendToUser(id,"/message",json);
}else{
this.template.convertAndSendToUser(id,"/message",json);
}
public void message(JSONObject message, StompHeaderAccessor headerAccessor) {
this.template.convertAndSendToUser(message.getString("to"), "/message", message);
}
}

View File

@ -37,16 +37,16 @@ public class UserController {
@Resource
private FollowService followService;
@GetMapping("/{nickname}")
@GetMapping("/{account}")
@VisitLogger
public GlobalResult detail(@PathVariable String nickname){
UserDTO userDTO = userService.findUserDTOByNickname(nickname);
public GlobalResult detail(@PathVariable String account){
UserDTO userDTO = userService.findUserDTOByAccount(account);
return GlobalResultGenerator.genSuccessResult(userDTO);
}
@GetMapping("/{nickname}/articles")
public GlobalResult userArticles(@RequestParam(defaultValue = "0") Integer page, @RequestParam(defaultValue = "12") Integer rows, @PathVariable String nickname){
UserDTO userDTO = userService.findUserDTOByNickname(nickname);
@GetMapping("/{account}/articles")
public GlobalResult userArticles(@RequestParam(defaultValue = "0") Integer page, @RequestParam(defaultValue = "12") Integer rows, @PathVariable String account){
UserDTO userDTO = userService.findUserDTOByAccount(account);
if (userDTO == null){
return GlobalResultGenerator.genErrorResult("用户不存在!");
}
@ -57,9 +57,9 @@ public class UserController {
return GlobalResultGenerator.genSuccessResult(map);
}
@GetMapping("/{nickname}/portfolios")
public GlobalResult userPortfolios(@RequestParam(defaultValue = "0") Integer page, @RequestParam(defaultValue = "12") Integer rows, @PathVariable String nickname){
UserDTO userDTO = userService.findUserDTOByNickname(nickname);
@GetMapping("/{account}/portfolios")
public GlobalResult userPortfolios(@RequestParam(defaultValue = "0") Integer page, @RequestParam(defaultValue = "12") Integer rows, @PathVariable String account){
UserDTO userDTO = userService.findUserDTOByAccount(account);
if (userDTO == null){
return GlobalResultGenerator.genErrorResult("用户不存在!");
}
@ -73,9 +73,9 @@ public class UserController {
return GlobalResultGenerator.genSuccessResult(map);
}
@GetMapping("/{nickname}/followers")
public GlobalResult userFollowers(@RequestParam(defaultValue = "0") Integer page, @RequestParam(defaultValue = "12") Integer rows, @PathVariable String nickname){
UserDTO userDTO = userService.findUserDTOByNickname(nickname);
@GetMapping("/{account}/followers")
public GlobalResult userFollowers(@RequestParam(defaultValue = "0") Integer page, @RequestParam(defaultValue = "12") Integer rows, @PathVariable String account){
UserDTO userDTO = userService.findUserDTOByAccount(account);
if (userDTO == null){
return GlobalResultGenerator.genErrorResult("用户不存在!");
}
@ -89,9 +89,9 @@ public class UserController {
return GlobalResultGenerator.genSuccessResult(map);
}
@GetMapping("/{nickname}/followings")
public GlobalResult userFollowings(@RequestParam(defaultValue = "0") Integer page, @RequestParam(defaultValue = "12") Integer rows, @PathVariable String nickname){
UserDTO userDTO = userService.findUserDTOByNickname(nickname);
@GetMapping("/{account}/followings")
public GlobalResult userFollowings(@RequestParam(defaultValue = "0") Integer page, @RequestParam(defaultValue = "12") Integer rows, @PathVariable String account){
UserDTO userDTO = userService.findUserDTOByAccount(account);
if (userDTO == null){
return GlobalResultGenerator.genErrorResult("用户不存在!");
}
@ -105,9 +105,9 @@ public class UserController {
return GlobalResultGenerator.genSuccessResult(map);
}
@GetMapping("/{nickname}/user-extend")
public GlobalResult userExtend(@PathVariable String nickname) {
UserExtend userExtend = userService.selectUserExtendByNickname(nickname);
@GetMapping("/{account}/user-extend")
public GlobalResult userExtend(@PathVariable String account) {
UserExtend userExtend = userService.selectUserExtendByAccount(account);
return GlobalResultGenerator.genSuccessResult(userExtend);
}

View File

@ -1,5 +1,6 @@
package com.rymcu.forest.wx.mp.controller;
import com.rymcu.forest.wx.mp.service.WxMenuService;
import lombok.AllArgsConstructor;
import me.chanjar.weixin.common.bean.menu.WxMenu;
import me.chanjar.weixin.common.bean.menu.WxMenuButton;
@ -7,8 +8,11 @@ import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.menu.WxMpGetSelfMenuInfoResult;
import me.chanjar.weixin.mp.bean.menu.WxMpMenu;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.io.IOException;
import java.net.MalformedURLException;
import static me.chanjar.weixin.common.api.WxConsts.MenuButtonType;
@ -21,7 +25,8 @@ import static me.chanjar.weixin.common.api.WxConsts.MenuButtonType;
@RequestMapping("/wx/menu/{appId}")
public class WxMenuController {
private final WxMpService wxService;
@Resource
private WxMenuService wxMenuService;
/**
* <pre>
* 自定义菜单创建接口
@ -38,52 +43,9 @@ public class WxMenuController {
}
@GetMapping("/create")
public String menuCreateSample(@PathVariable String appId) throws WxErrorException, MalformedURLException {
WxMenu menu = new WxMenu();
WxMenuButton button1 = new WxMenuButton();
button1.setType(MenuButtonType.VIEW);
button1.setName("官方网站");
button1.setUrl("https://rymcu.com");
// WxMenuButton button2 = new WxMenuButton();
// button2.setType(WxConsts.BUTTON_MINIPROGRAM);
// button2.setName("小程序");
// button2.setAppId("wx286b93c14bbf93aa");
// button2.setPagePath("pages/lunar/index.html");
// button2.setUrl("http://mp.weixin.qq.com");
WxMenuButton button3 = new WxMenuButton();
button3.setName("学习教程");
menu.getButtons().add(button1);
// menu.getButtons().add(button2);
menu.getButtons().add(button3);
WxMenuButton button31 = new WxMenuButton();
button31.setType(MenuButtonType.VIEW);
button31.setName("51单片机入门教程");
button31.setUrl("https://mp.weixin.qq.com/mp/homepage?__biz=MzA3NjMzMzM1Mw==&hid=1&sn=672df75323f9976d990f6be14355070b");
// WxMenuButton button34 = new WxMenuButton();
// button34.setType(MenuButtonType.VIEW);
// button34.setName("获取用户信息");
//
// ServletRequestAttributes servletRequestAttributes =
// (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
// if (servletRequestAttributes != null) {
// HttpServletRequest request = servletRequestAttributes.getRequest();
// URL requestURL = new URL(request.getRequestURL().toString());
// String url = this.wxService.switchoverTo(appId).oauth2buildAuthorizationUrl(
// String.format("%s://%s/wx/redirect/%s/greet", requestURL.getProtocol(), requestURL.getHost(), appId),
// WxConsts.OAuth2Scope.SNSAPI_USERINFO, null);
// button34.setUrl(url);
// }
button3.getSubButtons().add(button31);
// button3.getSubButtons().add(button34);
public String menuCreateSample(@PathVariable String appId) throws WxErrorException, IOException {
this.wxService.switchover(appId);
return this.wxService.getMenuService().menuCreate(menu);
return this.wxService.getMenuService().menuCreate(wxMenuService.getMenus());
}
/**

View File

@ -1,10 +1,10 @@
package com.rymcu.forest.wx.mp.controller;
import lombok.AllArgsConstructor;
import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
@ -27,8 +27,8 @@ public class WxRedirectController {
}
try {
WxMpOAuth2AccessToken accessToken = wxService.getOAuth2Service().getAccessToken(code);
WxMpUser user = wxService.getOAuth2Service().getUserInfo(accessToken, null);
WxOAuth2AccessToken accessToken = wxService.getOAuth2Service().getAccessToken(code);
WxOAuth2UserInfo user = wxService.getOAuth2Service().getUserInfo(accessToken, null);
map.put("user", user);
} catch (WxErrorException e) {
e.printStackTrace();

View File

@ -4,10 +4,10 @@ import com.rymcu.forest.service.WxUserService;
import com.rymcu.forest.util.ContextHolderUtils;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.bean.WxJsapiSignature;
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.http.URIUtil;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
@ -44,7 +44,7 @@ public class WxoAuthController {
baseUrl = new StringBuilder(domain).append(contextPath);
}
StringBuilder accessTokenUrl = baseUrl.append("/wx/oauth/" + appId + "/getAccessToken?redirectUrl=").append(URIUtil.encodeURIComponent(redirectUrl));
String oauth2Url = wxMpService.getOAuth2Service().buildAuthorizationUrl(accessTokenUrl.toString(), WxConsts.OAuth2Scope.SNSAPI_BASE, null);
String oauth2Url = wxMpService.getOAuth2Service().buildAuthorizationUrl(accessTokenUrl.toString(), WxConsts.OAuth2Scope.SNSAPI_USERINFO, null);
return "redirect:" + oauth2Url;
}
@ -52,7 +52,7 @@ public class WxoAuthController {
@GetMapping("getAccessToken")
public String getAccessToken(@PathVariable String appId, @RequestParam(name = "code") String code, @RequestParam(name = "redirectUrl") String redirectUrl) throws Exception {
wxMpService.switchoverTo(appId);
WxMpOAuth2AccessToken oAuth2AccessToken = wxMpService.getOAuth2Service().getAccessToken(code);
WxOAuth2AccessToken oAuth2AccessToken = wxMpService.getOAuth2Service().getAccessToken(code);
boolean valid = wxMpService.getOAuth2Service().validateAccessToken(oAuth2AccessToken);
if (!valid) {
throw new Exception("无权限");

View File

@ -0,0 +1,17 @@
package com.rymcu.forest.wx.mp.service;
import me.chanjar.weixin.common.bean.menu.WxMenu;
import java.io.IOException;
/**
* @author ronger
*/
public interface WxMenuService {
/**
* 获取公众号菜单配置
* @return
* @throws IOException
*/
WxMenu getMenus() throws IOException;
}

View File

@ -0,0 +1,46 @@
package com.rymcu.forest.wx.mp.service.impl;
import com.rymcu.forest.wx.mp.service.WxMenuService;
import me.chanjar.weixin.common.bean.menu.WxMenu;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
/**
* @author ronger
*/
@Service
public class WxMenuServiceImpl implements WxMenuService {
@Value("classpath:wxMpMenus.json")
private Resource menuResource;
@Override
public WxMenu getMenus() throws IOException {
File file = menuResource.getFile();
String menuJson = this.jsonRead(file);
WxMenu wxMenu = WxMenu.fromJson(menuJson);
return wxMenu;
}
private String jsonRead(File file) {
Scanner scanner = null;
StringBuilder buffer = new StringBuilder();
try {
scanner = new Scanner(file, "utf-8");
while (scanner.hasNextLine()) {
buffer.append(scanner.nextLine());
}
} catch (Exception e) {
} finally {
if (scanner != null) {
scanner.close();
}
}
return buffer.toString();
}
}

View File

@ -97,6 +97,9 @@
<delete id="deleteLinkedPortfolioData">
delete from forest_portfolio_article where id_article = #{id}
</delete>
<delete id="deleteArticleContent">
delete from forest_article_content where id_article = #{idArticle}
</delete>
<select id="selectArticles" resultMap="DTOResultMap">
select art.*,su.nickname,su.avatar_url from forest_article art join forest_user su on art.article_author_id = su.id
where article_status = 0
@ -142,12 +145,14 @@
and instr(art.article_title, #{searchText}) > 0 and art.id not in (select id_article from forest_portfolio_article where id_portfolio = #{idPortfolio}) order by updated_time desc
</select>
<select id="selectPortfolioArticles" resultMap="PortfolioArticleResultMap">
select vp.portfolio_title,vp.portfolio_head_img_url,vpa.id_portfolio,vpa.id_article,vpa.sort_no from forest_portfolio vp join forest_portfolio_article vpa on vp.id = vpa.id_portfolio where vpa.id_article = #{idArticle}
select vp.portfolio_title,vp.portfolio_head_img_url,vpa.id_portfolio,vpa.id_article,vpa.sort_no from forest_portfolio vp
join forest_portfolio_article vpa on vp.id = vpa.id_portfolio where vpa.id_article = #{idArticle}
</select>
<select id="existsCommentWithPrimaryKey" resultType="java.lang.Boolean">
select exists (select * from forest_comment where comment_article_id = #{id})
</select>
<select id="selectPortfolioArticlesByIdPortfolioAndSortNo" resultMap="DTOResultMap">
select va.article_title, va.id, va.article_permalink from forest_portfolio_article vpa join forest_article va on va.id = vpa.id_article where id_portfolio = #{idPortfolio} order by sort_no
select va.article_title, va.id, va.article_permalink from forest_portfolio_article vpa
join forest_article va on va.id = vpa.id_article where va.article_status = '0' and id_portfolio = #{idPortfolio} order by sort_no
</select>
</mapper>

View File

@ -33,6 +33,7 @@
<result column="id" property="idUser"/>
<result column="nickname" property="userNickname"/>
<result column="avatar_url" property="userAvatarURL"/>
<result column="account" property="userAccount"/>
</resultMap>
<update id="updateCommentSharpUrl">
update forest_comment set comment_sharp_url = #{commentSharpUrl} where id = #{idComment}
@ -41,9 +42,9 @@
select * from forest_comment where comment_article_id = #{idArticle} order by created_time desc
</select>
<select id="selectAuthor" resultMap="AuthorResultMap">
select id,nickname,avatar_url from forest_user where id = #{commentAuthorId}
select id,nickname,avatar_url,account from forest_user where id = #{commentAuthorId}
</select>
<select id="selectCommentOriginalAuthor" resultMap="AuthorResultMap">
select vu.id,vu.nickname,vu.avatar_url from forest_comment vc left join forest_user vu on vu.id = vc.comment_author_id where vc.id = #{commentOriginalCommentId}
select vu.id,vu.nickname,vu.avatar_url,vu.account from forest_comment vc left join forest_user vu on vu.id = vc.comment_author_id where vc.id = #{commentOriginalCommentId}
</select>
</mapper>

View File

@ -10,6 +10,15 @@
<result column="has_read" property="hasRead"></result>
<result column="created_time" property="createdTime"></result>
</resultMap>
<resultMap id="DTOResultMapper" type="com.rymcu.forest.dto.NotificationDTO">
<id column="id" property="idNotification"></id>
<result column="id_user" property="idUser"></result>
<result column="data_type" property="dataType"></result>
<result column="data_id" property="dataId"></result>
<result column="data_summary" property="dataSummary"></result>
<result column="has_read" property="hasRead"></result>
<result column="created_time" property="createdTime"></result>
</resultMap>
<insert id="insertNotification">
insert into forest_notification (id_user, data_type, data_id, data_summary, created_time) values (#{idUser}, #{dataType}, #{dataId}, #{dataSummary}, sysdate())
</insert>
@ -19,8 +28,8 @@
<select id="selectUnreadNotifications" resultMap="BaseResultMapper">
select * from forest_notification where has_read = '0' and id_user = #{idUser} order by created_time desc
</select>
<select id="selectNotifications" resultMap="BaseResultMapper">
select * from forest_notification where id_user = #{idUser} order by created_time desc
<select id="selectNotifications" resultMap="DTOResultMapper">
select * from forest_notification where id_user = #{idUser} order by has_read,created_time desc
</select>
<select id="selectNotification" resultMap="BaseResultMapper">
select * from forest_notification where id_user = #{idUser} and data_id = #{dataId} and data_type = #{dataType}

View File

@ -51,4 +51,7 @@
<select id="selectMaxSortNo" resultType="java.lang.Integer">
select ifnull(max(sort_no),0) + 1 from forest_portfolio_article where id_portfolio = #{idPortfolio}
</select>
<select id="selectPortfolios" resultMap="BaseResultMap">
select * from forest_portfolio order by updated_time desc
</select>
</mapper>

View File

@ -13,6 +13,6 @@
select portfolio_title as label, id as value, 'portfolio' as type from forest_portfolio
</select>
<select id="searchInitialUserSearch" resultMap="BaseResultMap">
select nickname as label, nickname as value, 'user' as type from forest_user where status = 0
select nickname as label, account as value, 'user' as type from forest_user where status = 0
</select>
</mapper>

View File

@ -16,6 +16,10 @@
update forest_bank_account set account_balance = account_balance + #{money} where bank_account = #{toBankAccount};
</update>
<select id="selectTransactionRecords" resultMap="DTOResultMap">
select * from forest_transaction_record where form_bank_account = #{bankAccount} or to_bank_account = #{bankAccount}
select * from forest_transaction_record where form_bank_account = #{bankAccount} or to_bank_account = #{bankAccount} order by transaction_time desc
</select>
<select id="existsWithBankAccountAndFunds" resultType="java.lang.Boolean">
select ifnull((select false from forest_transaction_record where to_bank_account = #{bankAccount}
and funds = #{funds} and transaction_time > str_to_date(date_format(sysdate(),'%Y-%m-%d'),'%Y-%m-%d') limit 1), true)
</select>
</mapper>

View File

@ -45,6 +45,7 @@
<result column="id" property="idUser"/>
<result column="nickname" property="userNickname"/>
<result column="avatar_url" property="userAvatarURL"/>
<result column="account" property="userAccount"/>
</resultMap>
<insert id="insertUserRole">
insert into forest_user_role (id_user,id_role,created_time) values (#{idUser},#{idRole},sysdate())
@ -81,8 +82,8 @@
<select id="findUserInfoByAccount" resultMap="UserInfoResultMapper">
select id, nickname, sex, avatar_type, avatar_url, email, phone, account, status, signature, last_login_time from forest_user where account = #{account}
</select>
<select id="selectUserDTOByNickname" resultMap="DTOResultMapper">
select id, nickname, avatar_type, avatar_url, account, signature from forest_user where nickname = #{nickname} and status = 0
<select id="selectUserDTOByAccount" resultMap="DTOResultMapper">
select id, nickname, avatar_type, avatar_url, account, signature from forest_user where account = #{account} and status = 0
</select>
<select id="selectRoleWeightsByUser" resultType="java.lang.Integer">
select vr.weights from forest_role vr left join forest_user_role vur on vr.id = vur.id_role where vur.id_user = #{idUser}
@ -99,5 +100,14 @@
<select id="selectAuthor" resultMap="AuthorResultMap">
select * from forest_user where id = #{id}
</select>
<select id="selectUsers" resultMap="BaseResultMap">
select id, nickname, sex, avatar_type, avatar_url, email, account, status, last_login_time, created_time from forest_user
<where>
<if test="searchDTO.nickname != null and searchDTO.nickname != ''">
and instr(nickname, #{searchDTO.nickname}) > 0
</if>
</where>
order by last_login_time desc
</select>
</mapper>

View File

@ -84,7 +84,7 @@ create table forest_comment
comment_original_comment_id bigint null comment '父评论 id',
comment_status char default '0' null comment '状态',
comment_ip varchar(128) null comment '评论 IP',
comment_ua varchar(128) null comment 'User-Agent',
comment_ua varchar(512) null comment 'User-Agent',
comment_anonymous char null comment '0公开回帖1匿名回帖',
comment_reply_count int null comment '回帖计数',
comment_visible char null comment '0所有人可见1仅楼主和自己可见',

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -0,0 +1,162 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<title> RYMCU 消息通知 </title>
<!--[if !mso]> -->
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<!--<![endif]-->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<style type="text/css">
#outlook a {
padding: 0;
}
body {
margin: 0;
padding: 0;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
table,
td {
border-collapse: collapse;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
}
img {
border: 0;
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
-ms-interpolation-mode: bicubic;
}
p {
display: block;
margin: 13px 0;
}
</style>
<!--[if mso]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<!--[if lte mso 11]>
<style type="text/css">
.mj-outlook-group-fix {
width: 100% !important;
}
</style>
<![endif]-->
<!--[if !mso]><!-->
<link href="https://fonts.googleapis.com/css2?family=Quattrocento:wght@400;700&amp;display=swap" rel="stylesheet"
type="text/css"/>
<style type="text/css">
@import url(https://fonts.googleapis.com/css2?family=Quattrocento:wght@400;700&amp;display=swap);
</style>
<!--<![endif]-->
<style type="text/css">
@media only screen and (min-width: 480px) {
.mj-column-per-100 {
width: 100% !important;
max-width: 100%;
}
}
</style>
<style type="text/css">
@media only screen and (max-width: 480px) {
table.mj-full-width-mobile {
width: 100% !important;
}
td.mj-full-width-mobile {
width: auto !important;
}
}
</style>
<style type="text/css">
a,
span,
td,
th {
-webkit-font-smoothing: antialiased !important;
-moz-osx-font-smoothing: grayscale !important;
}
</style>
</head>
<body style="background-color:#f6ab0c;text-align: center;">
<div style="background-color:#ffffff;margin:0px auto;border-radius:8px 8px 0 0;max-width:600px;">
<div style="max-width:600px;">
<div style="margin-top: 40px;">
<table align="center">
<tbody>
<tr>
<td width="150" style="padding: 20px 0;">
<img alt="Logo" height="auto"
src="https://static.rymcu.com/article/1619013470064"
style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:14px;"
width="150">
</td>
</tr>
<tr>
<td width="200">
<img alt="welcome image" height="auto"
src="https://static.rymcu.com/article/1619013419068"
style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:14px;"
width="200"/>
</td>
</tr>
</tbody>
</table>
</div>
<div>
<table>
<tbody>
<tr>
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-size:18px;font-weight:400;line-height:24px;text-align:left;color:#000000;">
<h1 style="margin: 0; font-size: 32px; line-height: 40px; font-weight: 700;">
消息通知</h1>
</div>
</td>
</tr>
<tr>
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-size:18px;font-weight:400;line-height:24px;text-align:left;color:#000000;">
<div style="margin: 0;">
<span th:text="${user}"></span> 评论了您的文章《 <span th:text="${articleTitle}"></span>》:
</div>
<div th:text="*{content}"></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div style="margin:0px auto;max-width:600px;">
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tbody><tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Quattrocento;font-size:16px;font-weight:400;line-height:24px;text-align:center;color:#333333;">
<a th:href="${url}" style="color: #428dfc; text-decoration: none; font-weight: bold;"> 点击这里进行查看 </a>
</div>
</td>
</tr>
</tbody></table>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,26 @@
{
"menu": {
"button": [
{
"type": "view",
"name": "官方网站",
"url": "https://rymcu.com"
},
{
"type": "view",
"name": "交流群",
"url": "https://mp.weixin.qq.com/s/0XDVL3mgrSpeGEALOQz-4Q"
},
{
"name": "学习教程",
"sub_button": [
{
"type": "view",
"name": "51单片机入门教程",
"url": "https://mp.weixin.qq.com/mp/homepage?__biz=MzA3NjMzMzM1Mw==&hid=1&sn=672df75323f9976d990f6be14355070b"
}
]
}
]
}
}