This commit is contained in:
chc 2022-11-23 18:44:54 +08:00
parent fc71d2d1ca
commit 49b517f1ab
58 changed files with 1775 additions and 148 deletions

View File

@ -51,7 +51,10 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>

View File

@ -68,6 +68,12 @@ public class AuthUser implements Serializable {
*/
private Boolean isSuper = false;
/**
* 租户id
*/
private String tenantId;
public AuthUser(String username, String id, String nickName, String face, UserEnums role) {
this.username = username;
this.face = face;

View File

@ -14,7 +14,8 @@ public enum UserEnums {
MEMBER("会员"),
STORE("商家"),
MANAGER("管理员"),
SYSTEM("系统");
SYSTEM("系统"),
SEAT("坐席");
private final String role;
UserEnums(String role) {

View File

@ -95,6 +95,19 @@ public class Swagger2Config {
.securityContexts(securityContexts());
}
@Bean
public Docket imRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("im")
.apiInfo(apiInfo()).select()
//扫描所有有注解的api用这种方式更灵活
.apis(RequestHandlerSelectors.basePackage("cn.lili.controller.im"))
.paths(PathSelectors.any())
.build()
.securitySchemes(securitySchemes())
.securityContexts(securityContexts());
}
@Bean
public Docket memberRestApi() {
return new Docket(DocumentationType.SWAGGER_2)

View File

@ -1,26 +0,0 @@
package cn.lili.im.entity;
import cn.lili.mybatis.BaseTenantEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import lombok.Data;
/**
* @author Chopper
*/
@Data
@TableName("li_im_users")
@ApiModel(value = "Im消息")
public class ImUser extends BaseTenantEntity {
private static final long serialVersionUID = 1L;
/**
* 头像
*/
private String face;
/**
* 昵称
*/
private String name;
}

View File

@ -1,13 +0,0 @@
package cn.lili.im.mapper;
import cn.lili.im.entity.ImUser;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* Im消息 Dao层
*
* @author Chopper
*/
public interface ImUserMapper extends BaseMapper<ImUser> {
}

View File

@ -1,21 +0,0 @@
package cn.lili.im.service;
import cn.lili.im.entity.ImUser;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* Im消息 业务层
*
* @author Chopper
*/
public interface ImUserService extends IService<ImUser> {
/**
* 注册用户
*
* @param accessToken
* @return
*/
ImUser register(String accessToken);
}

View File

@ -1,39 +0,0 @@
package cn.lili.im.serviceimpl;
import cn.lili.common.security.AuthUser;
import cn.lili.common.security.context.UserContext;
import cn.lili.im.entity.ImUser;
import cn.lili.im.mapper.ImUserMapper;
import cn.lili.im.service.ImUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* Im消息 业务实现
*
* @author Chopper
*/
@Service
@Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ImUserServiceImpl extends ServiceImpl<ImUserMapper, ImUser> implements ImUserService {
@Override
public ImUser register(String accessToken) {
AuthUser authUser = UserContext.getAuthUser(accessToken);
ImUser imUser;
//如果用户存在
imUser = this.getById(authUser.getId());
if (imUser == null) {
imUser = new ImUser();
imUser.setId(authUser.getId());
imUser.setName(authUser.getNickName());
this.save(imUser);
}
return imUser;
}
}

View File

@ -1,4 +1,4 @@
package cn.lili.im.config;
package cn.lili.modules.im.config;
import org.springframework.beans.BeansException;

View File

@ -1,4 +1,4 @@
package cn.lili.im.config;
package cn.lili.modules.im.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;

View File

@ -1,6 +1,6 @@
package cn.lili.im.entity;
package cn.lili.modules.im.entity.dos;
import cn.lili.im.entity.enums.MessageType;
import cn.lili.modules.im.entity.enums.MessageTypeEnum;
import cn.lili.mybatis.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
@ -34,7 +34,7 @@ public class ImMessage extends BaseEntity {
/**
* 消息类型
*/
private MessageType messageType;
private MessageTypeEnum messageType;
/**
* 聊天id

View File

@ -1,4 +1,4 @@
package cn.lili.im.entity;
package cn.lili.modules.im.entity.dos;
import cn.lili.common.utils.SnowFlake;

View File

@ -0,0 +1,31 @@
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.NoArgsConstructor;
/**
* 问题答案
*
* @author Chopper
* @version v1.0
* 2022-02-09 17:59
*/
@Data
@TableName("li_qa")
@ApiModel(value = "租户问答")
@NoArgsConstructor
public class QA extends BaseEntity {
@ApiModelProperty(value = "租户id")
private Integer tenantId;
@ApiModelProperty(value = "问题")
private String question;
@ApiModelProperty(value = "答案")
private String answer;
}

View File

@ -0,0 +1,47 @@
package cn.lili.modules.im.entity.dos;
import cn.lili.mybatis.BaseTenantEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty;
/**
* 坐席
*
* @author Chopper
* @version v1.0
* 2022-02-09 17:08
*/
@Data
@TableName("li_seat")
@ApiModel(value = "坐席")
@NoArgsConstructor
public class Seat extends BaseTenantEntity {
@ApiModelProperty(value = "租户id")
private String tenantId;
@ApiModelProperty(value = "坐席用户名")
private String username;
@ApiModelProperty(value = "会员头像")
private String face;
@ApiModelProperty(value = "坐席密码")
private String password;
@ApiModelProperty(value = "昵称")
private String nickName;
@ApiModelProperty(value = "坐席状态")
private Boolean disabled;
@NotEmpty(message = "手机号码不能为空")
@ApiModelProperty(value = "手机号码", required = true)
private String mobile;
}

View File

@ -0,0 +1,36 @@
package cn.lili.modules.im.entity.dos;
import cn.lili.mybatis.BaseTenantEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 坐席设置
*
* @author Chopper
* @version v1.0
* 2022-02-09 17:55
*/
@Data
@TableName("li_seat_setting")
@ApiModel(value = "坐席设置")
@NoArgsConstructor
public class SeatSetting extends BaseTenantEntity {
@ApiModelProperty(value = "租户idid")
private String tenantId;
@ApiModelProperty(value = "欢迎语")
private String welcome;
@ApiModelProperty(value = "离线自动回复")
private String outLineAutoReply;
@ApiModelProperty(value = "长时间自动回复")
private String longTermAutoReply;
}

View File

@ -1,9 +1,9 @@
package cn.lili.im.entity.dto;
package cn.lili.modules.im.entity.dto;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.utils.StringUtils;
import cn.lili.im.entity.ImMessage;
import cn.lili.modules.im.entity.dos.ImMessage;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.Data;

View File

@ -1,4 +1,4 @@
package cn.lili.im.entity.enums;
package cn.lili.modules.im.entity.enums;
/**
* 返回消息类型枚举

View File

@ -0,0 +1,31 @@
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;
}

View File

@ -1,11 +1,11 @@
package cn.lili.im.entity.enums;
package cn.lili.modules.im.entity.enums;
/**
* 消息类型
*
* @author liushuai
*/
public enum MessageType {
public enum MessageTypeEnum {
/**
* 消息类型枚举
* <p>

View File

@ -0,0 +1,14 @@
package cn.lili.modules.im.entity.enums;
/**
* 坐席在线状态
*
* @author Chopper
* @version v1.0
* 2022-02-10 16:37
*/
public enum OnlineStatusEnum {
// 在线/下线
ONLINE,
OUTLINE;
}

View File

@ -1,4 +1,4 @@
package cn.lili.im.entity.enums;
package cn.lili.modules.im.entity.enums;
/**
* 操作类型枚举

View File

@ -1,6 +1,6 @@
package cn.lili.im.entity.vo;
package cn.lili.modules.im.entity.vo;
import cn.lili.im.entity.ImTalk;
import cn.lili.modules.im.entity.dos.ImTalk;
import cn.lili.mybatis.BaseTenantEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;

View File

@ -1,8 +1,8 @@
package cn.lili.im.entity.vo;
package cn.lili.modules.im.entity.vo;
import cn.lili.common.utils.StringUtils;
import cn.lili.im.entity.enums.MessageType;
import cn.lili.im.entity.enums.OperationType;
import cn.lili.modules.im.entity.enums.MessageTypeEnum;
import cn.lili.modules.im.entity.enums.OperationType;
import lombok.Data;
/**
@ -20,6 +20,11 @@ public class MessageOperation {
*/
private String to;
/**
* 发送者
*/
private String from;
/**
* 聊天id
*/
@ -28,7 +33,7 @@ public class MessageOperation {
/**
* 消息类型
*/
private MessageType messageType;
private MessageTypeEnum messageType;
/**
* 消息内容
*/
@ -42,7 +47,7 @@ public class MessageOperation {
public void setMessageType(String messageType) {
if (!StringUtils.isEmpty(messageType)) {
this.messageType = MessageType.valueOf(messageType);
this.messageType = MessageTypeEnum.valueOf(messageType);
}
}
}

View File

@ -1,6 +1,6 @@
package cn.lili.im.entity.vo;
package cn.lili.modules.im.entity.vo;
import cn.lili.im.entity.enums.MessageResultType;
import cn.lili.modules.im.entity.enums.MessageResultType;
import lombok.AllArgsConstructor;
import lombok.Data;

View File

@ -1,4 +1,4 @@
package cn.lili.im.entity.vo;
package cn.lili.modules.im.entity.vo;
import lombok.Data;

View File

@ -0,0 +1,22 @@
package cn.lili.modules.im.entity.vo;
import cn.lili.modules.im.entity.dos.Seat;
import lombok.Data;
/**
* 客服VO
*
* @author Chopper
* @version v1.0
* 2022-02-10 15:02
*/
@Data
public class SeatVO extends Seat {
/**
* 在线状态
*/
private String onlineStatus;
}

View File

@ -1,6 +1,6 @@
package cn.lili.im.mapper;
package cn.lili.modules.im.mapper;
import cn.lili.im.entity.ImMessage;
import cn.lili.modules.im.entity.dos.ImMessage;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**

View File

@ -1,6 +1,6 @@
package cn.lili.im.mapper;
package cn.lili.modules.im.mapper;
import cn.lili.im.entity.ImTalk;
import cn.lili.modules.im.entity.dos.ImTalk;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**

View File

@ -0,0 +1,16 @@
package cn.lili.modules.im.mapper;
import cn.lili.modules.im.entity.dos.QA;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* 问答处理层
*
* @author pikachu
* @since 2020-02-18 15:18:56
*/
public interface QAMapper extends BaseMapper<QA> {
}

View File

@ -0,0 +1,16 @@
package cn.lili.modules.im.mapper;
import cn.lili.modules.im.entity.dos.Seat;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* 坐席处理层
*
* @author pikachu
* @since 2020-02-18 15:18:56
*/
public interface SeatMapper extends BaseMapper<Seat> {
}

View File

@ -0,0 +1,16 @@
package cn.lili.modules.im.mapper;
import cn.lili.modules.im.entity.dos.SeatSetting;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* 坐席设置处理层
*
* @author pikachu
* @since 2020-02-18 15:18:56
*/
public interface SeatSettingMapper extends BaseMapper<SeatSetting> {
}

View File

@ -1,6 +1,6 @@
package cn.lili.im.service;
package cn.lili.modules.im.service;
import cn.lili.im.entity.ImMessage;
import cn.lili.modules.im.entity.dos.ImMessage;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;

View File

@ -1,6 +1,6 @@
package cn.lili.im.service;
package cn.lili.modules.im.service;
import cn.lili.im.entity.ImTalk;
import cn.lili.modules.im.entity.dos.ImTalk;
import com.baomidou.mybatisplus.extension.service.IService;
/**

View File

@ -0,0 +1,25 @@
package cn.lili.modules.im.service;
import cn.lili.common.vo.PageVO;
import cn.lili.modules.im.entity.dos.QA;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* 问答
*
* @author pikachu
* @since 2020-02-18 16:18:56
*/
public interface QAService extends IService<QA> {
/**
* 查询店铺问题
* @param word
* @param pageVO
* @return
*/
IPage<QA> getStoreQA(String word, PageVO pageVO);
}

View File

@ -0,0 +1,60 @@
package cn.lili.modules.im.service;
import cn.lili.common.security.token.Token;
import cn.lili.modules.im.entity.dos.Seat;
import cn.lili.modules.im.entity.vo.SeatVO;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* 坐席业务
*
* @author pikachu
* @since 2020-02-18 16:18:56
*/
public interface SeatService extends IService<Seat> {
/**
* 获取坐席列表
*
* @param storeId 店铺id
* @return
*/
List<SeatVO> seatVoList(String storeId);
/**
* 坐席登录
*
* @param username
* @param password
* @return
*/
Token usernameLogin(String username, String password);
/**
* 快捷登录code 生成
*
* @param username 用户名
* @return
*/
String createQuickLoginCode(String username);
/**
* 快捷登录
*
* @param code
* @return
*/
Token quickLogin(String code);
/**
* 查询坐席
*
* @param username
* @return
*/
Seat findByUsername(String username);
}

View File

@ -0,0 +1,30 @@
package cn.lili.modules.im.service;
import cn.lili.modules.im.entity.dos.SeatSetting;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* 坐席设置业务
*
* @author pikachu
* @since 2020-02-18 16:18:56
*/
public interface SeatSettingService extends IService<SeatSetting> {
/**
* 根据店铺id获取坐席配置
*
* @param storeId
* @return
*/
SeatSetting getSetting(String storeId);
/**
* 根据店铺修改坐席设置
*
* @param seatSetting 坐席设置
* @return
*/
SeatSetting updateByStore(SeatSetting seatSetting);
}

View File

@ -1,10 +1,10 @@
package cn.lili.im.serviceimpl;
package cn.lili.modules.im.serviceimpl;
import cn.lili.common.security.context.UserContext;
import cn.lili.im.entity.ImMessage;
import cn.lili.im.mapper.ImMessageMapper;
import cn.lili.im.service.ImMessageService;
import cn.lili.im.service.ImTalkService;
import cn.lili.modules.im.entity.dos.ImMessage;
import cn.lili.modules.im.mapper.ImMessageMapper;
import cn.lili.modules.im.service.ImMessageService;
import cn.lili.modules.im.service.ImTalkService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

View File

@ -1,19 +1,22 @@
package cn.lili.im.serviceimpl;
package cn.lili.modules.im.serviceimpl;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.security.context.UserContext;
import cn.lili.im.entity.ImTalk;
import cn.lili.im.entity.ImUser;
import cn.lili.im.mapper.ImTalkMapper;
import cn.lili.im.service.ImTalkService;
import cn.lili.im.service.ImUserService;
import cn.lili.modules.im.entity.dos.ImTalk;
import cn.lili.modules.im.mapper.ImTalkMapper;
import cn.lili.modules.im.service.ImTalkService;
import cn.lili.modules.member.entity.dos.Member;
import cn.lili.modules.member.service.MemberService;
import cn.lili.modules.store.entity.dos.Store;
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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.stringtemplate.v4.ST;
/**
* 聊天 业务实现
@ -26,7 +29,10 @@ import org.springframework.transaction.annotation.Transactional;
public class ImTalkServiceImpl extends ServiceImpl<ImTalkMapper, ImTalk> implements ImTalkService {
@Autowired
private ImUserService imUserService;
private MemberService memberService;
@Autowired
private StoreService storeService;
@Override
public ImTalk getTalkByUser(String userId1, String userId2) {
@ -34,15 +40,34 @@ public class ImTalkServiceImpl extends ServiceImpl<ImTalkMapper, ImTalk> impleme
queryWrapper.eq(ImTalk::getUserId2, userId2);
queryWrapper.eq(ImTalk::getUserId1, userId1);
ImTalk imTalk = this.getOne(queryWrapper);
ImUser imUser1 = imUserService.getById(userId1);
ImUser imUser2 = imUserService.getById(userId2);
Member self = memberService.getById(userId1);
Store selfStore = storeService.getById(userId1);
//如果没有聊天则创建聊天
if (imTalk == null) {
if (imUser1 == null || imUser2 == null) {
// 没有登录的这个账户信息
if (self == null && selfStore ==null) {
return null;
}
imTalk = new ImTalk(userId1, userId2, imUser1.getFace(), imUser2.getFace(), imUser1.getName(), imUser2.getName());
//当自己为店铺时
if(selfStore != null){
//没有这个用户信息
Member other = memberService.getById(userId2);
if(other == null){
return null;
}
//自己为店铺其他人必定为用户
imTalk = new ImTalk(userId1, userId2, selfStore.getStoreLogo(), other.getFace(), selfStore.getStoreName(), other.getNickName());
}else if(self != null){
//没有这个店铺信息
Store otherStore = storeService.getById(userId2);
if(otherStore == null){
return null;
}
//当自己为用户时 其他人必定为店铺
imTalk = new ImTalk(userId1, userId2, self.getFace(), otherStore.getStoreLogo(), self.getNickName(), otherStore.getStoreName());
}
this.save(imTalk);
} else {
imTalk = check(imTalk);

View File

@ -0,0 +1,32 @@
package cn.lili.modules.im.serviceimpl;
import cn.lili.common.security.context.UserContext;
import cn.lili.common.vo.PageVO;
import cn.lili.modules.im.entity.dos.QA;
import cn.lili.modules.im.mapper.QAMapper;
import cn.lili.modules.im.service.QAService;
import cn.lili.mybatis.util.PageUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 坐席业务层实现
*
* @author pikachu
* @since 2020-02-18 16:18:56
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class QAServiceImpl extends ServiceImpl<QAMapper, QA> implements QAService {
@Override
public IPage<QA> getStoreQA(String word, PageVO pageVo) {
LambdaQueryWrapper<QA> qaLambdaQueryWrapper = new LambdaQueryWrapper<>();
qaLambdaQueryWrapper.eq(QA::getTenantId, UserContext.getCurrentUser().getTenantId());
qaLambdaQueryWrapper.like(QA::getQuestion, word);
return this.page(PageUtil.initPage(pageVo), qaLambdaQueryWrapper);
}
}

View File

@ -0,0 +1,111 @@
package cn.lili.modules.im.serviceimpl;
import cn.lili.cache.Cache;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.security.token.Token;
import cn.lili.common.utils.StringUtils;
import cn.lili.modules.im.entity.dos.Seat;
import cn.lili.modules.im.entity.enums.OnlineStatusEnum;
import cn.lili.modules.im.entity.vo.SeatVO;
import cn.lili.modules.im.mapper.SeatMapper;
import cn.lili.modules.im.service.SeatService;
import cn.lili.modules.im.token.SeatTokenGenerate;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* 坐席业务层实现
*
* @author pikachu
* @since 2020-02-18 16:18:56
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class SeatServiceImpl extends ServiceImpl<SeatMapper, Seat> implements SeatService {
@Autowired
private SeatTokenGenerate seatTokenGenerate;
@Autowired
private Cache<String> cache;
/**
* 快捷登录缓存前缀
*/
private static String prefix = "{quick_login}_";
@Override
public List<SeatVO> seatVoList(String storeId) {
LambdaQueryWrapper<Seat> seatLambdaQueryWrapper = new LambdaQueryWrapper<>();
seatLambdaQueryWrapper.eq(Seat::getTenantId, storeId);
List<Seat> list = this.list(seatLambdaQueryWrapper);
//转换模型为VO
List<SeatVO> results = list.stream().map(item -> (SeatVO) item).collect(Collectors.toList());
//填充坐席当前状态
//todo
results.forEach(item -> {
item.setOnlineStatus(OnlineStatusEnum.ONLINE.name());
});
return results;
}
@Override
public Token usernameLogin(String username, String password) {
Seat seat = this.findByUsername(username);
//判断用户是否存在
if (seat == null || !seat.getDisabled()) {
throw new ServiceException(ResultCode.ERROR);
}
//判断密码是否输入正确
if (!new BCryptPasswordEncoder().matches(password, seat.getPassword())) {
throw new ServiceException(ResultCode.ERROR);
}
return seatTokenGenerate.createToken(seat, true);
}
@Override
public String createQuickLoginCode(String username) {
String code = UUID.randomUUID().toString();
cache.put(prefix + code, username, 20L);
return code;
}
@Override
public Token quickLogin(String code) {
String username = cache.get(prefix + code);
cache.remove(prefix + code);
if (StringUtils.isEmpty(username)) {
throw new ServiceException(ResultCode.ERROR);
}
return seatTokenGenerate.createToken(findByUsername(username), true);
}
/**
* 查询坐席
*
* @param username
* @return
*/
@Override
public Seat findByUsername(String username) {
LambdaQueryWrapper<Seat> seatLambdaQueryWrapper = new LambdaQueryWrapper<>();
seatLambdaQueryWrapper.eq(Seat::getUsername, username);
return this.getOne(seatLambdaQueryWrapper);
}
}

View File

@ -0,0 +1,48 @@
package cn.lili.modules.im.serviceimpl;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.exception.ServiceException;
import cn.lili.modules.im.entity.dos.SeatSetting;
import cn.lili.modules.im.mapper.SeatSettingMapper;
import cn.lili.modules.im.service.SeatSettingService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 坐席设置业务层实现
*
* @author pikachu
* @since 2020-02-18 16:18:56
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class SeatSettingServiceImpl extends ServiceImpl<SeatSettingMapper, SeatSetting> implements SeatSettingService {
@Override
public SeatSetting getSetting(String storeId) {
LambdaQueryWrapper<SeatSetting> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SeatSetting::getTenantId, storeId);
SeatSetting seatSetting = this.baseMapper.selectOne(queryWrapper);
if (seatSetting == null) {
seatSetting = new SeatSetting();
seatSetting.setOutLineAutoReply("您好,我现在不在线,请您留下关键内容和联系方式,我看到后会立马回电。");
seatSetting.setLongTermAutoReply("您好,我正在查阅相关资料,请您稍等。");
seatSetting.setWelcome("您好,请问有什么可以帮您?");
seatSetting.setTenantId(storeId);
this.save(seatSetting);
}
return seatSetting;
}
@Override
public SeatSetting updateByStore(SeatSetting seatSetting) {
SeatSetting oldSetting = this.baseMapper.selectById(seatSetting.getId());
if (oldSetting.getTenantId().equals(seatSetting.getTenantId())) {
this.updateById(seatSetting);
} else {
throw new ServiceException(ResultCode.ERROR);
}
return seatSetting;
}
}

View File

@ -0,0 +1,42 @@
package cn.lili.modules.im.token;
import cn.lili.common.security.AuthUser;
import cn.lili.common.security.enums.UserEnums;
import cn.lili.common.security.token.Token;
import cn.lili.common.security.token.TokenUtil;
import cn.lili.common.security.token.base.AbstractTokenGenerate;
import cn.lili.modules.im.entity.dos.Seat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 会员token生成
*
* @author Chopper
* @version v4.0
* @since 2020/11/16 10:50
*/
@Component
public class SeatTokenGenerate extends AbstractTokenGenerate<Seat> {
@Autowired
private TokenUtil tokenUtil;
@Override
public Token createToken(Seat seat, Boolean longTerm) {
AuthUser authUser = new AuthUser(
seat.getUsername(),
seat.getId(),
seat.getNickName(),
seat.getFace(),
UserEnums.SEAT);
authUser.setTenantId(seat.getTenantId());
//登陆成功生成token
return tokenUtil.createToken(seat.getUsername(), authUser, longTerm, UserEnums.SEAT);
}
@Override
public Token refreshToken(String refreshToken) {
return tokenUtil.refreshToken(refreshToken, UserEnums.SEAT);
}
}

34
im-api/pom.xml Normal file
View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>im-api</artifactId>
<parent>
<groupId>cn.lili</groupId>
<artifactId>lili-shop-parent</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>cn.lili</groupId>
<artifactId>framework</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,31 @@
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;
/**
* @author liushuai
*/
@SpringBootApplication
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

@ -33,7 +33,7 @@ import java.util.List;
@Slf4j
@RestController
@Api(tags = "管理员")
@RequestMapping("/manager/passport/user")
@RequestMapping("/manager/im/passport/user")
@Validated
public class ImManagerController {
@Autowired

View File

@ -0,0 +1,82 @@
package cn.lili.controller.im;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.enums.ResultUtil;
import cn.lili.common.vo.ResultMessage;
import cn.lili.modules.im.entity.dos.ImMessage;
import cn.lili.modules.im.entity.dto.MessageQueryParams;
import cn.lili.modules.im.service.ImMessageService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @author Chopper
*/
@RestController
@Api(tags = "Im消息接口")
@RequestMapping("/lili/imMessage")
@Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ImMessageController {
private final ImMessageService imMessageService;
@GetMapping(value = "/{id}")
@ApiOperation(value = "查看Im消息详情")
public ResultMessage<ImMessage> get(@PathVariable String id) {
ImMessage imMessage = imMessageService.getById(id);
return new ResultUtil<ImMessage>().setData(imMessage);
}
@GetMapping
@ApiOperation(value = "分页获取Im消息")
public ResultMessage<List<ImMessage>> historyMessage(MessageQueryParams messageQueryParams) {
List<ImMessage> data = imMessageService.list(messageQueryParams.initQueryWrapper());
return new ResultUtil<List<ImMessage>>().setData(data);
}
@PostMapping
@ApiOperation(value = "新增Im消息")
public ResultMessage<ImMessage> save(ImMessage imMessage) {
if (imMessageService.save(imMessage)) {
return new ResultUtil<ImMessage>().setData(imMessage);
}
return new ResultUtil<ImMessage>().setErrorMsg(ResultCode.ERROR);
}
@PutMapping("/{id}")
@ApiOperation(value = "更新Im消息")
public ResultMessage<ImMessage> update(@PathVariable String id, ImMessage imMessage) {
if (imMessageService.updateById(imMessage)) {
return new ResultUtil<ImMessage>().setData(imMessage);
}
return new ResultUtil<ImMessage>().setErrorMsg(ResultCode.ERROR);
}
@DeleteMapping(value = "/{ids}")
@ApiOperation(value = "删除Im消息")
public ResultMessage<Object> delAllByIds(@PathVariable List ids) {
imMessageService.removeByIds(ids);
return ResultUtil.success(ResultCode.SUCCESS);
}
@GetMapping(value = "/newMessage")
@ApiOperation(value = "删除Im消息")
public ResultMessage<Boolean> hasNewMessage(String accessToken) {
return ResultUtil.data(imMessageService.hasNewMessage(accessToken));
}
}

View File

@ -0,0 +1,93 @@
package cn.lili.controller.im;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.enums.ResultUtil;
import cn.lili.common.security.AuthUser;
import cn.lili.common.security.context.UserContext;
import cn.lili.common.vo.ResultMessage;
import cn.lili.modules.im.entity.dos.ImTalk;
import cn.lili.modules.im.entity.vo.ImTalkVO;
import cn.lili.modules.im.service.ImTalkService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author Chopper
*/
@RestController
@Api(tags = "聊天接口")
@RequestMapping("/lili/imTalk")
@Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ImTalkController {
private final ImTalkService imTalkService;
@GetMapping(value = "/{id}")
@ApiOperation(value = "查看聊天详情")
public ResultMessage<ImTalk> get(@PathVariable String id) {
ImTalk imTalk = imTalkService.getById(id);
return new ResultUtil<ImTalk>().setData(imTalk);
}
@GetMapping(value = "/user/{uid}")
@ApiOperation(value = "查看与某人聊天详情")
public ResultMessage<ImTalk> getUser(@PathVariable String uid) {
AuthUser authUser = UserContext.getCurrentUser();
return ResultUtil.data(imTalkService.getTalkByUser(authUser.getId(), uid));
}
@GetMapping(value = "/top")
@ApiOperation(value = "查看与某人聊天详情")
public ResultMessage top(String id, Boolean top) {
imTalkService.top(id, top);
return ResultUtil.success();
}
@GetMapping("/list")
@ApiOperation(value = "分页获取聊天")
public ResultMessage<List<ImTalkVO>> getByPage() {
AuthUser authUser = UserContext.getCurrentUser();
LambdaQueryWrapper<ImTalk> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ImTalk::getUserId1, authUser.getId()).or().eq(ImTalk::getUserId2, authUser.getId());
List<ImTalk> imTalks = imTalkService.list(queryWrapper);
List<ImTalkVO> results = imTalks.stream().map(imTalk -> {
return new ImTalkVO(imTalk, authUser.getId());
}).collect(Collectors.toList());
return ResultUtil.data(results);
}
@GetMapping("/store/list")
@ApiOperation(value = "分页获取商家聊天")
public ResultMessage<List<ImTalkVO>> getStoreTalkList() {
AuthUser authUser = UserContext.getCurrentUser();
LambdaQueryWrapper<ImTalk> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ImTalk::getUserId1, authUser.getStoreId()).or().eq(ImTalk::getUserId2, authUser.getStoreId());
List<ImTalk> imTalks = imTalkService.list(queryWrapper);
List<ImTalkVO> results = imTalks.stream().map(imTalk -> {
return new ImTalkVO(imTalk, authUser.getStoreId());
}).collect(Collectors.toList());
return ResultUtil.data(results);
}
@DeleteMapping(value = "/{id}")
@ApiOperation(value = "删除聊天")
public ResultMessage<Object> disable(@PathVariable String id) {
imTalkService.disable(id);
return ResultUtil.success(ResultCode.SUCCESS);
}
}

View File

@ -0,0 +1,51 @@
package cn.lili.controller.im;
import cn.lili.common.enums.ResultUtil;
import cn.lili.common.security.AuthUser;
import cn.lili.common.security.context.UserContext;
import cn.lili.common.vo.ResultMessage;
import cn.lili.modules.member.entity.dos.Member;
import cn.lili.modules.member.service.MemberService;
import cn.lili.modules.store.entity.dos.Store;
import cn.lili.modules.store.service.StoreService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Chopper
*/
@RestController
@Api(tags = "Im消息接口")
@RequestMapping("/lili/imUser")
@Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ImUserController {
private final MemberService memberService;
@Autowired
private StoreService storeService;
@GetMapping
@ApiOperation(value = "获取用户信息")
public ResultMessage<Member> getImUser() {
AuthUser authUser = UserContext.getCurrentUser();
return ResultUtil.data(memberService.getById(authUser.getId()));
}
@GetMapping("/store")
@ApiOperation(value = "获取用户信息")
public ResultMessage<Store> getStoreUser() {
AuthUser authUser = UserContext.getCurrentUser();
return ResultUtil.data(storeService.getById(authUser.getStoreId()));
}
}

View File

@ -0,0 +1,62 @@
package cn.lili.controller.im;
import cn.lili.common.enums.ResultUtil;
import cn.lili.common.vo.PageVO;
import cn.lili.common.vo.ResultMessage;
import cn.lili.modules.im.entity.dos.QA;
import cn.lili.modules.im.service.QAService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
/**
* 管理端,自定义分词接口
*
* @author paulG
* @since 2020/10/16
**/
@Slf4j
@RestController
@Api(tags = "管理端,自定义分词接口")
@RequestMapping("/store/qa")
public class QAStoreController {
@Autowired
private QAService qaService;
@ApiOperation(value = "添加问答")
@PostMapping
public ResultMessage<QA> addCustomWords(@Valid QA qa) {
qaService.save(qa);
return ResultUtil.data(qa);
}
@ApiOperation(value = "修改自定义问答")
@PutMapping
public ResultMessage<QA> updateCustomWords(@Valid QA qa) {
qaService.updateById(qa);
return ResultUtil.data(qa);
}
@ApiOperation(value = "删除自定义分词")
@DeleteMapping("/{id}")
public ResultMessage<String> deleteCustomWords(@NotNull @PathVariable String id) {
qaService.removeById(id);
return ResultUtil.success();
}
@ApiOperation(value = "分页获取自定义分词")
@ApiImplicitParam(name = "word", value = "问题", required = true, dataType = "String", paramType = "query")
@GetMapping("/page")
public ResultMessage<IPage<QA>> getCustomWords(@RequestParam String word, PageVO pageVo) {
return ResultUtil.data(qaService.getStoreQA(word, pageVo));
}
}

View File

@ -0,0 +1,59 @@
package cn.lili.controller.im;
import cn.lili.common.enums.ResultUtil;
import cn.lili.common.vo.ResultMessage;
import cn.lili.modules.im.service.SeatService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 坐席登录接口
*
* @author Chopper
* @version v1.0
* 2022-02-10 16:40
*/
@Slf4j
@RestController
@Api(tags = "坐席端")
@RequestMapping("/seat/login")
public class SeatLogin {
@Autowired
private SeatService seatService;
@ApiOperation(value = "登录接口")
@ApiImplicitParams({
@ApiImplicitParam(name = "username", value = "用户名", required = true, paramType = "query"),
@ApiImplicitParam(name = "password", value = "密码", required = true, paramType = "query")
})
@PostMapping("/userLogin")
public ResultMessage<Object> userLogin(String username, String password) {
return ResultUtil.data(this.seatService.usernameLogin(username, password));
}
@ApiOperation(value = "商家快捷登录客服")
@PostMapping("/quicklogin")
public ResultMessage<Object> quickLogin(String code) {
return ResultUtil.data(this.seatService.quickLogin(code));
}
@ApiOperation(value = "登出")
@PostMapping("/logout")
public ResultMessage<Object> logout() {
//todo
// UserContext.getCurrentUser().getId()
// verificationServiceClient.check(uuid);
return ResultUtil.success();
}
}

View File

@ -0,0 +1,44 @@
package cn.lili.controller.im;
import cn.lili.common.enums.ResultUtil;
import cn.lili.common.security.context.UserContext;
import cn.lili.common.vo.ResultMessage;
import cn.lili.modules.im.entity.dos.SeatSetting;
import cn.lili.modules.im.service.SeatSettingService;;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 店铺端,分类绑定参数组管理接口
*
* @author pikachu
* @since 2020-02-18 15:18:56
*/
@RestController
@Api(tags = "店铺端,坐席设置")
@RequestMapping("/store/seat/setting")
@Transactional(rollbackFor = Exception.class)
public class SeatSettingStoreController {
@Autowired
private SeatSettingService seatSettingService;
@ApiOperation(value = "查询坐席设置")
@GetMapping
public ResultMessage<SeatSetting> getSetting() {
return ResultUtil.data(seatSettingService.getSetting(UserContext.getCurrentUser().getTenantId()));
}
@ApiOperation(value = "更新坐席设置")
@PutMapping
public void update(SeatSetting seatSetting) {
seatSetting.setTenantId(UserContext.getCurrentUser().getTenantId());
seatSettingService.updateByStore(seatSetting);
}
}

View File

@ -0,0 +1,42 @@
package cn.lili.controller.im;
import cn.lili.common.enums.ResultUtil;
import cn.lili.common.security.context.UserContext;
import cn.lili.common.vo.ResultMessage;
import cn.lili.modules.im.entity.vo.SeatVO;
import cn.lili.modules.im.service.SeatService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* SeatController
*
* @author Chopper
* @version v1.0
* 2022-02-10 11:50
*/
@RestController
@Api(tags = "店铺端,坐席管理")
@RequestMapping("/store/seat/setting")
@Transactional(rollbackFor = Exception.class)
public class SeatStoreController {
@Autowired
private SeatService seatService;
@ApiOperation(value = "分页获取坐席")
@GetMapping("/list")
public ResultMessage<List<SeatVO>> getSeats() {
return ResultUtil.data(seatService.seatVoList(UserContext.getCurrentUser().getTenantId()));
}
}

View File

@ -0,0 +1,39 @@
package cn.lili.controller.im;
import cn.lili.common.enums.ResultUtil;
import cn.lili.common.vo.ResultMessage;
import cn.lili.modules.im.entity.vo.SeatVO;
import cn.lili.modules.im.service.SeatService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* SeatController
*
* @author Chopper
* @version v1.0
* 2022-02-10 11:50
*/
@RestController
@Api(tags = "店铺端,坐席管理")
@RequestMapping("/manager/seat/setting")
@Transactional(rollbackFor = Exception.class)
public class SeatStoreManagerController {
@Autowired
private SeatService seatService;
@ApiOperation(value = "查看店铺坐席列表")
@GetMapping("/list")
public ResultMessage<List<SeatVO>> getSeats(String storeId) {
return ResultUtil.data(seatService.seatVoList(storeId));
}
}

View File

@ -0,0 +1,172 @@
package cn.lili.controller.im;
import cn.lili.cache.Cache;
import cn.lili.common.security.AuthUser;
import cn.lili.common.security.context.UserContext;
import cn.lili.common.utils.SnowFlake;
import cn.lili.modules.im.config.CustomSpringConfigurator;
import cn.lili.modules.im.entity.dos.ImMessage;
import cn.lili.modules.im.entity.enums.MessageResultType;
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.member.entity.dos.Member;
import cn.lili.modules.member.service.MemberService;
import com.alibaba.druid.util.StringUtils;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author liushuai
*/
@Component
@ServerEndpoint(value = "/lili/webSocket/{accessToken}", configurator = CustomSpringConfigurator.class)
@Slf4j
public class WebSocketServer {
/**
* 消息服务
*/
@Autowired
private ImMessageService imMessageService;
/**
* im用户服务
*/
@Autowired
private MemberService memberService;
@Autowired
private Cache cache;
/**
* 在线人数
* PS 注意只能单节点如果多节点部署需要自行寻找方案
*/
private static ConcurrentHashMap<String, Session> sessionPools = new ConcurrentHashMap<>();
/**
* 建立连接
*
* @param session
*/
@OnOpen
public void onOpen(@PathParam("accessToken") String accessToken, Session session) throws IOException {
AuthUser authUser = UserContext.getAuthUser(accessToken);
Member member = memberService.getById(authUser.getId());
sessionPools.put(authUser.getId(), session);
MessageVO messageVO = new MessageVO(MessageResultType.FRIENDS, member);
sendMessage(authUser.getId(), messageVO);
}
/**
* 关闭连接
*/
@OnClose
public void onClose(@PathParam("accessToken") String accessToken) {
log.info("断开连接:{}", accessToken);
sessionPools.remove(UserContext.getAuthUser(accessToken).getId());
}
/**
* 发送消息
*
* @param msg
* @throws IOException
*/
@OnMessage
public void onMessage(@PathParam("accessToken") String accessToken, String msg) {
log.error(msg);
MessageOperation messageOperation = JSON.parseObject(msg, MessageOperation.class);
operation(accessToken, messageOperation);
}
/**
* IM操作
*
* @param accessToken
* @param messageOperation
*/
private void operation(String accessToken, MessageOperation messageOperation) {
AuthUser authUser = UserContext.getAuthUser(accessToken);
switch (messageOperation.getOperationType()) {
case PING:
break;
case MESSAGE:
//保存消息
ImMessage imMessage = new ImMessage();
imMessage.setFromUser(messageOperation.getFrom());
imMessage.setMessageType(messageOperation.getMessageType());
imMessage.setIsRead(false);
imMessage.setText(messageOperation.getContext());
imMessage.setTalkId(messageOperation.getTalkId());
imMessage.setCreateTime(new Date());
imMessage.setToUser(messageOperation.getTo());
imMessage.setId(SnowFlake.getIdStr());
imMessageService.save(imMessage);
//发送消息
sendMessage(messageOperation.getTo(), new MessageVO(MessageResultType.MESSAGE, imMessage));
break;
case READ:
if (!StringUtils.isEmpty(messageOperation.getContext())) {
imMessageService.read(messageOperation.getTalkId(), accessToken);
}
break;
case UNREAD:
sendMessage(authUser.getId(), new MessageVO(MessageResultType.UN_READ, imMessageService.unReadMessages(accessToken)));
break;
case HISTORY:
sendMessage(authUser.getId(), new MessageVO(MessageResultType.HISTORY, imMessageService.historyMessage(accessToken, messageOperation.getTo())));
break;
default:
break;
}
}
/**
* 发送消息
*
* @param key 密钥
* @param message 消息对象
*/
private void sendMessage(String key, MessageVO message) {
Session session = sessionPools.get(key);
if (session != null) {
try {
session.getBasicRemote().sendText(JSON.toJSONString(message, true));
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* socket exception
*
* @param session
* @param throwable
*/
@OnError
public void onError(Session session, Throwable throwable) {
throwable.printStackTrace();
}
/**
* 获取店铺id
*
* @return
*/
private String storeKey(String storeId) {
return "STORE_" + storeId;
}
}

View File

@ -0,0 +1,53 @@
package cn.lili.controller.security;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.web.cors.CorsConfigurationSource;
/**
* spring Security 核心配置类 通用安全
*
* @author Chopper
* @version v4.0
* @since 2020/11/14 16:20
*/
@Slf4j
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ImSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* spring security - 权限不足处理
*/
@Autowired
private CorsConfigurationSource corsConfigurationSource;
@Override
protected void configure(HttpSecurity http) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http
.authorizeRequests();
registry
.and()
//禁止网页iframe
.headers().frameOptions().disable()
.and()
.authorizeRequests()
//任何请求
.anyRequest()
//需要身份认证
.permitAll()
.and()
//允许跨域
.cors().configurationSource(corsConfigurationSource).and()
//关闭跨站请求防护
.csrf().disable();
}
}

View File

@ -0,0 +1,275 @@
server:
port: 8885
servlet:
context-path: /
tomcat:
uri-encoding: UTF-8
threads:
min-spare: 50
max: 1000
# 与Spring Boot 2一样默认情况下大多数端点都不通过http公开我们公开了所有端点。对于生产您应该仔细选择要公开的端点。
management:
# health:
# elasticsearch:
# enabled: false
# datasource:
# enabled: false
endpoints:
web:
exposure:
include: '*'
spring:
application:
name: im-api
# 要在其中注册的Spring Boot Admin Server的URL。
boot:
admin:
client:
url: http://127.0.0.1:8000
cache:
type: redis
# Redis
redis:
host: 127.0.0.1
port: 6379
password: lilishop
lettuce:
pool:
# 连接池最大连接数(使用负值表示没有限制) 默认 8
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
max-wait: 20
# 连接池中的最大空闲连接 默认 8
max-idle: 10
# 连接池中的最小空闲连接 默认 8
min-idle: 8
# 文件大小上传配置
servlet:
multipart:
max-file-size: 20MB
max-request-size: 20MB
jackson:
time-zone: GMT+8
serialization:
#关闭jackson 对json做解析
fail-on-empty-beans: false
shardingsphere:
datasource:
# 数据库名称,可自定义,可以为多个,以逗号隔开,每个在这里定义的库,都要在下面定义连接属性
names: default-datasource
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
maxActive: 20
initialSize: 5
maxWait: 60000
minIdle: 5
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
#是否缓存preparedStatement也就是PSCache。在mysql下建议关闭。 PSCache对支持游标的数据库性能提升巨大比如说oracle。
poolPreparedStatements: false
#要启用PSCache-1为关闭 必须配置大于0当大于0时poolPreparedStatements自动触发修改为true 可以把这个数值配置大一些比如说100
maxOpenPreparedStatements: -1
#配置监控统计拦截的filters去掉后监控界面sql无法统计'wall'用于防火墙
filters: stat,wall,log4j2
#通过connectProperties属性来打开mergeSql功能慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
#合并多个DruidDataSource的监控数据
useGlobalDataSourceStat: true
loginUsername: druid
loginPassword: druid
# sharding:
# default-data-source-name: default-datasource
# #需要拆分的表,可以设置多个 在 li_order 级别即可
# tables:
# #需要进行分表的逻辑表名
# li_order:
# #实际的表结点,下面代表的是li_order_为开头的所有表如果能确定表的范围例如按月份分表这里的写法是data2020.li_order_$->{2020..2021}_$->{01..12} 表示例如 li_order_2020_01 li_order_2020_03 li_order_2021_01
# actual-data-nodes: data2020.li_order_$->{2019..2021}_$->{01..12}
# table-strategy:
# # 分表策略,根据创建日期
# standard:
# sharding-column: create_time
# #分表策略
# precise-algorithm-class-name: cn.lili.mybatis.sharding.CreateTimeShardingTableAlgorithm
# #范围查询实现
# range-algorithm-class-name: cn.lili.mybatis.sharding.CreateTimeShardingTableAlgorithm
props:
#是否打印逻辑SQL语句和实际SQL语句建议调试时打印在生产环境关闭
sql:
show: false
# 忽略鉴权url
ignored:
urls:
- /editor-app/**
- /actuator**
- /actuator/**
- /MP_verify_qSyvBPhDsPdxvOhC.txt
- /weixin/**
- /source/**
- /manager/passport/user/login
- /manager/passport/user/refresh/**
- /manager/other/elasticsearch
- /manager/other/customWords
- /druid/**
- /swagger-ui.html
- /doc.html
- /swagger-resources/**
- /swagger/**
- /webjars/**
- /v2/api-docs
- /configuration/ui
- /boot-admin
- /**/*.js
- /**/*.css
- /**/*.png
- /**/*.ico
# Swagger界面内容配置
swagger:
title: lili API接口文档
description: lili Api Documentation
version: 1.0.0
termsOfServiceUrl: https://pickmall.cn
contact:
name: lili
url: https://pickmall.cn
email: admin@pickmall.com
# Mybatis-plus
mybatis-plus:
mapper-locations: classpath*:mapper/*.xml
configuration:
#缓存开启
cache-enabled: true
#日志
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 日志
logging:
config: classpath:logback-spring.xml
# 输出级别
level:
cn.lili: info
# org.hibernate: debug
# org.springframework: debug
file:
# 指定路径
path: lili-logs
logback:
rollingpolicy:
# 最大保存天数
max-history: 7
# 每个文件最大大小
max-file-size: 5MB
#加密参数
jasypt:
encryptor:
password: lili
lili:
system:
isDemoSite: true
# 脱敏级别:
# 0不做脱敏处理
# 1管理端用户手机号等信息脱敏
# 2商家端信息脱敏为2时表示管理端商家端同时脱敏
sensitiveLevel: 1
statistics:
# 在线人数统计 X 小时。这里设置48即统计过去48小时每小时在线人数
onlineMember: 48
# 当前在线人数刷新时间间隔单位秒设置为600则每10分钟刷新一次
currentOnlineUpdate: 600
#qq lbs 申请
lbs:
key: 4BYBZ-7MT6S-PUAOA-6BNWL-FJUD7-UUFXT
sk: zhNKVrJK6UPOhqIjn8AQvG37b9sz6
#域名
domain:
pc: https://pc.b2b2c.pickmall.cn
wap: https://m.b2b2c.pickmall.cn
store: https://store.b2b2c.pickmall.cn
admin: https://admin.b2b2c.pickmall.cn
#api地址
api:
buyer: https://buyer-api.pickmall.cn
common: https://common-api.pickmall.cn
manager: https://admin-api.pickmall.cn
store: https://store-api.pickmall.cn
# jwt 细节设定
jwt-setting:
# token过期时间分钟
tokenExpireTime: 60
# 使用Spring @Cacheable注解失效时间
cache:
# 过期时间 单位秒 永久不过期设为-1
timeout: 1500
#多线程配置
thread:
corePoolSize: 5
maxPoolSize: 50
queueCapacity: 50
data:
elasticsearch:
cluster-name: elasticsearch
cluster-nodes: 127.0.0.1:9200
index:
number-of-replicas: 0
number-of-shards: 3
index-prefix: lili
schema: http
# account:
# username: elastic
# password: LiLiShopES
logstash:
server: 127.0.0.1:4560
rocketmq:
promotion-topic: lili_promotion_topic
promotion-group: lili_promotion_group
msg-ext-topic: lili_msg_topic
msg-ext-group: lili_msg_group
goods-topic: lili_goods_topic
goods-group: lili_goods_group
order-topic: lili_order_topic
order-group: lili_order_group
member-topic: lili_member_topic
member-group: lili_member_group
other-topic: lili_other_topic
other-group: lili_other_group
notice-topic: lili_notice_topic
notice-group: lili_notice_group
notice-send-topic: lili_send_notice_topic
notice-send-group: lili_send_notice_group
after-sale-topic: lili_after_sale_topic
after-sale-group: lili_after_sale_group
rocketmq:
name-server: 127.0.0.1:9876
producer:
group: lili_group
send-message-timeout: 30000
xxl:
job:
admin:
addresses: http://127.0.0.1:9001/xxl-job-admin
executor:
appname: xxl-job-executor-lilishop
address:
ip:
port: 8891
logpath: ./xxl-job/executor
logretentiondays: 7

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
<!--应用名称-->
<springProperty scope="context" name="APP_NAME" source="spring.application.name"/>
<!--日志文件保存路径-->
<springProperty scope="context" name="LOG_FILE_PATH" source="logging.file.path"/>
<springProperty scope="context" name="LOGSTASH_SERVER" source="lili.data.logstash.server"/>
<contextName>${APP_NAME}</contextName>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE_PATH}/${APP_NAME}-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<appender name="RocketmqClientAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE_PATH}/rocketmq.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE_PATH}/rocketmq/rocketmq-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>30MB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{yy-MM-dd.HH:mm:ss.SSS} [%-16t] %-5p %-22c{0} %X{ServiceId} - %m%n</pattern>
</encoder>
</appender>
<logger name="RocketmqClient" additivity="false">
<level value="info" />
<appender-ref ref="RocketmqClientAppender"/>
</logger>
<!--输出到elk的LOGSTASH-->
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<!-- 配置elk日志收集 配饰的是 LOGSTASH 的地址-->
<destination>${LOGSTASH_SERVER}</destination>
<encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder">
<providers>
<timestamp>
<timeZone>UTC</timeZone>
</timestamp>
</providers>
<!--自定义字段 区分项目-->
<customFields>{"appName":"${APP_NAME}"}</customFields>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
<appender-ref ref="LOGSTASH"/>
</root>
</configuration>

View File

@ -71,6 +71,7 @@
<module>common-api</module>
<module>consumer</module>
<module>admin</module>
<module>im-api</module>
</modules>
<build>