commit
4b5a62406d
66 changed files with 4254 additions and 0 deletions
@ -0,0 +1,3 @@ |
|||||
|
/.idea/ |
||||
|
|
||||
|
|
||||
@ -0,0 +1,61 @@ |
|||||
|
<?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"> |
||||
|
<parent> |
||||
|
<artifactId>lx-im</artifactId> |
||||
|
<groupId>com.lx</groupId> |
||||
|
<version>${im.version}</version> |
||||
|
</parent> |
||||
|
<modelVersion>4.0.0</modelVersion> |
||||
|
<artifactId>commom</artifactId> |
||||
|
|
||||
|
<properties> |
||||
|
<maven.compiler.source>8</maven.compiler.source> |
||||
|
<maven.compiler.target>8</maven.compiler.target> |
||||
|
</properties> |
||||
|
|
||||
|
<dependencies> |
||||
|
<dependency> |
||||
|
<groupId>com.baomidou</groupId> |
||||
|
<artifactId>mybatis-plus-generator</artifactId> |
||||
|
<version>3.3.2</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>mysql</groupId> |
||||
|
<artifactId>mysql-connector-java</artifactId> |
||||
|
<scope>compile</scope> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>cn.hutool</groupId> |
||||
|
<artifactId>hutool-all</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.projectlombok</groupId> |
||||
|
<artifactId>lombok</artifactId> |
||||
|
<version>1.18.16</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.apache.commons</groupId> |
||||
|
<artifactId>commons-lang3</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework</groupId> |
||||
|
<artifactId>spring-beans</artifactId> |
||||
|
</dependency> |
||||
|
<!-- 引入redis --> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-data-redis</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.apache.velocity</groupId> |
||||
|
<artifactId>velocity</artifactId> |
||||
|
<version>${velocity.version}</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>mysql</groupId> |
||||
|
<artifactId>mysql-connector-java</artifactId> |
||||
|
</dependency> |
||||
|
</dependencies> |
||||
|
</project> |
||||
@ -0,0 +1,10 @@ |
|||||
|
package com.lx.common.contant; |
||||
|
|
||||
|
public class RedisKey { |
||||
|
|
||||
|
public final static String IM_USER_SERVER_ID = "im:user_server_id:"; |
||||
|
|
||||
|
public final static String IM_UNREAD_MESSAGE = "im:unread_msg:"; |
||||
|
|
||||
|
public final static String IM_ALREADY_READED_MESSAGE = "im:already_read_msg"; |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
package com.lx.common.enums; |
||||
|
|
||||
|
|
||||
|
public enum MessageStatusEnum { |
||||
|
|
||||
|
UNREAD(0,"未读"), |
||||
|
ALREADY_READ(1,"已读"); |
||||
|
|
||||
|
|
||||
|
private Integer code; |
||||
|
|
||||
|
private String desc; |
||||
|
|
||||
|
MessageStatusEnum(Integer index, String desc) { |
||||
|
this.code =index; |
||||
|
this.desc=desc; |
||||
|
} |
||||
|
|
||||
|
public static MessageStatusEnum fromCode(Integer code){ |
||||
|
for (MessageStatusEnum typeEnum:values()) { |
||||
|
if (typeEnum.code.equals(code)) { |
||||
|
return typeEnum; |
||||
|
} |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public String getDesc() { |
||||
|
return desc; |
||||
|
} |
||||
|
|
||||
|
public Integer getCode(){ |
||||
|
return this.code; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
package com.lx.common.enums; |
||||
|
|
||||
|
|
||||
|
public enum MessageTypeEnum { |
||||
|
|
||||
|
TEXT(0,"文字"), |
||||
|
FILE(1,"文件"), |
||||
|
IMAGE(2,"图片"), |
||||
|
VIDEO(3,"视频"); |
||||
|
|
||||
|
private Integer code; |
||||
|
|
||||
|
private String desc; |
||||
|
|
||||
|
MessageTypeEnum(Integer index, String desc) { |
||||
|
this.code =index; |
||||
|
this.desc=desc; |
||||
|
} |
||||
|
|
||||
|
public static MessageTypeEnum fromCode(Integer code){ |
||||
|
for (MessageTypeEnum typeEnum:values()) { |
||||
|
if (typeEnum.code.equals(code)) { |
||||
|
return typeEnum; |
||||
|
} |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public String getDesc() { |
||||
|
return desc; |
||||
|
} |
||||
|
|
||||
|
public Integer getCode(){ |
||||
|
return this.code; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,49 @@ |
|||||
|
package com.lx.common.enums; |
||||
|
|
||||
|
/** |
||||
|
* 响应码枚举 |
||||
|
* |
||||
|
* @author Blue |
||||
|
* @date 2020/10/19 |
||||
|
* |
||||
|
**/ |
||||
|
public enum ResultCode { |
||||
|
SUCCESS(200,"成功"), |
||||
|
LOGIN_ERROR(400,"登录异常"), |
||||
|
NO_LOGIN(401,"未登录"), |
||||
|
FORBIDDEN(403,"禁止访问"), |
||||
|
NOT_FIND(404,"无法找到文件"), |
||||
|
PROGRAM_ERROR(500,"系统繁忙,请稍后再试"), |
||||
|
PASSWOR_ERROR(10001,"密码不正确"), |
||||
|
VERITY_CODE_NOT_EXIST(10002,"验证码不存在"), |
||||
|
VERITY_CODE_ERROR(10003,"验证码不正确"), |
||||
|
USERNAME_ALREADY_REGISTER(10004,"该用户名已注册"), |
||||
|
MOBILE_ALREADY_REGISTER(10005,"该手机号码已注册"), |
||||
|
; |
||||
|
|
||||
|
|
||||
|
private int code; |
||||
|
private String msg; |
||||
|
|
||||
|
// 构造方法
|
||||
|
ResultCode(int code, String msg) { |
||||
|
this.code = code; |
||||
|
this.msg = msg; |
||||
|
} |
||||
|
public int getCode() { |
||||
|
return code; |
||||
|
} |
||||
|
|
||||
|
public void setCode(int code) { |
||||
|
this.code = code; |
||||
|
} |
||||
|
|
||||
|
public String getMsg() { |
||||
|
return msg; |
||||
|
} |
||||
|
|
||||
|
public void setMsg(String msg) { |
||||
|
this.msg = msg; |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,39 @@ |
|||||
|
package com.lx.common.enums; |
||||
|
|
||||
|
public enum WSCmdEnum { |
||||
|
|
||||
|
HEARTBEAT(0,"心跳"), |
||||
|
SINGLE_MESSAGE(1,"单发消息"), |
||||
|
GROUP_MESSAGE(2,"群发消息"); |
||||
|
|
||||
|
|
||||
|
private Integer code; |
||||
|
|
||||
|
private String desc; |
||||
|
|
||||
|
WSCmdEnum(Integer index, String desc) { |
||||
|
this.code =index; |
||||
|
this.desc=desc; |
||||
|
} |
||||
|
|
||||
|
public static WSCmdEnum fromCode(Integer code){ |
||||
|
for (WSCmdEnum typeEnum:values()) { |
||||
|
if (typeEnum.code.equals(code)) { |
||||
|
return typeEnum; |
||||
|
} |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public String getDesc() { |
||||
|
return desc; |
||||
|
} |
||||
|
|
||||
|
public Integer getCode(){ |
||||
|
return this.code; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
|
|
||||
@ -0,0 +1,102 @@ |
|||||
|
package com.lx.common.generator; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.annotation.IdType; |
||||
|
import com.baomidou.mybatisplus.core.toolkit.StringPool; |
||||
|
import com.baomidou.mybatisplus.generator.AutoGenerator; |
||||
|
import com.baomidou.mybatisplus.generator.InjectionConfig; |
||||
|
import com.baomidou.mybatisplus.generator.config.*; |
||||
|
import com.baomidou.mybatisplus.generator.config.po.TableInfo; |
||||
|
import com.baomidou.mybatisplus.generator.config.rules.DateType; |
||||
|
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; |
||||
|
import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
|
||||
|
|
||||
|
public class CodeGenerator { |
||||
|
public static void main(String[] args) { |
||||
|
// 代码生成器
|
||||
|
AutoGenerator mpg = new AutoGenerator(); |
||||
|
|
||||
|
// 全局配置
|
||||
|
GlobalConfig gc = new GlobalConfig(); |
||||
|
//生成的代码输出路径,自己根据需要修改
|
||||
|
String projectPath = "d:\\work\\project\\code"; |
||||
|
gc.setOutputDir(projectPath + "/src/main/java"); |
||||
|
gc.setAuthor("blue"); |
||||
|
gc.setOpen(false); |
||||
|
gc.setFileOverride(true); |
||||
|
gc.setActiveRecord(true); |
||||
|
gc.setBaseColumnList(true); |
||||
|
gc.setBaseResultMap(true); |
||||
|
gc.setIdType(IdType.AUTO); |
||||
|
gc.setDateType(DateType.ONLY_DATE); |
||||
|
mpg.setGlobalConfig(gc); |
||||
|
|
||||
|
// 数据源配置
|
||||
|
DataSourceConfig dsc = new DataSourceConfig(); |
||||
|
dsc.setUrl("jdbc:mysql://localhost:3306/simple-im?useUnicode=true&characterEncoding=utf-8"); |
||||
|
dsc.setDriverName("com.mysql.jdbc.Driver"); |
||||
|
dsc.setUsername("root"); |
||||
|
dsc.setPassword("root"); |
||||
|
mpg.setDataSource(dsc); |
||||
|
|
||||
|
// 包配置
|
||||
|
PackageConfig pc = new PackageConfig(); |
||||
|
pc.setModuleName(""); |
||||
|
pc.setParent("com.lx"); |
||||
|
mpg.setPackageInfo(pc); |
||||
|
|
||||
|
// 如果模板引擎是 velocity
|
||||
|
String templatePath = "/templates/mapper.xml.vm"; |
||||
|
|
||||
|
// 自定义输出配置
|
||||
|
List<FileOutConfig> focList = new ArrayList<>(); |
||||
|
// 自定义配置会被优先输出
|
||||
|
focList.add(new FileOutConfig(templatePath) { |
||||
|
@Override |
||||
|
public String outputFile(TableInfo tableInfo) { |
||||
|
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
|
||||
|
return projectPath + "/src/main/resources/mapper/" |
||||
|
+ tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// 自定义配置
|
||||
|
InjectionConfig cfg = new InjectionConfig() { |
||||
|
@Override |
||||
|
public void initMap() { |
||||
|
// to do nothing
|
||||
|
} |
||||
|
}; |
||||
|
cfg.setFileOutConfigList(focList); |
||||
|
mpg.setCfg(cfg); |
||||
|
|
||||
|
// 配置模板
|
||||
|
TemplateConfig templateConfig = new TemplateConfig(); |
||||
|
templateConfig.setXml(null); |
||||
|
mpg.setTemplate(templateConfig); |
||||
|
|
||||
|
// 策略配置
|
||||
|
StrategyConfig strategy = new StrategyConfig(); |
||||
|
// 下划线转驼峰
|
||||
|
strategy.setNaming(NamingStrategy.underline_to_camel); |
||||
|
strategy.setColumnNaming(NamingStrategy.underline_to_camel); |
||||
|
strategy.setEntityTableFieldAnnotationEnable(true); |
||||
|
strategy.setVersionFieldName("version"); |
||||
|
//逻辑删除的字段
|
||||
|
strategy.setLogicDeleteFieldName("deleted"); |
||||
|
strategy.setEntityLombokModel(true); |
||||
|
strategy.setRestControllerStyle(true); |
||||
|
|
||||
|
|
||||
|
//多张表的时候直接在代码中写表名
|
||||
|
strategy.setInclude("friends"); |
||||
|
strategy.setTablePrefix(""); |
||||
|
mpg.setStrategy(strategy); |
||||
|
|
||||
|
mpg.setTemplateEngine(new VelocityTemplateEngine()); |
||||
|
mpg.execute(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
package com.lx.common.model.im; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
public class HeartbeatInfo { |
||||
|
|
||||
|
private long userId; |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
package com.lx.common.model.im; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
public class SendInfo<T> { |
||||
|
|
||||
|
private Integer cmd; |
||||
|
private T data; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
package com.lx.common.model.im; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
import java.util.Date; |
||||
|
|
||||
|
@Data |
||||
|
public class SingleMessageInfo { |
||||
|
|
||||
|
private long id; |
||||
|
|
||||
|
private Long sendUserId; |
||||
|
|
||||
|
private Long recvUserId; |
||||
|
|
||||
|
private String content; |
||||
|
|
||||
|
private Integer type; |
||||
|
|
||||
|
private Date sendTime; |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
package com.lx.common.result; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
|
||||
|
@Data |
||||
|
public class Result<T> { |
||||
|
|
||||
|
|
||||
|
private int code; |
||||
|
|
||||
|
private String message; |
||||
|
|
||||
|
private T data; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,83 @@ |
|||||
|
package com.lx.common.result; |
||||
|
|
||||
|
|
||||
|
import com.lx.common.enums.ResultCode; |
||||
|
|
||||
|
import java.util.concurrent.ExecutorService; |
||||
|
import java.util.concurrent.Executors; |
||||
|
|
||||
|
public class ResultUtils { |
||||
|
|
||||
|
|
||||
|
|
||||
|
public static final <T> Result<T> success(){ |
||||
|
Result result=new Result(); |
||||
|
result.setCode(ResultCode.SUCCESS.getCode()); |
||||
|
result.setMessage(ResultCode.SUCCESS.getMsg()); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public static final <T> Result<T> success(T data){ |
||||
|
ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); |
||||
|
cachedThreadPool.submit(new Runnable() { |
||||
|
|
||||
|
@Override |
||||
|
public void run() { |
||||
|
System.out.println("当前线程"+Thread.currentThread().getName()); |
||||
|
} |
||||
|
}); |
||||
|
Result result=new Result(); |
||||
|
result.setCode(ResultCode.SUCCESS.getCode()); |
||||
|
result.setMessage(ResultCode.SUCCESS.getMsg()); |
||||
|
result.setData(data); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public static final <T> Result<T> success(T data, String messsage){ |
||||
|
Result result=new Result(); |
||||
|
result.setCode(ResultCode.SUCCESS.getCode()); |
||||
|
result.setMessage(messsage); |
||||
|
result.setData(data); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public static final <T> Result<T> success(String messsage){ |
||||
|
Result result=new Result(); |
||||
|
result.setCode(ResultCode.SUCCESS.getCode()); |
||||
|
result.setMessage(messsage); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public static final <T> Result<T> error(Integer code, String messsage){ |
||||
|
Result result=new Result(); |
||||
|
result.setCode(code); |
||||
|
result.setMessage(messsage); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public static final <T> Result<T> error(ResultCode resultCode, String messsage){ |
||||
|
Result result=new Result(); |
||||
|
result.setCode(resultCode.getCode()); |
||||
|
result.setMessage(messsage); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public static final <T> Result<T> error(ResultCode resultCode, String messsage,T data){ |
||||
|
Result result=new Result(); |
||||
|
result.setCode(resultCode.getCode()); |
||||
|
result.setMessage(messsage); |
||||
|
result.setData(data); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public static final <T> Result<T> error(ResultCode resultCode){ |
||||
|
Result result=new Result(); |
||||
|
result.setCode(resultCode.getCode()); |
||||
|
result.setMessage(resultCode.getMsg()); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
} |
||||
@ -0,0 +1,166 @@ |
|||||
|
package com.lx.common.util; |
||||
|
|
||||
|
import org.springframework.beans.BeanWrapper; |
||||
|
import org.springframework.beans.BeanWrapperImpl; |
||||
|
import org.springframework.util.ReflectionUtils; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.HashSet; |
||||
|
import java.util.List; |
||||
|
import java.util.Set; |
||||
|
|
||||
|
public class BeanUtils { |
||||
|
|
||||
|
|
||||
|
|
||||
|
private static void handleReflectionException(Exception e) { |
||||
|
ReflectionUtils.handleReflectionException(e); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
public static <T, U> List<U> copyProperties(List<T> sourceList, Class<U> clazz) { |
||||
|
|
||||
|
if(sourceList == null || sourceList.size() <= 0) { |
||||
|
return new ArrayList<>(); |
||||
|
} |
||||
|
|
||||
|
List<U> result = new ArrayList<>(); |
||||
|
for (T source : sourceList) { |
||||
|
result.add(copyProperties(source, clazz)); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
/** |
||||
|
* 如果source , 为空返回空对象 |
||||
|
* @param sourceList |
||||
|
* @param clazz |
||||
|
* @return |
||||
|
*/ |
||||
|
public static <T, U> List<U> copyPropertiesList(List<T> sourceList, Class<U> clazz) { |
||||
|
|
||||
|
if(sourceList == null || sourceList.size() <= 0) { |
||||
|
return new ArrayList<U>(); |
||||
|
} |
||||
|
|
||||
|
List<U> result = new ArrayList<>(); |
||||
|
U target = null; |
||||
|
for (T source : sourceList) { |
||||
|
try { |
||||
|
target = clazz.newInstance(); |
||||
|
copyProperties(source, target); |
||||
|
}catch(Exception e) { |
||||
|
handleReflectionException(e); |
||||
|
return new ArrayList<>(); |
||||
|
} |
||||
|
result.add(target); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* source空为null |
||||
|
* @param orig |
||||
|
* @param destClass |
||||
|
* @param <T> |
||||
|
* @return |
||||
|
*/ |
||||
|
public static <T> T copyProperties(Object orig, Class<T> destClass) { |
||||
|
|
||||
|
try { |
||||
|
Object target = destClass.newInstance(); |
||||
|
if(orig == null) { |
||||
|
return null; |
||||
|
} |
||||
|
copyProperties(orig, (Object)target); |
||||
|
return (T) target; |
||||
|
}catch(Exception e) { |
||||
|
handleReflectionException(e); |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* source 为null 返回 空对象 |
||||
|
* @param orig |
||||
|
* @param destClass |
||||
|
* @param <T> |
||||
|
* @return |
||||
|
*/ |
||||
|
public static <T> T copyProperty(Object orig, Class<T> destClass) { |
||||
|
|
||||
|
try { |
||||
|
Object target = destClass.newInstance(); |
||||
|
if(orig == null) { |
||||
|
return (T)target; |
||||
|
} |
||||
|
|
||||
|
copyProperties(orig, (Object)target); |
||||
|
return (T) target; |
||||
|
}catch(Exception e) { |
||||
|
handleReflectionException(e); |
||||
|
Object o1 = new Object(); |
||||
|
try { |
||||
|
o1 = destClass.newInstance(); |
||||
|
}catch(Exception ex) { |
||||
|
handleReflectionException(e); |
||||
|
} |
||||
|
return (T)o1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static <T, U> List<U> copyProperties(List<T> sourceList, Class<U> clazz, String... ignoreProperties) { |
||||
|
|
||||
|
if(sourceList == null || sourceList.size() <= 0) { |
||||
|
return new ArrayList<U>(); |
||||
|
} |
||||
|
|
||||
|
List<U> result = new ArrayList<>(); |
||||
|
for (T source : sourceList) { |
||||
|
result.add(copyProperties(source, clazz, ignoreProperties)); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public static <T> T copyProperties(Object orig, Class<T> destClass, String... ignoreProperties) { |
||||
|
|
||||
|
if(orig == null) { |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
Object target = destClass.newInstance(); |
||||
|
org.springframework.beans.BeanUtils.copyProperties(orig, (Object)target, ignoreProperties); |
||||
|
return (T)target; |
||||
|
}catch(Exception e) { |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static String[] getNullPropertyNames(Object source) { |
||||
|
final BeanWrapper beanWrapper = new BeanWrapperImpl(source); |
||||
|
java.beans.PropertyDescriptor[] propDesc = beanWrapper.getPropertyDescriptors(); |
||||
|
|
||||
|
Set<String> emptynames = new HashSet<String>(); |
||||
|
|
||||
|
for(java.beans.PropertyDescriptor pd : propDesc) { |
||||
|
Object srcValue = beanWrapper.getPropertyValue(pd.getName()); |
||||
|
if (srcValue == null) {emptynames.add(pd.getName());} |
||||
|
} |
||||
|
|
||||
|
String[] result = new String[emptynames.size()]; |
||||
|
return emptynames.toArray(result); |
||||
|
} |
||||
|
|
||||
|
public static void copyProperties(Object orig, Object dest) { |
||||
|
try { |
||||
|
org.springframework.beans.BeanUtils.copyProperties(orig, dest); |
||||
|
} catch (Exception e) { |
||||
|
handleReflectionException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,40 @@ |
|||||
|
package com.lx.common.util; |
||||
|
|
||||
|
import org.springframework.beans.BeansException; |
||||
|
import org.springframework.context.ApplicationContext; |
||||
|
import org.springframework.context.ApplicationContextAware; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
|
||||
|
@Component |
||||
|
public class SpringContextHolder implements ApplicationContextAware { |
||||
|
|
||||
|
private static ApplicationContext applicationContext; |
||||
|
|
||||
|
@Override |
||||
|
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { |
||||
|
SpringContextHolder.applicationContext = applicationContext; |
||||
|
} |
||||
|
|
||||
|
public static ApplicationContext getApplicationContext() { |
||||
|
assertApplicationContext(); |
||||
|
return applicationContext; |
||||
|
} |
||||
|
|
||||
|
public static <T> T getBean(String beanName) { |
||||
|
assertApplicationContext(); |
||||
|
return (T) applicationContext.getBean(beanName); |
||||
|
} |
||||
|
|
||||
|
public static <T> T getBean(Class<T> requiredType) { |
||||
|
assertApplicationContext(); |
||||
|
return applicationContext.getBean(requiredType); |
||||
|
} |
||||
|
|
||||
|
private static void assertApplicationContext() { |
||||
|
if (SpringContextHolder.applicationContext == null) { |
||||
|
throw new RuntimeException("applicaitonContext属性为null,请检查是否注入了SpringContextHolder!"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,83 @@ |
|||||
|
<?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"> |
||||
|
<parent> |
||||
|
<artifactId>lx-im</artifactId> |
||||
|
<groupId>com.lx</groupId> |
||||
|
<version>${im.version}</version> |
||||
|
</parent> |
||||
|
<modelVersion>4.0.0</modelVersion> |
||||
|
|
||||
|
<artifactId>im-platform</artifactId> |
||||
|
|
||||
|
|
||||
|
<dependencies> |
||||
|
<dependency> |
||||
|
<groupId>com.lx</groupId> |
||||
|
<artifactId>commom</artifactId> |
||||
|
<version>${im.version}</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-web</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>com.baomidou</groupId> |
||||
|
<artifactId>mybatis-plus-boot-starter</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>com.alibaba</groupId> |
||||
|
<artifactId>druid</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>mysql</groupId> |
||||
|
<artifactId>mysql-connector-java</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-jdbc</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>io.springfox</groupId> |
||||
|
<artifactId>springfox-swagger2</artifactId> |
||||
|
<version>${swagger.version}</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>io.springfox</groupId> |
||||
|
<artifactId>springfox-swagger-ui</artifactId> |
||||
|
<version>${swagger.version}</version> |
||||
|
</dependency> |
||||
|
|
||||
|
<!-- 引入redis --> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-data-redis</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-security</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.session</groupId> |
||||
|
<artifactId>spring-session-data-redis</artifactId> |
||||
|
</dependency> |
||||
|
</dependencies> |
||||
|
|
||||
|
<build> |
||||
|
<plugins> |
||||
|
<plugin> |
||||
|
<groupId>org.apache.maven.plugins</groupId> |
||||
|
<artifactId>maven-compiler-plugin</artifactId> |
||||
|
<configuration> |
||||
|
<source>8</source> |
||||
|
<target>8</target> |
||||
|
</configuration> |
||||
|
</plugin> |
||||
|
</plugins> |
||||
|
</build> |
||||
|
</project> |
||||
@ -0,0 +1,19 @@ |
|||||
|
package com.lx.implatform; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.mybatis.spring.annotation.MapperScan; |
||||
|
import org.springframework.boot.SpringApplication; |
||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication; |
||||
|
import org.springframework.context.annotation.ComponentScan; |
||||
|
|
||||
|
|
||||
|
@Slf4j |
||||
|
@MapperScan(basePackages = {"com.lx.implatform.mapper"}) |
||||
|
@ComponentScan(basePackages={"com.lx"}) |
||||
|
@SpringBootApplication |
||||
|
public class ImplatformApp { |
||||
|
|
||||
|
public static void main(String[] args) { |
||||
|
SpringApplication.run(ImplatformApp.class); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,106 @@ |
|||||
|
package com.lx.implatform.config; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonAutoDetect; |
||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo; |
||||
|
import com.fasterxml.jackson.annotation.PropertyAccessor; |
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
import com.fasterxml.jackson.databind.SerializationFeature; |
||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; |
||||
|
import org.springframework.cache.CacheManager; |
||||
|
import org.springframework.cache.annotation.CachingConfigurerSupport; |
||||
|
import org.springframework.cache.annotation.EnableCaching; |
||||
|
import org.springframework.cache.interceptor.CacheErrorHandler; |
||||
|
import org.springframework.cache.interceptor.CacheResolver; |
||||
|
import org.springframework.cache.interceptor.SimpleCacheErrorHandler; |
||||
|
import org.springframework.cache.interceptor.SimpleCacheResolver; |
||||
|
import org.springframework.context.annotation.Bean; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import org.springframework.context.annotation.Primary; |
||||
|
import org.springframework.data.redis.cache.RedisCacheConfiguration; |
||||
|
import org.springframework.data.redis.cache.RedisCacheManager; |
||||
|
import org.springframework.data.redis.connection.RedisConnectionFactory; |
||||
|
import org.springframework.data.redis.core.RedisTemplate; |
||||
|
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; |
||||
|
import org.springframework.data.redis.serializer.RedisSerializationContext; |
||||
|
import org.springframework.data.redis.serializer.StringRedisSerializer; |
||||
|
|
||||
|
import javax.annotation.Resource; |
||||
|
import java.time.Duration; |
||||
|
|
||||
|
/** |
||||
|
* redis配置 |
||||
|
* @author zsq |
||||
|
*/ |
||||
|
@EnableCaching |
||||
|
@Configuration |
||||
|
public class RedisConfig extends CachingConfigurerSupport { |
||||
|
|
||||
|
@Resource |
||||
|
private RedisConnectionFactory factory; |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 重写Redis序列化方式,使用Json方式: |
||||
|
* 当我们的数据存储到Redis的时候,我们的键(key)和值(value)都是通过Spring提供的Serializer序列化到数据库的。RedisTemplate默认使用的是JdkSerializationRedisSerializer,StringRedisTemplate默认使用的是StringRedisSerializer。 |
||||
|
* Spring Data JPA为我们提供了下面的Serializer: |
||||
|
* GenericToStringSerializer、Jackson2JsonRedisSerializer、JacksonJsonRedisSerializer、JdkSerializationRedisSerializer、OxmSerializer、StringRedisSerializer。 |
||||
|
* 在此我们将自己配置RedisTemplate并定义Serializer。 |
||||
|
* |
||||
|
* @param redisConnectionFactory |
||||
|
* @return |
||||
|
*/ |
||||
|
@Primary |
||||
|
@Bean |
||||
|
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { |
||||
|
RedisTemplate<String, Object> redisTemplate = new RedisTemplate(); |
||||
|
redisTemplate.setConnectionFactory(redisConnectionFactory); |
||||
|
|
||||
|
// 设置值(value)的序列化采用jackson2JsonRedisSerializer
|
||||
|
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer()); |
||||
|
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer()); |
||||
|
// 设置键(key)的序列化采用StringRedisSerializer。
|
||||
|
redisTemplate.setKeySerializer(new StringRedisSerializer()); |
||||
|
redisTemplate.setHashKeySerializer(new StringRedisSerializer()); |
||||
|
redisTemplate.afterPropertiesSet(); |
||||
|
return redisTemplate; |
||||
|
} |
||||
|
|
||||
|
@Bean |
||||
|
public Jackson2JsonRedisSerializer jackson2JsonRedisSerializer(){ |
||||
|
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); |
||||
|
ObjectMapper om = new ObjectMapper(); |
||||
|
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); |
||||
|
// 解决jackson2无法反序列化LocalDateTime的问题
|
||||
|
om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); |
||||
|
om.registerModule(new JavaTimeModule()); |
||||
|
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); |
||||
|
jackson2JsonRedisSerializer.setObjectMapper(om); |
||||
|
return jackson2JsonRedisSerializer; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@Bean |
||||
|
@Override |
||||
|
public CacheResolver cacheResolver() { |
||||
|
return new SimpleCacheResolver(cacheManager()); |
||||
|
} |
||||
|
|
||||
|
@Bean |
||||
|
@Override |
||||
|
public CacheErrorHandler errorHandler() { |
||||
|
// 用于捕获从Cache中进行CRUD时的异常的回调处理器。
|
||||
|
return new SimpleCacheErrorHandler(); |
||||
|
} |
||||
|
|
||||
|
@Bean |
||||
|
@Override |
||||
|
public CacheManager cacheManager() { |
||||
|
RedisCacheConfiguration cacheConfiguration = |
||||
|
RedisCacheConfiguration.defaultCacheConfig() |
||||
|
.disableCachingNullValues() |
||||
|
.entryTtl(Duration.ofMinutes(15)) |
||||
|
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer())); |
||||
|
return RedisCacheManager.builder(factory).cacheDefaults(cacheConfiguration).build(); |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
package com.lx.implatform.config; |
||||
|
|
||||
|
import io.swagger.annotations.ApiOperation; |
||||
|
import org.springframework.context.annotation.Bean; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import springfox.documentation.builders.ApiInfoBuilder; |
||||
|
import springfox.documentation.builders.PathSelectors; |
||||
|
import springfox.documentation.builders.RequestHandlerSelectors; |
||||
|
import springfox.documentation.service.ApiInfo; |
||||
|
import springfox.documentation.spi.DocumentationType; |
||||
|
import springfox.documentation.spring.web.plugins.Docket; |
||||
|
import springfox.documentation.swagger2.annotations.EnableSwagger2; |
||||
|
|
||||
|
|
||||
|
@Configuration |
||||
|
@EnableSwagger2 |
||||
|
public class SwaggerConfig { |
||||
|
|
||||
|
@Bean |
||||
|
public Docket createRestApi() { |
||||
|
|
||||
|
return new Docket(DocumentationType.SWAGGER_2) |
||||
|
.apiInfo(apiInfo()) |
||||
|
.select() |
||||
|
//这里采用包含注解的方式来确定要显示的接口
|
||||
|
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) |
||||
|
//这里采用包扫描的方式来确定要显示的接口
|
||||
|
.paths(PathSelectors.any()) |
||||
|
.build(); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
private ApiInfo apiInfo() { |
||||
|
return new ApiInfoBuilder() |
||||
|
.title("uaa service Doc") |
||||
|
.description("用户授权服务Api文档") |
||||
|
.termsOfServiceUrl("http://www.baidu.com/") |
||||
|
.version("1.0") |
||||
|
.build(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,180 @@ |
|||||
|
package com.lx.implatform.config; |
||||
|
|
||||
|
import com.alibaba.fastjson.JSON; |
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
import com.lx.common.enums.ResultCode; |
||||
|
import com.lx.common.result.Result; |
||||
|
import com.lx.common.result.ResultUtils; |
||||
|
import com.lx.implatform.service.IUserService; |
||||
|
import com.lx.implatform.session.UserSession; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.beans.factory.annotation.Qualifier; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.context.annotation.Bean; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import org.springframework.security.authentication.*; |
||||
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; |
||||
|
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.EnableWebSecurity; |
||||
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; |
||||
|
import org.springframework.security.core.userdetails.User; |
||||
|
import org.springframework.security.core.userdetails.UserDetailsService; |
||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; |
||||
|
import org.springframework.security.crypto.password.PasswordEncoder; |
||||
|
import org.springframework.security.web.AuthenticationEntryPoint; |
||||
|
import org.springframework.security.web.authentication.AuthenticationFailureHandler; |
||||
|
import org.springframework.security.web.authentication.AuthenticationSuccessHandler; |
||||
|
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; |
||||
|
|
||||
|
import java.io.PrintWriter; |
||||
|
|
||||
|
/* |
||||
|
* SpringSecurity安全框架配置 |
||||
|
* @Author Blue |
||||
|
* @Date 2022/10/21 |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
@Configuration |
||||
|
@EnableWebSecurity |
||||
|
@EnableGlobalMethodSecurity(prePostEnabled=true) |
||||
|
public class WebSecurityConfg extends WebSecurityConfigurerAdapter { |
||||
|
|
||||
|
|
||||
|
@Value("${web-ui.login-page}") |
||||
|
private String loginPage; |
||||
|
|
||||
|
@Qualifier("securityUserDetailsServiceImpl") |
||||
|
@Autowired |
||||
|
private UserDetailsService userDetailsService; |
||||
|
|
||||
|
@Autowired |
||||
|
private IUserService userService; |
||||
|
|
||||
|
@Override |
||||
|
protected void configure(HttpSecurity http) throws Exception { |
||||
|
http.authorizeRequests() |
||||
|
.antMatchers("/login","/logout","/register","/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**") |
||||
|
.permitAll() |
||||
|
.anyRequest() //任何其它请求
|
||||
|
.authenticated() //都需要身份认证
|
||||
|
.and() |
||||
|
//2、登录配置表单认证方式
|
||||
|
.formLogin() |
||||
|
.loginPage(loginPage)//自定义登录页面的url
|
||||
|
.usernameParameter("username")//设置登录账号参数,与表单参数一致
|
||||
|
.passwordParameter("password")//设置登录密码参数,与表单参数一致
|
||||
|
.loginProcessingUrl("/login")//配置默认登录入口
|
||||
|
.successHandler(successHandler()) |
||||
|
.failureHandler(failureHandler()) |
||||
|
.and() |
||||
|
//3、注销
|
||||
|
.logout() |
||||
|
.logoutUrl("/logout") |
||||
|
.logoutSuccessHandler(logoutHandler()) |
||||
|
.permitAll() |
||||
|
.and() |
||||
|
//4、session管理
|
||||
|
.sessionManagement() |
||||
|
.invalidSessionUrl(loginPage) //失效后跳转到登陆页面
|
||||
|
.and() |
||||
|
//5、禁用跨站csrf攻击防御
|
||||
|
.csrf() |
||||
|
.disable() |
||||
|
.exceptionHandling() |
||||
|
.authenticationEntryPoint(entryPoint()); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
@Bean |
||||
|
AuthenticationFailureHandler failureHandler(){ |
||||
|
return (request, response, exception) -> { |
||||
|
response.setContentType("application/json;charset=utf-8"); |
||||
|
PrintWriter out = response.getWriter(); |
||||
|
Result result = ResultUtils.error(ResultCode.LOGIN_ERROR,exception.getMessage()); |
||||
|
if (exception instanceof LockedException) { |
||||
|
result =ResultUtils.error(ResultCode.LOGIN_ERROR,"账户被锁定,请联系管理员!"); |
||||
|
} else if (exception instanceof CredentialsExpiredException) { |
||||
|
result = ResultUtils.error(ResultCode.LOGIN_ERROR,"密码过期,请联系管理员!"); |
||||
|
} else if (exception instanceof AccountExpiredException) { |
||||
|
result =ResultUtils.error(ResultCode.LOGIN_ERROR,"账户过期,请联系管理员!"); |
||||
|
} else if (exception instanceof DisabledException) { |
||||
|
result = ResultUtils.error(ResultCode.LOGIN_ERROR,"账户被禁用,请联系管理员!"); |
||||
|
} else if (exception instanceof BadCredentialsException) { |
||||
|
result =ResultUtils.error(ResultCode.LOGIN_ERROR,"用户名或者密码输入错误,请重新输入!"); |
||||
|
} |
||||
|
out.write(new ObjectMapper().writeValueAsString(result)); |
||||
|
out.flush(); |
||||
|
out.close(); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
@Bean |
||||
|
AuthenticationSuccessHandler successHandler(){ |
||||
|
return (request, response, authentication) -> { |
||||
|
// 响应
|
||||
|
response.setContentType("application/json;charset=utf-8"); |
||||
|
PrintWriter out = response.getWriter(); |
||||
|
Result result = ResultUtils.success(); |
||||
|
out.write(new ObjectMapper().writeValueAsString(result)); |
||||
|
out.flush(); |
||||
|
out.close(); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@Bean |
||||
|
LogoutSuccessHandler logoutHandler(){ |
||||
|
return (request, response, authentication) -> { |
||||
|
|
||||
|
User useDetail = (User)authentication.getPrincipal(); |
||||
|
String strJson = useDetail.getUsername(); |
||||
|
UserSession userSession = JSON.parseObject(strJson,UserSession.class); |
||||
|
log.info("{}退出", userSession.getUserName()); |
||||
|
|
||||
|
// 响应
|
||||
|
response.setContentType("application/json;charset=utf-8"); |
||||
|
PrintWriter out = response.getWriter(); |
||||
|
Result result = ResultUtils.success(); |
||||
|
out.write(new ObjectMapper().writeValueAsString(result)); |
||||
|
out.flush(); |
||||
|
out.close(); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
@Bean |
||||
|
AuthenticationEntryPoint entryPoint(){ |
||||
|
return (request, response, exception) -> { |
||||
|
response.setContentType("application/json;charset=utf-8"); |
||||
|
PrintWriter out = response.getWriter(); |
||||
|
Result result = ResultUtils.error(ResultCode.NO_LOGIN); |
||||
|
out.write(new ObjectMapper().writeValueAsString(result)); |
||||
|
out.flush(); |
||||
|
out.close(); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
@Bean |
||||
|
public PasswordEncoder passwordEncoder(){ |
||||
|
// 使用BCrypt加密密码
|
||||
|
return new BCryptPasswordEncoder(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 密码加密 |
||||
|
* @param auth |
||||
|
* @throws Exception |
||||
|
*/ |
||||
|
@Override |
||||
|
protected void configure(AuthenticationManagerBuilder auth) throws Exception { |
||||
|
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -0,0 +1,58 @@ |
|||||
|
package com.lx.implatform.controller; |
||||
|
|
||||
|
|
||||
|
import com.lx.common.result.Result; |
||||
|
import com.lx.common.result.ResultUtils; |
||||
|
import com.lx.common.util.BeanUtils; |
||||
|
import com.lx.implatform.entity.Friends; |
||||
|
import com.lx.implatform.service.IFriendsService; |
||||
|
import com.lx.implatform.session.SessionContext; |
||||
|
import com.lx.implatform.vo.FriendsVO; |
||||
|
import io.swagger.annotations.Api; |
||||
|
import io.swagger.annotations.ApiOperation; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.web.bind.annotation.*; |
||||
|
|
||||
|
import javax.validation.constraints.NotEmpty; |
||||
|
import java.util.List; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
@Api(tags = "好友相关API") |
||||
|
@RestController |
||||
|
@RequestMapping("/friends") |
||||
|
public class FriendsController { |
||||
|
|
||||
|
@Autowired |
||||
|
private IFriendsService friendsService; |
||||
|
|
||||
|
@GetMapping("/list") |
||||
|
@ApiOperation(value = "好友列表",notes="获取好友列表") |
||||
|
public Result findFriends(){ |
||||
|
List<Friends> friendsList = friendsService.findFriendsByUserId(SessionContext.getSession().getId()); |
||||
|
List<FriendsVO> vos = friendsList.stream().map(f->{ |
||||
|
FriendsVO vo = BeanUtils.copyProperties(f,FriendsVO.class); |
||||
|
return vo; |
||||
|
}).collect(Collectors.toList()); |
||||
|
return ResultUtils.success(vos); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
@PostMapping("/add") |
||||
|
@ApiOperation(value = "添加好友",notes="双方建立好友关系") |
||||
|
public Result addFriends(@NotEmpty(message = "好友id不可为空") @RequestParam("friendId") Long friendId){ |
||||
|
friendsService.addFriends(friendId); |
||||
|
return ResultUtils.success(); |
||||
|
} |
||||
|
|
||||
|
@DeleteMapping("/delete") |
||||
|
@ApiOperation(value = "删除好友",notes="解除好友关系") |
||||
|
public Result delFriends(@NotEmpty(message = "好友id不可为空") @RequestParam("friendId") Long friendId){ |
||||
|
friendsService.delFriends(friendId); |
||||
|
return ResultUtils.success(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
} |
||||
|
|
||||
@ -0,0 +1,32 @@ |
|||||
|
package com.lx.implatform.controller; |
||||
|
|
||||
|
|
||||
|
import com.lx.common.result.Result; |
||||
|
import com.lx.common.result.ResultUtils; |
||||
|
import com.lx.implatform.service.IUserService; |
||||
|
import com.lx.implatform.vo.RegisterVO; |
||||
|
import io.swagger.annotations.Api; |
||||
|
import io.swagger.annotations.ApiOperation; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.web.bind.annotation.PostMapping; |
||||
|
import org.springframework.web.bind.annotation.RequestBody; |
||||
|
import org.springframework.web.bind.annotation.RestController; |
||||
|
|
||||
|
import javax.validation.Valid; |
||||
|
|
||||
|
|
||||
|
|
||||
|
@Api(tags = "用户注册") |
||||
|
@RestController |
||||
|
public class RegisterController { |
||||
|
|
||||
|
@Autowired |
||||
|
private IUserService userService; |
||||
|
|
||||
|
@PostMapping("/register") |
||||
|
@ApiOperation(value = "用户注册",notes="用户注册") |
||||
|
public Result register(@Valid @RequestBody RegisterVO dto){ |
||||
|
userService.register(dto); |
||||
|
return ResultUtils.success(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
package com.lx.implatform.controller; |
||||
|
|
||||
|
|
||||
|
import com.lx.common.result.Result; |
||||
|
import com.lx.common.result.ResultUtils; |
||||
|
import com.lx.implatform.service.ISingleMessageService; |
||||
|
import com.lx.implatform.vo.SingleMessageVO; |
||||
|
import io.swagger.annotations.Api; |
||||
|
import io.swagger.annotations.ApiOperation; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.web.bind.annotation.PostMapping; |
||||
|
import org.springframework.web.bind.annotation.RequestBody; |
||||
|
import org.springframework.web.bind.annotation.RequestMapping; |
||||
|
import org.springframework.web.bind.annotation.RestController; |
||||
|
|
||||
|
import javax.validation.Valid; |
||||
|
|
||||
|
@Api(tags = "单发消息相关api") |
||||
|
@RestController |
||||
|
@RequestMapping("/message/single") |
||||
|
public class SingleMessageController { |
||||
|
|
||||
|
@Autowired |
||||
|
private ISingleMessageService singleMessageService; |
||||
|
|
||||
|
@PostMapping("/send") |
||||
|
@ApiOperation(value = "发送消息",notes="发送单人消息") |
||||
|
public Result register(@Valid @RequestBody SingleMessageVO vo){ |
||||
|
singleMessageService.sendMessage(vo); |
||||
|
return ResultUtils.success(); |
||||
|
} |
||||
|
|
||||
|
@PostMapping("/pullUnreadMessage") |
||||
|
@ApiOperation(value = "拉取未读消息",notes="拉取未读消息") |
||||
|
public Result pullUnreadMessage(){ |
||||
|
singleMessageService.pullUnreadMessage(); |
||||
|
return ResultUtils.success(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,59 @@ |
|||||
|
package com.lx.implatform.controller; |
||||
|
|
||||
|
|
||||
|
import com.lx.common.result.Result; |
||||
|
import com.lx.common.result.ResultUtils; |
||||
|
import com.lx.common.util.BeanUtils; |
||||
|
import com.lx.implatform.entity.User; |
||||
|
import com.lx.implatform.service.IUserService; |
||||
|
import com.lx.implatform.session.SessionContext; |
||||
|
import com.lx.implatform.session.UserSession; |
||||
|
import com.lx.implatform.vo.UserVO; |
||||
|
import io.swagger.annotations.Api; |
||||
|
import io.swagger.annotations.ApiOperation; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.web.bind.annotation.*; |
||||
|
|
||||
|
import javax.validation.constraints.NotEmpty; |
||||
|
import java.util.List; |
||||
|
|
||||
|
|
||||
|
@Api(tags = "用户相关API") |
||||
|
@RestController |
||||
|
@RequestMapping("/user") |
||||
|
public class UserController { |
||||
|
|
||||
|
@Autowired |
||||
|
private IUserService userService; |
||||
|
|
||||
|
|
||||
|
@GetMapping("/online") |
||||
|
public Result checkOnline(@NotEmpty @RequestParam("userIds") String userIds){ |
||||
|
List<Long> onlineIds = userService.checkOnline(userIds); |
||||
|
return ResultUtils.success(onlineIds); |
||||
|
} |
||||
|
|
||||
|
@GetMapping("/self") |
||||
|
public Result findSelfInfo(){ |
||||
|
UserSession session = SessionContext.getSession(); |
||||
|
User user = userService.getById(session.getId()); |
||||
|
UserVO userVO = BeanUtils.copyProperties(user,UserVO.class); |
||||
|
return ResultUtils.success(userVO); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@GetMapping("/find/{id}") |
||||
|
public Result findByIde(@NotEmpty @PathVariable("id") long id){ |
||||
|
User user = userService.getById(id); |
||||
|
UserVO userVO = BeanUtils.copyProperties(user,UserVO.class); |
||||
|
return ResultUtils.success(userVO); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@GetMapping("/findByNickName") |
||||
|
@ApiOperation(value = "查找非好友用户",notes="查找非好友用户") |
||||
|
public Result findUnfriendsUser(@NotEmpty(message = "用户昵称不可为空") @RequestParam("nickName") String nickName){ |
||||
|
return ResultUtils.success( userService.findUserByNickName(nickName)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,71 @@ |
|||||
|
package com.lx.implatform.entity; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.annotation.IdType; |
||||
|
import com.baomidou.mybatisplus.annotation.TableField; |
||||
|
import com.baomidou.mybatisplus.annotation.TableId; |
||||
|
import com.baomidou.mybatisplus.annotation.TableName; |
||||
|
import com.baomidou.mybatisplus.extension.activerecord.Model; |
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
|
||||
|
import java.io.Serializable; |
||||
|
import java.util.Date; |
||||
|
|
||||
|
/** |
||||
|
* <p> |
||||
|
* |
||||
|
* </p> |
||||
|
* |
||||
|
* @author blue |
||||
|
* @since 2022-10-22 |
||||
|
*/ |
||||
|
@Data |
||||
|
@EqualsAndHashCode(callSuper = false) |
||||
|
@TableName("friends") |
||||
|
public class Friends extends Model<Friends> { |
||||
|
|
||||
|
private static final long serialVersionUID=1L; |
||||
|
|
||||
|
/** |
||||
|
* id |
||||
|
*/ |
||||
|
@TableId(value = "id", type = IdType.AUTO) |
||||
|
private Long id; |
||||
|
|
||||
|
/** |
||||
|
* 用户id |
||||
|
*/ |
||||
|
@TableField("user_id") |
||||
|
private Long userId; |
||||
|
|
||||
|
/** |
||||
|
* 好友id |
||||
|
*/ |
||||
|
@TableField("friend_id") |
||||
|
private Long friendId; |
||||
|
|
||||
|
/** |
||||
|
* 用户昵称 |
||||
|
*/ |
||||
|
@TableField("friend_nick_name") |
||||
|
private String friendNickName; |
||||
|
|
||||
|
/** |
||||
|
* 用户头像 |
||||
|
*/ |
||||
|
@TableField("friend_head_image") |
||||
|
private String friendHeadImage; |
||||
|
|
||||
|
/** |
||||
|
* 创建时间 |
||||
|
*/ |
||||
|
@TableField("created_time") |
||||
|
private Date createdTime; |
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
protected Serializable pkVal() { |
||||
|
return this.id; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,78 @@ |
|||||
|
package com.lx.implatform.entity; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.annotation.IdType; |
||||
|
import com.baomidou.mybatisplus.annotation.TableField; |
||||
|
import com.baomidou.mybatisplus.annotation.TableId; |
||||
|
import com.baomidou.mybatisplus.annotation.TableName; |
||||
|
import com.baomidou.mybatisplus.extension.activerecord.Model; |
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
|
||||
|
import java.io.Serializable; |
||||
|
import java.util.Date; |
||||
|
|
||||
|
/** |
||||
|
* <p> |
||||
|
* |
||||
|
* </p> |
||||
|
* |
||||
|
* @author blue |
||||
|
* @since 2022-10-01 |
||||
|
*/ |
||||
|
@Data |
||||
|
@EqualsAndHashCode(callSuper = false) |
||||
|
@TableName("single_message") |
||||
|
public class SingleMessage extends Model<SingleMessage> { |
||||
|
|
||||
|
private static final long serialVersionUID=1L; |
||||
|
|
||||
|
/** |
||||
|
* id |
||||
|
*/ |
||||
|
@TableId(value = "id", type = IdType.AUTO) |
||||
|
private Long id; |
||||
|
|
||||
|
/** |
||||
|
* 发送用户id |
||||
|
*/ |
||||
|
@TableField("send_user_id") |
||||
|
private Long sendUserId; |
||||
|
|
||||
|
/** |
||||
|
* 接收用户id |
||||
|
*/ |
||||
|
@TableField("recv_user_id") |
||||
|
private Long recvUserId; |
||||
|
|
||||
|
/** |
||||
|
* 发送内容 |
||||
|
*/ |
||||
|
@TableField("content") |
||||
|
private String content; |
||||
|
|
||||
|
/** |
||||
|
* 消息类型 |
||||
|
*/ |
||||
|
@TableField("type") |
||||
|
private Integer type; |
||||
|
|
||||
|
/** |
||||
|
* 状态 |
||||
|
*/ |
||||
|
@TableField("status") |
||||
|
private Integer status; |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 发送时间 |
||||
|
*/ |
||||
|
@TableField("send_time") |
||||
|
private Date sendTime; |
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
protected Serializable pkVal() { |
||||
|
return this.id; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,77 @@ |
|||||
|
package com.lx.implatform.entity; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.annotation.IdType; |
||||
|
import com.baomidou.mybatisplus.annotation.TableField; |
||||
|
import com.baomidou.mybatisplus.annotation.TableId; |
||||
|
import com.baomidou.mybatisplus.annotation.TableName; |
||||
|
import com.baomidou.mybatisplus.extension.activerecord.Model; |
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
|
||||
|
import java.io.Serializable; |
||||
|
import java.util.Date; |
||||
|
|
||||
|
/** |
||||
|
* <p> |
||||
|
* |
||||
|
* </p> |
||||
|
* |
||||
|
* @author blue |
||||
|
* @since 2022-10-01 |
||||
|
*/ |
||||
|
@Data |
||||
|
@EqualsAndHashCode(callSuper = false) |
||||
|
@TableName("user") |
||||
|
public class User extends Model<User> { |
||||
|
|
||||
|
private static final long serialVersionUID=1L; |
||||
|
|
||||
|
/** |
||||
|
* id |
||||
|
*/ |
||||
|
@TableId(value = "id", type = IdType.AUTO) |
||||
|
private Long id; |
||||
|
|
||||
|
/** |
||||
|
* 用户名 |
||||
|
*/ |
||||
|
@TableField("user_name") |
||||
|
private String userName; |
||||
|
|
||||
|
/** |
||||
|
* 用户名 |
||||
|
*/ |
||||
|
@TableField("nick_name") |
||||
|
private String nickName; |
||||
|
|
||||
|
/** |
||||
|
* 用户名 |
||||
|
*/ |
||||
|
@TableField("head_image") |
||||
|
private String headImage; |
||||
|
|
||||
|
/** |
||||
|
* 密码(明文) |
||||
|
*/ |
||||
|
@TableField("password") |
||||
|
private String password; |
||||
|
|
||||
|
/** |
||||
|
* 最后登录时间 |
||||
|
*/ |
||||
|
@TableField("last_login_time") |
||||
|
private Date lastLoginTime; |
||||
|
|
||||
|
/** |
||||
|
* 创建时间 |
||||
|
*/ |
||||
|
@TableField("created_time") |
||||
|
private Date createdTime; |
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
protected Serializable pkVal() { |
||||
|
return this.id; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
package com.lx.implatform.exception; |
||||
|
|
||||
|
import com.lx.common.enums.ResultCode; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
import java.io.Serializable; |
||||
|
|
||||
|
|
||||
|
@Data |
||||
|
public class GlobalException extends RuntimeException implements Serializable { |
||||
|
private static final long serialVersionUID = 8134030011662574394L; |
||||
|
private Integer code; |
||||
|
private String message; |
||||
|
|
||||
|
public GlobalException(Integer code, String message){ |
||||
|
this.code=code; |
||||
|
this.message=message; |
||||
|
} |
||||
|
|
||||
|
public GlobalException(ResultCode resultCode, String message){ |
||||
|
this.code = resultCode.getCode(); |
||||
|
this.message=message; |
||||
|
} |
||||
|
|
||||
|
public GlobalException(ResultCode resultCode) { |
||||
|
this.code = resultCode.getCode(); |
||||
|
this.message = resultCode.getMsg(); |
||||
|
} |
||||
|
|
||||
|
public GlobalException(String message){ |
||||
|
this.code= ResultCode.PROGRAM_ERROR.getCode(); |
||||
|
this.message=message; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,98 @@ |
|||||
|
package com.lx.implatform.exception; |
||||
|
|
||||
|
import cn.hutool.json.JSONException; |
||||
|
import com.lx.common.enums.ResultCode; |
||||
|
import com.lx.common.result.Result; |
||||
|
import com.lx.common.result.ResultUtils; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.http.HttpStatus; |
||||
|
import org.springframework.http.converter.HttpMessageNotReadableException; |
||||
|
import org.springframework.validation.BindException; |
||||
|
import org.springframework.validation.BindingResult; |
||||
|
import org.springframework.validation.ObjectError; |
||||
|
import org.springframework.web.bind.MethodArgumentNotValidException; |
||||
|
import org.springframework.web.bind.annotation.ControllerAdvice; |
||||
|
import org.springframework.web.bind.annotation.ExceptionHandler; |
||||
|
import org.springframework.web.bind.annotation.ResponseBody; |
||||
|
import org.springframework.web.bind.annotation.ResponseStatus; |
||||
|
|
||||
|
import java.lang.reflect.UndeclaredThrowableException; |
||||
|
import java.time.format.DateTimeParseException; |
||||
|
import java.util.List; |
||||
|
|
||||
|
|
||||
|
@ControllerAdvice |
||||
|
@ResponseBody |
||||
|
@Slf4j |
||||
|
public class GlobalExceptionHandler { |
||||
|
|
||||
|
@ExceptionHandler(value = Exception.class) |
||||
|
public Result handleException(Exception e){ |
||||
|
if(e instanceof GlobalException) { |
||||
|
GlobalException ex = (GlobalException)e; |
||||
|
log.error("全局异常捕获:msg:{},log:{},{}" , ex.getMessage(), e); |
||||
|
return ResultUtils.error(ex.getCode(), ex.getMessage()); |
||||
|
} |
||||
|
else if(e instanceof UndeclaredThrowableException) { |
||||
|
GlobalException ex = (GlobalException) e.getCause(); |
||||
|
log.error("全局异常捕获:msg:{},log:{},{}" , ex.getMessage(), e); |
||||
|
return ResultUtils.error(ex.getCode(), ex.getMessage()); |
||||
|
}else{ |
||||
|
log.error("全局异常捕获:msg:{},{}" , e.getMessage(), e); |
||||
|
return ResultUtils.error(ResultCode.PROGRAM_ERROR); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 数据解析错误 |
||||
|
**/ |
||||
|
@ExceptionHandler(value = HttpMessageNotReadableException.class) |
||||
|
public Result handleMessageNotReadableException(HttpMessageNotReadableException e){ |
||||
|
log.error("全局异常捕获:msg:{}" , e.getMessage()); |
||||
|
Throwable t = e.getCause(); |
||||
|
if(t instanceof JSONException){ |
||||
|
t = t.getCause(); |
||||
|
if(t instanceof DateTimeParseException){ |
||||
|
return ResultUtils.error(ResultCode.PROGRAM_ERROR, "日期格式不正确"); |
||||
|
} |
||||
|
return ResultUtils.error(ResultCode.PROGRAM_ERROR, "数据格式不正确"); |
||||
|
} |
||||
|
return ResultUtils.error(ResultCode.PROGRAM_ERROR); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 处理请求参数格式错误 @RequestBody上validate失败后抛出的异常 |
||||
|
* @param exception |
||||
|
* @return |
||||
|
*/ |
||||
|
@ExceptionHandler(value = { MethodArgumentNotValidException.class}) |
||||
|
@ResponseStatus(HttpStatus.OK) |
||||
|
public Result handleValidationExceptionHandler(MethodArgumentNotValidException exception) { |
||||
|
BindingResult bindResult = exception.getBindingResult(); |
||||
|
String msg; |
||||
|
if (bindResult != null && bindResult.hasErrors()) { |
||||
|
msg = bindResult.getAllErrors().get(0).getDefaultMessage(); |
||||
|
if (msg.contains("NumberFormatException")) { |
||||
|
msg = "参数类型错误!"; |
||||
|
} |
||||
|
}else { |
||||
|
msg = "系统繁忙,请稍后重试..."; |
||||
|
} |
||||
|
return ResultUtils.error(ResultCode.PROGRAM_ERROR,msg); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@ExceptionHandler(BindException.class) |
||||
|
@ResponseStatus(HttpStatus.OK) |
||||
|
public Result handleBindException(BindException e){ |
||||
|
//抛出异常可能不止一个 输出为一个List集合
|
||||
|
List<ObjectError> errors = e.getAllErrors(); |
||||
|
//取第一个异常
|
||||
|
ObjectError error = errors.get(0); |
||||
|
//获取异常信息
|
||||
|
String errorMsg = error.getDefaultMessage(); |
||||
|
return ResultUtils.error(ResultCode.PROGRAM_ERROR,errorMsg); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
package com.lx.implatform.mapper; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
||||
|
import com.lx.implatform.entity.Friends; |
||||
|
|
||||
|
/** |
||||
|
* <p> |
||||
|
* Mapper 接口 |
||||
|
* </p> |
||||
|
* |
||||
|
* @author blue |
||||
|
* @since 2022-10-22 |
||||
|
*/ |
||||
|
public interface FriendsMapper extends BaseMapper<Friends> { |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
package com.lx.implatform.mapper; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
||||
|
import com.lx.implatform.entity.SingleMessage; |
||||
|
|
||||
|
/** |
||||
|
* <p> |
||||
|
* Mapper 接口 |
||||
|
* </p> |
||||
|
* |
||||
|
* @author blue |
||||
|
* @since 2022-10-01 |
||||
|
*/ |
||||
|
public interface SingleMessageMapper extends BaseMapper<SingleMessage> { |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
package com.lx.implatform.mapper; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
||||
|
import com.lx.implatform.entity.User; |
||||
|
|
||||
|
/** |
||||
|
* <p> |
||||
|
* Mapper 接口 |
||||
|
* </p> |
||||
|
* |
||||
|
* @author blue |
||||
|
* @since 2022-10-01 |
||||
|
*/ |
||||
|
public interface UserMapper extends BaseMapper<User> { |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
package com.lx.implatform.service; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.extension.service.IService; |
||||
|
import com.lx.implatform.entity.Friends; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* <p> |
||||
|
* 服务类 |
||||
|
* </p> |
||||
|
* |
||||
|
* @author blue |
||||
|
* @since 2022-10-22 |
||||
|
*/ |
||||
|
public interface IFriendsService extends IService<Friends> { |
||||
|
|
||||
|
Boolean isFriends(Long userId1,long userId2); |
||||
|
|
||||
|
List<Friends> findFriendsByUserId(long UserId); |
||||
|
|
||||
|
void addFriends(long friendId); |
||||
|
|
||||
|
void delFriends(long friendId); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
package com.lx.implatform.service; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.extension.service.IService; |
||||
|
import com.lx.implatform.entity.SingleMessage; |
||||
|
import com.lx.implatform.vo.SingleMessageVO; |
||||
|
|
||||
|
/** |
||||
|
* <p> |
||||
|
* 服务类 |
||||
|
* </p> |
||||
|
* |
||||
|
* @author blue |
||||
|
* @since 2022-10-01 |
||||
|
*/ |
||||
|
public interface ISingleMessageService extends IService<SingleMessage> { |
||||
|
|
||||
|
void sendMessage(SingleMessageVO vo); |
||||
|
|
||||
|
void pullUnreadMessage(); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
package com.lx.implatform.service; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.extension.service.IService; |
||||
|
import com.lx.implatform.entity.User; |
||||
|
import com.lx.implatform.vo.RegisterVO; |
||||
|
import com.lx.implatform.vo.UserVO; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* <p> |
||||
|
* 用户服务类 |
||||
|
* </p> |
||||
|
* |
||||
|
* @author blue |
||||
|
* @since 2022-10-01 |
||||
|
*/ |
||||
|
public interface IUserService extends IService<User> { |
||||
|
|
||||
|
void register(RegisterVO registerDTO); |
||||
|
|
||||
|
User findUserByName(String username); |
||||
|
|
||||
|
List<UserVO> findUserByNickName(String nickname); |
||||
|
|
||||
|
List<Long> checkOnline(String userIds); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,104 @@ |
|||||
|
package com.lx.implatform.service.impl; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
||||
|
import com.lx.common.enums.ResultCode; |
||||
|
import com.lx.implatform.entity.Friends; |
||||
|
import com.lx.implatform.entity.User; |
||||
|
import com.lx.implatform.exception.GlobalException; |
||||
|
import com.lx.implatform.mapper.FriendsMapper; |
||||
|
import com.lx.implatform.service.IFriendsService; |
||||
|
import com.lx.implatform.service.IUserService; |
||||
|
import com.lx.implatform.session.SessionContext; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.springframework.transaction.annotation.Transactional; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* <p> |
||||
|
* 好友服务实现类 |
||||
|
* </p> |
||||
|
* |
||||
|
* @author blue |
||||
|
* @since 2022-10-22 |
||||
|
*/ |
||||
|
@Service |
||||
|
public class FriendsServiceImpl extends ServiceImpl<FriendsMapper, Friends> implements IFriendsService { |
||||
|
|
||||
|
@Autowired |
||||
|
private IUserService userService; |
||||
|
|
||||
|
@Override |
||||
|
public List<Friends> findFriendsByUserId(long UserId) { |
||||
|
QueryWrapper<Friends> queryWrapper = new QueryWrapper<>(); |
||||
|
queryWrapper.lambda().eq(Friends::getUserId,UserId); |
||||
|
List<Friends> friendsList = this.list(queryWrapper); |
||||
|
return friendsList; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@Transactional |
||||
|
@Override |
||||
|
public void addFriends(long friendId) { |
||||
|
long userId = SessionContext.getSession().getId(); |
||||
|
if(userId == friendId){ |
||||
|
throw new GlobalException(ResultCode.PROGRAM_ERROR,"不允许添加自己为好友"); |
||||
|
} |
||||
|
// 互相绑定好友关系
|
||||
|
bindFriends(userId,friendId); |
||||
|
bindFriends(friendId,userId); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@Transactional |
||||
|
@Override |
||||
|
public void delFriends(long friendId) { |
||||
|
long userId = SessionContext.getSession().getId(); |
||||
|
// 互相解除好友关系
|
||||
|
unbindFriends(userId,friendId); |
||||
|
unbindFriends(friendId,userId); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
public Boolean isFriends(Long userId1, long userId2) { |
||||
|
QueryWrapper<Friends> queryWrapper = new QueryWrapper<>(); |
||||
|
queryWrapper.lambda() |
||||
|
.eq(Friends::getUserId,userId1) |
||||
|
.eq(Friends::getFriendId,userId2); |
||||
|
return this.count(queryWrapper) > 0; |
||||
|
} |
||||
|
|
||||
|
private void bindFriends(long userId, long friendsId) { |
||||
|
QueryWrapper<Friends> queryWrapper = new QueryWrapper<>(); |
||||
|
queryWrapper.lambda() |
||||
|
.eq(Friends::getUserId,userId) |
||||
|
.eq(Friends::getFriendId,friendsId); |
||||
|
if(this.count(queryWrapper)==0){ |
||||
|
Friends friends = new Friends(); |
||||
|
friends.setUserId(userId); |
||||
|
friends.setFriendId(friendsId); |
||||
|
User friendsInfo = userService.getById(friendsId); |
||||
|
friends.setFriendHeadImage(friendsInfo.getHeadImage()); |
||||
|
friends.setFriendNickName(friendsInfo.getNickName()); |
||||
|
this.save(friends); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private void unbindFriends(long userId, long friendsId) { |
||||
|
QueryWrapper<Friends> queryWrapper = new QueryWrapper<>(); |
||||
|
queryWrapper.lambda() |
||||
|
.eq(Friends::getUserId,userId) |
||||
|
.eq(Friends::getFriendId,friendsId); |
||||
|
List<Friends> friendsList = this.list(queryWrapper); |
||||
|
friendsList.stream().forEach(friends -> { |
||||
|
this.removeById(friends.getId()); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
package com.lx.implatform.service.impl; |
||||
|
|
||||
|
|
||||
|
import com.alibaba.fastjson.JSON; |
||||
|
import com.lx.common.util.BeanUtils; |
||||
|
import com.lx.implatform.entity.User; |
||||
|
import com.lx.implatform.service.IUserService; |
||||
|
import com.lx.implatform.session.UserSession; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.security.core.GrantedAuthority; |
||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority; |
||||
|
import org.springframework.security.core.userdetails.UserDetails; |
||||
|
import org.springframework.security.core.userdetails.UserDetailsService; |
||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
|
||||
|
@Service |
||||
|
public class SecurityUserDetailsServiceImpl implements UserDetailsService { |
||||
|
|
||||
|
@Autowired |
||||
|
private IUserService userService; |
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { |
||||
|
User user = userService.findUserByName(username); |
||||
|
if(user == null) { |
||||
|
throw new UsernameNotFoundException("用户不存在"); |
||||
|
} |
||||
|
//定义权限列表.
|
||||
|
List<GrantedAuthority> authorities = new ArrayList<>(); |
||||
|
// 用户可以访问的资源名称(或者说用户所拥有的权限) 注意:必须"ROLE_"开头
|
||||
|
authorities.add(new SimpleGrantedAuthority("ROLE_XX")); |
||||
|
|
||||
|
UserSession session = BeanUtils.copyProperties(user,UserSession.class); |
||||
|
String strJson = JSON.toJSONString(session); |
||||
|
UserDetails userDetails = new org.springframework.security.core.userdetails.User(strJson,user.getPassword(),authorities); |
||||
|
return userDetails; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,95 @@ |
|||||
|
package com.lx.implatform.service.impl; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
||||
|
import com.lx.common.contant.RedisKey; |
||||
|
import com.lx.common.enums.MessageStatusEnum; |
||||
|
import com.lx.common.enums.ResultCode; |
||||
|
import com.lx.common.model.im.SingleMessageInfo; |
||||
|
import com.lx.common.util.BeanUtils; |
||||
|
import com.lx.implatform.entity.SingleMessage; |
||||
|
import com.lx.implatform.exception.GlobalException; |
||||
|
import com.lx.implatform.mapper.SingleMessageMapper; |
||||
|
import com.lx.implatform.service.IFriendsService; |
||||
|
import com.lx.implatform.service.ISingleMessageService; |
||||
|
import com.lx.implatform.session.SessionContext; |
||||
|
import com.lx.implatform.vo.SingleMessageVO; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.data.redis.core.RedisTemplate; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
import java.util.Date; |
||||
|
import java.util.List; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
/** |
||||
|
* <p> |
||||
|
* 单人消息服务实现类 |
||||
|
* </p> |
||||
|
* |
||||
|
* @author blue |
||||
|
* @since 2022-10-01 |
||||
|
*/ |
||||
|
@Service |
||||
|
public class SingleMessageServiceImpl extends ServiceImpl<SingleMessageMapper, SingleMessage> implements ISingleMessageService { |
||||
|
|
||||
|
@Autowired |
||||
|
private IFriendsService friendsService; |
||||
|
@Autowired |
||||
|
private RedisTemplate<String, Object> redisTemplate; |
||||
|
|
||||
|
@Override |
||||
|
public void sendMessage(SingleMessageVO vo) { |
||||
|
|
||||
|
Long userId = SessionContext.getSession().getId(); |
||||
|
Boolean isFriends = friendsService.isFriends(userId,vo.getRecvUserId()); |
||||
|
if(!isFriends){ |
||||
|
throw new GlobalException(ResultCode.PROGRAM_ERROR,"您已不是对方好友,无法发送消息"); |
||||
|
} |
||||
|
|
||||
|
// 保存消息
|
||||
|
SingleMessage msg = BeanUtils.copyProperties(vo,SingleMessage.class); |
||||
|
msg.setSendUserId(userId); |
||||
|
msg.setStatus(MessageStatusEnum.UNREAD.getCode()); |
||||
|
msg.setSendTime(new Date()); |
||||
|
this.save(msg); |
||||
|
|
||||
|
// 获取对方连接的channelId
|
||||
|
String key = RedisKey.IM_USER_SERVER_ID+msg.getRecvUserId(); |
||||
|
String serverId = (String)redisTemplate.opsForValue().get(key); |
||||
|
// 如果对方在线,将数据存储至redis,等待拉取推送
|
||||
|
if(!StringUtils.isEmpty(serverId)){ |
||||
|
String sendKey = RedisKey.IM_UNREAD_MESSAGE + serverId; |
||||
|
SingleMessageInfo msgInfo = BeanUtils.copyProperties(msg,SingleMessageInfo.class); |
||||
|
redisTemplate.opsForList().rightPush(sendKey,msgInfo); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void pullUnreadMessage() { |
||||
|
// 获取当前连接的channelId
|
||||
|
Long userId = SessionContext.getSession().getId(); |
||||
|
String key = RedisKey.IM_USER_SERVER_ID+userId; |
||||
|
String serverId = (String)redisTemplate.opsForValue().get(key); |
||||
|
if(StringUtils.isEmpty(serverId)){ |
||||
|
throw new GlobalException(ResultCode.PROGRAM_ERROR,"用户未建立连接"); |
||||
|
} |
||||
|
// 获取当前用户所有未读消息
|
||||
|
QueryWrapper<SingleMessage> queryWrapper = new QueryWrapper<>(); |
||||
|
queryWrapper.lambda().eq(SingleMessage::getRecvUserId,userId) |
||||
|
.eq(SingleMessage::getStatus,MessageStatusEnum.UNREAD); |
||||
|
List<SingleMessage> messages = this.list(queryWrapper); |
||||
|
|
||||
|
// 上传至redis,等待推送
|
||||
|
if(!messages.isEmpty()){ |
||||
|
List<SingleMessageInfo> infos = messages.stream().map(m->{ |
||||
|
SingleMessageInfo msgInfo = BeanUtils.copyProperties(m,SingleMessageInfo.class); |
||||
|
return msgInfo; |
||||
|
}).collect(Collectors.toList()); |
||||
|
String sendKey = RedisKey.IM_UNREAD_MESSAGE + serverId; |
||||
|
redisTemplate.opsForList().rightPushAll(sendKey,infos.toArray()); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,95 @@ |
|||||
|
package com.lx.implatform.service.impl; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
||||
|
import com.lx.common.contant.RedisKey; |
||||
|
import com.lx.common.enums.ResultCode; |
||||
|
import com.lx.common.util.BeanUtils; |
||||
|
import com.lx.implatform.entity.User; |
||||
|
import com.lx.implatform.exception.GlobalException; |
||||
|
import com.lx.implatform.mapper.UserMapper; |
||||
|
import com.lx.implatform.service.IUserService; |
||||
|
import com.lx.implatform.vo.RegisterVO; |
||||
|
import com.lx.implatform.vo.UserVO; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.data.redis.core.RedisTemplate; |
||||
|
import org.springframework.security.crypto.password.PasswordEncoder; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
import java.util.LinkedList; |
||||
|
import java.util.List; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
/** |
||||
|
* <p> |
||||
|
* 用户服务实现类 |
||||
|
* </p> |
||||
|
* |
||||
|
* @author blue |
||||
|
* @since 2022-10-01 |
||||
|
*/ |
||||
|
@Service |
||||
|
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService { |
||||
|
|
||||
|
@Autowired |
||||
|
RedisTemplate<String,Object> redisTemplate; |
||||
|
|
||||
|
@Autowired |
||||
|
private PasswordEncoder passwordEncoder; |
||||
|
|
||||
|
@Override |
||||
|
public void register(RegisterVO vo) { |
||||
|
User user = findUserByName(vo.getUserName()); |
||||
|
if(null != user){ |
||||
|
throw new GlobalException(ResultCode.USERNAME_ALREADY_REGISTER); |
||||
|
} |
||||
|
user = BeanUtils.copyProperties(vo,User.class); |
||||
|
user.setPassword(passwordEncoder.encode(user.getPassword())); |
||||
|
this.save(user); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public User findUserByName(String username) { |
||||
|
QueryWrapper<User> queryWrapper = new QueryWrapper<>(); |
||||
|
queryWrapper.lambda().eq(User::getUserName,username); |
||||
|
return this.getOne(queryWrapper); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
public List<UserVO> findUserByNickName(String nickname) { |
||||
|
QueryWrapper<User> queryWrapper = new QueryWrapper<>(); |
||||
|
queryWrapper.lambda() |
||||
|
.like(User::getNickName,nickname) |
||||
|
.last("limit 10"); |
||||
|
List<User> users = this.list(queryWrapper); |
||||
|
List<UserVO> vos = users.stream().map(u-> { |
||||
|
UserVO vo = BeanUtils.copyProperties(u,UserVO.class); |
||||
|
vo.setOnline(isOnline(u.getId())); |
||||
|
return vo; |
||||
|
}).collect(Collectors.toList()); |
||||
|
|
||||
|
return vos; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
public List<Long> checkOnline(String userIds) { |
||||
|
String[] idArr = userIds.split(","); |
||||
|
List<Long> onlineIds = new LinkedList<>(); |
||||
|
for(String userId:idArr){ |
||||
|
if(isOnline(Long.parseLong(userId))){ |
||||
|
onlineIds.add(Long.parseLong(userId)); |
||||
|
} |
||||
|
} |
||||
|
return onlineIds; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private boolean isOnline(Long userId){ |
||||
|
String key = RedisKey.IM_USER_SERVER_ID + userId; |
||||
|
String serverId = (String) redisTemplate.opsForValue().get(key); |
||||
|
return StringUtils.isNotEmpty(serverId); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
package com.lx.implatform.session; |
||||
|
|
||||
|
import com.alibaba.fastjson.JSON; |
||||
|
import org.springframework.security.core.context.SecurityContextHolder; |
||||
|
import org.springframework.security.core.userdetails.User; |
||||
|
|
||||
|
/* |
||||
|
* @Description |
||||
|
* @Author Blue |
||||
|
* @Date 2022/10/21 |
||||
|
*/ |
||||
|
public class SessionContext { |
||||
|
|
||||
|
|
||||
|
public static UserSession getSession(){ |
||||
|
User useDetail = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal(); |
||||
|
String strJson = useDetail.getUsername(); |
||||
|
UserSession userSession = JSON.parseObject(strJson,UserSession.class); |
||||
|
return userSession; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
package com.lx.implatform.session; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
public class UserSession { |
||||
|
|
||||
|
private Long id; |
||||
|
private String userName; |
||||
|
private String nickName; |
||||
|
} |
||||
@ -0,0 +1,60 @@ |
|||||
|
package com.lx.implatform.task; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; |
||||
|
import com.lx.common.contant.RedisKey; |
||||
|
import com.lx.common.enums.MessageStatusEnum; |
||||
|
import com.lx.implatform.entity.SingleMessage; |
||||
|
import com.lx.implatform.service.ISingleMessageService; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.data.redis.core.RedisTemplate; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import javax.annotation.PostConstruct; |
||||
|
import java.util.concurrent.ExecutorService; |
||||
|
import java.util.concurrent.Executors; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Component |
||||
|
public class PullAlreadyReadMessageTask { |
||||
|
|
||||
|
private int threadNum = 8; |
||||
|
|
||||
|
private ExecutorService executorService = Executors.newFixedThreadPool(threadNum); |
||||
|
|
||||
|
@Autowired |
||||
|
private RedisTemplate<String,Object> redisTemplate; |
||||
|
|
||||
|
@Autowired |
||||
|
private ISingleMessageService singleMessageService; |
||||
|
|
||||
|
@PostConstruct |
||||
|
public void init(){ |
||||
|
for(int i=0;i<threadNum;i++){ |
||||
|
executorService.submit(new Task()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected class Task implements Runnable{ |
||||
|
@Override |
||||
|
public void run() { |
||||
|
try { |
||||
|
String key = RedisKey.IM_ALREADY_READED_MESSAGE; |
||||
|
Integer msgId = (Integer)redisTemplate.opsForList().leftPop(key,1, TimeUnit.SECONDS); |
||||
|
if(msgId!=null){ |
||||
|
UpdateWrapper<SingleMessage> updateWrapper = new UpdateWrapper<>(); |
||||
|
updateWrapper.lambda().eq(SingleMessage::getId,msgId) |
||||
|
.set(SingleMessage::getStatus, MessageStatusEnum.ALREADY_READ.getCode()); |
||||
|
singleMessageService.update(updateWrapper); |
||||
|
log.info("消息已读,id:{}",msgId); |
||||
|
} |
||||
|
}catch (Exception e){ |
||||
|
log.error(e.getMessage()); |
||||
|
}finally { |
||||
|
// 下一次循环
|
||||
|
executorService.submit(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
package com.lx.implatform.vo; |
||||
|
|
||||
|
|
||||
|
import io.swagger.annotations.ApiModel; |
||||
|
import io.swagger.annotations.ApiModelProperty; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
@ApiModel("好友信息VO") |
||||
|
public class FriendsVO { |
||||
|
|
||||
|
@ApiModelProperty(value = "好友id") |
||||
|
private Long friendId; |
||||
|
|
||||
|
|
||||
|
@ApiModelProperty(value = "用户昵称") |
||||
|
private String friendNickName; |
||||
|
|
||||
|
|
||||
|
@ApiModelProperty(value = "用户头像") |
||||
|
private String friendHeadImage; |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
package com.lx.implatform.vo; |
||||
|
|
||||
|
import io.swagger.annotations.ApiModel; |
||||
|
import io.swagger.annotations.ApiModelProperty; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
import javax.validation.constraints.NotEmpty; |
||||
|
|
||||
|
@Data |
||||
|
@ApiModel("用户注册VO") |
||||
|
public class RegisterVO { |
||||
|
|
||||
|
@NotEmpty(message="用户名不可为空") |
||||
|
@ApiModelProperty(value = "用户名") |
||||
|
private String userName; |
||||
|
|
||||
|
@NotEmpty(message="用户密码不可为空") |
||||
|
@ApiModelProperty(value = "用户密码") |
||||
|
private String password; |
||||
|
|
||||
|
@NotEmpty(message="用户昵称不可为空") |
||||
|
@ApiModelProperty(value = "用户昵称") |
||||
|
private String nickName; |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
package com.lx.implatform.vo; |
||||
|
|
||||
|
|
||||
|
import io.swagger.annotations.ApiModel; |
||||
|
import io.swagger.annotations.ApiModelProperty; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
import javax.validation.constraints.NotEmpty; |
||||
|
import javax.validation.constraints.NotNull; |
||||
|
|
||||
|
@Data |
||||
|
@ApiModel("单发消息VO") |
||||
|
public class SingleMessageVO { |
||||
|
|
||||
|
|
||||
|
@NotNull(message="接收用户id不可为空") |
||||
|
@ApiModelProperty(value = "接收用户id") |
||||
|
private Long recvUserId; |
||||
|
|
||||
|
|
||||
|
@NotEmpty(message="发送内容不可为空") |
||||
|
@ApiModelProperty(value = "发送内容") |
||||
|
private String content; |
||||
|
|
||||
|
@NotNull(message="发送内容不可为空") |
||||
|
@ApiModelProperty(value = "消息类型") |
||||
|
private Integer type; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
package com.lx.implatform.vo; |
||||
|
|
||||
|
|
||||
|
import io.swagger.annotations.ApiModel; |
||||
|
import io.swagger.annotations.ApiModelProperty; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
@ApiModel("非好友用户信息VO") |
||||
|
public class UnfriendsUserVO { |
||||
|
|
||||
|
@ApiModelProperty(value = "id") |
||||
|
private Long id; |
||||
|
|
||||
|
@ApiModelProperty(value = "用户名") |
||||
|
private String userName; |
||||
|
|
||||
|
@ApiModelProperty(value = "用户昵称") |
||||
|
private String nickName; |
||||
|
|
||||
|
@ApiModelProperty(value = "头像") |
||||
|
private String headImage; |
||||
|
|
||||
|
@ApiModelProperty(value = "是否已是好友") |
||||
|
private Boolean isFriend; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
package com.lx.implatform.vo; |
||||
|
|
||||
|
|
||||
|
import io.swagger.annotations.ApiModel; |
||||
|
import io.swagger.annotations.ApiModelProperty; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
@ApiModel("用户信息VO") |
||||
|
public class UserVO { |
||||
|
|
||||
|
@ApiModelProperty(value = "id") |
||||
|
private Long id; |
||||
|
|
||||
|
@ApiModelProperty(value = "用户名") |
||||
|
private String userName; |
||||
|
|
||||
|
@ApiModelProperty(value = "用户昵称") |
||||
|
private String nickName; |
||||
|
|
||||
|
@ApiModelProperty(value = "头像") |
||||
|
private String headImage; |
||||
|
|
||||
|
@ApiModelProperty(value = "是否在线") |
||||
|
private Boolean online; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
#这是配置服务的端口 |
||||
|
server: |
||||
|
port: 8888 |
||||
|
#配置项目的数据源 |
||||
|
spring: |
||||
|
datasource: |
||||
|
driver-class-name: com.mysql.jdbc.Driver |
||||
|
url: jdbc:mysql://localhost:3306/lx-im?useUnicode=true&characterEncoding=utf-8 |
||||
|
username: root |
||||
|
password: root |
||||
|
|
||||
|
redis: |
||||
|
host: 127.0.0.1 |
||||
|
port: 6379 |
||||
|
database: 1 |
||||
|
|
||||
|
mybatis-plus: |
||||
|
configuration: |
||||
|
# 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN(下划线命名) 到经典 Java 属性名 aColumn(驼峰命名) 的类似映射 |
||||
|
map-underscore-to-camel-case: false |
||||
|
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl |
||||
|
# mapper |
||||
|
mapper-locations: |
||||
|
# *.xml的具体路径 |
||||
|
- classpath*:mapper/*.xml |
||||
|
|
||||
|
web-ui: |
||||
|
login-page: http://localhost:8080 |
||||
@ -0,0 +1,35 @@ |
|||||
|
use `lx-im`; |
||||
|
create table `user`( |
||||
|
`id` bigint not null auto_increment primary key comment 'id', |
||||
|
`user_name` varchar(255) not null comment '用户名', |
||||
|
`nick_name` varchar(255) not null comment '用户昵称', |
||||
|
`head_image` varchar(255) default '' comment '用户头像', |
||||
|
`password` varchar(255) not null comment '密码(明文)', |
||||
|
`last_login_time` datetime DEFAULT null comment '最后登录时间', |
||||
|
`created_time` datetime DEFAULT CURRENT_TIMESTAMP comment '创建时间', |
||||
|
unique key `idx_user_name`(user_name), |
||||
|
key `idx_nick_name`(nick_name) |
||||
|
); |
||||
|
|
||||
|
create table `friends`( |
||||
|
`id` bigint not null auto_increment primary key comment 'id', |
||||
|
`user_id` bigint not null comment '用户id', |
||||
|
`friend_id` bigint not null comment '好友id', |
||||
|
`friend_nick_name` varchar(255) not null comment '用户昵称', |
||||
|
`friend_head_image` varchar(255) default '' comment '用户头像', |
||||
|
`created_time` datetime DEFAULT CURRENT_TIMESTAMP comment '创建时间', |
||||
|
key `idx_user_id` (`user_id`), |
||||
|
key `idx_friend_id` (`friend_id`) |
||||
|
); |
||||
|
|
||||
|
|
||||
|
create table `single_message`( |
||||
|
`id` bigint not null auto_increment primary key comment 'id', |
||||
|
`send_user_id` bigint not null comment '发送用户id', |
||||
|
`recv_user_id` bigint not null comment '接收用户id', |
||||
|
`content` text comment '发送内容', |
||||
|
`type` tinyint(1) NOT NULL comment '消息类型 0:文字 1:图片 2:文件', |
||||
|
`status` tinyint(1) NOT NULL comment '状态 0:未读 1:已读 ', |
||||
|
`send_time` datetime DEFAULT CURRENT_TIMESTAMP comment '发送时间', |
||||
|
key `idx_send_recv_user_id` (`send_user_id`,`recv_user_id`) |
||||
|
); |
||||
@ -0,0 +1,53 @@ |
|||||
|
<?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"> |
||||
|
<parent> |
||||
|
<artifactId>lx-im</artifactId> |
||||
|
<groupId>com.lx</groupId> |
||||
|
<version>${im.version}</version> |
||||
|
</parent> |
||||
|
<modelVersion>4.0.0</modelVersion> |
||||
|
|
||||
|
<artifactId>im-server</artifactId> |
||||
|
|
||||
|
|
||||
|
<dependencies> |
||||
|
<dependency> |
||||
|
<groupId>com.lx</groupId> |
||||
|
<artifactId>commom</artifactId> |
||||
|
<version>${im.version}</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-web</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>io.netty</groupId> |
||||
|
<artifactId>netty-all</artifactId> |
||||
|
<version>4.1.42.Final</version> |
||||
|
</dependency> |
||||
|
<!-- 引入redis --> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-data-redis</artifactId> |
||||
|
</dependency> |
||||
|
</dependencies> |
||||
|
|
||||
|
<build> |
||||
|
<plugins> |
||||
|
<plugin> |
||||
|
<groupId>org.apache.maven.plugins</groupId> |
||||
|
<artifactId>maven-compiler-plugin</artifactId> |
||||
|
<configuration> |
||||
|
<source>8</source> |
||||
|
<target>8</target> |
||||
|
</configuration> |
||||
|
</plugin> |
||||
|
</plugins> |
||||
|
</build> |
||||
|
</project> |
||||
@ -0,0 +1,32 @@ |
|||||
|
package com.lx.implatform.imserver; |
||||
|
|
||||
|
|
||||
|
import com.lx.implatform.imserver.websocket.WebsocketServer; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.boot.CommandLineRunner; |
||||
|
import org.springframework.boot.SpringApplication; |
||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication; |
||||
|
import org.springframework.context.annotation.ComponentScan; |
||||
|
import org.springframework.scheduling.annotation.EnableAsync; |
||||
|
import org.springframework.scheduling.annotation.EnableScheduling; |
||||
|
|
||||
|
|
||||
|
@EnableAsync |
||||
|
@EnableScheduling |
||||
|
@ComponentScan(basePackages={"com.lx"}) |
||||
|
@SpringBootApplication |
||||
|
public class IMServerApp implements CommandLineRunner { |
||||
|
|
||||
|
@Value("${websocket.port}") |
||||
|
private int port; |
||||
|
|
||||
|
|
||||
|
public static void main(String[] args) { |
||||
|
SpringApplication.run(IMServerApp.class); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public void run(String... args) throws Exception { |
||||
|
new WebsocketServer().start(port); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
package com.lx.implatform.imserver.config; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonAutoDetect; |
||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo; |
||||
|
import com.fasterxml.jackson.annotation.PropertyAccessor; |
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
import com.fasterxml.jackson.databind.SerializationFeature; |
||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; |
||||
|
import org.springframework.context.annotation.Bean; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import org.springframework.data.redis.connection.RedisConnectionFactory; |
||||
|
import org.springframework.data.redis.core.RedisTemplate; |
||||
|
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; |
||||
|
import org.springframework.data.redis.serializer.StringRedisSerializer; |
||||
|
|
||||
|
import javax.annotation.Resource; |
||||
|
|
||||
|
@Configuration |
||||
|
public class RedisConfig { |
||||
|
|
||||
|
@Resource |
||||
|
private RedisConnectionFactory factory; |
||||
|
|
||||
|
|
||||
|
@Bean |
||||
|
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { |
||||
|
RedisTemplate<String, Object> redisTemplate = new RedisTemplate(); |
||||
|
redisTemplate.setConnectionFactory(redisConnectionFactory); |
||||
|
|
||||
|
// 设置值(value)的序列化采用jackson2JsonRedisSerializer
|
||||
|
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer()); |
||||
|
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer()); |
||||
|
// 设置键(key)的序列化采用StringRedisSerializer。
|
||||
|
redisTemplate.setKeySerializer(new StringRedisSerializer()); |
||||
|
redisTemplate.setHashKeySerializer(new StringRedisSerializer()); |
||||
|
redisTemplate.afterPropertiesSet(); |
||||
|
return redisTemplate; |
||||
|
} |
||||
|
|
||||
|
@Bean |
||||
|
public Jackson2JsonRedisSerializer jackson2JsonRedisSerializer(){ |
||||
|
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); |
||||
|
ObjectMapper om = new ObjectMapper(); |
||||
|
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); |
||||
|
// 解决jackson2无法反序列化LocalDateTime的问题
|
||||
|
om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); |
||||
|
om.registerModule(new JavaTimeModule()); |
||||
|
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); |
||||
|
jackson2JsonRedisSerializer.setObjectMapper(om); |
||||
|
return jackson2JsonRedisSerializer; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
package com.lx.implatform.imserver.constant; |
||||
|
|
||||
|
public class Constant { |
||||
|
|
||||
|
// public static String LOCAL_SERVER_ID = UUID.randomUUID().toString();
|
||||
|
|
||||
|
|
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
package com.lx.implatform.imserver.task; |
||||
|
|
||||
|
|
||||
|
import com.lx.common.contant.RedisKey; |
||||
|
import com.lx.common.enums.WSCmdEnum; |
||||
|
import com.lx.common.model.im.SingleMessageInfo; |
||||
|
import com.lx.implatform.imserver.websocket.WebsocketChannelCtxHloder; |
||||
|
import com.lx.implatform.imserver.websocket.WebsocketServer; |
||||
|
import com.lx.implatform.imserver.websocket.processor.MessageProcessor; |
||||
|
import com.lx.implatform.imserver.websocket.processor.ProcessorFactory; |
||||
|
import io.netty.channel.ChannelHandlerContext; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.data.redis.core.RedisTemplate; |
||||
|
import org.springframework.scheduling.annotation.Scheduled; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
|
||||
|
@Slf4j |
||||
|
@Component |
||||
|
public class PullUnreadMessageTask { |
||||
|
|
||||
|
|
||||
|
@Autowired |
||||
|
private RedisTemplate<String,Object> redisTemplate; |
||||
|
|
||||
|
@Scheduled(fixedRate=100) |
||||
|
public void pullUnreadMessage() { |
||||
|
|
||||
|
// 从redis拉取未读消息
|
||||
|
String key = RedisKey.IM_UNREAD_MESSAGE + WebsocketServer.LOCAL_SERVER_ID; |
||||
|
List messageInfos = redisTemplate.opsForList().range(key,0,-1); |
||||
|
for(Object o: messageInfos){ |
||||
|
redisTemplate.opsForList().leftPop(key); |
||||
|
SingleMessageInfo messageInfo = (SingleMessageInfo)o; |
||||
|
ChannelHandlerContext ctx = WebsocketChannelCtxHloder.getChannelCtx(messageInfo.getRecvUserId()); |
||||
|
if(ctx != null){ |
||||
|
MessageProcessor processor = ProcessorFactory.createProcessor(WSCmdEnum.SINGLE_MESSAGE); |
||||
|
processor.process(ctx,messageInfo); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,86 @@ |
|||||
|
package com.lx.implatform.imserver.websocket; |
||||
|
|
||||
|
import com.lx.common.contant.RedisKey; |
||||
|
import com.lx.common.enums.WSCmdEnum; |
||||
|
import com.lx.common.model.im.SendInfo; |
||||
|
import com.lx.common.util.SpringContextHolder; |
||||
|
import com.lx.implatform.imserver.websocket.processor.MessageProcessor; |
||||
|
import com.lx.implatform.imserver.websocket.processor.ProcessorFactory; |
||||
|
import io.netty.channel.ChannelHandlerContext; |
||||
|
import io.netty.channel.SimpleChannelInboundHandler; |
||||
|
import io.netty.handler.timeout.IdleState; |
||||
|
import io.netty.handler.timeout.IdleStateEvent; |
||||
|
import io.netty.util.AttributeKey; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.data.redis.core.RedisTemplate; |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* WebSocket 长连接下 文本帧的处理器 |
||||
|
* 实现浏览器发送文本回写 |
||||
|
* 浏览器连接状态监控 |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public class WebSocketHandler extends SimpleChannelInboundHandler<SendInfo> { |
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
protected void channelRead0(ChannelHandlerContext ctx, SendInfo sendInfo) throws Exception { |
||||
|
// 创建处理器进行处理
|
||||
|
MessageProcessor processor = ProcessorFactory.createProcessor(WSCmdEnum.fromCode(sendInfo.getCmd())); |
||||
|
processor.process(ctx,processor.transform(sendInfo.getData())); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 出现异常的处理 打印报错日志 |
||||
|
* |
||||
|
* @param ctx the ctx |
||||
|
* @param cause the cause |
||||
|
* @throws Exception the Exception |
||||
|
*/ |
||||
|
@Override |
||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { |
||||
|
log.error(cause.getMessage()); |
||||
|
//关闭上下文
|
||||
|
//ctx.close();
|
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 监控浏览器上线 |
||||
|
* |
||||
|
* @param ctx the ctx |
||||
|
* @throws Exception the Exception |
||||
|
*/ |
||||
|
@Override |
||||
|
public void handlerAdded(ChannelHandlerContext ctx) throws Exception { |
||||
|
log.info(ctx.channel().id().asLongText() + "连接"); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { |
||||
|
|
||||
|
AttributeKey<Long> attr = AttributeKey.valueOf("USER_ID"); |
||||
|
Long userId = ctx.channel().attr(attr).get(); |
||||
|
// 移除channel
|
||||
|
WebsocketChannelCtxHloder.removeChannelCtx(userId); |
||||
|
// 用户下线
|
||||
|
RedisTemplate redisTemplate = SpringContextHolder.getBean("redisTemplate"); |
||||
|
String key = RedisKey.IM_USER_SERVER_ID + userId; |
||||
|
redisTemplate.delete(key); |
||||
|
log.info(ctx.channel().id().asLongText() + "断开连接"); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { |
||||
|
if (evt instanceof IdleStateEvent) { |
||||
|
IdleState state = ((IdleStateEvent) evt).state(); |
||||
|
if (state == IdleState.READER_IDLE) { |
||||
|
// 在规定时间内没有收到客户端的上行数据, 主动断开连接
|
||||
|
ctx.channel().close(); |
||||
|
} |
||||
|
} else { |
||||
|
super.userEventTriggered(ctx, evt); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
package com.lx.implatform.imserver.websocket; |
||||
|
|
||||
|
import io.netty.channel.ChannelHandlerContext; |
||||
|
|
||||
|
import java.util.Map; |
||||
|
import java.util.Set; |
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
|
||||
|
public class WebsocketChannelCtxHloder { |
||||
|
|
||||
|
private static Map<Long, ChannelHandlerContext> channelMap = new ConcurrentHashMap(); |
||||
|
|
||||
|
|
||||
|
public static void addChannelCtx(Long userId,ChannelHandlerContext ctx){ |
||||
|
channelMap.put(userId,ctx); |
||||
|
} |
||||
|
|
||||
|
public static void removeChannelCtx(Long userId){ |
||||
|
channelMap.remove(userId); |
||||
|
} |
||||
|
|
||||
|
public static ChannelHandlerContext getChannelCtx(Long userId){ |
||||
|
return channelMap.get(userId); |
||||
|
} |
||||
|
|
||||
|
public static Set<Long> getAllChannelIds(){ |
||||
|
return channelMap.keySet(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -0,0 +1,71 @@ |
|||||
|
package com.lx.implatform.imserver.websocket; |
||||
|
|
||||
|
import com.lx.implatform.imserver.websocket.endecode.MessageProtocolDecoder; |
||||
|
import com.lx.implatform.imserver.websocket.endecode.MessageProtocolEncoder; |
||||
|
import io.netty.bootstrap.ServerBootstrap; |
||||
|
import io.netty.channel.*; |
||||
|
import io.netty.channel.nio.NioEventLoopGroup; |
||||
|
import io.netty.channel.socket.nio.NioServerSocketChannel; |
||||
|
import io.netty.handler.codec.http.HttpObjectAggregator; |
||||
|
import io.netty.handler.codec.http.HttpServerCodec; |
||||
|
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; |
||||
|
import io.netty.handler.stream.ChunkedWriteHandler; |
||||
|
import io.netty.handler.timeout.IdleStateHandler; |
||||
|
|
||||
|
import java.util.UUID; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
public class WebsocketServer { |
||||
|
|
||||
|
public static String LOCAL_SERVER_ID = UUID.randomUUID().toString(); |
||||
|
|
||||
|
public void start(int port) { |
||||
|
// 服务端启动辅助类,用于设置TCP相关参数
|
||||
|
ServerBootstrap bootstrap = new ServerBootstrap(); |
||||
|
// 获取Reactor线程池
|
||||
|
EventLoopGroup bossGroup = new NioEventLoopGroup(); |
||||
|
EventLoopGroup workGroup = new NioEventLoopGroup(); |
||||
|
// 设置为主从线程模型
|
||||
|
bootstrap.group(bossGroup, workGroup) |
||||
|
// 设置服务端NIO通信类型
|
||||
|
.channel(NioServerSocketChannel.class) |
||||
|
// 设置ChannelPipeline,也就是业务职责链,由处理的Handler串联而成,由从线程池处理
|
||||
|
.childHandler(new ChannelInitializer<Channel>() { |
||||
|
// 添加处理的Handler,通常包括消息编解码、业务处理,也可以是日志、权限、过滤等
|
||||
|
@Override |
||||
|
protected void initChannel(Channel ch) throws Exception { |
||||
|
// 获取职责链
|
||||
|
ChannelPipeline pipeline = ch.pipeline(); |
||||
|
//
|
||||
|
pipeline.addLast(new IdleStateHandler(15, 0, 0, TimeUnit.SECONDS)); |
||||
|
pipeline.addLast("http-codec", new HttpServerCodec()); |
||||
|
pipeline.addLast("aggregator", new HttpObjectAggregator(65535)); |
||||
|
pipeline.addLast("http-chunked", new ChunkedWriteHandler()); |
||||
|
pipeline.addLast(new WebSocketServerProtocolHandler("/im")); |
||||
|
pipeline.addLast("encode",new MessageProtocolEncoder()); |
||||
|
pipeline.addLast("decode",new MessageProtocolDecoder()); |
||||
|
pipeline.addLast("handler", new WebSocketHandler()); |
||||
|
} |
||||
|
}) |
||||
|
// bootstrap 还可以设置TCP参数,根据需要可以分别设置主线程池和从线程池参数,来优化服务端性能。
|
||||
|
// 其中主线程池使用option方法来设置,从线程池使用childOption方法设置。
|
||||
|
// backlog表示主线程池中在套接口排队的最大数量,队列由未连接队列(三次握手未完成的)和已连接队列
|
||||
|
.option(ChannelOption.SO_BACKLOG, 5) |
||||
|
// 表示连接保活,相当于心跳机制,默认为7200s
|
||||
|
.childOption(ChannelOption.SO_KEEPALIVE, true); |
||||
|
|
||||
|
try { |
||||
|
// 绑定端口,启动select线程,轮询监听channel事件,监听到事件之后就会交给从线程池处理
|
||||
|
Channel channel = bootstrap.bind(port).sync().channel(); |
||||
|
// 等待服务端口关闭
|
||||
|
channel.closeFuture().sync(); |
||||
|
} catch (InterruptedException e) { |
||||
|
e.printStackTrace(); |
||||
|
} finally { |
||||
|
// 优雅退出,释放线程池资源
|
||||
|
bossGroup.shutdownGracefully(); |
||||
|
workGroup.shutdownGracefully(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
package com.lx.implatform.imserver.websocket.endecode; |
||||
|
|
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
import com.lx.common.model.im.SendInfo; |
||||
|
import io.netty.channel.ChannelHandlerContext; |
||||
|
import io.netty.handler.codec.MessageToMessageDecoder; |
||||
|
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
public class MessageProtocolDecoder extends MessageToMessageDecoder<TextWebSocketFrame> { |
||||
|
|
||||
|
@Override |
||||
|
protected void decode(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame, List<Object> list) throws Exception { |
||||
|
ObjectMapper objectMapper = new ObjectMapper(); |
||||
|
SendInfo sendInfo = objectMapper.readValue(textWebSocketFrame.text(), SendInfo.class); |
||||
|
list.add(sendInfo); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
package com.lx.implatform.imserver.websocket.endecode; |
||||
|
|
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
import com.lx.common.model.im.SendInfo; |
||||
|
import io.netty.channel.ChannelHandlerContext; |
||||
|
import io.netty.handler.codec.MessageToMessageEncoder; |
||||
|
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
public class MessageProtocolEncoder extends MessageToMessageEncoder<SendInfo> { |
||||
|
|
||||
|
@Override |
||||
|
protected void encode(ChannelHandlerContext channelHandlerContext, SendInfo sendInfo, List<Object> list) throws Exception { |
||||
|
ObjectMapper objectMapper = new ObjectMapper(); |
||||
|
String text = objectMapper.writeValueAsString(sendInfo); |
||||
|
TextWebSocketFrame frame = new TextWebSocketFrame(text); |
||||
|
list.add(frame); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
package com.lx.implatform.imserver.websocket.processor; |
||||
|
|
||||
|
import cn.hutool.core.bean.BeanUtil; |
||||
|
import com.lx.common.contant.RedisKey; |
||||
|
import com.lx.common.enums.WSCmdEnum; |
||||
|
import com.lx.common.model.im.HeartbeatInfo; |
||||
|
import com.lx.common.model.im.SendInfo; |
||||
|
import com.lx.implatform.imserver.websocket.WebsocketChannelCtxHloder; |
||||
|
import com.lx.implatform.imserver.websocket.WebsocketServer; |
||||
|
import io.netty.channel.ChannelHandlerContext; |
||||
|
import io.netty.util.AttributeKey; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.data.redis.core.RedisTemplate; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import java.util.HashMap; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Component |
||||
|
public class HeartbeatProcessor implements MessageProcessor<HeartbeatInfo> { |
||||
|
|
||||
|
@Autowired |
||||
|
RedisTemplate<String,Object> redisTemplate; |
||||
|
|
||||
|
@Override |
||||
|
public void process(ChannelHandlerContext ctx, HeartbeatInfo beatInfo) { |
||||
|
log.info("接收到心跳,channelId:{},userId:{}",ctx.channel().id().asLongText(),beatInfo.getUserId()); |
||||
|
// 绑定用户和channel
|
||||
|
WebsocketChannelCtxHloder.addChannelCtx(beatInfo.getUserId(),ctx); |
||||
|
// 设置属性
|
||||
|
AttributeKey<Long> attr = AttributeKey.valueOf("USER_ID"); |
||||
|
ctx.channel().attr(attr).set(beatInfo.getUserId()); |
||||
|
|
||||
|
// 在redis上记录每个user的channelId,15秒没有心跳,则自动过期
|
||||
|
String key = RedisKey.IM_USER_SERVER_ID+beatInfo.getUserId(); |
||||
|
redisTemplate.opsForValue().set(key, WebsocketServer.LOCAL_SERVER_ID,15, TimeUnit.SECONDS); |
||||
|
|
||||
|
// 响应ws
|
||||
|
SendInfo sendInfo = new SendInfo(); |
||||
|
sendInfo.setCmd(WSCmdEnum.HEARTBEAT.getCode()); |
||||
|
ctx.channel().writeAndFlush(sendInfo); |
||||
|
} |
||||
|
|
||||
|
public HeartbeatInfo transform(Object o){ |
||||
|
HashMap map = (HashMap)o; |
||||
|
HeartbeatInfo beatInfo =BeanUtil.fillBeanWithMap(map, new HeartbeatInfo(), false); |
||||
|
return beatInfo; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
package com.lx.implatform.imserver.websocket.processor; |
||||
|
|
||||
|
import io.netty.channel.ChannelHandlerContext; |
||||
|
|
||||
|
public interface MessageProcessor<T> { |
||||
|
|
||||
|
void process(ChannelHandlerContext ctx,T data); |
||||
|
|
||||
|
T transform(Object o); |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
package com.lx.implatform.imserver.websocket.processor; |
||||
|
|
||||
|
import com.lx.common.enums.WSCmdEnum; |
||||
|
import com.lx.common.util.SpringContextHolder; |
||||
|
|
||||
|
public class ProcessorFactory { |
||||
|
|
||||
|
public static MessageProcessor createProcessor(WSCmdEnum cmd){ |
||||
|
MessageProcessor processor = null; |
||||
|
switch (cmd){ |
||||
|
case HEARTBEAT: |
||||
|
processor = (MessageProcessor) SpringContextHolder.getApplicationContext().getBean("heartbeatProcessor"); |
||||
|
break; |
||||
|
case SINGLE_MESSAGE: |
||||
|
processor = (MessageProcessor)SpringContextHolder.getApplicationContext().getBean("singleMessageProcessor"); |
||||
|
break; |
||||
|
default: |
||||
|
break; |
||||
|
} |
||||
|
return processor; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
package com.lx.implatform.imserver.websocket.processor; |
||||
|
|
||||
|
import cn.hutool.core.bean.BeanUtil; |
||||
|
import com.lx.common.contant.RedisKey; |
||||
|
import com.lx.common.enums.WSCmdEnum; |
||||
|
import com.lx.common.model.im.SendInfo; |
||||
|
import com.lx.common.model.im.SingleMessageInfo; |
||||
|
import io.netty.channel.ChannelHandlerContext; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.data.redis.core.RedisTemplate; |
||||
|
import org.springframework.scheduling.annotation.Async; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import java.util.HashMap; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Component |
||||
|
public class SingleMessageProcessor implements MessageProcessor<SingleMessageInfo> { |
||||
|
|
||||
|
@Autowired |
||||
|
private RedisTemplate<String,Object> redisTemplate; |
||||
|
|
||||
|
@Async |
||||
|
@Override |
||||
|
public void process(ChannelHandlerContext ctx, SingleMessageInfo data) { |
||||
|
log.info("接收到消息,发送者:{},接收者:{},内容:{}",data.getSendUserId(),data.getRecvUserId(),data.getContent()); |
||||
|
// 推送消息到用户
|
||||
|
SendInfo sendInfo = new SendInfo(); |
||||
|
sendInfo.setCmd(WSCmdEnum.SINGLE_MESSAGE.getCode()); |
||||
|
sendInfo.setData(data); |
||||
|
ctx.channel().writeAndFlush(sendInfo); |
||||
|
|
||||
|
// 已读消息推送至redis,等待更新数据库
|
||||
|
String key = RedisKey.IM_ALREADY_READED_MESSAGE; |
||||
|
redisTemplate.opsForList().rightPush(key,data.getId()); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public SingleMessageInfo transform(Object o) { |
||||
|
HashMap map = (HashMap)o; |
||||
|
SingleMessageInfo info = BeanUtil.fillBeanWithMap(map, new SingleMessageInfo(), false); |
||||
|
return info; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
server: |
||||
|
port: 8877 |
||||
|
|
||||
|
websocket: |
||||
|
port: 8878 |
||||
|
|
||||
|
spring: |
||||
|
redis: |
||||
|
host: 127.0.0.1 |
||||
|
port: 6379 |
||||
|
database: 1 |
||||
@ -0,0 +1,159 @@ |
|||||
|
<?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>lx-im</artifactId> |
||||
|
<groupId>com.lx</groupId> |
||||
|
<packaging>pom</packaging> |
||||
|
<version>${im.version}</version> |
||||
|
|
||||
|
<modules> |
||||
|
<module>im-platform</module> |
||||
|
<module>im-server</module> |
||||
|
<module>commom</module> |
||||
|
</modules> |
||||
|
|
||||
|
<dependencies> |
||||
|
<dependency> |
||||
|
<groupId>org.projectlombok</groupId> |
||||
|
<artifactId>lombok</artifactId> |
||||
|
<version>1.18.16</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>cn.hutool</groupId> |
||||
|
<artifactId>hutool-all</artifactId> |
||||
|
</dependency> |
||||
|
<!--FastJson--> |
||||
|
<dependency> |
||||
|
<groupId>com.alibaba</groupId> |
||||
|
<artifactId>fastjson</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.apache.commons</groupId> |
||||
|
<artifactId>commons-lang3</artifactId> |
||||
|
</dependency> |
||||
|
</dependencies> |
||||
|
|
||||
|
<properties> |
||||
|
<im.version>1.0.0</im.version> |
||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
||||
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> |
||||
|
<java.version>1.8</java.version> |
||||
|
<mybatisplus-spring-boot-starter.version>3.3.2</mybatisplus-spring-boot-starter.version> |
||||
|
<fastjson.version>1.2.40</fastjson.version> |
||||
|
<velocity.version>1.7</velocity.version> |
||||
|
<ehcache.version>3.3.1</ehcache.version> |
||||
|
<swagger.version>2.7.0</swagger.version> |
||||
|
<hutool.version>5.3.9</hutool.version> |
||||
|
<druid.version>1.1.22</druid.version> |
||||
|
<alipay.version>3.7.110.ALL</alipay.version> |
||||
|
<zxing.version>3.3.3</zxing.version> |
||||
|
<commons-lang3.version>3.8.1</commons-lang3.version> |
||||
|
<lombok.version>1.18.16</lombok.version> |
||||
|
</properties> |
||||
|
|
||||
|
|
||||
|
<dependencyManagement> |
||||
|
<dependencies> |
||||
|
<dependency> |
||||
|
<!-- Import dependency management from Spring Boot --> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-dependencies</artifactId> |
||||
|
<version>2.0.3.RELEASE</version> |
||||
|
<type>pom</type> |
||||
|
<scope>import</scope> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.projectlombok</groupId> |
||||
|
<artifactId>lombok</artifactId> |
||||
|
<version>${lombok.version}</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>com.baomidou</groupId> |
||||
|
<artifactId>mybatis-plus-boot-starter</artifactId> |
||||
|
<version>${mybatisplus-spring-boot-starter.version}</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.apache.commons</groupId> |
||||
|
<artifactId>commons-lang3</artifactId> |
||||
|
<version>${commons-lang3.version}</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>com.alibaba</groupId> |
||||
|
<artifactId>fastjson</artifactId> |
||||
|
<version>${fastjson.version}</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>com.alibaba</groupId> |
||||
|
<artifactId>druid</artifactId> |
||||
|
<version>${druid.version}</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.apache.velocity</groupId> |
||||
|
<artifactId>velocity</artifactId> |
||||
|
<version>${velocity.version}</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>io.springfox</groupId> |
||||
|
<artifactId>springfox-swagger2</artifactId> |
||||
|
<version>${swagger.version}</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>io.springfox</groupId> |
||||
|
<artifactId>springfox-swagger-ui</artifactId> |
||||
|
<version>${swagger.version}</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>cn.hutool</groupId> |
||||
|
<artifactId>hutool-all</artifactId> |
||||
|
<version>${hutool.version}</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.apache.poi</groupId> |
||||
|
<artifactId>poi-ooxml</artifactId> |
||||
|
<version>4.1.2</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>commons-io</groupId> |
||||
|
<artifactId>commons-io</artifactId> |
||||
|
<version>2.6</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>dom4j</groupId> |
||||
|
<artifactId>dom4j</artifactId> |
||||
|
<version>1.6.1</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.codehaus.jackson</groupId> |
||||
|
<artifactId>jackson-mapper-asl</artifactId> |
||||
|
<version>1.9.13</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>javax.interceptor</groupId> |
||||
|
<artifactId>javax.interceptor-api</artifactId> |
||||
|
<version>1.2</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.security</groupId> |
||||
|
<artifactId>spring-security-jwt</artifactId> |
||||
|
<version>1.0.10.RELEASE</version> |
||||
|
</dependency> |
||||
|
|
||||
|
</dependencies> |
||||
|
|
||||
|
</dependencyManagement> |
||||
|
|
||||
|
|
||||
|
<repositories> |
||||
|
<repository> |
||||
|
<id>aliyun-maven</id> |
||||
|
<name>aliyun maven</name> |
||||
|
<url>http://maven.aliyun.com/nexus/content/groups/public/</url> |
||||
|
<snapshots> |
||||
|
<enabled>false</enabled> |
||||
|
</snapshots> |
||||
|
</repository> |
||||
|
</repositories> |
||||
|
</project> |
||||
Loading…
Reference in new issue