diff --git a/.gitignore b/.gitignore index 9154f4c..32de00b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,26 +1,34 @@ -# ---> Java -# Compiled class file -*.class +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ -# Log file -*.log +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache -# BlueJ files -*.ctxt +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* -replay_pid* +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ +### VS Code ### +.vscode/ +/.mvn/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..a98cca4 --- /dev/null +++ b/pom.xml @@ -0,0 +1,124 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.1 + + + com.dx.easychat + EasyChatting + 0.0.1-SNAPSHOT + war + EasyChatting + EasyChatting + + + + + + + + + + + + + + + 21 + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-websocket + + + + com.alibaba.fastjson2 + fastjson2 + 2.0.51 + + + + com.mysql + mysql-connector-j + runtime + + + + + com.baomidou + mybatis-plus-spring-boot3-starter + 3.5.5 + + + + + com.baomidou + mybatis-plus-generator + 3.5.7 + + + + org.freemarker + freemarker + compile + + + + + + org.apache.commons + commons-lang3 + 3.14.0 + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-tomcat + provided + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/src/main/java/com/dx/easychat/easychatting/CustomerApplicationRunner.java b/src/main/java/com/dx/easychat/easychatting/CustomerApplicationRunner.java new file mode 100644 index 0000000..5379e0b --- /dev/null +++ b/src/main/java/com/dx/easychat/easychatting/CustomerApplicationRunner.java @@ -0,0 +1,16 @@ +package com.dx.easychat.easychatting; + +import lombok.extern.log4j.Log4j2; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; + +@Log4j2 +@Component +public class CustomerApplicationRunner implements ApplicationRunner { + + @Override + public void run(ApplicationArguments args) throws Exception { + log.info("启动netty"); + } +} diff --git a/src/main/java/com/dx/easychat/easychatting/EasyChattingApplication.java b/src/main/java/com/dx/easychat/easychatting/EasyChattingApplication.java new file mode 100644 index 0000000..00b84b1 --- /dev/null +++ b/src/main/java/com/dx/easychat/easychatting/EasyChattingApplication.java @@ -0,0 +1,15 @@ +package com.dx.easychat.easychatting; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; + +@SpringBootApplication +@EnableScheduling +public class EasyChattingApplication { + + public static void main(String[] args) { + SpringApplication.run(EasyChattingApplication.class, args); + } + +} diff --git a/src/main/java/com/dx/easychat/easychatting/ServletInitializer.java b/src/main/java/com/dx/easychat/easychatting/ServletInitializer.java new file mode 100644 index 0000000..7d996f4 --- /dev/null +++ b/src/main/java/com/dx/easychat/easychatting/ServletInitializer.java @@ -0,0 +1,13 @@ +package com.dx.easychat.easychatting; + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +public class ServletInitializer extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(EasyChattingApplication.class); + } + +} diff --git a/src/main/java/com/dx/easychat/easychatting/channel/ReceiveChannel.java b/src/main/java/com/dx/easychat/easychatting/channel/ReceiveChannel.java new file mode 100644 index 0000000..2e00839 --- /dev/null +++ b/src/main/java/com/dx/easychat/easychatting/channel/ReceiveChannel.java @@ -0,0 +1,56 @@ +package com.dx.easychat.easychatting.channel; + + +import jakarta.websocket.*; +import jakarta.websocket.server.ServerEndpoint; +import lombok.extern.log4j.Log4j2; + +import java.io.IOException; +import java.time.Instant; + +@Log4j2 +@ServerEndpoint(value = "/channel/echo") +public class ReceiveChannel { + + private Session session; + + // 收到消息 + @OnMessage + public void onMessage(String message) throws IOException { + + log.info("[websocket] 收到消息:id={},message={}", this.session.getId(), message); + + if (message.equalsIgnoreCase("bye")) { + // 由服务器主动关闭连接。状态码为 NORMAL_CLOSURE(正常关闭)。 + this.session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "Bye"));; + return; + } + + + this.session.getAsyncRemote().sendText("["+ Instant.now().toEpochMilli() +"] Hello " + message); + } + + // 连接打开 + @OnOpen + public void onOpen(Session session, EndpointConfig endpointConfig){ + // 保存 session 到对象 + this.session = session; + log.info("[websocket] 新的连接:id={}", this.session.getId()); + } + + // 连接关闭 + @OnClose + public void onClose(CloseReason closeReason){ + log.info("[websocket] 连接断开:id={},reason={}", this.session.getId(),closeReason); + } + + // 连接异常 + @OnError + public void onError(Throwable throwable) throws IOException { + + log.info("[websocket] 连接异常:id={},throwable={}", this.session.getId(), throwable.getMessage()); + + // 关闭连接。状态码为 UNEXPECTED_CONDITION(意料之外的异常) + this.session.close(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, throwable.getMessage())); + } +} diff --git a/src/main/java/com/dx/easychat/easychatting/configuration/WebSocketConfiguration.java b/src/main/java/com/dx/easychat/easychatting/configuration/WebSocketConfiguration.java new file mode 100644 index 0000000..1a30790 --- /dev/null +++ b/src/main/java/com/dx/easychat/easychatting/configuration/WebSocketConfiguration.java @@ -0,0 +1,25 @@ +package com.dx.easychat.easychatting.configuration; + +import com.dx.easychat.easychatting.handler.WebSocketReceiveHandler; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; +import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor; + + +@Configuration +@EnableWebSocket +public class WebSocketConfiguration implements WebSocketConfigurer { + + @Resource + private WebSocketReceiveHandler webSocketReceiveHandler; + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + registry.addHandler( webSocketReceiveHandler, "/websocket/message") + .setAllowedOrigins("*") + .addInterceptors(new HttpSessionHandshakeInterceptor()); + } +} diff --git a/src/main/java/com/dx/easychat/easychatting/controller/AutoGeneratorController.java b/src/main/java/com/dx/easychat/easychatting/controller/AutoGeneratorController.java new file mode 100644 index 0000000..04c9e5d --- /dev/null +++ b/src/main/java/com/dx/easychat/easychatting/controller/AutoGeneratorController.java @@ -0,0 +1,59 @@ +package com.dx.easychat.easychatting.controller; + + +import com.alibaba.fastjson2.JSONObject; +import com.baomidou.mybatisplus.generator.FastAutoGenerator; +import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; +import jakarta.annotation.Resource; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/code") +@Log4j2 +public class AutoGeneratorController { + + @Resource + private DataSourceProperties dataSourceProperties; + + @Value("${spring.customer.config.file-path:/opt/file_temp/}") + private String filePath; + + @GetMapping("/generator") + @ResponseBody + public JSONObject generatorCode(@RequestParam String tableName) { + JSONObject jsonObject = new JSONObject(); + String url = dataSourceProperties.getUrl(); + String username = dataSourceProperties.getUsername(); + String password = dataSourceProperties.getPassword(); + FastAutoGenerator.create(url, username, password) + .globalConfig(builder -> builder + .author("xu.x") + .outputDir(filePath) + .disableOpenDir() + .commentDate("yyyy-MM-dd") + ) + .packageConfig(builder -> builder + .parent("com.dx.easychat.easychatting") + .entity("entity") + .mapper("mapper") + .service("service") + .serviceImpl("service.impl") + .xml("mapper.xml") + ) + .strategyConfig(builder -> + builder.addInclude(tableName) // 设置需要生成的表名 + .addTablePrefix("db_") // 设置过滤表前缀 + .entityBuilder() + .enableLombok() + ) + .templateEngine(new FreemarkerTemplateEngine()) + .execute(); + jsonObject.put("code", "success"); + jsonObject.put("data", null); + return jsonObject; + } + +} diff --git a/src/main/java/com/dx/easychat/easychatting/controller/FileController.java b/src/main/java/com/dx/easychat/easychatting/controller/FileController.java new file mode 100644 index 0000000..ba5dfad --- /dev/null +++ b/src/main/java/com/dx/easychat/easychatting/controller/FileController.java @@ -0,0 +1,109 @@ +package com.dx.easychat.easychatting.controller; + +import com.alibaba.fastjson2.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.dx.easychat.easychatting.entity.FileEntity; +import com.dx.easychat.easychatting.service.FileEntityService; +import jakarta.annotation.Resource; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Date; +import java.util.UUID; + +@RestController +@Log4j2 +@RequestMapping("/api/file") +public class FileController { + + // TODO 文件上传同名问题 + // TODO 删除消息时 同步删除物理文件 + @Value("${spring.customer.config.file-path:/opt/file_temp/}") + private String filePath; + + @Resource + private FileEntityService fileEntityService; + + @PostMapping("/upload") + @ResponseBody + public String uploadImageFile(@RequestParam("file") MultipartFile multipartFile) throws IOException { + JSONObject jsonObject = new JSONObject(); + //获取文件名 + String fileName = multipartFile.getOriginalFilename(); + //获取文件后缀名 + assert fileName != null; + String contentType = multipartFile.getContentType(); + String fileType = ""; + int index = fileName.lastIndexOf("."); + if (index != -1) { + fileType = fileName.substring(index + 1); + } + FileEntity fileEntity = new FileEntity(); + String uuid = UUID.randomUUID().toString(); + fileEntity.setUuid(uuid); + fileEntity.setFileName(fileName); + String realFilePath = filePath + uuid; + fileEntity.setFilePath(realFilePath); + fileEntity.setUploadTime(new Date()); + fileEntity.setFileMimeType(contentType); + fileEntity.setFileType(fileType); + log.info("文件名:{}", fileName); + log.info("filePath:{}", filePath); + File uploadFile = new File(realFilePath); + //将临时文件转存到指定磁盘位置 + multipartFile.transferTo(uploadFile); + fileEntityService.save(fileEntity); + jsonObject.put("code", 200); + jsonObject.put("uuid", uuid); + jsonObject.put("fileType", fileType); + jsonObject.put("fileMimeType", contentType); + return jsonObject.toJSONString(); + } + + /** + * 下载文件:将输入流中的数据循环写入到响应输出流中,而不是一次性读取到内存 + * + * @param fileId 文档id + * @param response 返回体 + */ + @GetMapping("/{fileId}") + public void downLoadFile(HttpServletResponse response, @PathVariable String fileId) throws IOException { + response.setContentType("text/json;charset=utf-8"); + PrintWriter writer = response.getWriter(); + try { + // 读到流中 + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(FileEntity::getUuid, fileId); + FileEntity fileEntity = fileEntityService.getOne(lambdaQueryWrapper); + log.info("下载时的查询:{}", fileEntity); + InputStream inputStream = Files.newInputStream(Paths.get(fileEntity.getFilePath())); + String fileName = fileEntity.getFileName(); + response.reset(); + response.setContentType("application/octet-stream"); + response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8)); + ServletOutputStream outputStream = response.getOutputStream(); + byte[] b = new byte[1024]; + int len; + //从输入流中读取一定数量的字节,并将其存储在缓冲区字节数组中,读到末尾返回-1 + while ((len = inputStream.read(b)) > 0) { + outputStream.write(b, 0, len); + } + inputStream.close(); + } catch (Exception e) { + log.error("下载文件异常:{}", e.getMessage()); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("code",500); + jsonObject.put("message",e.getMessage()); + writer.write(jsonObject.toJSONString()); + } + } +} diff --git a/src/main/java/com/dx/easychat/easychatting/controller/MessageController.java b/src/main/java/com/dx/easychat/easychatting/controller/MessageController.java new file mode 100644 index 0000000..a195e49 --- /dev/null +++ b/src/main/java/com/dx/easychat/easychatting/controller/MessageController.java @@ -0,0 +1,91 @@ +package com.dx.easychat.easychatting.controller; + +import com.alibaba.fastjson2.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.dx.easychat.easychatting.entity.FileEntity; +import com.dx.easychat.easychatting.entity.Message; +import com.dx.easychat.easychatting.service.FileEntityService; +import com.dx.easychat.easychatting.service.MessageService; +import jakarta.annotation.Resource; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController() +@Log4j2 +@RequestMapping("/api/message") +public class MessageController { + + @Resource + private MessageService messageService; + + @Resource + private FileEntityService fileEntityService; + + @PostMapping("/config") + @ResponseBody + public JSONObject getConfig(@RequestBody JSONObject jsonObject) { + JSONObject result = new JSONObject(); + String string = jsonObject.getString("id"); + log.info(string); + int length = string.length(); + log.info(length); + log.info(jsonObject); + return result; + } + + // todo 增加校验webservice的session + @GetMapping("/get") + @ResponseBody + public JSONObject getHistoryMessage() { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("code", 200); + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(Message::getIsDelete, 0); + List list = messageService.list(lambdaQueryWrapper); + for (Message message : list) { + if ("file".equals(message.getType())) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FileEntity::getUuid, message.getFile()); + FileEntity fileEntity = fileEntityService.getOne(queryWrapper); + if (fileEntity != null) { + String fileMimeType = fileEntity.getFileMimeType(); + message.setFileMimeType(fileMimeType); + message.setFileName(fileEntity.getFileName()); + } + } + } + jsonObject.put("data", list); + return jsonObject; + } + + @GetMapping("/message") + @ResponseBody + public JSONObject getMessage(String message_id) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("code", 200); + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(Message::getIsDelete, 0); + if (StringUtils.isNotBlank(message_id)) { + lambdaQueryWrapper.and(wrapper -> + wrapper.eq(Message::getId, message_id).or() + .eq(Message::getId, 1)); + } + List list = messageService.list(lambdaQueryWrapper); + for (Message message : list) { + if ("file".equals(message.getType())) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FileEntity::getUuid, message.getFile()); + List fileEntityList = fileEntityService.list(queryWrapper); + if (fileEntityList != null) { + message.setFileEntityList(fileEntityList); + } + } + } + jsonObject.put("data", list); + return jsonObject; + } + +} diff --git a/src/main/java/com/dx/easychat/easychatting/cron/DeleteFileCronJob.java b/src/main/java/com/dx/easychat/easychatting/cron/DeleteFileCronJob.java new file mode 100644 index 0000000..5369d86 --- /dev/null +++ b/src/main/java/com/dx/easychat/easychatting/cron/DeleteFileCronJob.java @@ -0,0 +1,34 @@ +package com.dx.easychat.easychatting.cron; + +import com.dx.easychat.easychatting.mapper.FileEntityMapper; +import jakarta.annotation.Resource; +import lombok.extern.log4j.Log4j2; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.util.List; + +/** + * 定时任务:每五分钟执行一次 + * 查询出已经到达过期时间的数据,删除物理文件同时更新回表单 + */ +@Component +@Log4j2 +public class DeleteFileCronJob { + + @Resource + FileEntityMapper fileEntityMapper; + + @Scheduled(cron ="0 0/5 * * * ?") + public void executeCronJob() { + List allDeleteFilePath = fileEntityMapper.findAllDeleteFilePath(); + for (String filePath : allDeleteFilePath) { + File file = new File(filePath); + if (file.exists()) { + file.delete(); + } + } + } + +} diff --git a/src/main/java/com/dx/easychat/easychatting/entity/FileEntity.java b/src/main/java/com/dx/easychat/easychatting/entity/FileEntity.java new file mode 100644 index 0000000..0cdc7f2 --- /dev/null +++ b/src/main/java/com/dx/easychat/easychatting/entity/FileEntity.java @@ -0,0 +1,38 @@ +package com.dx.easychat.easychatting.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.extern.log4j.Log4j2; + +import java.util.Date; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@ToString +@Log4j2 +@TableName("db_file") +public class FileEntity { + + @TableId(type = IdType.AUTO) + private Integer id; + + private String uuid; + + private String fileName; + + private String fileType; + + private String fileMimeType; + + private String filePath; + + private Integer isDelete; + + private Date uploadTime; +} diff --git a/src/main/java/com/dx/easychat/easychatting/entity/Message.java b/src/main/java/com/dx/easychat/easychatting/entity/Message.java new file mode 100644 index 0000000..963324d --- /dev/null +++ b/src/main/java/com/dx/easychat/easychatting/entity/Message.java @@ -0,0 +1,56 @@ +package com.dx.easychat.easychatting.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.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.extern.log4j.Log4j2; + +import java.util.Date; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@ToString +@Log4j2 +@TableName("db_message") +public class Message { + + @TableId(type = IdType.AUTO) + private Integer id; + + private String content; + + private Date time; + + private String type; + + private String file; + + private Integer isDelete; + + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) + private String ip; + + @TableField(exist = false) + private String sessionId; + + @TableField(exist = false) + private String messageType; + + @TableField(exist = false) + private String fileName; + + @TableField(exist = false) + private String fileMimeType; + + @TableField(exist = false) + private List fileEntityList; + +} diff --git a/src/main/java/com/dx/easychat/easychatting/entity/vo/request/SendOfferEntity.java b/src/main/java/com/dx/easychat/easychatting/entity/vo/request/SendOfferEntity.java new file mode 100644 index 0000000..bbbb1a5 --- /dev/null +++ b/src/main/java/com/dx/easychat/easychatting/entity/vo/request/SendOfferEntity.java @@ -0,0 +1,79 @@ +package com.dx.easychat.easychatting.entity.vo.request; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +/** + * 前端请求的发送offer参数实体类 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class SendOfferEntity { + + /** + * 是否快速入职 + */ + private Integer isFastIn; + + /** + * 是否快速入职 + */ + private Integer isUploadMedicalReport; + + /** + * 是否短信通知 + */ + private Boolean isMessageNotice; + + /** + * 短信内容 + */ + private String messageContent; + + /** + * 是否邮件通知 + */ + private Boolean isEmailNotice; + + /** + * 邮件标题 + */ + private String emailTitle; + + /** + * 邮件内容 + */ + private String emailContent; + + /** + * 选中的行ID 以逗号分隔 + */ + private String ids; + + /** + * 入职须知(文件上传) + */ + private List file; + + + @Override + public String toString() { + return "SendOfferEntity{" + + "isFastIn=" + isFastIn + + ", \nisUploadMedicalReport=" + isUploadMedicalReport + + ", \nisMessageNotice=" + isMessageNotice + + ", \nmessageContent='" + messageContent + '\'' + + ", \nisEmailNotice=" + isEmailNotice + + ", \nemailTitle='" + emailTitle + '\'' + + ", \nemailContent='" + emailContent + '\'' + + ", \nids='" + ids + '\'' + + ", \nfile=" + file + + '}'; + } +} diff --git a/src/main/java/com/dx/easychat/easychatting/filter/CorsCustomerFilter.java b/src/main/java/com/dx/easychat/easychatting/filter/CorsCustomerFilter.java new file mode 100644 index 0000000..eae4ee7 --- /dev/null +++ b/src/main/java/com/dx/easychat/easychatting/filter/CorsCustomerFilter.java @@ -0,0 +1,55 @@ +package com.dx.easychat.easychatting.filter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpFilter; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +@Component +@Order(-102) +@Log4j2 +public class CorsCustomerFilter extends HttpFilter { + + @Value("${spring.security.cors.origin:http://localhost:5173,http://127.0.0.1:5173}") + private String allowOrigin; + + @Override + protected void doFilter(HttpServletRequest request, + HttpServletResponse response, + FilterChain chain) throws IOException, ServletException { + response.addHeader("Access-Control-Allow-Origin",this.resolveOrigin(request)); + response.addHeader("Access-Control-Allow-Methods","GET, POST, PUT, DELETE, OPTIONS"); + // response.addHeader("Access-Control-Allow-Headers","Authorization, Content-Type"); + response.addHeader("Access-Control-Allow-Headers","*"); + chain.doFilter(request, response); + } + + private void addCorsHeader(HttpServletRequest request, + HttpServletResponse response) { + response.addHeader("Access-Control-Allow-Origin",allowOrigin); + response.addHeader("Access-Control-Allow-Methods","GET, POST, PUT, DELETE, OPTIONS"); + response.addHeader("Access-Control-Allow-Headers","Authorization, Content-Type"); + } + + /** + * 解析配置文件中的请求原始站点 + * @param request 请求 + * @return 解析得到的请求头值 + */ + private String resolveOrigin(HttpServletRequest request){ + List list = Arrays.asList(allowOrigin.split(",")); + String origin = request.getHeader("Origin"); + if (list.isEmpty()) return "http://localhost:5173"; + if ("*".equals(allowOrigin) || list.contains(origin)) return origin; + else return list.getFirst(); + } +} diff --git a/src/main/java/com/dx/easychat/easychatting/handler/WebSocketReceiveHandler.java b/src/main/java/com/dx/easychat/easychatting/handler/WebSocketReceiveHandler.java new file mode 100644 index 0000000..7900799 --- /dev/null +++ b/src/main/java/com/dx/easychat/easychatting/handler/WebSocketReceiveHandler.java @@ -0,0 +1,147 @@ +package com.dx.easychat.easychatting.handler; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.dx.easychat.easychatting.entity.FileEntity; +import com.dx.easychat.easychatting.entity.Message; +import com.dx.easychat.easychatting.service.FileEntityService; +import com.dx.easychat.easychatting.service.impl.MessageServiceImpl; +import jakarta.annotation.Resource; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.*; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import java.io.File; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +@Log4j2 +@Component +public class WebSocketReceiveHandler extends TextWebSocketHandler{ + + @Resource + private MessageServiceImpl messageService; + + @Resource + private FileEntityService fileEntityService; + + public static ConcurrentHashMap SessionsPool = new ConcurrentHashMap<>(); + + @Override + public void afterConnectionEstablished(WebSocketSession session) throws Exception { + log.info("建立ws链接: id={}", session.getId()); + SessionsPool.put(session.getId(), session); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("id", session.getId()); + jsonObject.put("messageType", "heartbeat"); + jsonObject.put("status", true); + jsonObject.put("onlineNumber", SessionsPool.size()); + session.sendMessage(new TextMessage(jsonObject.toJSONString())); + super.afterConnectionEstablished(session); + } + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + String payload = message.getPayload(); + InetSocketAddress remoteAddress = session.getRemoteAddress(); + log.info("接收到消息:id={},message={},ip={}", session.getId(), payload, remoteAddress); + boolean valid = JSON.isValid(payload); + if (valid) { + Message messageEntity = JSONObject.parseObject(payload, Message.class); + log.info("转换实体类:{}", messageEntity); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("id", session.getId()); + jsonObject.put("messageType", "heartbeat"); + jsonObject.put("status", true); + jsonObject.put("onlineNumber", SessionsPool.size()); + if (StringUtils.isNoneEmpty(messageEntity.getSessionId()) && SessionsPool.containsKey(session.getId())) { + switch (messageEntity.getMessageType()) { + case "delete": + messageService.updateById(messageEntity); + if ("file".equals(messageEntity.getType())){ + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(FileEntity::getUuid, messageEntity.getFile()); + FileEntity fileEntity = fileEntityService.getOne(lambdaQueryWrapper); + if (fileEntity != null) { + File file = new File(fileEntity.getFilePath()); + if (file.exists()) { + file.delete(); + } + fileEntityService.removeById(fileEntity.getId()); + } + } + break; + case "heartbeat": + session.sendMessage(new TextMessage(jsonObject.toJSONString())); + break; + case "send": + Message messageDto = new Message(); + BeanUtils.copyProperties(messageEntity, messageDto); + messageDto.setIp(Objects.requireNonNull(session.getRemoteAddress()).getAddress().getHostAddress()); + messageService.save(messageDto); + if ("file".equals(messageEntity.getType())){ + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(FileEntity::getUuid, messageEntity.getFile()); + FileEntity fileEntity = fileEntityService.getOne(lambdaQueryWrapper); + String fileMimeType = fileEntity.getFileMimeType(); + messageEntity.setFileMimeType(fileMimeType); + messageEntity.setFileName(fileEntity.getFileName()); + } + messageEntity.setMessageType("receive"); + SessionsPool.forEach((key, value) -> { + try { + value.sendMessage(new TextMessage(JSONObject.toJSONString(messageEntity))); + } catch (IOException e) { + log.info("广播消息:{}", e.getMessage()); + } + }); + break; + default: + log.error("未知类型数据"); + } + }else { + log.error("请求sessionID不存在,重新获取session"); + jsonObject.put("status", false); + jsonObject.put("message", "请求sessionID不存在,重新下发session"); + session.sendMessage(new TextMessage(jsonObject.toJSONString())); + } + }else { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("code", -1); + jsonObject.put("message", "请求错误"); + jsonObject.put("time", System.currentTimeMillis()); + session.sendMessage(new TextMessage(jsonObject.toJSONString())); + } + super.handleTextMessage(session, message); + } + + @Override + protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) { + log.info("接收到二进制消息::id={},message={}", session.getId(), message.getPayload()); + super.handleBinaryMessage(session, message); + } + + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { + log.error("出现异常:id={},message={}", session.getId(), exception.getMessage()); + super.handleTransportError(session, exception); + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { + String sessionId = session.getId(); + log.info("断开链接:id={},status={}", sessionId, status); + WebSocketSession remove = SessionsPool.remove(sessionId); + if (remove != null) { + remove.close(); + } + super.afterConnectionClosed(session, status); + } + +} diff --git a/src/main/java/com/dx/easychat/easychatting/mapper/FileEntityMapper.java b/src/main/java/com/dx/easychat/easychatting/mapper/FileEntityMapper.java new file mode 100644 index 0000000..fa589d0 --- /dev/null +++ b/src/main/java/com/dx/easychat/easychatting/mapper/FileEntityMapper.java @@ -0,0 +1,16 @@ +package com.dx.easychat.easychatting.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.dx.easychat.easychatting.entity.FileEntity; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +@Mapper +public interface FileEntityMapper extends BaseMapper { + + @Select("SELECT file_path FROM db_message t1 LEFT JOIN db_file t2 on t1.file = t2.uuid WHERE type = 'file' AND t1.is_delete = 1") + List findAllDeleteFilePath(); + +} diff --git a/src/main/java/com/dx/easychat/easychatting/mapper/MessageMapper.java b/src/main/java/com/dx/easychat/easychatting/mapper/MessageMapper.java new file mode 100644 index 0000000..c360929 --- /dev/null +++ b/src/main/java/com/dx/easychat/easychatting/mapper/MessageMapper.java @@ -0,0 +1,9 @@ +package com.dx.easychat.easychatting.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.dx.easychat.easychatting.entity.Message; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface MessageMapper extends BaseMapper { +} diff --git a/src/main/java/com/dx/easychat/easychatting/service/FileEntityService.java b/src/main/java/com/dx/easychat/easychatting/service/FileEntityService.java new file mode 100644 index 0000000..9e3499a --- /dev/null +++ b/src/main/java/com/dx/easychat/easychatting/service/FileEntityService.java @@ -0,0 +1,7 @@ +package com.dx.easychat.easychatting.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.dx.easychat.easychatting.entity.FileEntity; + +public interface FileEntityService extends IService { +} diff --git a/src/main/java/com/dx/easychat/easychatting/service/MessageService.java b/src/main/java/com/dx/easychat/easychatting/service/MessageService.java new file mode 100644 index 0000000..eec8da1 --- /dev/null +++ b/src/main/java/com/dx/easychat/easychatting/service/MessageService.java @@ -0,0 +1,7 @@ +package com.dx.easychat.easychatting.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.dx.easychat.easychatting.entity.Message; + +public interface MessageService extends IService { +} diff --git a/src/main/java/com/dx/easychat/easychatting/service/impl/FileEntityServiceImpl.java b/src/main/java/com/dx/easychat/easychatting/service/impl/FileEntityServiceImpl.java new file mode 100644 index 0000000..20c6508 --- /dev/null +++ b/src/main/java/com/dx/easychat/easychatting/service/impl/FileEntityServiceImpl.java @@ -0,0 +1,12 @@ +package com.dx.easychat.easychatting.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.dx.easychat.easychatting.entity.FileEntity; +import com.dx.easychat.easychatting.mapper.FileEntityMapper; +import com.dx.easychat.easychatting.service.FileEntityService; +import org.springframework.stereotype.Service; + +@Service +public class FileEntityServiceImpl extends ServiceImpl implements FileEntityService { + +} diff --git a/src/main/java/com/dx/easychat/easychatting/service/impl/MessageServiceImpl.java b/src/main/java/com/dx/easychat/easychatting/service/impl/MessageServiceImpl.java new file mode 100644 index 0000000..6f92bdc --- /dev/null +++ b/src/main/java/com/dx/easychat/easychatting/service/impl/MessageServiceImpl.java @@ -0,0 +1,12 @@ +package com.dx.easychat.easychatting.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.dx.easychat.easychatting.entity.Message; +import com.dx.easychat.easychatting.mapper.MessageMapper; +import com.dx.easychat.easychatting.service.MessageService; +import org.springframework.stereotype.Service; + +@Service +public class MessageServiceImpl extends ServiceImpl implements MessageService { + +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..6c28edc --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,31 @@ +spring: + servlet: + multipart: + enabled: true + max-file-size: 20MB + max-request-size: 20MB + security: + filter: + order: -100 + cors: + origin: http://127.0.0.1:5173,http://localhost:5173 + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/project + username: root + password: XXdove521.. + customer: + config: + file-path: /Users/xx/Dev/file/ + + +mybatis-plus: + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + call-setters-on-nulls: false +logging: + file: + path: ./ + name: spring.log + level: + com.dx.easychat.easychatting.service: info \ No newline at end of file diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 0000000..48a47b5 --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,30 @@ +spring: + servlet: + multipart: + enabled: true + max-file-size: 20MB + max-request-size: 20MB + security: + filter: + order: -100 + cors: + origin: http://127.0.0.1:5173,http://localhost:5173 + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + # 生产环境 + url: jdbc:mysql://127.0.0.1:3306/project + username: xiaoxu + password: xxdove521 + customer: + config: + file-path: /opt/easychatting/file_temp +mybatis-plus: + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + call-setters-on-nulls: false +logging: + file: + path: ./ + name: spring.log +server: + port: 8020 \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..686b110 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,3 @@ +spring: + profiles: + active: 'dev' \ No newline at end of file diff --git a/src/test/java/com/dx/easychat/easychatting/EasyChattingApplicationTests.java b/src/test/java/com/dx/easychat/easychatting/EasyChattingApplicationTests.java new file mode 100644 index 0000000..b405b0e --- /dev/null +++ b/src/test/java/com/dx/easychat/easychatting/EasyChattingApplicationTests.java @@ -0,0 +1,13 @@ +package com.dx.easychat.easychatting; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class EasyChattingApplicationTests { + + @Test + void contextLoads() { + } + +}