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