Compare commits

...

10 Commits

Author SHA1 Message Date
cjh
c79086cf5e init 2025-03-04 09:54:15 +08:00
cjh
de4cc4d73f 好友管理 2025-02-24 18:10:38 +08:00
Chopper711
5a435dd19c 好友管理 2025-02-24 18:03:28 +08:00
Chopper711
98f25179d3 fix: 异常消息拼接问题处理 2025-02-20 10:53:40 +08:00
pikachu1995@126.com
0cf464e549 feat(payment): 微信支付支持公钥和证书两种验证方式- 在 WechatPaymentSetting 中添加 publicType 字段,用于选择验证方式
- 修改 WechatPlugin 中的支付、退款等方法,支持两种验证方式
- 新增 getPublicKeyConfig 和 getCertificateConfig 方法,分别用于获取公钥和证书配置
- 优化退款通知处理逻辑,使用 NotificationParser 进行验证和解析
2025-02-11 15:42:35 +08:00
chc
633b94c375 获取用户信息时判断用户是否已禁用 2025-01-20 17:09:24 +08:00
chc
6441018d95 获取用户信息时判断用户是否已禁用 2025-01-20 16:38:10 +08:00
pikachu1995@126.com
e706431df5 refactor(payment): 重构微信支付模块
- 更新微信支付相关工具类和接口实现
- 新增微信支付配置项
- 优化微信支付流程和参数处理
- 引入微信支付官方SDK
2025-01-08 16:13:39 +08:00
pikachu1995@126.com
db1a3566ae 初始化 2024-12-27 14:07:35 +08:00
pikachu1995@126.com
9225b4ff10 refactor(payment): 重构微信支付私钥处理逻辑
- 将方法参数从 keyPath 修改为 key,移除对文件路径的依赖
- 更新方法签名和文档注释,以适应新的参数
- 删除 WechatPaymentSetting 中未使用的证书字段
2024-12-27 11:02:57 +08:00
69 changed files with 4124 additions and 1485 deletions

View File

@ -54,7 +54,7 @@ spring:
redis:
host: 127.0.0.1
port: 6379
password: lilishop
# password: lilishop
lettuce:
pool:
# 连接池最大连接数(使用负值表示没有限制) 默认 8
@ -73,9 +73,9 @@ spring:
default-datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/lilishop?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
username: root
password: lilishop
url: jdbc:mysql://82.156.121.2:23306/shop_dev?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
username: wzj
password: A085F27A43B0
maxActive: 50
initialSize: 10
maxWait: 60000

View File

@ -35,7 +35,7 @@ spring:
redis:
host: 127.0.0.1
port: 6379
password: lilishop
# password: lilishop
lettuce:
pool:
# 连接池最大连接数(使用负值表示没有限制) 默认 8

View File

@ -29,9 +29,9 @@ spring:
type: redis
# Redis
redis:
host: 192.168.31.100
port: 30379
password: lilishop
host: 127.0.0.1
port: 6379
# password: e4ea0caebfd2
lettuce:
pool:
# 连接池最大连接数(使用负值表示没有限制) 默认 8
@ -60,9 +60,9 @@ spring:
default-datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.31.100:30306/lilishop?useUnicode=true&characterEncoding=utf-8&useSSL=false&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
username: root
password: lilishop
url: jdbc:mysql://82.156.121.2:23306/shop_dev?useUnicode=true&characterEncoding=utf-8&useSSL=false&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
username: wzj
password: A085F27A43B0
maxActive: 50
initialSize: 20
maxWait: 60000
@ -256,18 +256,18 @@ lili:
data:
elasticsearch:
cluster-name: elasticsearch
cluster-nodes: 192.168.31.100:30920
cluster-nodes: 82.156.121.2:19200
index:
number-of-replicas: 0
number-of-shards: 3
index-prefix: lili
schema: http
# account:
# username: elastic
# password: LiLiShopES
account:
username: elastic
password: B5254c5953d
logstash:
server: 192.168.31.100:30560
server: 127.0.0.1:30560
rocketmq:
promotion-topic: shop_lili_promotion_topic
promotion-group: shop_lili_promotion_group
@ -288,7 +288,7 @@ lili:
after-sale-topic: shop_lili_after_sale_topic
after-sale-group: shop_lili_after_sale_group
rocketmq:
name-server: 192.168.31.100:30876
name-server: 127.0.0.1:9876
isVIPChannel: false
producer:
group: lili_group
@ -297,7 +297,7 @@ rocketmq:
xxl:
job:
admin:
addresses: http://192.168.31.100:30001/xxl-job-admin
addresses: http://127.0.1:30001/xxl-job-admin
executor:
appname: xxl-job-executor-lilishop
address:

View File

@ -38,7 +38,7 @@ spring:
redis:
host: 127.0.0.1
port: 6379
password: lilishop
# password: lilishop
lettuce:
pool:
# 连接池最大连接数(使用负值表示没有限制) 默认 8
@ -67,9 +67,9 @@ spring:
default-datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/lilishop?useUnicode=true&characterEncoding=utf-8&useSSL=false&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
url: jdbc:mysql://127.0.0.1:3306/wuzhongjie?useUnicode=true&characterEncoding=utf-8&useSSL=false&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
username: root
password: lilishop
password: 123
maxActive: 20
initialSize: 5
maxWait: 60000
@ -274,12 +274,12 @@ rocketmq:
name-server: 127.0.0.1:9876
producer:
group: lili_group
send-message-timeout: 30000
send-message-timeout: 100000
xxl:
job:
admin:
addresses: http://127.0.0.1:9001/xxl-job-admin
addresses: http://127.0.0.1:30001/xxl-job-admin
executor:
appname: xxl-job-executor-lilishop
address:

View File

@ -15,6 +15,11 @@
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.belerweb</groupId>
<artifactId>pinyin4j</artifactId>
<version>2.5.1</version>
</dependency>
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
@ -111,10 +116,10 @@
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- &lt;!&ndash; Websocket &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-websocket</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- MybatisPlus -->
<dependency>
<groupId>com.baomidou</groupId>
@ -468,6 +473,11 @@
<scope>runtime</scope>
<classifier>osx-aarch_64</classifier>
</dependency>
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.15</version>
</dependency>
</dependencies>

View File

@ -131,4 +131,13 @@ public class ResultUtil<T> {
return this.resultMessage;
}
/**
* 返回失败
*
* @return 消息
*/
public static <T> ResultMessage<T> error() {
return new ResultUtil<T>().setErrorMsg(ResultCode.ERROR);
}
}

View File

@ -1,5 +1,6 @@
package cn.lili.common.exception;
import cn.hutool.core.text.CharSequenceUtil;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.enums.ResultUtil;
import cn.lili.common.vo.ResultMessage;
@ -42,26 +43,21 @@ public class GlobalControllerExceptionHandler {
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public ResultMessage<Object> handleServiceException(HttpServletRequest request, final Exception e, HttpServletResponse response) {
//如果是自定义异常则获取异常返回自定义错误消息
if (e instanceof ServiceException) {
ServiceException serviceException = ((ServiceException) e);
ResultCode resultCode = serviceException.getResultCode();
Integer code = null;
String message = null;
Integer code = resultCode.code();
String message = resultCode.message();
if (resultCode != null) {
code = resultCode.code();
message = resultCode.message();
}
//如果有扩展消息则输出异常中跟随补充异常
if (!serviceException.getMsg().equals(ServiceException.DEFAULT_MESSAGE)) {
message += ":" + serviceException.getMsg();
if (message != null) {
message = appendErrorMessage(message, serviceException.getMsg());
}
// 对一些特殊异常处理不再打印error级别的日志
assert serviceException.getResultCode() != null;
if (serviceException.getResultCode().equals(ResultCode.DEMO_SITE_EXCEPTION)) {
log.debug("[DEMO_SITE_EXCEPTION]:{}", serviceException.getResultCode().message(), e);
return ResultUtil.error(code, message);
@ -103,7 +99,30 @@ public class GlobalControllerExceptionHandler {
log.error("全局异常[RuntimeException]:", e);
return ResultUtil.error(ResultCode.ERROR);
// 检查异常链是否包含 ServiceException
ServiceException serviceException = findServiceException(e);
if (serviceException != null) {
ResultCode resultCode = serviceException.getResultCode();
Integer code = resultCode.code();
String message = resultCode.message();
if (message != null) {
message = appendErrorMessage(message, serviceException.getMsg());
}
return ResultUtil.error(code, message);
}
return ResultUtil.error();
}
// 遍历异常链查找 ServiceException
private ServiceException findServiceException(Throwable ex) {
while (ex != null) {
if (ex instanceof ServiceException) {
return (ServiceException) ex;
}
ex = ex.getCause();
}
return null;
}
// /**
@ -168,4 +187,41 @@ public class GlobalControllerExceptionHandler {
ConstraintViolationException exception = (ConstraintViolationException) e;
return ResultUtil.error(ResultCode.PARAMS_ERROR.code(), exception.getMessage());
}
/**
* 拼接错误消息
*
* @param message 原始消息
* @param appendMessage 需要拼接的消息
* @return 拼接后的消息
*/
private String appendErrorMessage(String message, String appendMessage) {
//这里的代码看起来有点乱简单解释一下
//场景1服务A服务B=
// 服务A调用服务B=
// 服务B抛出异常{扩展消息}拼接后成为{默认消息}{扩展消息}
// 异常被服务A捕获=
// 最终消息拼接过程中当前方法体参数message是{默认消息}参数appendMessage是服务A给的{默认消息}+{扩展消息}最终会形成{默认消息}+{默认消息}+{扩展消息}
//场景2只有服务A=
// 服务A抛出异常{扩展消息}=
// 当前方法体拼接{默认消息}{扩展消息} 并输出返回
//
//总的来说由于消息拼接是一个流式传递由服务间传递所以这里的消息可能存在A包含B也可能出现B包含A
// 所以这里需要双重判定A包含B=返回AB包含A=返回B否则返回拼接后的消息
if (message.contains(appendMessage)) {
return message;
}
if (appendMessage.contains(message)) {
return appendMessage;
}
//忽略默认错误信息如果有其他错误消息体就不再返回默认的错误消息
if (message.equals(ResultCode.ERROR.message())) {
return appendMessage;
}
if (appendMessage.equals(ResultCode.ERROR.message())) {
return message;
}
return CharSequenceUtil.format("{}-{}", message, appendMessage);
}
}

View File

@ -15,12 +15,11 @@ public class ServiceException extends RuntimeException {
private static final long serialVersionUID = 3447728300174142127L;
public static final String DEFAULT_MESSAGE = "网络错误,请稍后重试!";
/**
* 异常消息
*/
private String msg = DEFAULT_MESSAGE;
private String msg = ResultCode.ERROR.message();
/**
* 错误码

View File

@ -125,13 +125,21 @@ public final class CurrencyUtil {
return (int) price;
}
public static Long getFenLong(Double money) {
BigDecimal bigDecimalValue = BigDecimal.valueOf(money);
// 乘以 100 并四舍五入到最接近的整数
BigDecimal fenValue = bigDecimalValue.multiply(BigDecimal.valueOf(100)).setScale(0, RoundingMode.HALF_UP);
return fenValue.longValue();
}
/**
* 金额转分
*
* @param money 金额
* @return double类型分
*/
public static double reversalFen(Double money) {
public static double reversalFen(Integer money) {
return div(money, 100);
}
}

View File

@ -1,12 +1,20 @@
package cn.lili.common.vo;
import cn.hutool.core.text.CharSequenceUtil;
import cn.lili.common.utils.StringUtils;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 分页视图对象
* @author Chopper
*/
import cn.hutool.core.text.CharSequenceUtil;
import cn.lili.common.utils.StringUtils;
/**
* 查询参数
*
@ -42,5 +50,5 @@ public class PageVO implements Serializable {
}
return sort;
}
}

View File

@ -1,6 +1,6 @@
package cn.lili.common.vo;
import cn.lili.common.enums.ResultCode;
import lombok.Data;
import java.io.Serializable;
@ -39,4 +39,81 @@ public class ResultMessage<T> implements Serializable {
* 结果对象
*/
private T result;
/**
* 构造空参对象
*/
public ResultMessage() {
}
/**
* 构建消息对象
* @param success 是否成功
* @param code 状态码
* @param message 消息
* @param result 结果
*/
public ResultMessage(boolean success, Integer code, String message, T result) {
this.success = success;
this.code = code;
this.message = message;
this.result = result;
}
/**
* 构建成功消息
* @param result 结果
* @return 消息对象
*/
public static <T> ResultMessage<T> success(T result) {
return new ResultMessage<>(true, ResultCode.SUCCESS.code(), ResultCode.SUCCESS.message(), result);
}
/**
* 构建成功消息
* @param message 消息
* @param result 结果
* @return 消息对象
*/
public static <T> ResultMessage<T> success(String message, T result) {
return new ResultMessage<>(true, ResultCode.SUCCESS.code(), message, result);
}
/**
* 构建错误消息
* @param code 错误码
* @return 消息对象
*/
public static <T> ResultMessage<T> error(Integer code) {
return new ResultMessage<>(false, code, null, null);
}
/**
* 构建错误消息
* @param code 错误码
* @param message 错误消息
* @return 消息对象
*/
public static <T> ResultMessage<T> error(Integer code, String message) {
return new ResultMessage<>(false, code, message, null);
}
/**
* 构建错误消息
* @param resultCode 结果枚举
* @return 消息对象
*/
public static <T> ResultMessage<T> error(ResultCode resultCode) {
return new ResultMessage<>(false, resultCode.code(), resultCode.message(), null);
}
/**
* 构建错误消息
* @param resultCode 结果枚举
* @param message 错误消息
* @return 消息对象
*/
public static <T> ResultMessage<T> error(ResultCode resultCode, String message) {
return new ResultMessage<>(false, resultCode.code(), message, null);
}
}

View File

@ -0,0 +1,46 @@
package cn.lili.common.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 分页结果视图对象
* @author Chopper
*/
@Data
public class ResultPageVO<T> implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "总条数")
private Long total;
@ApiModelProperty(value = "当前页数")
private Long currentPage;
@ApiModelProperty(value = "总页数")
private Long pages;
@ApiModelProperty(value = "每页显示的条数")
private Long size;
@ApiModelProperty(value = "数据列表")
private List<T> records;
public ResultPageVO() {
}
public ResultPageVO(Long total, Long pages, Long currentPage, Long size, List<T> records) {
this.total = total;
this.pages = pages;
this.currentPage = currentPage;
this.size = size;
this.records = records;
}
public static <T> ResultPageVO<T> empty() {
return new ResultPageVO<>(0L, 0L, 1L, 10L, null);
}
}

View File

@ -1,10 +1,10 @@
package cn.lili.modules.im.config;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import javax.websocket.server.ServerEndpointConfig;
@ -15,6 +15,7 @@ import javax.websocket.server.ServerEndpointConfig;
* @version v1.0
* 2021-12-31 11:53
*/
@Component
public class CustomSpringConfigurator extends ServerEndpointConfig.Configurator implements ApplicationContextAware {
/**
@ -22,13 +23,13 @@ public class CustomSpringConfigurator extends ServerEndpointConfig.Configurator
*/
private static volatile BeanFactory context;
@Override
public <T> T getEndpointInstance(Class<T> clazz) throws InstantiationException {
return context.getBean(clazz);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
CustomSpringConfigurator.context = applicationContext;
}
@Override
public <T> T getEndpointInstance(Class<T> clazz) throws InstantiationException {
return context.getBean(clazz);
}
}

View File

@ -0,0 +1,14 @@
package cn.lili.modules.im.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}

View File

@ -1,24 +0,0 @@
package cn.lili.modules.im.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* WebSocketConfigurator
*
* @author Chopper
* @version v1.0
* 2021-12-31 11:53
*/
@ConditionalOnWebApplication
@Configuration
public class WebSocketConfigurator {
@Bean
public CustomSpringConfigurator customSpringConfigurator() {
// This is just to get context
return new CustomSpringConfigurator();
}
}

View File

@ -0,0 +1,82 @@
package cn.lili.modules.im.entity.dos;
import cn.lili.common.vo.SerializableStream;
import cn.lili.mybatis.BaseEntity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
@TableName("li_friend")
@ApiModel(value = "好友关系")
public class Friend implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_ID)
@ApiModelProperty(value = "ID")
private String id;
@ApiModelProperty(value = "用户ID")
private String userId;
@ApiModelProperty(value = "好友ID")
private String friendId;
@ApiModelProperty(value = "好友备注")
private String remark;
@ApiModelProperty(value = "状态 1:已关注")
private Integer status;
@ApiModelProperty(value = "是否是店铺 0:否 1:是")
private Integer storeFlag;
@ApiModelProperty(value = "创建时间")
private Date createTime;
@ApiModelProperty(value = "更新时间")
private Date updateTime;
@ApiModelProperty(value = "是否关注")
private Integer isMutual;
}

View File

@ -0,0 +1,32 @@
package cn.lili.modules.im.entity.dos;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.util.Date;
@Data
@TableName("li_im_group")
public class ImGroup {
@TableId(type = IdType.ASSIGN_ID)
private String id;
// 群名称
private String name;
// 群头像
private String avatar;
// 群公告
private String notice;
// 群主ID
private String ownerId;
// 创建时间
private Date createTime;
// 更新时间
private Date updateTime;
}

View File

@ -0,0 +1,51 @@
package cn.lili.modules.im.entity.dos;
import cn.lili.mybatis.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@Data
@TableName("li_im_group_member")
@ApiModel(value = "群成员")
public class ImGroupMember extends BaseEntity {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "成员ID")
private String id;
@ApiModelProperty(value = "群ID")
private String groupId;
@ApiModelProperty(value = "成员ID")
private String memberId;
@ApiModelProperty(value = "成员昵称")
private String nickname;
@ApiModelProperty(value = "角色(0:普通成员 1:管理员 2:群主)")
private Integer role;
@ApiModelProperty(value = "是否被禁言")
private int isMuted;
@ApiModelProperty(value = "禁言结束时间")
private Date muteEndTime;
@ApiModelProperty(value = "加入时间")
private Date joinTime;
@ApiModelProperty(value = "创建者")
private String createBy;
@ApiModelProperty(value = "创建时间")
private Date createTime;
@ApiModelProperty(value = "更新时间")
private Date updateTime;
@ApiModelProperty(value = "删除标志")
private Boolean deleteFlag;
}

View File

@ -0,0 +1,28 @@
package cn.lili.modules.im.entity.dos;
import cn.lili.mybatis.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("li_im_member")
@ApiModel(value = "IM用户")
public class ImMember extends BaseEntity {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "用户名")
private String username;
@ApiModelProperty(value = "昵称")
private String nickname;
@ApiModelProperty(value = "头像")
private String avatar;
// 其他需要的字段...
}

View File

@ -4,8 +4,10 @@ import cn.lili.common.utils.SnowFlake;
import cn.lili.modules.im.entity.enums.MessageTypeEnum;
import cn.lili.modules.im.entity.vo.MessageOperation;
import cn.lili.mybatis.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@ -17,48 +19,83 @@ import java.util.Date;
*/
@Data
@TableName("li_im_message")
@ApiModel(value = "Im消息")
@ApiModel(value = "即时通讯消息")
@NoArgsConstructor
@AllArgsConstructor
public class ImMessage extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 发送者
*/
@TableField("talk_id")
@ApiModelProperty(value = "会话ID")
private String talkId;
@TableField("from_user")
@ApiModelProperty(value = "发送者ID")
private String fromUser;
/**
* 接收者
*/
@TableField("to_user")
@ApiModelProperty(value = "接收者ID")
private String toUser;
/**
* 已阅
*/
private Boolean isRead;
/**
* 消息类型
*/
private MessageTypeEnum messageType;
/**
* 聊天id
*/
private String talkId;
/**
* 消息实体
*/
@ApiModelProperty(value = "消息内容")
private String text;
@ApiModelProperty(value = "消息类型")
private String type;
@TableField("message_type")
@ApiModelProperty(value = "聊天类型")
private MessageTypeEnum messageType;
@ApiModelProperty(value = "消息状态")
private Integer status;
@TableField("is_read")
@ApiModelProperty(value = "是否已读")
private Boolean isRead;
// @TableField("read_time")
// @ApiModelProperty(value = "阅读时间")
// private Date readTime; // 确保类型为 Date
// 文件相关字段
@TableField("file_name")
@ApiModelProperty(value = "文件名")
private String fileName;
@TableField("file_size")
@ApiModelProperty(value = "文件大小")
private Long fileSize;
@TableField("file_url")
@ApiModelProperty(value = "文件URL")
private String fileUrl;
// 图片相关字段
@TableField("image_url")
@ApiModelProperty(value = "图片URL")
private String imageUrl;
@TableField("image_width")
@ApiModelProperty(value = "图片宽度")
private Integer imageWidth;
@TableField("image_height")
@ApiModelProperty(value = "图片高度")
private Integer imageHeight;
@TableField("thumbnail_url")
@ApiModelProperty(value = "缩略图URL")
private String thumbnailUrl;
// 语音相关字段
@TableField("voice_url")
@ApiModelProperty(value = "语音URL")
private String voiceUrl;
@ApiModelProperty(value = "语音时长")
private Integer duration;
@ApiModelProperty(value = "额外信息")
private String extra;
private String groupId;
public ImMessage(MessageOperation messageOperation){
this.setFromUser(messageOperation.getFrom());
this.setMessageType(messageOperation.getMessageType());
this.setIsRead(false);
// this.setReadTime(new Date());
this.setText(messageOperation.getContext());
this.setTalkId(messageOperation.getTalkId());
this.setCreateTime(new Date());
@ -66,4 +103,11 @@ public class ImMessage extends BaseEntity {
this.setId(SnowFlake.getIdStr());
}
public void setMessageType(MessageTypeEnum messageType) {
this.messageType = messageType;
// if (messageType != null) {
// this.messageType = MessageTypeEnum.valueOf(messageType.getType()); // 确保type字段也使用大写
// }
}
}

View File

@ -80,6 +80,8 @@ public class ImTalk extends BaseTenantEntity {
@ApiModelProperty(value = "坐席名称")
private String tenantName;
@ApiModelProperty(value = "是否关注")
private Integer isMutual;
public ImTalk(String userId1, String userId2,
@ -135,6 +137,14 @@ public class ImTalk extends BaseTenantEntity {
this.name2 = member2.getNickName();
}
}
// public ImTalk2(String userId1, String userId2, String face1, String face2, String name1, String name2) {
// this.userId1 = userId1;
// this.userId2 = userId2;
// this.face1 = face1;
// this.face2 = face2;
// this.name1 = name1;
// this.name2 = name2;
// }
public ImTalk(Member member, Store store){
if(Long.parseLong(member.getId()) > Long.parseLong(store.getId())){
this.userId1 = store.getId();

View File

@ -0,0 +1,84 @@
package cn.lili.modules.im.entity.dos;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.util.Date;
@Data
@TableName("li_member")
public class Member {
@TableId(type = IdType.AUTO)
private Long id;
// 创建者
private String createBy;
// 创建时间
private Date createTime;
// 删除标志 true/false
private Boolean deleteFlag;
// 更新者
private String updateBy;
// 更新时间
private Date updateTime;
// 会员生日
private Date birthday;
// 会员状态
private Boolean disabled;
// 会员头像
private String face;
// 是否开通店铺
private Boolean haveStore;
// 手机号码
private String mobile;
// 会员昵称
private String nickName;
// 会员密码
private String password;
// 积分数量
private Long point;
// 会员性别
private Integer sex;
// 店铺ID
private String storeId;
// 会员用户名
private String username;
// 会员地址
private String region;
// 会员地址ID
private String regionId;
// 客户端
private String clientEnum;
// 最后一次登录时间
private Date lastLoginDate;
// 等级ID
private String gradeId;
// 经验值
private Long experience;
// 总积分
private Long totalPoint;
}

View File

@ -0,0 +1,44 @@
package cn.lili.modules.im.entity.dos;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
@Data
@TableName("li_store") // 表名
public class Store {
@TableId
private Long id; // 主键 ID
private String createBy; // 创建者
private Date createTime; // 创建时间
private Boolean deleteFlag; // 删除标志
private String updateBy; // 更新者
private Date updateTime; // 更新时间
private String memberId; // 会员 ID
private String memberName; // 会员名称
private Boolean selfOperated; // 是否自营
private String storeDisable; // 店铺状态
private Date storeEndTime; // 店铺结束时间
private String storeLogo; // 店铺 logo
private String storeName; // 店铺名称
private String storeAddressDetail; // 详细地址
private String storeAddressIdPath; // 地址 ID 路径
private String storeAddressPath; // 地址路径
private String storeCenter; // 经纬度
private String storeDesc; // 店铺介绍
private BigDecimal deliveryScore; // 送货评分
private BigDecimal descriptionScore; // 描述评分
private BigDecimal serviceScore; // 服务评分
private Integer goodsNum; // 商品数量
private Integer collectionNum; // 收藏数量
private String yzfMpSign; // yzf mp sign
private String yzfSign; // yzf sign
// 其他字段可以根据需要添加
}

View File

@ -6,9 +6,13 @@ import cn.lili.common.exception.ServiceException;
import cn.lili.common.vo.PageVO;
import cn.lili.modules.im.entity.dos.ImMessage;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* MessageQueryParams
*
@ -18,6 +22,7 @@ import lombok.EqualsAndHashCode;
*/
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "消息查询参数")
public class MessageQueryParams extends PageVO {
private static final long serialVersionUID = 3504156704697214077L;
@ -25,15 +30,39 @@ public class MessageQueryParams extends PageVO {
/**
* 聊天窗口
*/
@ApiModelProperty(value = "会话ID")
private String talkId;
/**
* 后一个消息
* 早消息ID(用于向上加载历史消息)
*/
private String lastMessageId;
@ApiModelProperty(value = "最早消息ID(用于向上加载历史消息)")
private String earliestMsgId;
/**
* 最新消息ID(用于获取新消息)
*/
@ApiModelProperty(value = "最新消息ID(用于获取新消息)")
private String latestMsgId;
/**
* 获取消息数量
*/
@ApiModelProperty(value = "返回消息数量", example = "20")
private Integer num;
/**
* 消息类型(text:文本,image:图片等)
*/
@ApiModelProperty(value = "消息类型(text:文本,image:图片等)")
private String type;
/**
* 发送者ID
*/
@ApiModelProperty(value = "发送者ID")
private String fromUser;
/**
* 接收者ID
*/
@ApiModelProperty(value = "接收者ID")
private String toUser;
public LambdaQueryWrapper<ImMessage> initQueryWrapper() {
if (CharSequenceUtil.isEmpty(talkId)) {
@ -45,8 +74,11 @@ public class MessageQueryParams extends PageVO {
LambdaQueryWrapper<ImMessage> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(ImMessage::getTalkId, talkId);
if (CharSequenceUtil.isNotEmpty(lastMessageId)) {
lambdaQueryWrapper.lt(ImMessage::getId, lastMessageId);
if (CharSequenceUtil.isNotEmpty(earliestMsgId)) {
lambdaQueryWrapper.ge(ImMessage::getId, earliestMsgId);
}
if (CharSequenceUtil.isNotEmpty(latestMsgId)) {
lambdaQueryWrapper.le(ImMessage::getId, latestMsgId);
}
lambdaQueryWrapper.orderByDesc(ImMessage::getCreateTime);
// lambdaQueryWrapper.last("limit " + num);

View File

@ -1,31 +1,53 @@
package cn.lili.modules.im.entity.enums;
/**
* 消息的类型
*
* @author liushuai(liushuai711 @ gmail.com)
* @version v4.0
* @Description:
* @since 2022/2/10 16:36
* 消息状态枚举
*/
public enum MessageStatusEnum {
//socket刚打开时发送的消息这个一般是是刚打开socket链接进行登录传入token用
CONNECT,
//心跳类型的消息此种类型的消息只有 type text 两种属性
HEARTBEAT,
//用户打开一个对话框准备跟某人聊天时
OPEN,
//客服进行自动回复客户端发起这种类型请求则是在拉取对方是否有自动回复如果有服务端就会给客户端发送过自动回复的信息
AUTO_REPLY,
//正常收发消息沟通文字表情等沟通
MSG,
//扩展比如发送商品发送订单
EXTEND,
//系统提示如提示 对方已离线
SYSTEM,
//服务端发送到客户端用于设置客户端的用户信息会吧 com.xnx3.yunkefu.core.vo.bean.User 传过去
SET_USER,
//结束服务
CLOSE_SERVICE;
NORMAL(0, "正常"),
DELETED(1, "已删除"),
RECALLED(2, "已撤回"),
// WebSocket相关状态
CONNECT(100, "连接"),
HEARTBEAT(101, "心跳"),
OPEN(102, "打开对话"),
AUTO_REPLY(103, "自动回复"),
MSG(104, "普通消息"),
EXTEND(105, "扩展消息"),
SYSTEM(106, "系统消息"),
SET_USER(107, "设置用户"),
CLOSE_SERVICE(108, "结束服务");
private final Integer status;
private final String description;
MessageStatusEnum(Integer status, String description) {
this.status = status;
this.description = description;
}
public Integer getStatus() {
return status;
}
public String getDescription() {
return description;
}
/**
* 根据状态码获取枚举
*/
public static MessageStatusEnum getByStatus(Integer status) {
if (status == null) {
return null;
}
for (MessageStatusEnum statusEnum : values()) {
if (statusEnum.getStatus().equals(status)) {
return statusEnum;
}
}
return null;
}
}

View File

@ -1,23 +1,45 @@
package cn.lili.modules.im.entity.enums;
/**
* 消息类型
*
* @author liushuai
* 消息类型枚举
*/
public enum MessageTypeEnum {
/**
* 消息类型枚举
* <p>
* 普通消息
* 图片
* 语音
* 视频
*/
MESSAGE,
PICTURE,
VOICE,
GOODS,
ORDER,
VIDEO
MESSAGE("message", "普通消息"),
TEXT("TEXT", "文本消息"),
IMAGE("IMAGE", "图片消息"),
FILE("FILE", "文件消息"),
VOICE("VOICE", "语音消息"),
VIDEO("VIDEO", "视频消息"),
SYSTEM("SYSTEM", "系统消息"),
GROUP("GROUP", "群聊消息"),
LOCATION("LOCATION", "位置消息");
private final String type;
private final String description;
MessageTypeEnum(String type, String description) {
this.type = type;
this.description = description;
}
public String getType() {
return type;
}
public String getDescription() {
return description;
}
public static MessageTypeEnum fromType(String type) {
if (type == null) {
return null;
}
for (MessageTypeEnum typeEnum : MessageTypeEnum.values()) {
if (typeEnum.getType().equalsIgnoreCase(type)) {
return typeEnum;
}
}
return null;
}
}

View File

@ -21,6 +21,5 @@ public enum OperationType {
MESSAGE,
HISTORY,
READ,
UNREAD,
UNREAD
}

View File

@ -0,0 +1,35 @@
package cn.lili.modules.im.entity.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@Data
@ApiModel(value = "好友申请VO")
public class FriendRequestVO {
@ApiModelProperty(value = "申请ID")
private String id;
@ApiModelProperty(value = "申请者ID")
private String userId;
@ApiModelProperty(value = "申请者昵称")
private String nickname;
@ApiModelProperty(value = "申请者头像")
private String avatar;
@ApiModelProperty(value = "申请者手机号")
private String mobile;
@ApiModelProperty(value = "备注")
private String remark;
@ApiModelProperty(value = "状态(0:待处理 1:已接受 2:已拒绝)")
private Integer status;
@ApiModelProperty(value = "申请时间")
private Date createTime;
}

View File

@ -0,0 +1,67 @@
package cn.lili.modules.im.entity.vo;
import cn.lili.modules.im.entity.dos.Friend;
import lombok.Data;
import java.util.Date;
import cn.lili.modules.im.entity.dos.Friend;
import lombok.Data;
import java.util.Date;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@Data
@ApiModel(value = "好友VO")
public class FriendVO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "用户ID")
private String id;
@ApiModelProperty(value = "关系ID")
private String friendId;
private String talkId;
@ApiModelProperty(value = "昵称")
private String nickname;
@ApiModelProperty(value = "头像")
private String avatar;
@ApiModelProperty(value = "地区")
private String region;
@ApiModelProperty(value = "手机号")
private String mobile;
@ApiModelProperty(value = "备注")
private String remark;
@ApiModelProperty(value = "是否是店铺 0:否 1:是")
private Integer storeFlag;
@ApiModelProperty(value = "是否互相关注")
private Integer isMutual;
@ApiModelProperty(value = "关注时间")
private Date createTime;
}

View File

@ -0,0 +1,33 @@
package cn.lili.modules.im.entity.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@Data
@ApiModel(value = "群成员信息")
public class ImGroupMemberVO {
@ApiModelProperty(value = "成员ID")
private String memberId;
@ApiModelProperty(value = "成员昵称")
private String nickname;
@ApiModelProperty(value = "成员头像")
private String avatar;
@ApiModelProperty(value = "角色(0:普通成员 1:管理员 2:群主)")
private Integer role;
@ApiModelProperty(value = "是否被禁言")
private Integer isMuted;
@ApiModelProperty(value = "禁言结束时间")
private Date muteEndTime;
@ApiModelProperty(value = "加入时间")
private Date joinTime;
private String displayName;
}

View File

@ -0,0 +1,39 @@
package cn.lili.modules.im.entity.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@Data
@ApiModel(value = "群聊信息")
public class ImGroupVO {
@ApiModelProperty(value = "群ID")
private String id;
@ApiModelProperty(value = "群名称")
private String name;
@ApiModelProperty(value = "群头像")
private String avatar;
@ApiModelProperty(value = "群公告")
private String notice;
@ApiModelProperty(value = "群主ID")
private String ownerId;
@ApiModelProperty(value = "成员数量")
private Integer memberCount;
@ApiModelProperty(value = "创建时间")
private Date createTime;
private Integer role;
private String displayName;
@ApiModelProperty(value = "最后一条消息")
private ImMessageVO lastMessage;
@ApiModelProperty(value = "未读消息数")
private Integer unreadCount;
}

View File

@ -0,0 +1,78 @@
package cn.lili.modules.im.entity.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@Data
@ApiModel(value = "即时通讯消息VO")
public class ImMessageVO {
@ApiModelProperty(value = "消息ID")
private String id;
@ApiModelProperty(value = "会话ID")
private String talkId;
@ApiModelProperty(value = "发送者ID")
private String fromUser;
@ApiModelProperty(value = "发送者昵称")
private String fromName;
@ApiModelProperty(value = "发送者头像")
private String fromAvatar;
@ApiModelProperty(value = "接收者ID")
private String toUser;
@ApiModelProperty(value = "消息内容")
private String text;
@ApiModelProperty(value = "消息类型")
private String type;
@ApiModelProperty(value = "聊天类型(单聊/群聊)")
private String messageType;
@ApiModelProperty(value = "消息状态")
private Integer status;
@ApiModelProperty(value = "是否已读")
private Boolean isRead;
@ApiModelProperty(value = "创建时间")
private Date createTime;
// 扩展字段
@ApiModelProperty(value = "文件名")
private String fileName;
@ApiModelProperty(value = "文件大小")
private Long fileSize;
@ApiModelProperty(value = "文件URL")
private String fileUrl;
@ApiModelProperty(value = "图片URL")
private String imageUrl;
@ApiModelProperty(value = "图片宽度")
private Integer imageWidth;
@ApiModelProperty(value = "图片高度")
private Integer imageHeight;
@ApiModelProperty(value = "缩略图URL")
private String thumbnailUrl;
@ApiModelProperty(value = "语音URL")
private String voiceUrl;
@ApiModelProperty(value = "语音时长")
private Integer duration;
@ApiModelProperty(value = "额外信息")
private String extra;
}

View File

@ -53,6 +53,8 @@ public class ImTalkVO extends BaseTenantEntity {
@ApiModelProperty(value = "未读数量")
private Long unread;
@ApiModelProperty(value = "是否关注")
private Integer isMutual;
public ImTalkVO() {
@ -66,6 +68,7 @@ public class ImTalkVO extends BaseTenantEntity {
name = imTalk.getName1();
face = imTalk.getFace1();
storeFlag = imTalk.getStoreFlag1();
isMutual=imTalk.getIsMutual();
} else {
userId = imTalk.getUserId2();
top = imTalk.getTop2();
@ -73,6 +76,7 @@ public class ImTalkVO extends BaseTenantEntity {
name = imTalk.getName2();
face = imTalk.getFace2();
storeFlag = imTalk.getStoreFlag2();
isMutual=imTalk.getIsMutual();
}
lastTalkMessage = imTalk.getLastTalkMessage();
lastTalkTime = imTalk.getLastTalkTime();

View File

@ -41,13 +41,13 @@ public class MessageOperation {
public void setOperationType(String operationType) {
if (!StringUtils.isEmpty(operationType)) {
this.operationType = OperationType.valueOf(operationType);
this.operationType = OperationType.valueOf(operationType.toUpperCase());
}
}
public void setMessageType(String messageType) {
if (!StringUtils.isEmpty(messageType)) {
this.messageType = MessageTypeEnum.valueOf(messageType);
this.messageType = MessageTypeEnum.valueOf(messageType.toUpperCase());
}
}
}

View File

@ -0,0 +1,16 @@
package cn.lili.modules.im.mapper;
import cn.lili.modules.im.entity.dos.Friend;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface FriendMapper extends BaseMapper<Friend> {
}

View File

@ -0,0 +1,7 @@
package cn.lili.modules.im.mapper;
import cn.lili.modules.im.entity.dos.ImGroup;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface ImGroupMapper extends BaseMapper<ImGroup> {
}

View File

@ -0,0 +1,13 @@
package cn.lili.modules.im.mapper;
import cn.lili.modules.im.entity.dos.ImGroupMember;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ImGroupMemberMapper extends BaseMapper<ImGroupMember> {
}

View File

@ -0,0 +1,88 @@
package cn.lili.modules.im.service;
import cn.lili.modules.im.entity.dos.Friend;
import cn.lili.modules.im.entity.vo.FriendVO;
import cn.lili.modules.im.entity.vo.FriendRequestVO;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* 好友关系业务层
*
* @author Chopper
*/
public interface FriendService extends IService<Friend> {
/**
* 获取关注列表
* @param userId 用户ID
* @return 关注的用户列表
*/
List<FriendVO> getMutualFriends(String userId);
/**
* 获取用户详细信息
* @param friendId 用户ID
* @return 用户详细信息
*/
FriendVO getFriendDetails(String friendId);
/**
* 取消关注
* @param userId 当前用户ID
* @param friendId 要取消关注的用户ID
*/
void removeFriend(String userId, String friendId);
/**
* 关注用户
* @param userId 当前用户ID
* @param friendId 要关注的用户ID
*/
void addFriend(String userId, String friendId);
/**
* 通过手机号关注用户
* @param mobile 手机号
* @param remark 备注
*/
void addFriendByMobile(String mobile, String remark);
/**
* 更新备注
* @param userId 当前用户ID
* @param friendId 关注的用户ID
* @param remark 备注
*/
void updateRemark(String userId, String friendId, String remark);
//
// /**
// * 获取好友申请列表
// * @param userId 用户ID
// * @return 申请列表
// */
// List<FriendRequestVO> getFriendRequests(String userId);
//
// /**
// * 接受好友申请
// * @param requestId 申请ID
// * @param userId 当前用户ID
// */
// void acceptFriendRequest(String requestId, String userId);
//
// /**
// * 拒绝好友申请
// * @param requestId 申请ID
// * @param userId 当前用户ID
// */
// void rejectFriendRequest(String requestId, String userId);
/**
* 搜索用户
* @param keyword 搜索关键词(用户名/手机号)
* @param onlyStore 是否只搜索店铺
* @param currentUserId 当前用户ID
* @return 用户列表
*/
List<FriendVO> searchUsers(String keyword, Boolean onlyStore, String currentUserId);
}

View File

@ -0,0 +1,154 @@
package cn.lili.modules.im.service;
import cn.lili.modules.im.entity.dos.ImGroup;
import cn.lili.modules.im.entity.vo.FriendVO;
import cn.lili.modules.im.entity.vo.ImGroupMemberVO;
import cn.lili.modules.im.entity.vo.ImGroupVO;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
public interface ImGroupService extends IService<ImGroup> {
/**
* 获取用户加入的群聊列表
* @param userId 用户ID
* @return 群聊列表
*/
List<ImGroupVO> getUserGroups(String userId);
/**
* 获取群聊详情
* @param groupId 群ID
* @return 群聊详情
*/
ImGroupVO getGroupDetail(String groupId);
/**
* 获取群成员列表
* @param groupId 群ID
* @return 群成员列表
*/
List<ImGroupMemberVO> getGroupMembers(String groupId);
/**
* 退出群聊
* @param groupId 群ID
* @param userId 用户ID
*/
void quitGroup(String groupId, String userId);
/**
* 修改群信息
* @param groupId 群ID
* @param groupName 群名称
* @param notice 群公告
* @param avatar 群头像
*/
void updateGroupInfo(String groupId, String groupName, String notice, String avatar);
/**
* 创建群聊
* @param groupName 群名称
* @param memberIds 邀请的成员ID列表
* @return 群聊信息
*/
ImGroup createGroup(String groupName, List<String> memberIds);
/**
* 解散群聊
* @param groupId 群ID
*/
void dismissGroup(String groupId);
/**
* 邀请成员
* @param groupId 群ID
* @param memberIds 成员ID列表
*/
void inviteMembers(String groupId, List<String> memberIds);
/**
* 设置管理员
* @param groupId 群ID
* @param memberId 成员ID
*/
void setAdmin(String groupId, String memberId);
/**
* 取消管理员
* @param groupId 群ID
* @param memberId 成员ID
*/
void removeAdmin(String groupId, String memberId);
/**
* 禁言成员
* @param groupId 群ID
* @param memberId 成员ID
* @param duration 禁言时长(分钟)
*/
void muteMember(String groupId, String memberId, Integer duration);
/**
* 解除成员禁言
* @param groupId 群ID
* @param memberId 成员ID
*/
void unmuteMember(String groupId, String memberId);
}

View File

@ -1,7 +1,10 @@
package cn.lili.modules.im.service;
import cn.lili.common.vo.PageVO;
import cn.lili.common.vo.ResultPageVO;
import cn.lili.modules.im.entity.dos.ImMessage;
import cn.lili.modules.im.entity.dto.MessageQueryParams;
import cn.lili.modules.im.entity.vo.ImMessageVO;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
@ -13,6 +16,33 @@ import java.util.List;
*/
public interface ImMessageService extends IService<ImMessage> {
/**
* 获取历史消息
* @param talkId 对话ID
* @param earliestMsgId 最早消息ID
* @param pageSize 每页大小
* @return 消息列表
*/
List<ImMessageVO> getHistoryMessages(String talkId, String earliestMsgId, Integer pageSize);
/**
* 获取最近消息
* @param talkId 对话ID
* @param latestMsgId 最新消息ID
* @param pageSize 每页大小
* @return 消息列表
*/
List<ImMessageVO> getRecentMessages(String talkId, String latestMsgId, Integer pageSize);
/**
* 发送消息
* @param toId 接收者ID
* @param content 消息内容
* @param type 消息类型
* @return 消息对象
*/
ImMessageVO sendMessage(String toId, String content, String type);
/**
* 阅读消息
*
@ -63,4 +93,60 @@ public interface ImMessageService extends IService<ImMessage> {
* 清空所有未读消息
*/
void cleanUnreadMessage();
/**
* 获取群聊消息历史
* @param groupId 群ID
* @param pageNumber 页码
* @param pageSize 每页大小
* @return 消息列表
*/
ResultPageVO<ImMessageVO> getGroupMessages(String groupId, Integer pageNumber, Integer pageSize);
/**
* 发送群聊消息
* @param groupId 群ID
* @param content 消息内容
* @param type 消息类型
* @return 消息对象
*/
ImMessageVO sendGroupMessage(String groupId, String content, String type);
/**
* 撤回消息
* @param messageId 消息ID
*/
void recallMessage(String messageId);
/**
* 加载历史消息
* @param talkId 会话ID
* @param earliestMsgId 最早消息ID
* @param size 每页大小
* @return 消息列表
*/
List<ImMessageVO> loadHistoryMessages(String talkId, String earliestMsgId, Integer size);
/**
* 加载最新消息
* @param talkId 会话ID
* @param size 每页大小
* @return 消息列表
*/
List<ImMessageVO> loadLatestMessages(String talkId, Integer size);
/**
* 获取新消息
* @param talkId 会话ID
* @param latestMsgId 最新消息ID
* @return 新消息列表
*/
List<ImMessageVO> getNewerMessages(String talkId, String latestMsgId);
/**
* 标记消息已读
* @param messages 消息列表
* @param userId 用户ID
*/
void readMessages(List<ImMessage> messages, String userId);
}

View File

@ -0,0 +1,7 @@
package cn.lili.modules.im.service;
import cn.lili.modules.im.entity.vo.MessageVO;
public interface MessageSender {
void sendMessage(String sessionId, MessageVO message);
}

View File

@ -0,0 +1,404 @@
package cn.lili.modules.im.serviceimpl;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.security.AuthUser;
import cn.lili.common.security.context.UserContext;
import cn.lili.modules.im.entity.dos.Friend;
import cn.lili.modules.im.entity.dos.ImTalk;
import cn.lili.modules.im.entity.vo.FriendVO;
import cn.lili.modules.im.entity.vo.FriendRequestVO;
import cn.lili.modules.im.mapper.FriendMapper;
import cn.lili.modules.im.service.FriendService;
import cn.lili.modules.im.service.ImTalkService;
import cn.lili.modules.member.entity.dos.Member;
import cn.lili.modules.member.service.MemberService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 好友业务层实现
*
* @author Chopper
*/
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> implements FriendService {
private final ImTalkService imTalkService;
private final MemberService memberService;
private final FriendMapper friendMapper;
@Override
public List<FriendVO> getMutualFriends(String userId) {
// 验证用户登录状态
AuthUser currentUser = UserContext.getCurrentUser();
if (currentUser == null) {
throw new ServiceException(ResultCode.USER_NOT_LOGIN);
}
// 获取当前用户的ID
String currentUserId = currentUser.getId();
// 构建查询条件
LambdaQueryWrapper<Friend> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Friend::getUserId, currentUserId) // 查询当前用户的关注列表
.eq(Friend::getStatus, 1); // 只查询状态为1的记录
// 查询关注列表
List<Friend> friends = this.list(queryWrapper);
if (friends.isEmpty()) {
return new ArrayList<>(); // 如果没有找到返回空列表
}
// 收集所有关注的用户ID
List<String> friendIds = friends.stream()
.map(Friend::getFriendId) // 获取 friendId
.collect(Collectors.toList());
// 检查 friendIds 是否为空
if (friendIds.isEmpty()) {
log.warn("No friend IDs found for user: {}", currentUserId);
return new ArrayList<>(); // 或者抛出异常
}
// 批量查询用户信息
List<Member> members = memberService.listByIds(friendIds);
Map<String, Member> memberMap = members.stream()
.collect(Collectors.toMap(Member::getId, member -> member));
// 组装VO对象
return friends.stream().map(friend -> {
Member member = memberMap.get(friend.getFriendId());
if (member != null) {
FriendVO vo = new FriendVO();
vo.setId(member.getId().toString()); // 设置为用户的 ID
vo.setFriendId(friend.getFriendId()); // 设置为 Friend ID
vo.setNickname(member.getNickName());
vo.setAvatar(member.getFace());
// 其他字段设置...
return vo;
}
return null;
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
// 假设这个方法用于从 ImTalk 表中获取 talkId
private String getTalkId(String userId1, String userId2) {
LambdaQueryWrapper<ImTalk> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper
.or().eq(ImTalk::getUserId1, userId1).eq(ImTalk::getUserId2, userId2)
.or().eq(ImTalk::getUserId1, userId2).eq(ImTalk::getUserId2, userId1);
ImTalk imTalk = imTalkService.getOne(queryWrapper);
return imTalk != null ? imTalk.getId() : null; // 返回 talkId
}
@Override
public void addFriend(String userId, String friendId) {
// 验证用户登录状态
AuthUser currentUser = UserContext.getCurrentUser();
if (currentUser == null) {
throw new ServiceException(ResultCode.USER_NOT_LOGIN);
}
// 检查是否已经关注
LambdaQueryWrapper<Friend> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Friend::getUserId, userId)
.eq(Friend::getFriendId, friendId);
// 如果已经关注抛出异常
if (this.count(queryWrapper) > 0) {
throw new ServiceException("您已经关注过该用户");
}
// 创建新的关注关系
Friend friend = new Friend();
friend.setUserId(userId);
friend.setFriendId(friendId);
friend.setStatus(1); // 设置为已关注状态
friend.setIsMutual(1);
friend.setCreateTime(new Date());
// friend.setUpdateTime(new Date());
// 检查对方是否也关注了当前用户
Integer mutualStatus = isFollowing(friendId, userId); // 检查 friendId 是否关注了 userId
// 设置互相关注状态
if (mutualStatus == 1) {
friend.setIsMutual(2); // 设置为互相关注
} else {
friend.setIsMutual(1); // 设置为单向关注
}
// 保存关注关系
this.save(friend);
}
@Override
public FriendVO getFriendDetails(String friendId) {
// 验证用户登录状态
AuthUser currentUser = UserContext.getCurrentUser();
if (currentUser == null) {
throw new ServiceException(ResultCode.USER_NOT_LOGIN);
}
// 获取好友信息
Member member = memberService.getById(friendId);
if (member == null) {
throw new ServiceException("用户不存在");
}
// 转换为VO
FriendVO vo = new FriendVO();
vo.setId(member.getId().toString());
vo.setNickname(member.getNickName());
vo.setAvatar(member.getFace());
vo.setRegion(member.getRegion());
vo.setMobile(member.getMobile());
// 设置默认值
vo.setRemark(""); // 或者设置为 null
vo.setStoreFlag(member.getHaveStore() ? 1 : 0); // 假设你有这个字段
vo.setCreateTime(new Date()); // 或者设置为 null
// 获取当前用户ID
String currentUserId = currentUser.getId();
// 查询Friend表以获取isMutual值
LambdaQueryWrapper<Friend> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Friend::getUserId, currentUserId)
.eq(Friend::getFriendId, friendId);
Friend friendRelation = this.getOne(queryWrapper);
if (friendRelation != null) {
vo.setIsMutual(friendRelation.getIsMutual()); // 设置isMutual值
vo.setFriendId(friendRelation.getFriendId());
} else {
vo.setIsMutual(0); // 如果没有找到关系设置为未关注
}
return vo;
}
@Override
public void removeFriend(String userId, String friendId) {
// 验证用户登录状态
AuthUser currentUser = UserContext.getCurrentUser();
if (currentUser == null) {
throw new ServiceException(ResultCode.USER_NOT_LOGIN);
}
// 验证操作权限
if (!currentUser.getId().equals(userId)) {
throw new ServiceException(ResultCode.USER_AUTHORITY_ERROR);
}
// 取消关注
LambdaQueryWrapper<Friend> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Friend::getUserId, userId)
.eq(Friend::getFriendId, friendId);
if (!this.remove(queryWrapper)) {
throw new ServiceException("取消关注失败");
}
}
@Override
public void updateRemark(String userId, String friendId, String remark) {
// 验证用户登录状态
AuthUser currentUser = UserContext.getCurrentUser();
if (currentUser == null) {
throw new ServiceException(ResultCode.USER_NOT_LOGIN);
}
// 验证操作权限
if (!currentUser.getId().equals(userId)) {
throw new ServiceException(ResultCode.USER_AUTHORITY_ERROR);
}
// 更新备注
LambdaQueryWrapper<Friend> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Friend::getUserId, userId)
.eq(Friend::getFriendId, friendId)
.eq(Friend::getStatus, 1);
Friend friend = this.getOne(queryWrapper);
if (friend == null) {
throw new ServiceException("未关注该用户");
}
friend.setRemark(remark);
friend.setUpdateTime(new Date());
if (!this.updateById(friend)) {
throw new ServiceException("更新备注失败");
}
}
@Override
public void addFriendByMobile(String mobile, String remark) {
// 查找目标用户
Member targetMember = memberService.findByMobile(mobile);
if (targetMember == null) {
throw new ServiceException("该手机号未注册");
}
String currentUserId = UserContext.getCurrentUser().getId();
String targetUserId = targetMember.getId().toString();
// 不能关注自己
if (currentUserId.equals(targetUserId)) {
throw new ServiceException("不能关注自己");
}
// 检查是否已经关注
if (isFollowing(currentUserId, targetUserId) > 0) {
throw new ServiceException("已经关注该用户");
}
// 创建关注关系
Friend friend = new Friend();
friend.setUserId(currentUserId);
friend.setFriendId(targetUserId);
friend.setRemark(remark);
friend.setStatus(1);
friend.setIsMutual(1); // 直接设置为已关注状态
friend.setStoreFlag(targetMember.getHaveStore() ? 1 : 0); // 设置是否是店铺
friend.setCreateTime(new Date());
// friend.setUpdateTime(new Date());
// 检查对方是否也关注了当前用户
Integer mutualStatus = isFollowing(targetUserId, currentUserId);
// 设置互相关注状态
if (mutualStatus == 1) {
friend.setIsMutual(2); // 设置为互相关注
} else {
friend.setIsMutual(1); // 设置为单向关注
}
// 保存关注关系
this.save(friend);
}
/**
* 检查是否是好友关系互相关注
*/
private Integer isFriend(String userId, String friendId) {
// 检查是否互相关注
Integer isUserFollowingFriend = isFollowing(userId, friendId);
Integer isFriendFollowingUser = isFollowing(friendId, userId);
// 如果双方都关注返回 2如果只有一个方向关注返回 1否则返回 0
if (isUserFollowingFriend == 1 && isFriendFollowingUser == 1) {
return 2; // 互相关注
} else if (isUserFollowingFriend == 1 || isFriendFollowingUser == 1) {
return 1; // 单向关注
}
return 0; // 未关注
}
/**
* 检查是否已关注
*/
public Integer isFollowing(String currentUserId, String targetUserId) {
// 查询当前用户是否关注目标用户
Long count = friendMapper.selectCount(new LambdaQueryWrapper<Friend>()
.eq(Friend::getUserId, currentUserId)
.eq(Friend::getFriendId, targetUserId)
.eq(Friend::getIsMutual, 1)); // 确保状态为已关注
if (count > 0) {
// 查询目标用户是否也关注当前用户
Long mutualCount = friendMapper.selectCount(new LambdaQueryWrapper<Friend>()
.eq(Friend::getUserId, targetUserId)
.eq(Friend::getFriendId, currentUserId)
.eq(Friend::getIsMutual, 1)); // 确保状态为已关注
return mutualCount > 0 ? 2 : 1; // 2 表示互相关注1 表示单向关注
}
return 0; // 未关注
}
@Override
public List<FriendVO> searchUsers(String keyword, Boolean onlyStore, String currentUserId) {
// 构建查询条件
LambdaQueryWrapper<Member> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.and(wrapper -> wrapper
.like(Member::getNickName, keyword)
.or()
.like(Member::getMobile, keyword)
)
.ne(Member::getId, currentUserId); // 排除自己
if (Boolean.TRUE.equals(onlyStore)) {
queryWrapper.eq(Member::getHaveStore, true);
}
// 限制返回数量
queryWrapper.last("LIMIT 20");
// 查询用户
List<Member> members = memberService.list(queryWrapper);
if (members.isEmpty()) {
return new ArrayList<>();
}
// 获取已关注的用户ID
List<String> memberIds = members.stream()
.map(member -> member.getId().toString())
.distinct() // 确保唯一
.collect(Collectors.toList());
// 查询当前用户与这些用户的关注关系
LambdaQueryWrapper<Friend> friendWrapper = new LambdaQueryWrapper<>();
friendWrapper.eq(Friend::getUserId, currentUserId)
.in(Friend::getFriendId, memberIds)
.eq(Friend::getStatus, 1);
List<Friend> friends = this.list(friendWrapper);
// 构建已关注用户的Map
Map<String, Friend> friendMap = friends.stream()
.collect(Collectors.toMap(Friend::getFriendId, friend -> friend, (existing, replacement) -> existing)); // 处理重复键
// 转换为VO对象
return members.stream().map(member -> {
FriendVO vo = new FriendVO();
vo.setId(member.getId().toString());
vo.setNickname(member.getNickName());
vo.setAvatar(member.getFace());
vo.setRegion(member.getRegion());
vo.setMobile(member.getMobile());
vo.setStoreFlag(member.getHaveStore() ? 1 : 0);
// 设置是否互相关注
Friend friend = friendMap.get(member.getId().toString());
if (friend != null) {
vo.setFriendId(friend.getId());
vo.setRemark(friend.getRemark());
vo.setCreateTime(friend.getCreateTime());
// 检查是否互相关注
vo.setIsMutual(friend.getIsMutual() != null ? friend.getIsMutual() : 0);
} else {
vo.setIsMutual(0); // 如果没有找到对应的 Friend 记录设置为 0
}
return vo;
}).distinct() // 确保返回的 VO 对象唯一
.collect(Collectors.toList());
}
}

View File

@ -0,0 +1,405 @@
package cn.lili.modules.im.serviceimpl;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.security.AuthUser;
import cn.lili.common.security.context.UserContext;
import cn.lili.common.exception.ServiceException;
import cn.lili.modules.im.entity.dos.ImGroup;
import cn.lili.modules.im.entity.dos.ImGroupMember;
import cn.lili.modules.im.entity.vo.ImGroupMemberVO;
import cn.lili.modules.im.entity.vo.ImGroupVO;
import cn.lili.modules.im.mapper.ImGroupMapper;
import cn.lili.modules.im.mapper.ImGroupMemberMapper;
import cn.lili.modules.im.service.ImGroupService;
import cn.lili.modules.im.service.FriendService;
import cn.lili.modules.im.entity.vo.FriendVO;
import cn.lili.modules.member.entity.dos.Member;
import cn.lili.modules.member.mapper.MemberMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ImGroupServiceImpl extends ServiceImpl<ImGroupMapper, ImGroup> implements ImGroupService {
private final ImGroupMemberMapper groupMemberMapper;
private final MemberMapper memberMapper;
private final FriendService friendService;
@Override
public List<ImGroupVO> getUserGroups(String userId) {
// 查询用户加入的群组
LambdaQueryWrapper<ImGroupMember> memberQuery = new LambdaQueryWrapper<>();
memberQuery.eq(ImGroupMember::getMemberId, userId);
List<ImGroupMember> groupMembers = groupMemberMapper.selectList(memberQuery);
if (groupMembers.isEmpty()) {
return new ArrayList<>();
}
// 获取群组ID列表
List<String> groupIds = groupMembers.stream()
.map(ImGroupMember::getGroupId)
.collect(Collectors.toList());
// 批量查询群组信息
List<ImGroup> groups = this.listByIds(groupIds);
// 转换为VO对象
return groups.stream().map(group -> {
ImGroupVO vo = new ImGroupVO();
BeanUtils.copyProperties(group, vo);
// 设置成员数量
vo.setMemberCount(getGroupMemberCount(group.getId()));
// TODO: 设置最后一条消息和未读数
return vo;
}).collect(Collectors.toList());
}
@Override
public ImGroupVO getGroupDetail(String groupId) {
// 获取群组基本信息
ImGroup group = this.getById(groupId);
if (group == null) {
throw new ServiceException("群组不存在");
}
// 转换为VO
ImGroupVO vo = new ImGroupVO();
BeanUtils.copyProperties(group, vo);
// 设置成员数量
vo.setMemberCount(getGroupMemberCount(groupId));
// TODO: 设置最后一条消息和未读数
return vo;
}
@Override
public List<ImGroupMemberVO> getGroupMembers(String groupId) {
// 查询群成员
LambdaQueryWrapper<ImGroupMember> memberQuery = new LambdaQueryWrapper<>();
memberQuery.eq(ImGroupMember::getGroupId, groupId)
.orderByDesc(ImGroupMember::getRole)
.orderByAsc(ImGroupMember::getJoinTime);
List<ImGroupMember> members = groupMemberMapper.selectList(memberQuery);
if (members.isEmpty()) {
return new ArrayList<>();
}
// 获取成员用户信息
List<String> memberIds = members.stream()
.map(ImGroupMember::getMemberId)
.collect(Collectors.toList());
List<Member> users = memberMapper.selectBatchIds(memberIds);
Map<String, Member> userMap = users.stream()
.collect(Collectors.toMap(member -> member.getId().toString(), member -> member));
// 转换为VO
return members.stream().map(member -> {
ImGroupMemberVO vo = new ImGroupMemberVO();
BeanUtils.copyProperties(member, vo);
Member user = userMap.get(member.getMemberId());
if (user != null) {
vo.setNickname(user.getNickName());
vo.setAvatar(user.getFace());
}
return vo;
}).collect(Collectors.toList());
}
@Override
public void quitGroup(String groupId, String userId) {
// 验证群组是否存在
ImGroup group = this.getById(groupId);
if (group == null) {
throw new ServiceException("群组不存在");
}
// 群主不能退群
if (group.getOwnerId().equals(userId)) {
throw new ServiceException("群主不能退群");
}
// 删除群成员记录
LambdaQueryWrapper<ImGroupMember> memberQuery = new LambdaQueryWrapper<>();
memberQuery.eq(ImGroupMember::getGroupId, groupId)
.eq(ImGroupMember::getMemberId, userId);
groupMemberMapper.delete(memberQuery);
}
@Override
public void updateGroupInfo(String groupId, String groupName, String notice, String avatar) {
// 验证群组是否存在
ImGroup group = this.getById(groupId);
if (group == null) {
throw new ServiceException("群组不存在");
}
// 验证操作权限
String currentUserId = UserContext.getCurrentUser().getId();
LambdaQueryWrapper<ImGroupMember> memberQuery = new LambdaQueryWrapper<>();
memberQuery.eq(ImGroupMember::getGroupId, groupId)
.eq(ImGroupMember::getMemberId, currentUserId)
.ge(ImGroupMember::getRole, 1); // 管理员及以上权限
if (groupMemberMapper.selectCount(memberQuery) == 0) {
throw new ServiceException("没有权限修改群信息");
}
// 更新群信息
ImGroup updateGroup = new ImGroup();
updateGroup.setId(groupId);
if (groupName != null) {
updateGroup.setName(groupName);
}
if (notice != null) {
updateGroup.setNotice(notice);
}
if (avatar != null) {
updateGroup.setAvatar(avatar);
}
updateGroup.setUpdateTime(new Date());
this.updateById(updateGroup);
}
@Override
public ImGroup createGroup(String groupName, List<String> memberIds) {
AuthUser currentUser = UserContext.getCurrentUser();
if (currentUser == null) {
throw new ServiceException(ResultCode.USER_NOT_LOGIN);
}
// 创建群组
ImGroup group = new ImGroup();
group.setName(groupName);
group.setOwnerId(currentUser.getId());
group.setCreateTime(new Date());
group.setUpdateTime(new Date());
if (!this.save(group)) {
throw new ServiceException("创建群组失败");
}
// 添加群主为群成员
addGroupMember(group.getId(), currentUser.getId(), 2); // 2表示群主
// 添加初始成员
if (memberIds != null && !memberIds.isEmpty()) {
for (String memberId : memberIds) {
addGroupMember(group.getId(), memberId, 0); // 0表示普通成员
}
}
return group;
}
@Override
public void dismissGroup(String groupId) {
// 验证群组是否存在
ImGroup group = this.getById(groupId);
if (group == null) {
throw new ServiceException("群组不存在");
}
// 验证是否是群主
String currentUserId = UserContext.getCurrentUser().getId();
if (!group.getOwnerId().equals(currentUserId)) {
throw new ServiceException("只有群主能解散群组");
}
// 删除群成员
LambdaQueryWrapper<ImGroupMember> memberQuery = new LambdaQueryWrapper<>();
memberQuery.eq(ImGroupMember::getGroupId, groupId);
groupMemberMapper.delete(memberQuery);
// 删除群组
this.removeById(groupId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void inviteMembers(String groupId, List<String> memberIds) {
String currentUserId = UserContext.getCurrentUser().getId();
// 检查权限
ImGroupMember operator = getGroupMember(groupId, currentUserId);
if (operator == null || operator.getRole() < 1) {
throw new ServiceException("没有邀请权限");
}
// 添加成员
for (String memberId : memberIds) {
if (getGroupMember(groupId, memberId) == null) {
addGroupMember(groupId, memberId, 0);
}
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void setAdmin(String groupId, String memberId) {
String currentUserId = UserContext.getCurrentUser().getId();
// 检查是否是群主
ImGroupMember owner = getGroupMember(groupId, currentUserId);
if (owner == null || owner.getRole() != 2) {
throw new ServiceException("只有群主能设置管理员");
}
// 设置管理员
ImGroupMember member = getGroupMember(groupId, memberId);
if (member == null) {
throw new ServiceException("该成员不在群中");
}
member.setRole(1);
member.setUpdateTime(new Date());
groupMemberMapper.updateById(member);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void removeAdmin(String groupId, String memberId) {
String currentUserId = UserContext.getCurrentUser().getId();
// 检查是否是群主
ImGroupMember owner = getGroupMember(groupId, currentUserId);
if (owner == null || owner.getRole() != 2) {
throw new ServiceException("只有群主能取消管理员");
}
// 取消管理员
ImGroupMember member = getGroupMember(groupId, memberId);
if (member == null) {
throw new ServiceException("该成员不在群中");
}
member.setRole(0);
member.setUpdateTime(new Date());
groupMemberMapper.updateById(member);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void muteMember(String groupId, String memberId, Integer duration) {
String currentUserId = UserContext.getCurrentUser().getId();
// 检查操作权限
ImGroupMember operator = getGroupMember(groupId, currentUserId);
if (operator == null || operator.getRole() < 1) {
throw new ServiceException("没有禁言权限");
}
// 检查被禁言成员
ImGroupMember member = getGroupMember(groupId, memberId);
if (member == null) {
throw new ServiceException("该成员不在群中");
}
// 管理员不能禁言群主和其他管理员
if (operator.getRole() == 1 && member.getRole() > 0) {
throw new ServiceException("没有权限禁言该成员");
}
// 设置禁言
member.setIsMuted(1);
member.setMuteEndTime(new Date(System.currentTimeMillis() + duration * 60 * 1000));
member.setUpdateTime(new Date());
groupMemberMapper.updateById(member);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void unmuteMember(String groupId, String memberId) {
String currentUserId = UserContext.getCurrentUser().getId();
// 检查操作权限
ImGroupMember operator = getGroupMember(groupId, currentUserId);
if (operator == null || operator.getRole() < 1) {
throw new ServiceException("没有解除禁言权限");
}
// 检查被解除禁言成员
ImGroupMember member = getGroupMember(groupId, memberId);
if (member == null) {
throw new ServiceException("该成员不在群中");
}
// 管理员不能解除群主和其他管理员的禁言
if (operator.getRole() == 1 && member.getRole() > 0) {
throw new ServiceException("没有权限解除该成员的禁言");
}
// 解除禁言
member.setIsMuted(0);
member.setMuteEndTime(null);
member.setUpdateTime(new Date());
groupMemberMapper.updateById(member);
}
/**
* 获取群成员数量
*/
private Integer getGroupMemberCount(String groupId) {
LambdaQueryWrapper<ImGroupMember> countQuery = new LambdaQueryWrapper<>();
countQuery.eq(ImGroupMember::getGroupId, groupId);
return Math.toIntExact(groupMemberMapper.selectCount(countQuery));
}
/**
* 添加群成员
*/
private void addGroupMember(String groupId, String memberId, Integer role) {
ImGroupMember member = new ImGroupMember();
member.setGroupId(groupId);
member.setMemberId(memberId);
member.setRole(role);
member.setJoinTime(new Date());
member.setIsMuted(0);
groupMemberMapper.insert(member);
}
/**
* 获取群成员信息
*/
private ImGroupMember getGroupMember(String groupId, String memberId) {
LambdaQueryWrapper<ImGroupMember> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ImGroupMember::getGroupId, groupId)
.eq(ImGroupMember::getMemberId, memberId);
return groupMemberMapper.selectOne(queryWrapper);
}
/**
* 检查是否是好友关系
*/
private boolean isFriend(String userId, String friendId) {
List<FriendVO> friends = friendService.getMutualFriends(userId);
return friends.stream()
.anyMatch(friend -> friend.getFriendId().equals(friendId));
}
}

View File

@ -6,10 +6,12 @@ import cn.lili.common.security.AuthUser;
import cn.lili.common.security.context.UserContext;
import cn.lili.common.security.enums.UserEnums;
import cn.lili.modules.im.entity.dos.ImMessage;
import cn.lili.modules.im.entity.dos.ImTalk;
import cn.lili.modules.im.entity.dto.MessageQueryParams;
import cn.lili.modules.im.mapper.ImMessageMapper;
import cn.lili.modules.im.service.ImMessageService;
import cn.lili.modules.im.service.ImTalkService;
import cn.lili.modules.im.websocket.WebSocketServer;
import cn.lili.mybatis.util.PageUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@ -19,9 +21,14 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.*;
import java.util.stream.Collectors;
import cn.lili.modules.im.entity.vo.ImMessageVO;
import cn.lili.modules.im.entity.dos.ImGroup;
import cn.lili.modules.im.mapper.ImGroupMapper;
import cn.lili.modules.im.entity.vo.MessageOperation;
import cn.lili.common.vo.ResultPageVO;
/**
* Im消息 业务实现
@ -33,8 +40,26 @@ import java.util.Objects;
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ImMessageServiceImpl extends ServiceImpl<ImMessageMapper, ImMessage> implements ImMessageService {
// @Autowired
// private final WebSocketServer imTalkService;
@Autowired
private ImTalkService imTalkService;
private ImGroupMapper imGroupMapper;
@Override
public List<ImMessageVO> getHistoryMessages(String talkId, String earliestMsgId, Integer pageSize) {
return null;
}
@Override
public List<ImMessageVO> getRecentMessages(String talkId, String latestMsgId, Integer pageSize) {
return null;
}
@Override
public ImMessageVO sendMessage(String toId, String content, String type) {
return null;
}
@Override
public void read(String talkId, String accessToken) {
@ -43,6 +68,7 @@ public class ImMessageServiceImpl extends ServiceImpl<ImMessageMapper, ImMessage
updateWrapper.eq(ImMessage::getTalkId, talkId);
updateWrapper.eq(ImMessage::getToUser, userId);
updateWrapper.set(ImMessage::getIsRead, true);
// updateWrapper.set(ImMessage::getReadTime,new Date());
this.update(updateWrapper);
}
@ -51,7 +77,7 @@ public class ImMessageServiceImpl extends ServiceImpl<ImMessageMapper, ImMessage
String userId = UserContext.getAuthUser(accessToken).getId();
LambdaQueryWrapper<ImMessage> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ImMessage::getToUser, userId);
queryWrapper.eq(ImMessage::getIsRead, false);
// queryWrapper.eq(ImMessage::getIsRead, false);
return this.list(queryWrapper);
}
@ -77,12 +103,23 @@ public class ImMessageServiceImpl extends ServiceImpl<ImMessageMapper, ImMessage
@Override
public List<ImMessage> getList(MessageQueryParams messageQueryParams) {
List<ImMessage> messageList = this.page(PageUtil.initPage(messageQueryParams), messageQueryParams.initQueryWrapper()).getRecords();
// 使用 LambdaQueryWrapper 创建查询条件
LambdaQueryWrapper<ImMessage> queryWrapper = messageQueryParams.initQueryWrapper();
// 关联查询假设 ImTalkMapper 是你的 Mapper
queryWrapper.eq(ImMessage::getTalkId, messageQueryParams.getTalkId());
// 执行分页查询
List<ImMessage> messageList = this.page(PageUtil.initPage(messageQueryParams), queryWrapper).getRecords();
// 对消息列表进行排序和已读处理
ListSort(messageList);
readMessage(messageList);
return messageList;
}
@Override
public Long unreadMessageCount() {
AuthUser currentUser = UserContext.getCurrentUser();
@ -101,6 +138,63 @@ public class ImMessageServiceImpl extends ServiceImpl<ImMessageMapper, ImMessage
this.update(new LambdaUpdateWrapper<ImMessage>().eq(ImMessage::getToUser,currentUser.getId()).set(ImMessage::getIsRead,true));
}
@Override
public ResultPageVO<ImMessageVO> getGroupMessages(String groupId, Integer pageNumber, Integer pageSize) {
List<ImMessage> messages = this.getMessagesByGroupId(groupId, pageNumber, pageSize);
List<ImMessageVO> messageVOs = messages.stream()
.map(message -> new ImMessageVO())
.collect(Collectors.toList());
Long total = this.count(new LambdaQueryWrapper<ImMessage>().eq(ImMessage::getGroupId, groupId));
return new ResultPageVO<>(total, (long) Math.ceil((double) total / pageSize), (long) pageNumber, (long) pageSize, messageVOs);
}
@Override
public ImMessageVO sendGroupMessage(String groupId, String content, String type) {
ImMessage imMessage = new ImMessage();
imMessage.setText(content);
imMessage.setType(type);
imMessage.setGroupId(groupId);
imMessage.setCreateTime(new Date());
this.save(imMessage);
return new ImMessageVO();
}
@Override
public void recallMessage(String messageId) {
}
@Override
public List<ImMessageVO> loadHistoryMessages(String talkId, String earliestMsgId, Integer size) {
return null;
}
@Override
public List<ImMessageVO> loadLatestMessages(String talkId, Integer size) {
return null;
}
@Override
public List<ImMessageVO> getNewerMessages(String talkId, String latestMsgId) {
return null;
}
@Override
public void readMessages(List<ImMessage> messages, String userId) {
}
private List<ImMessage> getMessagesByGroupId(String groupId, Integer pageNumber, Integer pageSize) {
return this.list(new LambdaQueryWrapper<ImMessage>().eq(ImMessage::getGroupId, groupId)
.orderByDesc(ImMessage::getCreateTime)
.last("LIMIT " + (pageNumber - 1) * pageSize + ", " + pageSize));
}
/**
* 根据时间倒叙
*
@ -144,6 +238,7 @@ public class ImMessageServiceImpl extends ServiceImpl<ImMessageMapper, ImMessage
for (ImMessage imMessage : messageList) {
if(Boolean.FALSE.equals(imMessage.getIsRead()) && imMessage.getToUser().equals(toUserId)){
imMessage.setIsRead(true);
// imMessage.setReadTime(new Date());
}
}
}

View File

@ -6,10 +6,12 @@ import cn.lili.common.exception.ServiceException;
import cn.lili.common.security.AuthUser;
import cn.lili.common.security.context.UserContext;
import cn.lili.common.security.enums.UserEnums;
import cn.lili.modules.im.entity.dos.Friend;
import cn.lili.modules.im.entity.dos.ImMessage;
import cn.lili.modules.im.entity.dos.ImTalk;
import cn.lili.modules.im.entity.dto.IMTalkQueryParams;
import cn.lili.modules.im.entity.vo.ImTalkVO;
import cn.lili.modules.im.mapper.FriendMapper;
import cn.lili.modules.im.mapper.ImTalkMapper;
import cn.lili.modules.im.service.ImMessageService;
import cn.lili.modules.im.service.ImTalkService;
@ -20,33 +22,43 @@ import cn.lili.modules.store.service.StoreService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 聊天 业务实现
*
* @author Chopper
*/
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
//@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ImTalkServiceImpl extends ServiceImpl<ImTalkMapper, ImTalk> implements ImTalkService {
private final ImMessageService imMessageService;
@Autowired
public ImTalkServiceImpl(@Lazy ImMessageService imMessageService) {
this.imMessageService = imMessageService;
}
@Autowired
FriendMapper friendMapper;
@Autowired
private MemberService memberService;
@Autowired
private StoreService storeService;
@Autowired
private ImMessageService imMessageService;
public ImTalk getTalkByUser(String userId) {
LambdaQueryWrapper<ImTalk> queryWrapper = new LambdaQueryWrapper<>();
AuthUser currentUser = Objects.requireNonNull(UserContext.getCurrentUser());
@ -180,14 +192,52 @@ public class ImTalkServiceImpl extends ServiceImpl<ImTalkMapper, ImTalk> impleme
if (authUser == null) {
throw new ServiceException(ResultCode.USER_NOT_LOGIN);
}
LambdaQueryWrapper<ImTalk> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.and(wq -> wq.eq(ImTalk::getUserId1, authUser.getId()).or().eq(ImTalk::getUserId2, authUser.getId()));
if (CharSequenceUtil.isNotEmpty(imTalkQueryParams.getUserName())) {
queryWrapper.and(wq -> wq.ne(ImTalk::getUserId1, authUser.getId()).like(ImTalk::getName1, imTalkQueryParams.getUserName()).or().ne(ImTalk::getUserId2, authUser.getId()).like(ImTalk::getName2, imTalkQueryParams.getUserName()));
queryWrapper.and(wq -> wq.ne(ImTalk::getUserId1, authUser.getId())
.like(ImTalk::getName1, imTalkQueryParams.getUserName())
.or()
.ne(ImTalk::getUserId2, authUser.getId())
.like(ImTalk::getName2, imTalkQueryParams.getUserName()));
}
queryWrapper.orderByDesc(ImTalk::getLastTalkTime);
List<ImTalk> imTalks = this.list(queryWrapper);
List<ImTalkVO> imTalkVOList = imTalks.stream().map(imTalk -> new ImTalkVO(imTalk, authUser.getId())).collect(Collectors.toList());
// 获取好友关系
List<String> friendIds = imTalks.stream()
.flatMap(imTalk -> Stream.of(imTalk.getUserId1(), imTalk.getUserId2()))
.distinct()
.collect(Collectors.toList());
// 查询好友关系
List<Friend> friends = friendMapper.selectList(new LambdaQueryWrapper<Friend>()
.in(Friend::getFriendId, friendIds)
.eq(Friend::getUserId, authUser.getId())
.eq(Friend::getStatus, 1)); // 只查询状态为1的记录
// 创建一个 Set 来存储已关注的用户 ID
Set<String> mutualFriends = friends.stream()
.map(Friend::getFriendId)
.collect(Collectors.toSet());
// 更新 isMutual 字段
for (ImTalk imTalk : imTalks) {
if (mutualFriends.contains(imTalk.getUserId1()) || mutualFriends.contains(imTalk.getUserId2())) {
imTalk.setIsMutual(1); // 设置为单向关注
} else {
imTalk.setIsMutual(0); // 设置为未关注
}
}
// 将更新后的 imTalk 转换为 VO 对象
List<ImTalkVO> imTalkVOList = imTalks.stream()
.map(imTalk -> new ImTalkVO(imTalk, authUser.getId()))
.collect(Collectors.toList());
getUnread(imTalkVOList);
return imTalkVOList;
}

View File

@ -1,7 +1,8 @@
package cn.lili.controller.im;
package cn.lili.modules.im.websocket;
import cn.hutool.json.JSONUtil;
import cn.lili.cache.Cache;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.security.AuthUser;
import cn.lili.common.security.context.UserContext;
import cn.lili.common.security.enums.UserEnums;
@ -13,8 +14,11 @@ import cn.lili.modules.im.entity.vo.MessageOperation;
import cn.lili.modules.im.entity.vo.MessageVO;
import cn.lili.modules.im.service.ImMessageService;
import cn.lili.modules.im.service.ImTalkService;
import cn.lili.modules.member.entity.dos.Member;
import cn.lili.modules.member.service.MemberService;
import com.alibaba.druid.util.StringUtils;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
@ -40,6 +44,7 @@ import org.springframework.stereotype.Component;
@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class WebSocketServer {
private final MemberService memberService;
/**
* 在线人数 PS 注意只能单节点如果多节点部署需要自行寻找方案
*/
@ -107,21 +112,36 @@ public class WebSocketServer {
* @param messageOperation
*/
private void operation(String accessToken, MessageOperation messageOperation) {
AuthUser authUser = UserContext.getAuthUser(accessToken);
switch (messageOperation.getOperationType()) {
case PING:
break;
case MESSAGE:
//保存消息
// 获取或创建聊天记录
ImTalk imTalk = getOrCreateTalk(authUser.getId(), messageOperation.getTo());
// 如果 imTalk null说明创建失败记录日志并返回
if (imTalk == null) {
log.warn("Failed to create ImTalk for users: {} and {}", authUser.getId(), messageOperation.getTo());
return; // 或者抛出异常
}
// 保存消息
ImMessage imMessage = new ImMessage(messageOperation);
imMessage.setCreateBy(authUser.getNickName());
imMessage.setTalkId(imTalk.getId()); // 设置 talk_id imTalk id
imMessageService.save(imMessage);
//修改最后消息信息
imTalkService.update(new LambdaUpdateWrapper<ImTalk>().eq(ImTalk::getId, messageOperation.getTalkId())
log.info("Message saved: {}", imMessage);
// 更新最后消息信息
imTalkService.update(new LambdaUpdateWrapper<ImTalk>()
.eq(ImTalk::getId, imTalk.getId())
.set(ImTalk::getLastTalkMessage, messageOperation.getContext())
.set(ImTalk::getLastTalkTime, imMessage.getCreateTime())
.set(ImTalk::getLastMessageType, imMessage.getMessageType()));
//发送消息
// 发送消息
sendMessage(messageOperation.getTo(), new MessageVO(MessageResultType.MESSAGE, imMessage));
break;
case READ:
@ -141,7 +161,45 @@ public class WebSocketServer {
break;
}
}
// 获取或创建聊天记录
public ImTalk getOrCreateTalk(String userId1, String userId2) {
// 查询现有的聊天记录
LambdaQueryWrapper<ImTalk> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper
.or().eq(ImTalk::getUserId1, userId1).eq(ImTalk::getUserId2, userId2)
.or().eq(ImTalk::getUserId1, userId2).eq(ImTalk::getUserId2, userId1);
ImTalk imTalk = imTalkService.getOne(queryWrapper);
if (imTalk != null) {
log.info("Found existing talk: {}", imTalk);
return imTalk;
}
// 如果没有找到创建新的聊天记录
log.info("Creating new talk between {} and {}", userId1, userId2);
// 获取用户信息
Member member1 = memberService.getById(userId1);
Member member2 = memberService.getById(userId2);
// 检查用户是否存在
if (member1 == null || member2 == null) {
log.warn("One of the members does not exist: member1={}, member2={}", member1, member2);
throw new ServiceException("One of the users does not exist."); // 或者根据需要处理
}
String face1 = member1.getFace() != null ? member1.getFace() : ""; // 获取头像
String face2 = member2.getFace() != null ? member2.getFace() : ""; // 获取头像
String name1 = member1.getNickName() != null ? member1.getNickName() : ""; // 获取昵称
String name2 = member2.getNickName() != null ? member2.getNickName() : ""; // 获取昵称
// 创建新的聊天记录
imTalk = new ImTalk(userId1, userId2, face1, face2, name1, name2);
imTalkService.save(imTalk);
log.info("New talk created: {}", imTalk);
return imTalk;
}
/**
* 发送消息
*

View File

@ -6,6 +6,8 @@ import cn.lili.common.security.sensitive.enums.SensitiveStrategy;
import cn.lili.common.utils.CommonUtil;
import cn.lili.mybatis.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ -31,6 +33,24 @@ public class Member extends BaseEntity {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
private String id;
// 创建者
private String createBy;
// 创建时间
private Date createTime;
// 删除标志 true/false
private Boolean deleteFlag;
// 更新者
private String updateBy;
// 更新时间
private Date updateTime;
@ApiModelProperty(value = "会员用户名")
private String username;
@ -97,13 +117,13 @@ public class Member extends BaseEntity {
@ApiModelProperty(value = "经验值数量")
private Long experience;
public Member(String username, String password, String mobile) {
this.username = username;
this.password = password;
this.mobile = mobile;
this.nickName = CommonUtil.getSpecialStr("用户");
this.disabled = true;
this.deleteFlag=false;
this.haveStore = false;
this.sex = 0;
this.point = 0L;
@ -115,6 +135,7 @@ public class Member extends BaseEntity {
this.username = username;
this.password = password;
this.mobile = mobile;
this.deleteFlag=false;
this.nickName = nickName;
this.disabled = true;
this.haveStore = false;

View File

@ -9,6 +9,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.mapstruct.Mapper;
import java.util.List;
@ -17,7 +18,9 @@ import java.util.List;
*
* @author Bulbasaur
* @since 2020-02-25 14:10:16
* @
*/
@Mapper
public interface MemberMapper extends BaseMapper<Member> {
/**

View File

@ -118,7 +118,11 @@ public class MemberServiceImpl extends ServiceImpl<MemberMapper, Member> impleme
public Member getUserInfo() {
AuthUser tokenUser = UserContext.getCurrentUser();
if (tokenUser != null) {
return this.findByUsername(tokenUser.getUsername());
Member member = this.findByUsername(tokenUser.getUsername());
if(member != null && !member.getDisabled()){
throw new ServiceException(ResultCode.USER_STATUS_ERROR);
}
return member;
}
throw new ServiceException(ResultCode.USER_NOT_LOGIN);
}

View File

@ -313,16 +313,34 @@ public class PayKit {
* v3 接口创建签名
*
* @param signMessage 待签名的参数
* @param keyPath key.pem 证书路径
* @param key key.pem 证书
* @return 生成 v3 签名
* @throws Exception 异常信息
*/
public static String createSign(String signMessage, String keyPath) throws Exception {
public static String createSign(String signMessage, String key) throws Exception {
if (StrUtil.isEmpty(signMessage)) {
return null;
}
//获取商户私钥
PrivateKey privateKey = PayKit.getPrivateKey(keyPath);
PrivateKey privateKey = PayKit.getPrivateKey(key);
//生成签名
return RsaKit.encryptByPrivateKey(signMessage, privateKey);
}
/**
* v3 接口创建签名
*
* @param signMessage 待签名的参数
* @param key key.pem 证书
* @return 生成 v3 签名
* @throws Exception 异常信息
*/
public static String createPublicSign(String signMessage, String key) throws Exception {
if (StrUtil.isEmpty(signMessage)) {
return null;
}
//获取商户私钥
PrivateKey privateKey = PayKit.getPublicKey(key);
//生成签名
return RsaKit.encryptByPrivateKey(signMessage, privateKey);
}
@ -378,13 +396,13 @@ public class PayKit {
/**
* 获取商户私钥
*
* @param keyPath 商户私钥证书路径
* @param key 商户私钥证书
* @return {@link PrivateKey} 商户私钥
* @throws Exception 异常信息
*/
public static PrivateKey getPrivateKey(String keyPath) throws Exception {
String originalKey = FileUtil.readUtf8String(keyPath);
String privateKey = originalKey
public static PrivateKey getPrivateKey(String key) throws Exception {
// String originalKey = FileUtil.readUtf8String(keyPath);
String privateKey = key
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
@ -392,6 +410,23 @@ public class PayKit {
return RsaKit.loadPrivateKey(privateKey);
}
/**
* 获取商户公钥
*
* @param key 商户私钥证书
* @return {@link PrivateKey} 商户私钥
* @throws Exception 异常信息
*/
public static PrivateKey getPublicKey(String key) throws Exception {
// String originalKey = FileUtil.readUtf8String(keyPath);
String privateKey = key
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s+", "");
return RsaKit.loadPrivateKey(privateKey);
}
/**
* 获取证书
*

View File

@ -312,13 +312,18 @@ public class RsaKit {
*/
public static PrivateKey loadPrivateKey(String privateKeyStr) throws Exception {
try {
byte[] buffer = Base64.decode(privateKeyStr);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
return keyFactory.generatePrivate(keySpec);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此算法");
} catch (InvalidKeySpecException e) {
e.printStackTrace();
throw new Exception("私钥非法");
} catch (NullPointerException e) {
throw new Exception("私钥数据为空");

View File

@ -449,7 +449,8 @@ public class WxPayKit {
* @param urlSuffix 可通过 WxApiType 来获取URL挂载参数需要自行拼接
* @param mchId 商户Id
* @param serialNo 商户 API 证书序列号
* @param keyPath key.pem 证书路径
* @param key key.pem 证书
* @param publicKey 公钥证书
* @param body 接口请求参数
* @param nonceStr 随机字符库
* @param timestamp 时间戳
@ -458,11 +459,12 @@ public class WxPayKit {
* @throws Exception 异常信息
*/
public static String buildAuthorization(RequestMethodEnums method, String urlSuffix, String mchId,
String serialNo, String keyPath, String body, String nonceStr,
String serialNo, String key, String publicKey ,String body, String nonceStr,
long timestamp, String authType) throws Exception {
//构建签名参数
String buildSignMessage = PayKit.buildSignMessage(method, urlSuffix, timestamp, nonceStr, body);
String signature = PayKit.createSign(buildSignMessage, keyPath);
String publicKeySignature = PayKit.createPublicSign(buildSignMessage, publicKey);
String signature = PayKit.createSign(publicKeySignature, key);
//根据平台规则生成请求头 authorization
return PayKit.getAuthorization(mchId, serialNo, nonceStr, String.valueOf(timestamp), signature, authType);
}
@ -492,27 +494,27 @@ public class WxPayKit {
return PayKit.getAuthorization(mchId, serialNo, nonceStr, String.valueOf(timestamp), signature, authType);
}
/**
* 构建 v3 接口所需的 Authorization
*
* @param method {@link RequestMethodEnums} 请求方法
* @param urlSuffix 可通过 WxApiType 来获取URL挂载参数需要自行拼接
* @param mchId 商户Id
* @param serialNo 商户 API 证书序列号
* @param keyPath key.pem 证书路径
* @param body 接口请求参数
* @return {@link String} 返回 v3 所需的 Authorization
* @throws Exception 异常信息
*/
public static String buildAuthorization(RequestMethodEnums method, String urlSuffix, String mchId,
String serialNo, String keyPath, String body) throws Exception {
long timestamp = System.currentTimeMillis() / 1000;
String authType = "WECHATPAY2-SHA256-RSA2048";
String nonceStr = IdUtil.fastSimpleUUID();
return buildAuthorization(method, urlSuffix, mchId, serialNo, keyPath, body, nonceStr, timestamp, authType);
}
// /**
// * 构建 v3 接口所需的 Authorization
// *
// * @param method {@link RequestMethodEnums} 请求方法
// * @param urlSuffix 可通过 WxApiType 来获取URL挂载参数需要自行拼接
// * @param mchId 商户Id
// * @param serialNo 商户 API 证书序列号
// * @param keyPath key.pem 证书路径
// * @param body 接口请求参数
// * @return {@link String} 返回 v3 所需的 Authorization
// * @throws Exception 异常信息
// */
// public static String buildAuthorization(RequestMethodEnums method, String urlSuffix, String mchId,
// String serialNo, String keyPath, String body) throws Exception {
//
// long timestamp = System.currentTimeMillis() / 1000;
// String authType = "WECHATPAY2-SHA256-RSA2048";
// String nonceStr = IdUtil.fastSimpleUUID();
//
// return buildAuthorization(method, urlSuffix, mchId, serialNo, keyPath, body, nonceStr, timestamp, authType);
// }
/**
* 构建 v3 接口所需的 Authorization

View File

@ -2,15 +2,11 @@ package cn.lili.modules.payment.kit.plugin.wechat;
import cn.hutool.core.net.URLDecoder;
import cn.hutool.core.net.URLEncoder;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.lili.cache.Cache;
import cn.lili.cache.CachePrefix;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.enums.ResultUtil;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.properties.ApiProperties;
import cn.lili.common.security.context.UserContext;
import cn.lili.common.utils.CurrencyUtil;
import cn.lili.common.utils.SnowFlake;
@ -26,17 +22,16 @@ import cn.lili.modules.payment.entity.RefundLog;
import cn.lili.modules.payment.entity.enums.PaymentMethodEnum;
import cn.lili.modules.payment.kit.CashierSupport;
import cn.lili.modules.payment.kit.Payment;
import cn.lili.modules.payment.kit.core.PaymentHttpResponse;
import cn.lili.modules.payment.kit.core.enums.RequestMethodEnums;
import cn.lili.modules.payment.kit.core.enums.SignType;
import cn.lili.modules.payment.kit.core.kit.*;
import cn.lili.modules.payment.kit.core.kit.HttpKit;
import cn.lili.modules.payment.kit.core.kit.IpKit;
import cn.lili.modules.payment.kit.core.kit.WxPayKit;
import cn.lili.modules.payment.kit.core.utils.DateTimeZoneUtil;
import cn.lili.modules.payment.kit.dto.PayParam;
import cn.lili.modules.payment.kit.dto.PaymentSuccessParams;
import cn.lili.modules.payment.kit.params.dto.CashierParam;
import cn.lili.modules.payment.kit.plugin.wechat.enums.WechatApiEnum;
import cn.lili.modules.payment.kit.plugin.wechat.enums.WechatDomain;
import cn.lili.modules.payment.kit.plugin.wechat.model.*;
import cn.lili.modules.payment.kit.plugin.wechat.model.H5Info;
import cn.lili.modules.payment.kit.plugin.wechat.model.SceneInfo;
import cn.lili.modules.payment.service.PaymentService;
import cn.lili.modules.payment.service.RefundLogService;
import cn.lili.modules.system.entity.dos.Setting;
@ -51,6 +46,29 @@ import cn.lili.modules.wallet.entity.dto.TransferResultDTO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.google.gson.Gson;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.RSAPublicKeyConfig;
import com.wechat.pay.java.core.exception.ValidationException;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.app.AppService;
import com.wechat.pay.java.service.payments.h5.H5Service;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
import com.wechat.pay.java.service.payments.nativepay.model.Amount;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.AmountReq;
import com.wechat.pay.java.service.refund.model.CreateRequest;
import com.wechat.pay.java.service.refund.model.Refund;
import com.wechat.pay.java.service.transferbatch.TransferBatchService;
import com.wechat.pay.java.service.transferbatch.model.InitiateBatchTransferRequest;
import com.wechat.pay.java.service.transferbatch.model.InitiateBatchTransferResponse;
import com.wechat.pay.java.service.transferbatch.model.TransferDetailInput;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@ -58,12 +76,9 @@ import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* 微信支付
@ -141,31 +156,34 @@ public class WechatPlugin implements Payment {
if (appid == null) {
throw new ServiceException(ResultCode.WECHAT_PAYMENT_NOT_SETTING);
}
UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
.setAppid(appid)
.setMchid(setting.getMchId())
.setDescription(cashierParam.getDetail())
.setOut_trade_no(outOrderNo)
.setTime_expire(timeExpire)
.setAttach(attach)
.setNotify_url(notifyUrl(wechatPaymentSetting().getCallbackUrl(),PaymentMethodEnum.WECHAT))
.setAmount(new Amount().setTotal(fen)).setScene_info(sceneInfo);
log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
PaymentHttpResponse response = WechatApi.v3(
RequestMethodEnums.POST,
WechatDomain.CHINA.toString(),
WechatApiEnum.H5_PAY.toString(),
setting.getMchId(),
setting.getSerialNumber(),
null,
setting.getApiclient_key(),
JSONUtil.toJsonStr(unifiedOrderModel)
);
Config config =null;
if(setting.getPublicType().equals("CERT")){
config=this.getCertificateConfig(setting);
}else {
config=this.getPublicKeyConfig(setting);
}
// 构建service
H5Service service = new H5Service.Builder().config(config).build();
com.wechat.pay.java.service.payments.h5.model.PrepayRequest prepayRequest = new com.wechat.pay.java.service.payments.h5.model.PrepayRequest();
com.wechat.pay.java.service.payments.h5.model.Amount amount = new com.wechat.pay.java.service.payments.h5.model.Amount();
amount.setTotal(fen);
prepayRequest.setAmount(amount);
prepayRequest.setAppid(appid);
prepayRequest.setMchid(setting.getMchId());
prepayRequest.setDescription(cashierParam.getDetail());
prepayRequest.setNotifyUrl(notifyUrl(wechatPaymentSetting().getCallbackUrl(), PaymentMethodEnum.WECHAT));
prepayRequest.setAttach(attach);
prepayRequest.setTimeExpire(timeExpire);
prepayRequest.setOutTradeNo(outOrderNo);
// 调用下单方法得到应答
com.wechat.pay.java.service.payments.h5.model.PrepayResponse response = service.prepay(prepayRequest);
updateOrderPayNo(payParam,outOrderNo);
return ResultUtil.data(JSONUtil.toJsonStr(response.getBody()));
return ResultUtil.data(response.getH5Url());
} catch (Exception e) {
log.error("微信H5支付错误", e);
throw new ServiceException(ResultCode.PAY_ERROR);
@ -183,8 +201,7 @@ public class WechatPlugin implements Payment {
return null;
}
Payer payer = new Payer();
payer.setOpenid(connect.getUnionId());
CashierParam cashierParam = cashierSupport.cashierParam(payParam);
@ -202,46 +219,38 @@ public class WechatPlugin implements Payment {
if (appid == null) {
throw new ServiceException(ResultCode.WECHAT_PAYMENT_NOT_SETTING);
}
UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
.setAppid(appid)
.setMchid(setting.getMchId())
.setDescription(cashierParam.getDetail())
.setOut_trade_no(outOrderNo)
.setTime_expire(timeExpire)
.setAttach(attach)
.setNotify_url(notifyUrl(wechatPaymentSetting().getCallbackUrl(), PaymentMethodEnum.WECHAT))
.setAmount(new Amount().setTotal(fen))
.setPayer(payer);
log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
PaymentHttpResponse response = WechatApi.v3(
RequestMethodEnums.POST,
WechatDomain.CHINA.toString(),
WechatApiEnum.JS_API_PAY.toString(),
setting.getMchId(),
setting.getSerialNumber(),
null,
setting.getApiclient_key(),
JSONUtil.toJsonStr(unifiedOrderModel)
);
//根据证书序列号查询对应的证书来验证签名结果
boolean verifySignature = WxPayKit.verifySignature(response, getPlatformCert());
log.info("verifySignature: {}", verifySignature);
log.info("统一下单响应 {}", response);
Config config =null;
if(setting.getPublicType().equals("CERT")){
config=this.getCertificateConfig(setting);
}else {
config=this.getPublicKeyConfig(setting);
}
// 构建service
JsapiService service = new JsapiService.Builder().config(config).build();
if (verifySignature) {
String body = response.getBody();
JSONObject jsonObject = JSONUtil.parseObj(body);
String prepayId = jsonObject.getStr("prepay_id");
Map<String, String> map = WxPayKit.jsApiCreateSign(appid, prepayId, setting.getApiclient_key());
log.info("唤起支付参数:{}", map);
com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest prepayRequest = new com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest();
com.wechat.pay.java.service.payments.jsapi.model.Amount amount = new com.wechat.pay.java.service.payments.jsapi.model.Amount();
com.wechat.pay.java.service.payments.jsapi.model.Payer payer = new com.wechat.pay.java.service.payments.jsapi.model.Payer();
payer.setOpenid(connect.getUnionId());
amount.setTotal(fen);
prepayRequest.setAmount(amount);
prepayRequest.setAppid(appid);
prepayRequest.setMchid(setting.getMchId());
prepayRequest.setDescription(cashierParam.getDetail());
prepayRequest.setNotifyUrl(notifyUrl(wechatPaymentSetting().getCallbackUrl(), PaymentMethodEnum.WECHAT));
prepayRequest.setAttach(attach);
prepayRequest.setTimeExpire(timeExpire);
prepayRequest.setOutTradeNo(outOrderNo);
prepayRequest.setPayer(payer);
// 调用下单方法得到应答
com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse response = service.prepay(prepayRequest);
updateOrderPayNo(payParam,outOrderNo);
Map<String, String> map = WxPayKit.jsApiCreateSign(appid, response.getPrepayId(), setting.getApiclientKey());
log.info("唤起支付参数:{}", map);
return ResultUtil.data(map);
}
log.error("微信支付参数验证错误,请及时处理");
throw new ServiceException(ResultCode.PAY_ERROR);
} catch (Exception e) {
log.error("支付异常", e);
throw new ServiceException(ResultCode.PAY_ERROR);
@ -269,48 +278,39 @@ public class WechatPlugin implements Payment {
if (appid == null) {
throw new ServiceException(ResultCode.WECHAT_PAYMENT_NOT_SETTING);
}
UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
.setAppid(appid)
.setMchid(setting.getMchId())
.setDescription(cashierParam.getDetail())
.setOut_trade_no(outOrderNo)
.setTime_expire(timeExpire)
.setAttach(attach)
.setNotify_url(notifyUrl(wechatPaymentSetting().getCallbackUrl(), PaymentMethodEnum.WECHAT))
.setAmount(new Amount().setTotal(fen));
Config config =null;
if(setting.getPublicType().equals("CERT")){
config=this.getCertificateConfig(setting);
}else {
config=this.getPublicKeyConfig(setting);
}
// 构建service
AppService service = new AppService.Builder().config(config).build();
log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
PaymentHttpResponse response = WechatApi.v3(
RequestMethodEnums.POST,
WechatDomain.CHINA.toString(),
WechatApiEnum.APP_PAY.toString(),
setting.getMchId(),
setting.getSerialNumber(),
null,
setting.getApiclient_key(),
JSONUtil.toJsonStr(unifiedOrderModel)
);
//根据证书序列号查询对应的证书来验证签名结果
boolean verifySignature = WxPayKit.verifySignature(response, getPlatformCert());
log.info("verifySignature: {}", verifySignature);
log.info("统一下单响应 {}", response);
com.wechat.pay.java.service.payments.app.model.PrepayRequest prepayRequest = new com.wechat.pay.java.service.payments.app.model.PrepayRequest();
com.wechat.pay.java.service.payments.app.model.Amount amount = new com.wechat.pay.java.service.payments.app.model.Amount();
amount.setTotal(fen);
prepayRequest.setAmount(amount);
prepayRequest.setAppid(appid);
prepayRequest.setMchid(setting.getMchId());
prepayRequest.setDescription(cashierParam.getDetail());
prepayRequest.setNotifyUrl(notifyUrl(wechatPaymentSetting().getCallbackUrl(), PaymentMethodEnum.WECHAT));
prepayRequest.setAttach(attach);
prepayRequest.setTimeExpire(timeExpire);
prepayRequest.setOutTradeNo(outOrderNo);
if (verifySignature) {
JSONObject jsonObject = JSONUtil.parseObj(response.getBody());
String prepayId = jsonObject.getStr("prepay_id");
// 调用下单方法得到应答
com.wechat.pay.java.service.payments.app.model.PrepayResponse response = service.prepay(prepayRequest);
updateOrderPayNo(payParam,outOrderNo);
Map<String, String> map = WxPayKit.appPrepayIdCreateSign(appid,
setting.getMchId(),
prepayId,
setting.getApiclient_key(), SignType.MD5);
response.getPrepayId(),
setting.getApiclientKey(), SignType.MD5);
log.info("唤起支付参数:{}", map);
//修改付款单号
updateOrderPayNo(payParam,outOrderNo);
return ResultUtil.data(map);
}
log.error("微信支付参数验证错误,请及时处理");
throw new ServiceException(ResultCode.PAY_ERROR);
} catch (Exception e) {
log.error("支付异常", e);
throw new ServiceException(ResultCode.PAY_ERROR);
@ -339,41 +339,33 @@ public class WechatPlugin implements Payment {
if (appid == null) {
throw new ServiceException(ResultCode.WECHAT_PAYMENT_NOT_SETTING);
}
UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
.setAppid(appid)
.setMchid(setting.getMchId())
.setDescription(cashierParam.getDetail())
.setOut_trade_no(outOrderNo)
.setTime_expire(timeExpire)
//回传参数
.setAttach(attach)
.setNotify_url(notifyUrl(wechatPaymentSetting().getCallbackUrl(), PaymentMethodEnum.WECHAT))
.setAmount(new Amount().setTotal(fen));
log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
PaymentHttpResponse response = WechatApi.v3(
RequestMethodEnums.POST,
WechatDomain.CHINA.toString(),
WechatApiEnum.NATIVE_PAY.toString(),
setting.getMchId(),
setting.getSerialNumber(),
null,
setting.getApiclient_key(),
JSONUtil.toJsonStr(unifiedOrderModel)
);
log.info("统一下单响应 {}", response);
//根据证书序列号查询对应的证书来验证签名结果
boolean verifySignature = WxPayKit.verifySignature(response, getPlatformCert());
log.info("verifySignature: {}", verifySignature);
if (verifySignature) {
updateOrderPayNo(payParam,outOrderNo);
return ResultUtil.data(new JSONObject(response.getBody()).getStr("code_url"));
} else {
log.error("微信支付参数验证错误,请及时处理");
throw new ServiceException(ResultCode.PAY_ERROR);
Config config =null;
if(setting.getPublicType().equals("CERT")){
config=this.getCertificateConfig(setting);
}else {
config=this.getPublicKeyConfig(setting);
}
// 构建service
NativePayService service = new NativePayService.Builder().config(config).build();
PrepayRequest prepayRequest = new PrepayRequest();
Amount amount = new Amount();
amount.setTotal(fen);
prepayRequest.setAmount(amount);
prepayRequest.setAppid(appid);
prepayRequest.setMchid(setting.getMchId());
prepayRequest.setDescription(cashierParam.getDetail());
prepayRequest.setNotifyUrl(notifyUrl(wechatPaymentSetting().getCallbackUrl(), PaymentMethodEnum.WECHAT));
prepayRequest.setAttach(attach);
prepayRequest.setTimeExpire(timeExpire);
prepayRequest.setOutTradeNo(outOrderNo);
// 调用下单方法得到应答
PrepayResponse response = service.prepay(prepayRequest);
updateOrderPayNo(payParam,outOrderNo);
return ResultUtil.data(response.getCodeUrl());
} catch (ServiceException e) {
log.error("支付异常", e);
throw new ServiceException(ResultCode.PAY_ERROR);
@ -381,6 +373,7 @@ public class WechatPlugin implements Payment {
log.error("支付异常", e);
throw new ServiceException(ResultCode.PAY_ERROR);
}
}
@Override
@ -394,7 +387,7 @@ public class WechatPlugin implements Payment {
return null;
}
Payer payer = new Payer();
com.wechat.pay.java.service.payments.jsapi.model.Payer payer = new com.wechat.pay.java.service.payments.jsapi.model.Payer();
payer.setOpenid(connect.getUnionId());
CashierParam cashierParam = cashierSupport.cashierParam(payParam);
@ -415,44 +408,37 @@ public class WechatPlugin implements Payment {
String attach = URLEncoder.createDefault().encode(JSONUtil.toJsonStr(payParam), StandardCharsets.UTF_8);
WechatPaymentSetting setting = wechatPaymentSetting();
UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
.setAppid(appid)
.setMchid(setting.getMchId())
.setDescription(cashierParam.getDetail())
.setOut_trade_no(outOrderNo)
.setTime_expire(timeExpire)
.setAttach(attach)
.setNotify_url(notifyUrl(wechatPaymentSetting().getCallbackUrl(), PaymentMethodEnum.WECHAT))
.setAmount(new Amount().setTotal(fen))
.setPayer(payer);
log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
PaymentHttpResponse response = WechatApi.v3(
RequestMethodEnums.POST,
WechatDomain.CHINA.toString(),
WechatApiEnum.JS_API_PAY.toString(),
setting.getMchId(),
setting.getSerialNumber(),
null,
setting.getApiclient_key(),
JSONUtil.toJsonStr(unifiedOrderModel)
);
//根据证书序列号查询对应的证书来验证签名结果
boolean verifySignature = WxPayKit.verifySignature(response, getPlatformCert());
log.info("verifySignature: {}", verifySignature);
log.info("统一下单响应 {}", response);
if (verifySignature) {
String body = response.getBody();
JSONObject jsonObject = JSONUtil.parseObj(body);
String prepayId = jsonObject.getStr("prepay_id");
Map<String, String> map = WxPayKit.jsApiCreateSign(appid, prepayId, setting.getApiclient_key());
log.info("唤起支付参数:{}", map);
updateOrderPayNo(payParam,outOrderNo);
return ResultUtil.data(map);
Config config =null;
if(setting.getPublicType().equals("CERT")){
config=this.getCertificateConfig(setting);
}else {
config=this.getPublicKeyConfig(setting);
}
log.error("微信支付参数验证错误,请及时处理");
throw new ServiceException(ResultCode.PAY_ERROR);
// 构建service
JsapiService service = new JsapiService.Builder().config(config).build();
com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest prepayRequest = new com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest();
com.wechat.pay.java.service.payments.jsapi.model.Amount amount = new com.wechat.pay.java.service.payments.jsapi.model.Amount();
amount.setTotal(fen);
prepayRequest.setAmount(amount);
prepayRequest.setAppid(appid);
prepayRequest.setMchid(setting.getMchId());
prepayRequest.setDescription(cashierParam.getDetail());
prepayRequest.setNotifyUrl(notifyUrl(wechatPaymentSetting().getCallbackUrl(), PaymentMethodEnum.WECHAT));
prepayRequest.setAttach(attach);
prepayRequest.setTimeExpire(timeExpire);
prepayRequest.setOutTradeNo(outOrderNo);
prepayRequest.setPayer(payer);
// 调用下单方法得到应答
com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse response = service.prepay(prepayRequest);
updateOrderPayNo(payParam,outOrderNo);
Map<String, String> map = WxPayKit.jsApiCreateSign(appid, response.getPrepayId(), setting.getApiclientKey());
log.info("唤起支付参数:{}", map);
return ResultUtil.data(map);
} catch (Exception e) {
log.error("支付异常", e);
throw new ServiceException(ResultCode.PAY_ERROR);
@ -514,48 +500,51 @@ public class WechatPlugin implements Payment {
}
}
//获取微信设置
WechatPaymentSetting wechatPaymentSetting = wechatPaymentSetting();
//获取用户openId
Connect connect = connectService.queryConnect(
ConnectQueryDTO.builder().userId(memberWithdrawApply.getMemberId())
.unionType(source).build()
);
//构建提现发起申请
TransferModel transferModel = new TransferModel()
.setAppid(withdrawalSetting.getWechatAppId())
.setOut_batch_no(SnowFlake.createStr("T"))
.setBatch_name("用户提现")
.setBatch_remark("用户提现")
.setTotal_amount(CurrencyUtil.fen(memberWithdrawApply.getApplyMoney()))
.setTotal_num(1)
.setTransfer_scene_id("1000");
//获取微信设置
WechatPaymentSetting setting = wechatPaymentSetting();
Config config =null;
if(setting.getPublicType().equals("CERT")){
config=this.getCertificateConfig(setting);
}else {
config=this.getPublicKeyConfig(setting);
}
// 构建service
TransferBatchService service = new TransferBatchService.Builder().config(config).build();
InitiateBatchTransferRequest request = new InitiateBatchTransferRequest();
request.setAppid(withdrawalSetting.getWechatAppId());
request.setOutBatchNo(SnowFlake.createStr("T"));
request.setBatchName("用户提现");
request.setBatchRemark("用户提现");
request.setTotalAmount(CurrencyUtil.getFenLong(memberWithdrawApply.getApplyMoney()));
request.setTotalNum(1);
request.setTransferSceneId("1000");
List<TransferDetailInput> transferDetailListList = new ArrayList<>();
{
TransferDetailInput transferDetailInput = new TransferDetailInput();
transferDetailInput.setOut_detail_no(SnowFlake.createStr("TD"));
transferDetailInput.setTransfer_amount(CurrencyUtil.fen(memberWithdrawApply.getApplyMoney()));
transferDetailInput.setTransfer_remark("用户提现");
transferDetailInput.setOutDetailNo(SnowFlake.createStr("TD"));
transferDetailInput.setTransferAmount(CurrencyUtil.getFenLong(memberWithdrawApply.getApplyMoney()));
transferDetailInput.setTransferRemark("用户提现");
transferDetailInput.setOpenid(connect.getUnionId());
transferDetailListList.add(transferDetailInput);
}
transferModel.setTransfer_detail_list(transferDetailListList);
request.setTransferDetailList(transferDetailListList);
PaymentHttpResponse response = WechatApi.v3(
RequestMethodEnums.POST,
WechatDomain.CHINA.toString(),
WechatApiEnum.TRANSFER_BATCHES.toString(),
wechatPaymentSetting.getMchId(),
wechatPaymentSetting.getSerialNumber(),
null,
wechatPaymentSetting.getApiclient_key(),
JSONUtil.toJsonStr(transferModel)
);
// 调用下单方法得到应答
InitiateBatchTransferResponse response = service.initiateBatchTransfer(request);
log.info("微信提现响应 {}", response);
String body = response.getBody();
JSONObject jsonObject = JSONUtil.parseObj(body);
return TransferResultDTO.builder().result(jsonObject.getStr("batch_id") != null).response(body).build();
return TransferResultDTO.builder().result(response.getBatchId()!= null).build();
//根据自身业务进行接下来的任务处理
} catch (Exception e) {
e.printStackTrace();
@ -572,41 +561,61 @@ public class WechatPlugin implements Payment {
*/
private void verifyNotify(HttpServletRequest request) throws Exception {
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String serialNo = request.getHeader("Wechatpay-Serial");
String signature = request.getHeader("Wechatpay-Signature");
log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
String result = HttpKit.readData(request);
log.info("微信支付通知密文 {}", result);
// 构造 RequestParam
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(request.getHeader("Wechatpay-Serial"))
.nonce(request.getHeader("Wechatpay-Nonce"))
.signature(request.getHeader("Wechatpay-Signature"))
.timestamp(request.getHeader("Wechatpay-Timestamp"))
.body(HttpKit.readData(request))
.build();
WechatPaymentSetting setting = wechatPaymentSetting();
//校验服务器端响应¬
String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
setting.getApiKey3(), Objects.requireNonNull(getPlatformCert()));
NotificationConfig config=null;
if(setting.getPublicType().equals("CERT")) {
config = new RSAAutoCertificateConfig.Builder()
.merchantId(setting.getMchId())
.privateKey(setting.getApiclientKey())
.merchantSerialNumber(setting.getSerialNumber())
.apiV3Key(setting.getApiKey3())
.build();
}else{
config = new RSAPublicKeyConfig.Builder()
.merchantId(setting.getMchId())
.apiV3Key(setting.getApiKey3())
.privateKey(setting.getApiclientKey())
.merchantSerialNumber(setting.getSerialNumber())
.publicKeyId(setting.getPublicId())
.publicKey(setting.getPublicKey())
.build();
}
log.info("微信支付通知明文 {}", plainText);
// 初始化 NotificationParser
NotificationParser parser = new NotificationParser(config);
JSONObject jsonObject = JSONUtil.parseObj(plainText);
try {
// 以支付通知回调为例验签解密并转换成 Transaction
Transaction transaction = parser.parse(requestParam, Transaction.class);
String payParamStr = jsonObject.getStr("attach");
String payParamJson = URLDecoder.decode(payParamStr, StandardCharsets.UTF_8);
String payParamJson = URLDecoder.decode(transaction.getAttach(), StandardCharsets.UTF_8);
PayParam payParam = JSONUtil.toBean(payParamJson, PayParam.class);
String tradeNo = jsonObject.getStr("transaction_id");
Double totalAmount = CurrencyUtil.reversalFen(jsonObject.getJSONObject("amount").getDouble("total"));
Double totalAmount = CurrencyUtil.reversalFen(transaction.getAmount().getTotal());
PaymentSuccessParams paymentSuccessParams = new PaymentSuccessParams(
PaymentMethodEnum.WECHAT.name(),
tradeNo,
transaction.getTransactionId(),
totalAmount,
payParam
);
paymentService.success(paymentSuccessParams);
log.info("微信支付回调:支付成功{}", plainText);
} catch (ValidationException e) {
// 签名验证失败返回 401 UNAUTHORIZED 状态码
log.error("sign verification failed", e);
}
}
@Override
@ -614,39 +623,33 @@ public class WechatPlugin implements Payment {
try {
Amount amount = new Amount().setRefund(CurrencyUtil.fen(refundLog.getTotalAmount()))
.setTotal(CurrencyUtil.fen(orderService.getPaymentTotal(refundLog.getOrderSn())));
//退款参数准备
RefundModel refundModel = new RefundModel()
.setTransaction_id(refundLog.getPaymentReceivableNo())
.setOut_refund_no(refundLog.getOutOrderNo())
.setReason(refundLog.getRefundReason())
.setAmount(amount)
.setNotify_url(refundNotifyUrl(wechatPaymentSetting().getCallbackUrl(), PaymentMethodEnum.WECHAT));
AmountReq amount = new AmountReq();
amount.setRefund(CurrencyUtil.getFenLong(refundLog.getTotalAmount()));
amount.setTotal(CurrencyUtil.getFenLong(orderService.getPaymentTotal(refundLog.getOrderSn())));
amount.setCurrency("CNY");
//获取微信设置
WechatPaymentSetting setting = wechatPaymentSetting();
log.info("微信退款参数 {}", JSONUtil.toJsonStr(refundModel));
PaymentHttpResponse response = WechatApi.v3(
RequestMethodEnums.POST,
WechatDomain.CHINA.toString(),
WechatApiEnum.DOMESTIC_REFUNDS.toString(),
setting.getMchId(),
setting.getSerialNumber(),
null,
setting.getApiclient_key(),
JSONUtil.toJsonStr(refundModel)
);
log.info("微信退款响应 {}", response);
//退款申请成功
if (response.getStatus() == 200) {
refundLogService.save(refundLog);
} else {
//退款申请失败
refundLog.setErrorMessage(response.getBody());
refundLogService.save(refundLog);
Config config =null;
if(setting.getPublicType().equals("CERT")){
config=this.getCertificateConfig(setting);
}else {
config=this.getPublicKeyConfig(setting);
}
// 构建service
RefundService refundService = new RefundService.Builder().config(config).build();
CreateRequest request = new CreateRequest();
request.setTransactionId(refundLog.getPaymentReceivableNo());
request.setAmount(amount);
request.setOutRefundNo(refundLog.getOutOrderNo());
request.setReason(refundLog.getRefundReason());
request.setNotifyUrl(refundNotifyUrl(wechatPaymentSetting().getCallbackUrl(), PaymentMethodEnum.WECHAT));
Refund refund=refundService.create(request);
log.info("微信退款响应 {}", refund);
refundLogService.save(refundLog);
} catch (Exception e) {
log.error("微信退款申请失败", e);
}
@ -655,50 +658,46 @@ public class WechatPlugin implements Payment {
@Override
public void refundNotify(HttpServletRequest request) {
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String serialNo = request.getHeader("Wechatpay-Serial");
String signature = request.getHeader("Wechatpay-Signature");
// 构造 RequestParam
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(request.getHeader("Wechatpay-Serial"))
.nonce(request.getHeader("Wechatpay-Nonce"))
.signature(request.getHeader("Wechatpay-Signature"))
.timestamp(request.getHeader("Wechatpay-Timestamp"))
.body(HttpKit.readData(request))
.build();
log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
String result = HttpKit.readData(request);
log.info("微信退款通知密文 {}", result);
JSONObject ciphertext = JSONUtil.parseObj(result);
try { //校验服务器端响应¬
String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
wechatPaymentSetting().getApiKey3(), Objects.requireNonNull(getPlatformCert()));
log.info("微信退款通知明文 {}", plainText);
if (("REFUND.SUCCESS").equals(ciphertext.getStr("event_type"))) {
log.info("退款成功 {}", plainText);
//校验服务器端响应
JSONObject jsonObject = JSONUtil.parseObj(plainText);
String transactionId = jsonObject.getStr("transaction_id");
String refundId = jsonObject.getStr("refund_id");
WechatPaymentSetting setting = wechatPaymentSetting();
NotificationConfig config=null;
if(setting.getPublicType().equals("CERT")) {
config = new RSAAutoCertificateConfig.Builder()
.merchantId(setting.getMchId())
.privateKey(setting.getApiclientKey())
.merchantSerialNumber(setting.getSerialNumber())
.apiV3Key(setting.getApiKey3())
.build();
}else{
config = new RSAPublicKeyConfig.Builder()
.merchantId(setting.getMchId())
.apiV3Key(setting.getApiKey3())
.privateKey(setting.getApiclientKey())
.merchantSerialNumber(setting.getSerialNumber())
.publicKeyId(setting.getPublicId())
.publicKey(setting.getPublicKey())
.build();
}
// 初始化 NotificationParser
NotificationParser parser = new NotificationParser(config);
try {
Refund refund = parser.parse(requestParam, Refund.class);
RefundLog refundLog = refundLogService.getOne(new LambdaQueryWrapper<RefundLog>().eq(RefundLog::getPaymentReceivableNo,
transactionId));
refund.getTransactionId()));
if (refundLog != null) {
refundLog.setIsRefund(true);
refundLog.setReceivableNo(refundId);
refundLog.setReceivableNo(refund.getRefundId());
refundLogService.saveOrUpdate(refundLog);
}
} else {
log.info("退款失败 {}", plainText);
JSONObject jsonObject = JSONUtil.parseObj(plainText);
String transactionId = jsonObject.getStr("transaction_id");
String refundId = jsonObject.getStr("refund_id");
RefundLog refundLog = refundLogService.getOne(new LambdaQueryWrapper<RefundLog>().eq(RefundLog::getPaymentReceivableNo,
transactionId));
if (refundLog != null) {
refundLog.setReceivableNo(refundId);
refundLog.setErrorMessage(ciphertext.getStr("summary"));
refundLogService.saveOrUpdate(refundLog);
}
}
} catch (Exception e) {
log.error("微信退款失败", e);
}
@ -721,77 +720,34 @@ public class WechatPlugin implements Payment {
}
/**
* 获取平台公钥
*
* @return 平台公钥
* 获取微信公钥配置
* @param setting
* @return
*/
private X509Certificate getPlatformCert() {
//获取缓存中的平台公钥如果有则直接返回否则去微信请求
String publicCert = cache.getString(CachePrefix.WECHAT_PLAT_FORM_CERT.getPrefix());
if (!StringUtils.isEmpty(publicCert)) {
return PayKit.getCertificate(publicCert);
}
//获取平台证书列表
try {
WechatPaymentSetting setting = wechatPaymentSetting();
PaymentHttpResponse response = WechatApi.v3(
RequestMethodEnums.GET,
WechatDomain.CHINA.toString(),
WechatApiEnum.GET_CERTIFICATES.toString(),
setting.getMchId(),
setting.getSerialNumber(),
null,
setting.getApiclient_key(),
""
);
String body = response.getBody();
log.info("获取微信平台证书body: {}", body);
if (response.getStatus() == 200) {
JSONObject jsonObject = JSONUtil.parseObj(body);
JSONArray dataArray = jsonObject.getJSONArray("data");
//默认认为只有一个平台证书
JSONObject encryptObject = dataArray.getJSONObject(0);
JSONObject encryptCertificate = encryptObject.getJSONObject("encrypt_certificate");
String associatedData = encryptCertificate.getStr("associated_data");
String cipherText = encryptCertificate.getStr("ciphertext");
String nonce = encryptCertificate.getStr("nonce");
publicCert = getPlatformCertStr(associatedData, nonce, cipherText);
long second = (PayKit.getCertificate(publicCert).getNotAfter().getTime() - System.currentTimeMillis()) / 1000;
cache.put(CachePrefix.WECHAT_PLAT_FORM_CERT.getPrefix(), publicCert, second);
} else {
log.error("证书获取失败:{}" + body);
throw new ServiceException(ResultCode.WECHAT_CERT_ERROR);
}
return PayKit.getCertificate(publicCert);
} catch (Exception e) {
log.error("证书获取失败", e);
}
return null;
private RSAPublicKeyConfig getPublicKeyConfig(WechatPaymentSetting setting){
return
new RSAPublicKeyConfig.Builder()
.merchantId(setting.getMchId())
.privateKey(setting.getApiclientKey())
.publicKey(setting.getPublicKey())
.publicKeyId(setting.getPublicId())
.merchantSerialNumber(setting.getSerialNumber())
.apiV3Key(setting.getApiKey3())
.build();
}
/**
* 获取平台证书缓存的字符串
* 下列各个密钥参数
*
* @param associatedData 密钥参数
* @param nonce 密钥参数
* @param cipherText 密钥参数
* @return platform key
* @throws GeneralSecurityException 密钥获取异常
* 获取微信证书配置
* @param setting
* @return
*/
private String getPlatformCertStr(String associatedData, String nonce, String cipherText) throws GeneralSecurityException {
AesUtil aesUtil = new AesUtil(wechatPaymentSetting().getApiKey3().getBytes(StandardCharsets.UTF_8));
//平台证书密文解密
//encrypt_certificate 中的 associated_data nonce ciphertext
return aesUtil.decryptToString(
associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
cipherText
);
private RSAAutoCertificateConfig getCertificateConfig(WechatPaymentSetting setting) {
return new RSAAutoCertificateConfig.Builder()
.merchantId(setting.getMchId())
.privateKey(setting.getApiclientKey())
.merchantSerialNumber(setting.getSerialNumber())
.apiV3Key(setting.getApiKey3())
.build();
}
/**

View File

@ -37,22 +37,36 @@ public class WechatPaymentSetting {
* 商户号
*/
private String mchId;
/**
* 私钥
*/
private String apiclient_key;
/**
* pem 证书
*/
private String apiclient_cert_pem;
/**
* p12 证书
*/
private String apiclient_cert_p12;
/**
* 商户证书序列号
*/
private String serialNumber;
/**
* 私钥
*/
private String apiclientKey;
/**
* 公钥ID
*/
private String publicId;
/**
* 公钥
*/
private String publicKey;
/**
* 微信验证方式公钥/证书(KEY/CERT)
*/
private String publicType;
// /**
// * pem 证书
// */
// private String apiclient_cert_pem;
// /**
// * p12 证书
// */
// private String apiclient_cert_p12;
/**
* apiv3私钥
*/

View File

@ -19,11 +19,6 @@
<artifactId>framework</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -2,8 +2,6 @@ package cn.lili;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
@ -15,17 +13,4 @@ public class ImApiApplication {
public static void main(String[] args) {
SpringApplication.run(ImApiApplication.class, args);
}
/**
* 如果使用独立的servlet容器
* 而不是直接使用springboot的内置容器
* 就不要注入ServerEndpointExporter
* 因为它将由容器自己提供和管理
*
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}

View File

@ -0,0 +1,117 @@
package cn.lili.controller.im;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.enums.ResultUtil;
import cn.lili.common.security.context.UserContext;
import cn.lili.common.security.AuthUser;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.vo.ResultMessage;
import cn.lili.modules.im.entity.dos.Friend;
import cn.lili.modules.im.entity.vo.FriendVO;
import cn.lili.modules.im.service.FriendService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @author Chopper
*/
@RestController
@Api(tags = "关注管理接口")
@RequestMapping("/im/friend")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class FriendController {
private final FriendService friendService;
@GetMapping("/search")
@ApiOperation(value = "搜索用户")
public ResultMessage<List<FriendVO>> searchUsers(
@ApiParam(value = "搜索关键词(用户名/手机号)") @RequestParam String keyword,
@ApiParam(value = "是否只搜索店铺") @RequestParam(required = false) Boolean onlyStore) {
AuthUser currentUser = UserContext.getCurrentUser();
if (currentUser == null) {
throw new ServiceException(ResultCode.USER_NOT_LOGIN);
}
List<FriendVO> users = friendService.searchUsers(keyword, onlyStore, currentUser.getId());
return ResultUtil.data(users);
}
@GetMapping("/following")
@ApiOperation(value = "获取关注列表")
public ResultMessage<List<FriendVO>> getFollowingList() {
AuthUser currentUser = UserContext.getCurrentUser();
if (currentUser == null) {
throw new ServiceException(ResultCode.USER_NOT_LOGIN);
}
List<FriendVO> friends = friendService.getMutualFriends(currentUser.getId());
return ResultUtil.data(friends);
}
@GetMapping("/user/{userId}")
@ApiOperation(value = "获取用户详情")
public ResultMessage<FriendVO> getUserDetails(
@ApiParam(value = "用户ID", required = true) @PathVariable String userId) {
AuthUser currentUser = UserContext.getCurrentUser();
if (currentUser == null) {
throw new ServiceException(ResultCode.USER_NOT_LOGIN);
}
FriendVO friend = friendService.getFriendDetails(userId);
return ResultUtil.data(friend);
}
@PostMapping("/follow/{userId}")
@ApiOperation(value = "关注用户")
public ResultMessage<Object> followUser(
@ApiParam(value = "要关注的用户ID", required = true) @PathVariable String userId) {
AuthUser currentUser = UserContext.getCurrentUser();
if (currentUser == null) {
throw new ServiceException(ResultCode.USER_NOT_LOGIN);
}
friendService.addFriend(currentUser.getId(), userId);
return ResultUtil.success();
}
@PostMapping("/follow-by-mobile")
@ApiOperation(value = "通过手机号关注用户")
public ResultMessage<Object> followByMobile(
@ApiParam(value = "手机号", required = true) @RequestParam String mobile,
@ApiParam(value = "备注") @RequestParam(required = false) String remark) {
AuthUser currentUser = UserContext.getCurrentUser();
if (currentUser == null) {
throw new ServiceException(ResultCode.USER_NOT_LOGIN);
}
friendService.addFriendByMobile(mobile, remark);
return ResultUtil.success();
}
@DeleteMapping("/unfollow/{userId}")
@ApiOperation(value = "取消关注")
public ResultMessage<Object> unfollowUser(
@ApiParam(value = "要取消关注的用户ID", required = true) @PathVariable String userId) {
AuthUser currentUser = UserContext.getCurrentUser();
if (currentUser == null) {
throw new ServiceException(ResultCode.USER_NOT_LOGIN);
}
friendService.removeFriend(currentUser.getId(), userId);
return ResultUtil.success();
}
@PutMapping("/remark/{userId}")
@ApiOperation(value = "更新备注")
public ResultMessage<Object> updateRemark(
@ApiParam(value = "用户ID", required = true) @PathVariable String userId,
@ApiParam(value = "备注", required = true) @RequestParam String remark) {
AuthUser currentUser = UserContext.getCurrentUser();
if (currentUser == null) {
throw new ServiceException(ResultCode.USER_NOT_LOGIN);
}
friendService.updateRemark(currentUser.getId(), userId, remark);
return ResultUtil.success();
}
}

View File

@ -0,0 +1,183 @@
package cn.lili.controller.im;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.enums.ResultUtil;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.security.AuthUser;
import cn.lili.common.security.context.UserContext;
import cn.lili.common.vo.ResultMessage;
import cn.lili.common.vo.ResultPageVO;
import cn.lili.modules.im.entity.dos.ImGroup;
import cn.lili.modules.im.entity.vo.FriendVO;
import cn.lili.modules.im.entity.vo.ImGroupVO;
import cn.lili.modules.im.entity.vo.ImGroupMemberVO;
import cn.lili.modules.im.entity.vo.ImMessageVO;
import cn.lili.modules.im.service.ImGroupService;
import cn.lili.modules.im.service.FriendService;
import cn.lili.modules.im.service.ImMessageService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 群聊管理接口
*/
@RestController
@Api(tags = "群聊管理接口")
@RequestMapping("/im/group")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ImGroupController {
private final ImGroupService imGroupService;
private final FriendService friendService;
private final ImMessageService imMessageService;
@GetMapping("/list")
@ApiOperation(value = "获取我的群聊列表")
public ResultMessage<List<ImGroupVO>> getMyGroups() {
AuthUser currentUser = UserContext.getCurrentUser();
if (currentUser == null) {
throw new ServiceException(ResultCode.USER_NOT_LOGIN);
}
List<ImGroupVO> groups = imGroupService.getUserGroups(currentUser.getId());
return ResultUtil.data(groups);
}
@GetMapping("/{groupId}")
@ApiOperation(value = "获取群聊详情")
public ResultMessage<ImGroupVO> getGroupDetail(
@ApiParam(value = "群ID", required = true) @PathVariable String groupId) {
ImGroupVO group = imGroupService.getGroupDetail(groupId);
return ResultUtil.data(group);
}
@GetMapping("/{groupId}/members")
@ApiOperation(value = "获取群成员列表")
public ResultMessage<List<ImGroupMemberVO>> getGroupMembers(
@ApiParam(value = "群ID", required = true) @PathVariable String groupId) {
List<ImGroupMemberVO> members = imGroupService.getGroupMembers(groupId);
return ResultUtil.data(members);
}
@DeleteMapping("/{groupId}/quit")
@ApiOperation(value = "退出群聊")
public ResultMessage<Void> quitGroup(
@ApiParam(value = "群ID", required = true) @PathVariable String groupId) {
AuthUser currentUser = UserContext.getCurrentUser();
if (currentUser == null) {
throw new ServiceException(ResultCode.USER_NOT_LOGIN);
}
imGroupService.quitGroup(groupId, currentUser.getId());
return ResultUtil.success();
}
@PutMapping("/{groupId}")
@ApiOperation(value = "修改群信息")
public ResultMessage<Void> updateGroupInfo(
@ApiParam(value = "群ID", required = true) @PathVariable String groupId,
@ApiParam(value = "群名称") @RequestParam(required = false) String groupName,
@ApiParam(value = "群公告") @RequestParam(required = false) String notice,
@ApiParam(value = "群头像") @RequestParam(required = false) String avatar) {
imGroupService.updateGroupInfo(groupId, groupName, notice, avatar);
return ResultUtil.success();
}
@GetMapping("/{groupId}/messages")
@ApiOperation(value = "获取群聊消息历史")
public ResultMessage<ResultPageVO<ImMessageVO>> getGroupMessages(
@ApiParam(value = "群ID", required = true) @PathVariable String groupId,
@ApiParam(value = "页码") @RequestParam(defaultValue = "1") Integer pageNumber,
@ApiParam(value = "每页大小") @RequestParam(defaultValue = "20") Integer pageSize) {
ResultPageVO<ImMessageVO> messages = imMessageService.getGroupMessages(groupId, pageNumber, pageSize);
return ResultUtil.data(messages);
}
@PostMapping("/{groupId}/message")
@ApiOperation(value = "发送群聊消息")
public ResultMessage<ImMessageVO> sendGroupMessage(
@ApiParam(value = "群ID", required = true) @PathVariable String groupId,
@ApiParam(value = "消息内容", required = true) @RequestParam String content,
@ApiParam(value = "消息类型", required = true) @RequestParam String type) {
ImMessageVO message = imMessageService.sendGroupMessage(groupId, content, type);
return ResultUtil.data(message);
}
@GetMapping("/friends")
@ApiOperation(value = "获取可邀请的好友列表")
public ResultMessage<List<FriendVO>> getInvitableFriends() {
AuthUser currentUser = UserContext.getCurrentUser();
if (currentUser == null) {
throw new ServiceException(ResultCode.USER_NOT_LOGIN);
}
List<FriendVO> friends = friendService.getMutualFriends(currentUser.getId());
return ResultUtil.data(friends);
}
@PostMapping
@ApiOperation(value = "创建群聊")
public ResultMessage<ImGroup> createGroup(
@ApiParam(value = "群名称", required = true) @RequestParam String groupName,
@ApiParam(value = "初始成员ID列表") @RequestParam(required = false) List<String> memberIds) {
ImGroup group = imGroupService.createGroup(groupName, memberIds);
return ResultUtil.data(group);
}
@DeleteMapping("/{groupId}")
@ApiOperation(value = "解散群聊")
public ResultMessage<Void> dismissGroup(
@ApiParam(value = "群ID", required = true) @PathVariable String groupId) {
imGroupService.dismissGroup(groupId);
return ResultUtil.success();
}
@PostMapping("/{groupId}/invite")
@ApiOperation(value = "邀请成员")
public ResultMessage<Void> inviteMembers(
@ApiParam(value = "群ID", required = true) @PathVariable String groupId,
@ApiParam(value = "成员ID列表", required = true) @RequestParam List<String> memberIds) {
imGroupService.inviteMembers(groupId, memberIds);
return ResultUtil.success();
}
@PostMapping("/{groupId}/admin/{memberId}")
@ApiOperation(value = "设置管理员")
public ResultMessage<Void> setAdmin(
@ApiParam(value = "群ID", required = true) @PathVariable String groupId,
@ApiParam(value = "成员ID", required = true) @PathVariable String memberId) {
imGroupService.setAdmin(groupId, memberId);
return ResultUtil.success();
}
@DeleteMapping("/{groupId}/admin/{memberId}")
@ApiOperation(value = "取消管理员")
public ResultMessage<Void> removeAdmin(
@ApiParam(value = "群ID", required = true) @PathVariable String groupId,
@ApiParam(value = "成员ID", required = true) @PathVariable String memberId) {
imGroupService.removeAdmin(groupId, memberId);
return ResultUtil.success();
}
@PostMapping("/{groupId}/mute/{memberId}")
@ApiOperation(value = "禁言成员")
public ResultMessage<Void> muteMember(
@ApiParam(value = "群ID", required = true) @PathVariable String groupId,
@ApiParam(value = "成员ID", required = true) @PathVariable String memberId,
@ApiParam(value = "禁言时长(分钟)", required = true) @RequestParam Integer duration) {
imGroupService.muteMember(groupId, memberId, duration);
return ResultUtil.success();
}
@DeleteMapping("/{groupId}/mute/{memberId}")
@ApiOperation(value = "解除成员禁言")
public ResultMessage<Void> unmuteMember(
@ApiParam(value = "群ID", required = true) @PathVariable String groupId,
@ApiParam(value = "成员ID", required = true) @PathVariable String memberId) {
imGroupService.unmuteMember(groupId, memberId);
return ResultUtil.success();
}
}

View File

@ -4,11 +4,14 @@ import cn.lili.common.enums.ResultCode;
import cn.lili.common.enums.ResultUtil;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.vo.ResultMessage;
import cn.lili.common.vo.ResultPageVO;
import cn.lili.modules.im.entity.dos.ImMessage;
import cn.lili.modules.im.entity.dto.MessageQueryParams;
import cn.lili.modules.im.entity.vo.ImMessageVO;
import cn.lili.modules.im.service.ImMessageService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
@ -87,4 +90,24 @@ public class ImMessageController {
imMessageService.cleanUnreadMessage();
return ResultUtil.success();
}
@GetMapping("/{groupId}/messages")
@ApiOperation(value = "获取群聊消息历史")
public ResultMessage<ResultPageVO<ImMessageVO>> getGroupMessages(
@ApiParam(value = "群ID", required = true) @PathVariable String groupId,
@ApiParam(value = "页码") @RequestParam(defaultValue = "1") Integer pageNumber,
@ApiParam(value = "每页大小") @RequestParam(defaultValue = "20") Integer pageSize) {
ResultPageVO<ImMessageVO> messages = imMessageService.getGroupMessages(groupId, pageNumber, pageSize);
return ResultUtil.data(messages);
}
@PostMapping("/{groupId}/message")
@ApiOperation(value = "发送群聊消息")
public ResultMessage<ImMessageVO> sendGroupMessage(
@ApiParam(value = "群ID", required = true) @PathVariable String groupId,
@ApiParam(value = "消息内容", required = true) @RequestParam String content,
@ApiParam(value = "消息类型", required = true) @RequestParam String type) {
ImMessageVO message = imMessageService.sendGroupMessage(groupId, content, type);
return ResultUtil.data(message);
}
}

View File

@ -35,7 +35,7 @@ spring:
redis:
host: 127.0.0.1
port: 6379
password: lilishop
# password: lilishop
lettuce:
pool:
# 连接池最大连接数(使用负值表示没有限制) 默认 8
@ -64,9 +64,9 @@ spring:
default-datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/lilishop?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
url: jdbc:mysql://127.0.0.1:3306/wuzhongjie?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
username: root
password: lilishop
password: 123
maxActive: 20
initialSize: 5
maxWait: 60000
@ -259,7 +259,7 @@ lili:
after-sale-topic: lili_after_sale_topic
after-sale-group: lili_after_sale_group
rocketmq:
name-server: 127.0.0.1:9876
name-server: 82.156.121.2:9876
producer:
group: lili_group
send-message-timeout: 30000
@ -267,7 +267,7 @@ rocketmq:
xxl:
job:
admin:
addresses: http://127.0.0.1:9001/xxl-job-admin
addresses: http://82.156.121.2:9001/xxl-job-admin
executor:
appname: xxl-job-executor-lilishop
address:

View File

@ -35,7 +35,7 @@ spring:
redis:
host: 127.0.0.1
port: 6379
password: lilishop
# password: lilishop
lettuce:
pool:
# 连接池最大连接数(使用负值表示没有限制) 默认 8

View File

@ -35,7 +35,7 @@ spring:
redis:
host: 127.0.0.1
port: 6379
password: lilishop
# password: lilishop
lettuce:
pool:
# 连接池最大连接数(使用负值表示没有限制) 默认 8

View File

@ -1,5 +1,5 @@
### web
server.port=9001
server.port=30001
server.servlet.context-path=/xxl-job-admin
### actuator
@ -23,9 +23,9 @@ mybatis.mapper-locations=classpath:/mybatis-mapper/*Mapper.xml
#mybatis.type-aliases-package=com.xxl.job.admin.core.model
### xxl-job, datasource
spring.datasource.url=jdbc:mysql://192.168.0.106:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
spring.datasource.url=jdbc:mysql://localhost:3306/wuzhongjie?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=lilishop
spring.datasource.password=123
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
### datasource-pool
@ -35,8 +35,8 @@ spring.datasource.hikari.maximum-pool-size=30
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=HikariCP
spring.datasource.hikari.max-lifetime=900000
spring.datasource.hikari.connection-timeout=10000
spring.datasource.hikari.max-lifetime=90000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.connection-test-query=SELECT 1
spring.datasource.hikari.validation-timeout=1000

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.