diff --git a/README.md b/README.md index 3fb3fba9e21b48a12b7fdcadb41883422e3a57f7..6ac928fcbc262d62c25d9f43f80bffad4e560444 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,71 @@ # Springboot-websocket -支持自动更新在线用户,点对点聊天,下线,上线等,最简易的IM -项目参考:https://gitee.com/shenzhanwang/Spring-websocket.git 简化流程 +过渡阶段IM -直接请求:[http://127.0.0.1:8080/lili/im/room?name=TOM](http://127.0.0.1:8080/lili/im/room?name=TOM) 用户TOM上线 +前端仓库:https://gitee.com/beijing_hongye_huicheng/im -接着请求:[http://127.0.0.1:8080/lili/im/room?name=JIMI](http://127.0.0.1:8080/lili/im/room?name=JIMI) 用户JIMI上线 +后端仓库:https://gitee.com/beijing_hongye_huicheng/springboot-websocket -可以自行根据name值写入自己的名次,跳转计入聊天室后自动可以开启聊天。 -![img.png](img.png) -![img_1.png](img_1.png) -![img_2.png](img_2.png) \ No newline at end of file +部署方式: +1、导入数据库,配置resource目录下application.yml的数据库以及redis配置文件。 + +2、maven打包 + +3、启动jar包即可。 + +4、前端程序运行,测试环境需配置根目录 .env.development文件,正式环境需配置 .env 文件。 + +# 参考项目 +项目前端参考:https://gitee.com/gzydong/LumenIM.git 功能更丰富,大家可以去学习一波 + + +# IM体验 +浏览器1打开地址:http://127.0.0.1:8000/message?token=eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyQ29udGV4dCI6IntcInVzZXJuYW1lXCI6XCIxMzAxMTExMTExMVwiLFwibmlja05hbWVcIjpcIuW8oOS4ieWTiOWTiOWTiFwiLFwiZmFjZVwiOlwiaHR0cHM6Ly9saWxpc2hvcC1vc3Mub3NzLWNuLWJlaWppbmcuYWxpeXVuY3MuY29tLzQ1ZDUyOGYwMjRjZTQyMzI4NzYxNjFhZmQxN2Y0ZWExLmpwZ1wiLFwiaWRcIjpcIjEzNzY0MTc2ODQxNDAzMjY5MTJcIixcImxvbmdUZXJtXCI6ZmFsc2UsXCJyb2xlXCI6XCJTVE9SRVwiLFwic3RvcmVJZFwiOlwiMTM3NjQzMzU2NTI0NzQ3MTYxNlwiLFwic3RvcmVOYW1lXCI6XCLlrrblrrbkuZBcIixcImlzU3VwZXJcIjpmYWxzZX0iLCJzdWIiOiIxMzAxMTExMTExMSIsImV4cCI6MTY0NTQ2OTk1M30.fXpCZ2YiFYqACpmxVvKjIpXovfPRDJavHjftpQzdEps + +浏览器2打开地址:http://127.0.0.1:8000/message?token=eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyQ29udGV4dCI6IntcInVzZXJuYW1lXCI6XCIxMzAxMTExMTExMVwiLFwibmlja05hbWVcIjpcIuW8oOS4ieWTiOWTiOWTiFwiLFwiZmFjZVwiOlwiaHR0cHM6Ly9saWxpc2hvcC1vc3Mub3NzLWNuLWJlaWppbmcuYWxpeXVuY3MuY29tLzk2N2UzMzU1Yzg0NTRiNGFhMTk1N2M1NTQ5ZTZiNzIwLnBuZ1wiLFwiaWRcIjpcIjEzNzY0MTc2ODQxNDAzMjY5MTJcIixcImxvbmdUZXJtXCI6ZmFsc2UsXCJyb2xlXCI6XCJNRU1CRVJcIixcImlzU3VwZXJcIjpmYWxzZX0iLCJzdWIiOiIxMzAxMTExMTExMSIsImV4cCI6MTY0NTQ2OTAyNn0.GEkGpKRKF3rqzHRhaPCFilPpWe37cIXTT4KnwWR4Bt0&id=1376433565247471616 + +即可进行聊天。 + +# NGINX配置事例 +线上NGINX配置在测试阶段出现问题,这里吧相关的配置贴出来供大家参考。 +```` + server { + listen 443 ssl; + ssl_certificate "/etc/nginx/ssl/pickmall.cn.pem"; + ssl_certificate_key "/etc/nginx/ssl/pickmall.cn.key"; + ssl_session_cache shared:SSL:1m; + ssl_session_timeout 10m; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + include /etc/nginx/default.d/*.conf; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + server_name im-api.pickmall.cn; + location / { + proxy_pass http://127.0.0.1:8088; + } + } + + server { + listen 443 ssl; + ssl_certificate "/etc/nginx/ssl/pickmall.cn.pem"; + ssl_certificate_key "/etc/nginx/ssl/pickmall.cn.key"; + ssl_session_cache shared:SSL:1m; + ssl_session_timeout 10m; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + include /etc/nginx/default.d/*.conf; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header REMOTE-HOST $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + server_name im.pickmall.cn; + try_files $uri $uri/ /index.html; + root /home/im/im/dist; + } +```` \ No newline at end of file diff --git a/pom.xml b/pom.xml index c64517accea634d0ba2207090de901f19604cd56..8c031ab213b28326aa2c22099e0d2651d2f9cc85 100644 --- a/pom.xml +++ b/pom.xml @@ -1,76 +1,231 @@ - + 4.0.0 - - org.springframework.boot spring-boot-starter-parent 2.4.10 - shenzhanwang - Spring-websocket + 0.0.1 + cn.lili + im + + + + 1.8 + 4.2.3 + registry.cn-beijing.aliyuncs.com/lili-images + 1 + 4.13.40.ALL + 5.1.48 + 3.4.3.4 + 5.7.16 + 2.0.3.RELEASE + 3.0.0 + 2.9.10 + 1.18.22 + 4.5.18 + 3.11.1 + 2.0.1 + 2.1.1 + 0.10.7 + 4.7.2 + 4.0.0 + 1.1.20 + 1.0.3 + 4.7.2 + 4.4.1 + 4.5.12 + UTF-8 + UTF-8 + true + 2.0.9 + 2.3.1 + 1.21 + 1.2 + 4.1.2 + 4.1.2 + 6.6 + 3.4.1 + 1.7.28 + 2.2.0 + 1.4 + 4.3 + 2.3.0 + 1.2.2 + 2.3.1 + 20211018.2 + + + + org.springframework.boot + spring-boot-starter-websocket + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus-version} + + + org.mybatis.spring.boot + mybatis-spring-boot-starter-test + 2.2.0 + + + + io.jsonwebtoken + jjwt-api + ${jwt-version} + + + io.jsonwebtoken + jjwt-impl + ${jwt-version} + runtime + + + io.jsonwebtoken + jjwt-jackson + ${jwt-version} + runtime + + + com.alibaba + fastjson + 1.2.68 + org.projectlombok lombok - 1.18.22 + ${lombok-version} + + + + org.apache.shardingsphere + sharding-jdbc-spring-boot-starter + ${sharding-jdbc-version} + + + groovy + org.codehaus.groovy + + + + + + + org.codehaus.groovy + groovy + + + org.apache.shardingsphere + sharding-jdbc-spring-namespace + ${sharding-jdbc-version} + + + + com.alibaba + druid + ${druid-version} + + + + org.springframework.data + spring-data-commons + + + mysql + mysql-connector-java + 8.0.17 + + + com.github.xiaoymin + knife4j-spring-boot-starter + ${knife4j.version} + + + + org.springframework.boot + spring-boot-starter-data-redis + + + com.google.code.gson + gson + 2.8.7 cn.hutool hutool-all - 5.7.16 + ${Hutool-version} + org.springframework.boot - spring-boot-starter-websocket + spring-boot-starter-validation + - org.springframework.boot - spring-boot-starter-web + org.springframework.security + spring-security-crypto - org.springframework.boot - spring-boot-starter - + spring-boot-starter-data-elasticsearch + - org.springframework.boot - spring-boot-starter-logging + HdrHistogram + org.hdrhistogram + + + slf4j-api + org.slf4j - org.springframework.boot - spring-boot-starter-thymeleaf + org.apache.commons + commons-pool2 - - com.alibaba - fastjson - 1.2.68 + org.springframework.boot + spring-boot-starter-aop - - - - jdk-1.8 - - true - 1.8 - - - 1.8 - 1.8 - 1.8 - - - - - UTF-8 - + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + central + aliyun maven + http://maven.aliyun.com/nexus/content/groups/public/ + default + + + true + + + + false + + + \ No newline at end of file diff --git a/sql/im.sql b/sql/im.sql new file mode 100644 index 0000000000000000000000000000000000000000..1e2d2bdaba31c0ea6b4cd6486c323b3bba923ce0 --- /dev/null +++ b/sql/im.sql @@ -0,0 +1,118 @@ +/* + Navicat Premium Data Transfer + + Source Server : 192.168.0.116 + Source Server Type : MySQL + Source Server Version : 80025 + Source Host : 192.168.0.116:3306 + Source Schema : lilishop + + Target Server Type : MySQL + Target Server Version : 80025 + File Encoding : 65001 + + Date: 21/02/2022 17:25:34 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for li_im_message +-- ---------------------------- +DROP TABLE IF EXISTS `li_im_message`; +CREATE TABLE `li_im_message` ( + `id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `from_user` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `to_user` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `is_read` bit(1) DEFAULT NULL, + `message_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `text` text COLLATE utf8mb4_unicode_ci, + `create_time` datetime DEFAULT NULL, + `talk_id` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ---------------------------- +-- Records of li_im_message +-- ---------------------------- +BEGIN; +INSERT INTO `li_im_message` VALUES ('1494593764335812608', '1376417684140326912', '1394172225745059840', b'0', 'MESSAGE', '1', '2022-02-18 16:44:56', '1484066996948566016'); +INSERT INTO `li_im_message` VALUES ('1494593771566792704', '1376417684140326912', '1394172225745059840', b'0', 'MESSAGE', '2', '2022-02-18 16:44:58', '1484066996948566016'); +INSERT INTO `li_im_message` VALUES ('1494593778193793024', '1376417684140326912', '1394172225745059840', b'0', 'MESSAGE', '3', '2022-02-18 16:45:00', '1484066996948566016'); +INSERT INTO `li_im_message` VALUES ('1494593784086790144', '1376417684140326912', '1394172225745059840', b'0', 'MESSAGE', '4', '2022-02-18 16:45:01', '1484066996948566016'); +INSERT INTO `li_im_message` VALUES ('1494593792664141824', '1376417684140326912', '1394172225745059840', b'0', 'MESSAGE', '5', '2022-02-18 16:45:03', '1484066996948566016'); +INSERT INTO `li_im_message` VALUES ('1494593810909364224', '1376417684140326912', '1394172225745059840', b'0', 'MESSAGE', '6', '2022-02-18 16:45:07', '1484066996948566016'); +INSERT INTO `li_im_message` VALUES ('1494593819465744384', '1376417684140326912', '1394172225745059840', b'0', 'MESSAGE', '7', '2022-02-18 16:45:09', '1484066996948566016'); +INSERT INTO `li_im_message` VALUES ('1494593825996275712', '1376417684140326912', '1394172225745059840', b'0', 'MESSAGE', '8', '2022-02-18 16:45:11', '1484066996948566016'); +INSERT INTO `li_im_message` VALUES ('1494593833034317824', '1376417684140326912', '1394172225745059840', b'0', 'MESSAGE', '9', '2022-02-18 16:45:13', '1484066996948566016'); +INSERT INTO `li_im_message` VALUES ('1494593842828017664', '1376417684140326912', '1394172225745059840', b'0', 'MESSAGE', '10', '2022-02-18 16:45:15', '1484066996948566016'); +INSERT INTO `li_im_message` VALUES ('1494593851006910464', '1376417684140326912', '1394172225745059840', b'0', 'MESSAGE', '11', '2022-02-18 16:45:17', '1484066996948566016'); +INSERT INTO `li_im_message` VALUES ('1494593861786271744', '1376417684140326912', '1394172225745059840', b'0', 'MESSAGE', '12', '2022-02-18 16:45:20', '1484066996948566016'); +INSERT INTO `li_im_message` VALUES ('1494643639438016512', '1376417684140326912', '1394172225745059840', b'0', 'MESSAGE', '[足球]', '2022-02-18 20:03:07', '1484066996948566016'); +INSERT INTO `li_im_message` VALUES ('1495578234362789888', '1376417684140326912', '1384135843077160960', b'0', 'MESSAGE', '[亲亲]', '2022-02-21 09:56:52', '1480360433998102528'); +INSERT INTO `li_im_message` VALUES ('1495591815221346304', '1376417684140326912', '1394172225745059840', b'0', 'MESSAGE', '1', '2022-02-21 10:50:50', '1484066996948566016'); +INSERT INTO `li_im_message` VALUES ('1495591828328546304', '1376417684140326912', '1394172225745059840', b'0', 'MESSAGE', '1', '2022-02-21 10:50:53', '1484066996948566016'); +INSERT INTO `li_im_message` VALUES ('1495591834263486464', '1376417684140326912', '1394172225745059840', b'0', 'MESSAGE', '11', '2022-02-21 10:50:55', '1484066996948566016'); +INSERT INTO `li_im_message` VALUES ('1495591839695110144', '1376417684140326912', '1394172225745059840', b'0', 'MESSAGE', '1', '2022-02-21 10:50:56', '1484066996948566016'); +INSERT INTO `li_im_message` VALUES ('1495591844925407232', '1376417684140326912', '1394172225745059840', b'0', 'MESSAGE', '1', '2022-02-21 10:50:57', '1484066996948566016'); +INSERT INTO `li_im_message` VALUES ('1495609852146221056', '1376417684140326912', '1394172225745059840', b'0', 'MESSAGE', '1', '2022-02-21 12:02:30', '1484066996948566016'); +INSERT INTO `li_im_message` VALUES ('1495609858303459328', '1376417684140326912', '1394172225745059840', b'0', 'MESSAGE', '1111', '2022-02-21 12:02:32', '1484066996948566016'); +COMMIT; + +-- ---------------------------- +-- Table structure for li_im_talk +-- ---------------------------- +DROP TABLE IF EXISTS `li_im_talk`; +CREATE TABLE `li_im_talk` ( + `id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `user_id1` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `user_id2` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `top1` bit(1) DEFAULT NULL, + `top2` bit(1) DEFAULT NULL, + `disable1` bit(1) DEFAULT NULL, + `disable2` bit(1) DEFAULT NULL, + `last_talk_time` datetime DEFAULT NULL, + `name1` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `name2` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `face1` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `face2` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `talk_id` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `tenant_id` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `create_time` datetime DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ---------------------------- +-- Records of li_im_talk +-- ---------------------------- +BEGIN; +INSERT INTO `li_im_talk` VALUES ('1480360433998102528', '1376417684140326912', '1384135843077160960', b'0', b'0', b'0', b'0', '2022-01-10 10:06:46', '张三', 'Angle', 'https://lilishop-oss.oss-cn-beijing.aliyuncs.com/cec8b43b6bad41c69f393e93c55c528f.jpg', 'https://lilishop-oss.oss-cn-beijing.aliyuncs.com/35c2bc3d708346a2836d3df8fdabf30a.jpg', NULL, NULL, NULL); +INSERT INTO `li_im_talk` VALUES ('1484066996948566016', '1376417684140326912', '1394172225745059840', b'0', b'0', b'0', b'0', '2022-01-20 15:35:19', '张三', '13011111112', 'https://lilishop-oss.oss-cn-beijing.aliyuncs.com/cec8b43b6bad41c69f393e93c55c528f.jpg', NULL, NULL, NULL, NULL); +INSERT INTO `li_im_talk` VALUES ('1494228244897988608', '1394172225745059840', '1394172225745059840', b'0', b'0', b'0', b'0', '2022-02-17 16:32:30', '13011111112', '13011111112', NULL, NULL, NULL, NULL, '2022-02-17 16:32:30'); +COMMIT; + +-- ---------------------------- +-- Table structure for li_im_users +-- ---------------------------- +DROP TABLE IF EXISTS `li_im_users`; +CREATE TABLE `li_im_users` ( + `id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `face` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `tenant_id` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `create_time` datetime DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ---------------------------- +-- Records of li_im_users +-- ---------------------------- +BEGIN; +INSERT INTO `li_im_users` VALUES ('1337306110277476352', 'https://lilishop-oss.oss-cn-beijing.aliyuncs.com/ad5401f2cfe848a98ef9efcf7b083ccd.jpg', 'admin', NULL, NULL); +INSERT INTO `li_im_users` VALUES ('1376417684140326912', 'https://lilishop-oss.oss-cn-beijing.aliyuncs.com/cec8b43b6bad41c69f393e93c55c528f.jpg', '张三', NULL, NULL); +INSERT INTO `li_im_users` VALUES ('1384135843077160960', 'https://lilishop-oss.oss-cn-beijing.aliyuncs.com/35c2bc3d708346a2836d3df8fdabf30a.jpg', 'Angle', NULL, NULL); +INSERT INTO `li_im_users` VALUES ('1394172225745059840', NULL, '13011111112', NULL, NULL); +COMMIT; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/src/main/java/cn/lili/Application.java b/src/main/java/cn/lili/Application.java index 99356cec86f42549e523f502094855735dcbb185..df119461c413cd36d90662dd5677e97542ce79b4 100644 --- a/src/main/java/cn/lili/Application.java +++ b/src/main/java/cn/lili/Application.java @@ -2,6 +2,8 @@ package cn.lili; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; /** @@ -10,8 +12,20 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + /** + * 如果使用独立的servlet容器, + * 而不是直接使用springboot的内置容器, + * 就不要注入ServerEndpointExporter, + * 因为它将由容器自己提供和管理 + * + * @return + */ + @Bean + public ServerEndpointExporter serverEndpointExporter() { + return new ServerEndpointExporter(); + } } diff --git a/src/main/java/cn/lili/ChatController.java b/src/main/java/cn/lili/ChatController.java deleted file mode 100644 index 76b51d67fc96c80663210dc79956533e03c830f0..0000000000000000000000000000000000000000 --- a/src/main/java/cn/lili/ChatController.java +++ /dev/null @@ -1,27 +0,0 @@ -package cn.lili; - - -import cn.lili.result.ResultMessage; -import cn.lili.result.ResultUtil; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.servlet.ModelAndView; - -/** - * @author liushuai - */ -@RestController -@RequestMapping("/lili/im") -public class ChatController { - - - @Autowired - private WebSocketServer webSocketServer; - - @GetMapping("/room") - public ModelAndView room(String name) { - ModelAndView modelAndView = new ModelAndView("chatroom"); - modelAndView.addObject("name", name); - return modelAndView; - } -} diff --git a/src/main/java/cn/lili/WebSocketServer.java b/src/main/java/cn/lili/WebSocketServer.java deleted file mode 100644 index 52a509dcf7fbf63a684fea01bf5be9caf62a66c1..0000000000000000000000000000000000000000 --- a/src/main/java/cn/lili/WebSocketServer.java +++ /dev/null @@ -1,109 +0,0 @@ -package cn.lili; - -import java.io.IOException; -import java.util.Date; -import java.util.concurrent.ConcurrentHashMap; -import javax.websocket.*; -import javax.websocket.server.PathParam; -import javax.websocket.server.ServerEndpoint; - -import cn.lili.po.MessageType; -import org.springframework.stereotype.Component; -import com.alibaba.fastjson.JSON; -import cn.lili.po.Message; - -/** - * @author liushuai - */ -@Component -@ServerEndpoint("/lili/webSocket/{username}") -public class WebSocketServer { - /** - * 在线人数 - */ - private static ConcurrentHashMap sessionPools = new ConcurrentHashMap<>(); - - - /** - * 建立连接 - * - * @param session - */ - @OnOpen - public void onOpen(@PathParam("username") String username, Session session, EndpointConfig endpointConfig) { - - sessionPools.put(username, session); - //上下线通知 - currentUser(); - } - - /** - * 关闭连接 - */ - @OnClose - public void onClose(@PathParam("username") String username) { - - sessionPools.remove(username); - //上下线通知 - currentUser(); - } - - /** - * 发送消息 - * - * @param msg - * @throws IOException - */ - @OnMessage - public void onMessage(String msg) { - Message message = JSON.parseObject(msg, Message.class); - message.setDate(new Date()); - message.setMessageType(MessageType.MESSAGE); - Session session = sessionPools.get(message.getTo()); - try { - if (session != null) { - session.getBasicRemote().sendText(JSON.toJSONString(message, true)); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * socket exception - * - * @param session - * @param throwable - */ - @OnError - public void onError(Session session, Throwable throwable) { - throwable.printStackTrace(); - } - - /** - * 当前用户 - */ - public void currentUser() { - - for (String key : sessionPools.keySet()) { - Message message = new Message(); - Session session = sessionPools.get(key); - message.setMessageType(MessageType.ONLINE); - message.setUsers(sessionPools.keySet()); - try { - session.getBasicRemote().sendText(JSON.toJSONString(message, true)); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - /** - * 获取websocket 键值对 - * - * @return - */ - public ConcurrentHashMap getSessionPools() { - return sessionPools; - } -} diff --git a/src/main/java/cn/lili/aspect/annotation/SystemLogPoint.java b/src/main/java/cn/lili/aspect/annotation/SystemLogPoint.java new file mode 100644 index 0000000000000000000000000000000000000000..5e5c7cbfbb1af08f736c9667881f9fcebf689418 --- /dev/null +++ b/src/main/java/cn/lili/aspect/annotation/SystemLogPoint.java @@ -0,0 +1,29 @@ +package cn.lili.aspect.annotation; + +import java.lang.annotation.*; + +/** + * 系统日志AOP注解 + * + * @author Chopper + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface SystemLogPoint { + + /** + * 日志名称 + * + * @return + */ + String description() default ""; + + /** + * 自定义日志内容 + * + * @return + */ + String customerLog() default ""; +} diff --git a/src/main/java/cn/lili/aspect/interceptor/SystemLogAspect.java b/src/main/java/cn/lili/aspect/interceptor/SystemLogAspect.java new file mode 100644 index 0000000000000000000000000000000000000000..094c447f54a3deced05a2f230ad8a7899ffc9fb6 --- /dev/null +++ b/src/main/java/cn/lili/aspect/interceptor/SystemLogAspect.java @@ -0,0 +1,175 @@ +package cn.lili.aspect.interceptor; + +import cn.lili.aspect.annotation.SystemLogPoint; +import cn.lili.modules.permission.entity.vo.SystemLogVO; +import cn.lili.modules.permission.service.SystemLogService; +import cn.lili.security.AuthUser; +import cn.lili.security.UserContext; +import cn.lili.security.enums.UserEnums; +import cn.lili.utils.IpHelper; +import cn.lili.utils.IpUtils; +import cn.lili.utils.SpelUtil; +import cn.lili.utils.ThreadPoolUtil; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.NamedThreadLocal; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * Spring AOP实现日志管理 + * + * @author Chopper + */ +@Aspect +@Component +@Slf4j +public class SystemLogAspect { + + /** + * 启动线程异步记录日志 + */ + private static final ThreadLocal BEGIN_TIME_THREAD_LOCAL = new NamedThreadLocal<>("SYSTEM-LOG"); + + @Autowired + private SystemLogService systemLogService; + + @Autowired + private HttpServletRequest request; + + @Autowired + private IpHelper ipHelper; + + /** + * Controller层切点,注解方式 + */ + @Pointcut("@annotation(cn.lili.aspect.annotation.SystemLogPoint)") + public void controllerAspect() { + + } + + /** + * 前置通知 (在方法执行之前返回)用于拦截Controller层记录用户的操作的开始时间 + */ + @Before("controllerAspect()") + public void doBefore() { + BEGIN_TIME_THREAD_LOCAL.set(new Date()); + } + + + /** + * 后置通知(在方法执行之后并返回数据) 用于拦截Controller层无异常的操作 + * + * @param joinPoint 切点 + */ + @AfterReturning(returning = "rvt", pointcut = "controllerAspect()") + public void after(JoinPoint joinPoint, Object rvt) { + try { + Map map = spelFormat(joinPoint, rvt); + String description = map.get("description").toString(); + String customerLog = map.get("customerLog").toString(); + + Map logParams = request.getParameterMap(); + AuthUser authUser = UserContext.getCurrentUser(); + SystemLogVO systemLogVO = new SystemLogVO(); + + if (authUser == null) { + //如果是商家则记录商家id,否则记录-1,代表平台id + systemLogVO.setStoreId(-2L); + //请求用户 + systemLogVO.setUsername("游客"); + } else { + //如果是商家则记录商家id,否则记录-1,代表平台id + systemLogVO.setStoreId(authUser.getRole().equals(UserEnums.SELLER) ? Long.parseLong(authUser.getStoreId()) : -1); + //请求用户 + systemLogVO.setUsername(authUser.getUsername()); + } + + //日志标题 + systemLogVO.setName(description); + //日志请求url + systemLogVO.setRequestUrl(request.getRequestURI()); + //请求方式 + systemLogVO.setRequestType(request.getMethod()); + //请求参数 + systemLogVO.setMapToParams(logParams); + //响应参数 此处数据太大了,所以先注释掉 +// systemLogVO.setResponseBody(JSONUtil.toJsonStr(rvt)); + //请求IP + systemLogVO.setIp(IpUtils.getIpAddress(request)); + //IP地址 + systemLogVO.setIpInfo(ipHelper.getIpCity(request)); + //写入自定义日志内容 + systemLogVO.setCustomerLog(customerLog); + //请求开始时间 + long beginTime = BEGIN_TIME_THREAD_LOCAL.get().getTime(); + long endTime = System.currentTimeMillis(); + //请求耗时 + Long usedTime = endTime - beginTime; + systemLogVO.setCostTime(usedTime.intValue()); + //调用线程保存 + ThreadPoolUtil.getPool().execute(new SaveSystemLogThread(systemLogVO, systemLogService)); + + + BEGIN_TIME_THREAD_LOCAL.remove(); + } catch (Exception e) { + log.error("系统日志保存异常", e); + } + } + + /** + * 保存日志 + */ + private static class SaveSystemLogThread implements Runnable { + @Autowired + private SystemLogVO systemLogVO; + @Autowired + private SystemLogService systemLogService; + + public SaveSystemLogThread(SystemLogVO systemLogVO, SystemLogService systemLogService) { + this.systemLogVO = systemLogVO; + this.systemLogService = systemLogService; + } + + @Override + public void run() { + try { + systemLogService.saveLog(systemLogVO); + } catch (Exception e) { + log.error("系统日志保存异常,内容{}:", systemLogVO, e); + } + } + } + + + /** + * 获取注解中对方法的描述信息 用于Controller层注解 + * + * @param joinPoint 切点 + * @return 方法描述 + * @throws Exception + */ + private static Map spelFormat(JoinPoint joinPoint, Object rvt) { + + Map result = new HashMap<>(2); + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + SystemLogPoint systemLogPoint = signature.getMethod().getAnnotation(SystemLogPoint.class); + String description = systemLogPoint.description(); + String customerLog = SpelUtil.compileParams(joinPoint, rvt, systemLogPoint.customerLog()); + + result.put("description", description); + result.put("customerLog", customerLog); + return result; + } + +} diff --git a/src/main/java/cn/lili/cache/Cache.java b/src/main/java/cn/lili/cache/Cache.java new file mode 100644 index 0000000000000000000000000000000000000000..7bd4d48154c33fe7eaef6f0e52aabb7265ecbe41 --- /dev/null +++ b/src/main/java/cn/lili/cache/Cache.java @@ -0,0 +1,288 @@ +package cn.lili.cache; + + +import org.springframework.data.redis.core.ZSetOperations; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * 缓存接口 + * + * @author Chopper + */ +public interface Cache { + + /** + * Get an item from the cache, nontransactionally + * + * @param key 缓存key + * @return the cached object or null + */ + T get(Object key); + + /** + * Get an item from the cache, nontransactionally + * + * @param key 缓存key + * @return the cached object or null + */ + String getString(Object key); + + + /** + * multiGet + * + * @param keys 要查询的key集合 + * @return 集合 + */ + List multiGet(Collection keys); + + /** + * 批量set + * + * @param map 键值对 + */ + void multiSet(Map map); + + + /** + * 批量删除 + * + * @param keys 要删除的key集合 + */ + void multiDel(Collection keys); + + /** + * Add an item to the cache, nontransactionally, with + * failfast semantics + * + * @param key 缓存key + * @param value 缓存value + */ + void put(Object key, T value); + + /** + * 往缓存中写入内容 + * + * @param key 缓存key + * @param value 缓存value + * @param exp 超时时间,单位为秒 + */ + void put(Object key, T value, Long exp); + + /** + * 往缓存中写入内容 + * + * @param key 缓存key + * @param value 缓存value + * @param exp 过期时间 + * @param timeUnit 过期单位 + */ + void put(Object key, T value, Long exp, TimeUnit timeUnit); + + /** + * 删除 + * + * @param key 缓存key + */ + Boolean remove(Object key); + + /** + * 删除 + * + * @param key 缓存key + */ + void vagueDel(Object key); + + /** + * Clear the cache + */ + void clear(); + + + /** + * 往缓存中写入内容 + * + * @param key 缓存key + * @param hashKey 缓存中hashKey + * @param hashValue hash值 + */ + void putHash(Object key, Object hashKey, Object hashValue); + + /** + * 玩缓存中写入内容 + * + * @param key 缓存key + * @param map map value + */ + void putAllHash(Object key, Map map); + + /** + * 读取缓存值 + * + * @param key 缓存key + * @param hashKey map value + * @return 返回缓存中的数据 + */ + T getHash(Object key, Object hashKey); + + /** + * 读取缓存值 + * + * @param key 缓存key + * @return 缓存中的数据 + */ + Map getHash(Object key); + + /** + * 是否包含 + * + * @param key 缓存key + * @return 缓存中的数据 + */ + boolean hasKey(Object key); + + + /** + * 模糊匹配key + * + * @param pattern 模糊key + * @return 缓存中的数据 + */ + List keys(String pattern); + + + //-----------------------------------------------用于特殊场景,redis去重计数--------------------------------------------- + + /** + * 累计数目 + * 效率较高的 计数器 + * 如需清零,按照普通key 移除即可 + * + * @param key key值 + * @param value 去重统计值 + * @return 计数器结果 + */ + Long cumulative(Object key, Object value); + + /** + * 计数器结果 + *

+ * 效率较高的 计数器 统计返回 + * 如需清零,按照普通key 移除即可 + * + * @param key 计数器key + * @return 计数器结果 + */ + Long counter(Object key); + + /** + * 批量计数 + * + * @param keys 要查询的key集合 + * @return 批量计数 + */ + List multiCounter(Collection keys); + + /** + * 计数器结果 + *

+ * 效率较高的 计数器 统计返回 + * 如需清零,按照普通key 移除即可 + * + * @param key key值 + * @return 计数器结果 + */ + Long mergeCounter(Object... key); + //---------------------------------------------------用于特殊场景,redis去重统计----------------------------------------- + + //-----------------------------------------------redis计数--------------------------------------------- + + /** + * redis 计数器 累加 + * 注:到达liveTime之后,该次增加取消,即自动-1,而不是redis值为空 + * + * @param key 为累计的key,同一key每次调用则值 +1 + * @param liveTime 单位秒后失效 + * @return 计数器结果 + */ + Long incr(String key, long liveTime); + //-----------------------------------------------redis计数--------------------------------------------- + + /** + * 使用Sorted Set记录keyword + * zincrby命令,对于一个Sorted Set,存在的就把分数加x(x可自行设定),不存在就创建一个分数为1的成员 + * + * @param sortedSetName sortedSetName的Sorted Set不用预先创建,不存在会自动创建,存在则向里添加数据 + * @param keyword 关键词 + */ + void incrementScore(String sortedSetName, String keyword); + + /** + * 使用Sorted Set记录keyword + * zincrby命令,对于一个Sorted Set,存在的就把分数加x(x可自行设定),不存在就创建一个分数为1的成员 + * + * @param sortedSetName sortedSetName的Sorted Set不用预先创建,不存在会自动创建,存在则向里添加数据 + * @param keyword 关键词 + * @param score 分数 + */ + void incrementScore(String sortedSetName, String keyword, Integer score); + + /** + * zrevrange命令, 查询Sorted Set中指定范围的值 + * 返回的有序集合中,score大的在前面 + * zrevrange方法无需担心用于指定范围的start和end出现越界报错问题 + * + * @param sortedSetName sortedSetName + * @param start 查询范围开始位置 + * @param end 查询范围结束位置 + * @return 获取满足条件的集合 + */ + Set> reverseRangeWithScores(String sortedSetName, Integer start, Integer end); + + /** + * zrevrange命令, 查询Sorted Set中指定范围的值 + * 返回的有序集合中,score大的在前面 + * zrevrange方法无需担心用于指定范围的start和end出现越界报错问题 + * + * @param sortedSetName sortedSetName + * @param count 查询数量 + * @return 获取满足条件的集合 + */ + Set> reverseRangeWithScores(String sortedSetName, Integer count); + + + /** + * 向Zset里添加成员 + * + * @param key key值 + * @param score 分数 + * @param value 值 + * @return 增加状态 + */ + boolean zAdd(String key, long score, String value); + + + /** + * 获取 某key 下 某一分值区间的队列 + * + * @param key 缓存key + * @param from 开始时间 + * @param to 结束时间 + * @return 数据 + */ + Set> zRangeByScore(String key, int from, long to); + + /** + * 移除 Zset队列值 + * + * @param key key值 + * @param value 删除的集合 + * @return 删除数量 + */ + Long zRemove(String key, String... value); +} diff --git a/src/main/java/cn/lili/cache/CachePrefix.java b/src/main/java/cn/lili/cache/CachePrefix.java new file mode 100644 index 0000000000000000000000000000000000000000..6668b538c5fed8f0dc3082a25ae6f210408bd61b --- /dev/null +++ b/src/main/java/cn/lili/cache/CachePrefix.java @@ -0,0 +1,506 @@ +package cn.lili.cache; + +import cn.lili.security.enums.UserEnums; + +/** + * CachePrefix + * + * @author Chopper + * @version v1.0 + * 2022-02-11 15:22 + */ +public enum CachePrefix { + + /** + * nonce + */ + NONCE, + + /** + * 在线人数 + */ + ONLINE_NUM, + + + /** + * 会员分布数据 + */ + MEMBER_DISTRIBUTION, + + /** + * 在线会员统计 + */ + ONLINE_MEMBER, + + /** + * token 信息 + */ + ACCESS_TOKEN, + /** + * token 信息 + */ + REFRESH_TOKEN, + /** + * 联合登录响应 + */ + CONNECT_RESULT, + /** + * 微信联合登陆 session key + */ + SESSION_KEY, + /** + * 权限 + */ + PERMISSION_LIST, + /** + * 部门id列表 + */ + DEPARTMENT_IDS, + + /** + * 用户错误登录限制 + */ + LOGIN_TIME_LIMIT, + /** + * 系统设置 + */ + SETTING, + + /** + * 验证码滑块源 + */ + VERIFICATION, + + /** + * 验证码滑块源 + */ + VERIFICATION_IMAGE, + + /** + * 快递平台 + */ + EXPRESS, + + /** + * 图片验证码 + */ + CAPTCHA, + + /** + * 商品 + */ + GOODS, + + /** + * 商品SKU + */ + GOODS_SKU, + + /** + * 运费模板脚本 + */ + SHIP_SCRIPT, + + /** + * 商品sku + */ + SKU, + + /** + * sku库存 + */ + SKU_STOCK, + + /** + * 促销商品sku库存 + */ + PROMOTION_GOODS_STOCK, + + /** + * 商品库存 + */ + GOODS_STOCK, + + /** + * 商品分类 树状结构 + */ + CATEGORY, + /** + * 商品分类 集合 + */ + CATEGORY_ARRAY, + /** + * 浏览次数 + */ + VISIT_COUNT, + /** + * 存储方案 + */ + UPLOADER, + /** + * 地区 + */ + REGION, + + /** + * 短信网关 + */ + SPlATFORM, + /** + * 短信验证码前缀 + */ + _CODE_PREFIX, + /** + * smtp + */ + SMTP, + /** + * 系统设置 + */ + SETTINGS, + /** + * 电子面单 + */ + WAYBILL, + /** + * 短信验证码 + */ + SMS_CODE, + /** + * 邮箱验证码 + */ + EMAIL_CODE, + /** + * 管理员角色权限对照表 + */ + ADMIN_URL_ROLE, + + /** + * 店铺管理员角色权限对照表 + */ + STORE_URL_ROLE, + + /** + * 手机验证标识 + */ + MOBILE_VALIDATE, + + /** + * 邮箱验证标识 + */ + EMAIL_VALIDATE, + + /** + * 店铺运费模版列表 + */ + SHIP_TEMPLATE, + + /** + * 店铺中某个运费模版 + */ + SHIP_TEMPLATE_ONE, + + //================促销================= + /** + * 促销活动 + */ + PROMOTION, + /** + * 促销活动 + */ + PROMOTION_GOODS, + + /*** 单品立减 */ + STORE_ID_MINUS, + + /*** 第二件半价 */ + STORE_ID_HALF_PRICE, + + /*** 满优惠 */ + STORE_ID_FULL_DISCOUNT, + + /** + * 秒杀活动活动缓存key前缀 + */ + STORE_ID_SECKILL, + + /** + * 团购活动缓存key前缀 + */ + STORE_ID_GROUP_BUY, + + /** + * 积分商品缓存key前缀 + */ + STORE_ID_EXCHANGE, + + + //================交易================= + + /** + * 购物车原始数据 + */ + CART_ORIGIN_DATA_PREFIX, + + /** + * 立即购买原始数据 + */ + BUY_NOW_ORIGIN_DATA_PREFIX, + + /** + * 交易原始数据 + */ + TRADE_ORIGIN_DATA_PREFIX, + + /** + * 立即购买sku + */ + CART_SKU_PREFIX, + + /** + * 购物车视图 + */ + CART_MEMBER_ID_PREFIX, + + /** + * 购物车,用户选择的促销信息 + */ + CART_PROMOTION_PREFIX, + + + /** + * 交易_交易价格的前缀 + */ + PRICE_SESSION_ID_PREFIX, + + /** + * 交易_交易单 + */ + TRADE_SESSION_ID_PREFIX, + + + /** + * 结算参数 + */ + CHECKOUT_PARAM_ID_PREFIX, + + /** + * 交易单号前缀 + */ + TRADE_SN_CACHE_PREFIX, + + /** + * 订单编号前缀 + */ + ORDER_SN_CACHE_PREFIX, + /** + * 订单编号标记 + */ + ORDER_SN_SIGN_CACHE_PREFIX, + /** + * 订单编号前缀 + */ + PAY_LOG_SN_CACHE_PREFIX, + + /** + * 合同编号 + */ + CONTRACT_SN_CACHE_PREFIX, + + + /** + * 零钱 + */ + SMALL_CHANGE_CACHE_PREFIX, + + /** + * 售后服务单号前缀 + */ + AFTER_SALE_SERVICE_PREFIX, + + /** + * 交易 + */ + TRADE, + + /** + * 站点导航栏 + */ + SITE_NAVIGATION, + + /** + * 支付参数 + */ + PAYMENT_CONFIG, + + /** + * 流程控制 + */ + FLOW, + + /** + * 热门搜索 + */ + HOT_WORD, + + /** + * 会员积分 + */ + MEMBER_POINT, + + /** + * 会员积分 + */ + POINT_ORDER, + + + /** + * 微博登录 + */ + WEIBO_STATE, + /** + * 微博登录 + */ + QQ_STATE, + /** + * 微博登录 + */ + GITHUB_STATE, + /** + * 验证码key + */ + VERIFICATION_KEY, + /** + * 验证码验证结果 + */ + VERIFICATION_RESULT, + /** + * 微信CGItoken + */ + WECHAT_CGI_ACCESS_TOKEN, + /** + * 微信JSApitoken + */ + WECHAT_JS_API_TOKEN, + /** + * 微信会话信息 + */ + WECHAT_SESSION_PARAMS, + /** + * 第三方用户权限 + */ + ALIPAY_CONFIG, + /** + * 微信支付配置 + */ + WECHAT_PAY_CONFIG, + /** + * 微信支付平台证书配置 + */ + WECHAT_PLAT_FORM_CERT, + /** + * 第三方用户权限 + */ + CONNECT_AUTH, + /** + * 平台PageView 统计 + */ + PV, + /** + * 平台UserView 统计 + */ + UV, + /** + * 平台 商品PV 统计 + */ + GOODS_PV, + /** + * 平台 商品UV 统计 + */ + GOODS_UV, + /** + * 店铺PageView 统计 + */ + STORE_PV, + /** + * 店铺UserView 统计 + */ + STORE_UV, + /** + * 店铺 商品PV 统计 + */ + STORE_GOODS_PV, + /** + * 店铺 商品UV 统计 + */ + STORE_GOODS_UV, + /** + * 分销员 + */ + DISTRIBUTION, + + /** + * 找回手机 + */ + FIND_MOBILE, + /** + * 文章分类 + */ + ARTICLE_CATEGORY, + /** + * 文章 + */ + ARTICLE_CACHE, + + /** + * 初始化索引 + */ + INIT_INDEX_PROCESS, + + /** + * 初始化索引标示 + */ + INIT_INDEX_FLAG, + + /** + * 店铺分类 + */ + STORE_CATEGORY, + /** + * 用户菜单 + */ + MENU_USER_ID, + /** + * 用户菜单 + */ + USER_MENU, + /** + * 订单暂时缓存 + */ + ORDER, + /** + * 敏感词 + */ + SENSITIVE; + + /** + * 通用获取缓存key值 + * + * @return 缓存key值 + */ + public String getPrefix() { + return "{" + this.name() + "}_"; + } + /** + * 获取缓存key值 + 用户端 + * 例如:三端都有用户体系,需要分别登录,如果用户名一致,则redis中的权限可能会冲突出错 + * + * @param user 角色 + * @return 缓存key值 + 用户端 + */ + public String getPrefix(UserEnums user) { + return "{" + this.name() + "_" + user.name() + "}_"; + } + + +} diff --git a/src/main/java/cn/lili/cache/impl/RedisCache.java b/src/main/java/cn/lili/cache/impl/RedisCache.java new file mode 100644 index 0000000000000000000000000000000000000000..c9b6a32e104e097451441636fdd98219343b7dbd --- /dev/null +++ b/src/main/java/cn/lili/cache/impl/RedisCache.java @@ -0,0 +1,305 @@ +package cn.lili.cache.impl; + +import cn.lili.cache.Cache; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.core.*; +import org.springframework.data.redis.support.atomic.RedisAtomicLong; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * redis 缓存实现 + * + * @author Chopepr + */ +@Slf4j +@Component +public class RedisCache implements Cache { + + @Autowired + private RedisTemplate redisTemplate; + + public RedisCache() { + + } + + @Override + public Object get(Object key) { + + return redisTemplate.opsForValue().get(key); + } + + @Override + public String getString(Object key) { + try { + return redisTemplate.opsForValue().get(key).toString(); + } catch (Exception e) { + return null; + } + } + + @Override + public List multiGet(Collection keys) { + return redisTemplate.opsForValue().multiGet(keys); + + } + + + @Override + public void multiSet(Map map) { + redisTemplate.opsForValue().multiSet(map); + } + + @Override + public void multiDel(Collection keys) { + redisTemplate.delete(keys); + } + + @Override + public void put(Object key, Object value) { + redisTemplate.opsForValue().set(key, value); + } + + @Override + public void put(Object key, Object value, Long exp) { + put(key, value, exp, TimeUnit.SECONDS); + } + + @Override + public void put(Object key, Object value, Long exp, TimeUnit timeUnit) { + redisTemplate.opsForValue().set(key, value, exp, timeUnit); + } + + @Override + public Boolean remove(Object key) { + + return redisTemplate.delete(key); + } + + /** + * 删除 + * + * @param key 模糊删除key + */ + @Override + public void vagueDel(Object key) { + Set keys = redisTemplate.keys(key + "*"); + redisTemplate.delete(keys); + } + + @Override + public void clear() { + + Set keys = redisTemplate.keys("*"); + redisTemplate.delete(keys); + } + + @Override + public void putHash(Object key, Object hashKey, Object hashValue) { + redisTemplate.opsForHash().put(key, hashKey, hashValue); + } + + @Override + public void putAllHash(Object key, Map map) { + redisTemplate.opsForHash().putAll(key, map); + } + + @Override + public Object getHash(Object key, Object hashKey) { + return redisTemplate.opsForHash().get(key, hashKey); + } + + @Override + public Map getHash(Object key) { + return this.redisTemplate.opsForHash().entries(key); + } + + @Override + public boolean hasKey(Object key) { + return this.redisTemplate.opsForValue().get(key) != null; + } + + /** + * 获取符合条件的key + * + * @param pattern 表达式 + * @return 模糊匹配key + */ + @Override + public List keys(String pattern) { + List keys = new ArrayList<>(); + this.scan(pattern, item -> { + //符合条件的key + String key = new String(item, StandardCharsets.UTF_8); + keys.add(key); + }); + return keys; + } + + + /** + * scan 实现 + * + * @param pattern 表达式 + * @param consumer 对迭代到的key进行操作 + */ + private void scan(String pattern, Consumer consumer) { + this.redisTemplate.execute((RedisConnection connection) -> { + try (Cursor cursor = + connection.scan(ScanOptions.scanOptions() + .count(Long.MAX_VALUE) + .match(pattern).build())) { + cursor.forEachRemaining(consumer); + return null; + + } catch (IOException e) { + log.error("scan错误", e); + throw new RuntimeException(e); + } + }); + } + + + @Override + public Long cumulative(Object key, Object value) { + HyperLogLogOperations operations = redisTemplate.opsForHyperLogLog(); + //add 方法对应 PFADD 命令 + return operations.add(key, value); + + } + + @Override + public Long counter(Object key) { + HyperLogLogOperations operations = redisTemplate.opsForHyperLogLog(); + + //add 方法对应 PFADD 命令 + return operations.size(key); + } + + @Override + public List multiCounter(Collection keys) { + if (keys == null) { + return new ArrayList(); + } + List result = new ArrayList<>(); + for (Object key : keys) { + result.add(counter(key)); + } + return result; + } + + @Override + public Long mergeCounter(Object... key) { + HyperLogLogOperations operations = redisTemplate.opsForHyperLogLog(); + //计数器合并累加 + return operations.union(key[0], key); + } + + @Override + public Long incr(String key, long liveTime) { + RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory()); + Long increment = entityIdCounter.getAndIncrement(); + //初始设置过期时间 + if ((null == increment || increment == 0) && liveTime > 0) { + entityIdCounter.expire(liveTime, TimeUnit.SECONDS); + } + + return increment; + } + + /** + * 使用Sorted Set记录keyword + * zincrby命令,对于一个Sorted Set,存在的就把分数加x(x可自行设定),不存在就创建一个分数为1的成员 + * + * @param sortedSetName sortedSetName的Sorted Set不用预先创建,不存在会自动创建,存在则向里添加数据 + * @param keyword 关键词 + */ + @Override + public void incrementScore(String sortedSetName, String keyword) { + //指向key名为KEY的zset元素 + redisTemplate.opsForZSet().incrementScore(sortedSetName, keyword, 1); + } + + @Override + public void incrementScore(String sortedSetName, String keyword, Integer score) { + redisTemplate.opsForZSet().incrementScore(sortedSetName, keyword, score); + } + + /** + * zrevrange命令, 查询Sorted Set中指定范围的值 + * 返回的有序集合中,score大的在前面 + * zrevrange方法无需担心用于指定范围的start和end出现越界报错问题 + * + * @param sortedSetName sortedSetName + * @param start 查询范围开始位置 + * @param end 查询范围结束位置 + * @return 符合排序的集合 + */ + @Override + public Set> reverseRangeWithScores(String sortedSetName, Integer start, Integer end) { + return this.redisTemplate.opsForZSet().reverseRangeWithScores(sortedSetName, start, end); + } + + /** + * zrevrange命令, 查询Sorted Set中指定范围的值 + * 返回的有序集合中,score大的在前面 + * zrevrange方法无需担心用于指定范围的start和end出现越界报错问题 + * + * @param sortedSetName sortedSetName + * @param count 获取数量 + * @return 符合排序的集合 + */ + @Override + public Set> reverseRangeWithScores(String sortedSetName, Integer count) { + return this.redisTemplate.opsForZSet().reverseRangeWithScores(sortedSetName, 0, count); + } + + + /** + * 向Zset里添加成员 + * + * @param key key值 + * @param score 分数,通常用于排序 + * @param value 值 + * @return 增加状态 + */ + @Override + public boolean zAdd(String key, long score, String value) { + return redisTemplate.opsForZSet().add(key, value, score); + + } + + + /** + * 获取 某key 下 某一分值区间的队列 + * + * @param key 缓存key + * @param from 开始时间 + * @param to 结束时间 + * @return 数据 + */ + @Override + public Set> zRangeByScore(String key, int from, long to) { + Set> set = redisTemplate.opsForZSet().rangeByScoreWithScores(key, from, to); + return set; + } + + /** + * 移除 Zset队列值 + * + * @param key key值 + * @param value 删除的集合 + * @return 删除数量 + */ + @Override + public Long zRemove(String key, String... value) { + return redisTemplate.opsForZSet().remove(key, value); + } +} diff --git a/src/main/java/cn/lili/config/CustomSpringConfigurator.java b/src/main/java/cn/lili/config/CustomSpringConfigurator.java new file mode 100644 index 0000000000000000000000000000000000000000..c86d2382f097175a18e48131cfd7f1f41bc8f7bd --- /dev/null +++ b/src/main/java/cn/lili/config/CustomSpringConfigurator.java @@ -0,0 +1,34 @@ +package cn.lili.config; + + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +import javax.websocket.server.ServerEndpointConfig; + +/** + * CustomSpringConfigurator + * + * @author Chopper + * @version v1.0 + * 2021-12-31 11:53 + */ +public class CustomSpringConfigurator extends ServerEndpointConfig.Configurator implements ApplicationContextAware { + + /** + * Spring application context. + */ + private static volatile BeanFactory context; + + @Override + public T getEndpointInstance(Class clazz) throws InstantiationException { + return context.getBean(clazz); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + CustomSpringConfigurator.context = applicationContext; + } +} diff --git a/src/main/java/cn/lili/config/WebSocketConfig.java b/src/main/java/cn/lili/config/WebSocketConfig.java deleted file mode 100644 index d1d9ed60cba54b76b66d432342ac8ab016a9e0be..0000000000000000000000000000000000000000 --- a/src/main/java/cn/lili/config/WebSocketConfig.java +++ /dev/null @@ -1,27 +0,0 @@ -package cn.lili.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.socket.server.standard.ServerEndpointExporter; - -/** - * Servlet 容器配置 - * - * @author liushuai - */ -@Configuration -public class WebSocketConfig { - /** - * 如果使用独立的servlet容器, - * 而不是直接使用springboot的内置容器, - * 就不要注入ServerEndpointExporter, - * 因为它将由容器自己提供和管理 - * - * @return - */ - @Bean - public ServerEndpointExporter serverEndpointExporter() { - return new ServerEndpointExporter(); - } - -} diff --git a/src/main/java/cn/lili/config/WebSocketConfigurator.java b/src/main/java/cn/lili/config/WebSocketConfigurator.java new file mode 100644 index 0000000000000000000000000000000000000000..c41491ebe7b1948289635a30bbcc8a1498dcc1d8 --- /dev/null +++ b/src/main/java/cn/lili/config/WebSocketConfigurator.java @@ -0,0 +1,24 @@ +package cn.lili.config; + + +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * WebSocketConfigurator + * + * @author Chopper + * @version v1.0 + * 2021-12-31 11:53 + */ +@ConditionalOnWebApplication +@Configuration +public class WebSocketConfigurator { + + @Bean + public CustomSpringConfigurator customSpringConfigurator() { + // This is just to get context + return new CustomSpringConfigurator(); + } +} diff --git a/src/main/java/cn/lili/controller/im/ImMessageController.java b/src/main/java/cn/lili/controller/im/ImMessageController.java new file mode 100644 index 0000000000000000000000000000000000000000..52c88736113a64f5cce19c9f2ab28f8d9d69c30c --- /dev/null +++ b/src/main/java/cn/lili/controller/im/ImMessageController.java @@ -0,0 +1,70 @@ +package cn.lili.controller.im; + +import cn.lili.modules.im.entity.ImMessage; +import cn.lili.modules.im.entity.dto.MessageQueryParams; +import cn.lili.modules.im.service.ImMessageService; +import cn.lili.result.*; +import lombok.RequiredArgsConstructor; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + + +/** + * @author Chopper + */ +@RestController +@Api(tags = "Im消息接口") +@RequestMapping("/lili/imMessage") +@Transactional(rollbackFor = Exception.class) +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class ImMessageController { + + private final ImMessageService imMessageService; + + @GetMapping(value = "/{id}") + @ApiOperation(value = "查看Im消息详情") + public ResultMessage get(@PathVariable String id) { + + ImMessage imMessage = imMessageService.getById(id); + return new ResultUtil().setData(imMessage); + } + + @GetMapping + @ApiOperation(value = "分页获取Im消息") + public ResultMessage> historyMessage(MessageQueryParams messageQueryParams) { + List data = imMessageService.list(messageQueryParams.initQueryWrapper()); + return new ResultUtil>().setData(data); + } + + @PostMapping + @ApiOperation(value = "新增Im消息") + public ResultMessage save(ImMessage imMessage) { + + if (imMessageService.save(imMessage)) { + return new ResultUtil().setData(imMessage); + } + return new ResultUtil().setErrorMsg(ResultCode.ERROR); + } + + @PutMapping("/{id}") + @ApiOperation(value = "更新Im消息") + public ResultMessage update(@PathVariable String id, ImMessage imMessage) { + if (imMessageService.updateById(imMessage)) { + return new ResultUtil().setData(imMessage); + } + return new ResultUtil().setErrorMsg(ResultCode.ERROR); + } + + @DeleteMapping(value = "/{ids}") + @ApiOperation(value = "删除Im消息") + public ResultMessage delAllByIds(@PathVariable List ids) { + + imMessageService.removeByIds(ids); + return ResultUtil.success(ResultCode.SUCCESS); + } +} diff --git a/src/main/java/cn/lili/controller/im/ImTalkController.java b/src/main/java/cn/lili/controller/im/ImTalkController.java new file mode 100644 index 0000000000000000000000000000000000000000..0f00696ec2af8255d94d3fdf378729a637265a7e --- /dev/null +++ b/src/main/java/cn/lili/controller/im/ImTalkController.java @@ -0,0 +1,80 @@ +package cn.lili.controller.im; + +import cn.lili.modules.im.entity.ImTalk; +import cn.lili.modules.im.entity.vo.ImTalkVO; +import cn.lili.modules.im.service.ImTalkService; +import cn.lili.security.AuthUser; +import cn.lili.security.UserContext; +import cn.lili.result.*; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.RequiredArgsConstructor; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + + +/** + * @author Chopper + */ +@RestController +@Api(tags = "聊天接口") +@RequestMapping("/lili/imTalk") +@Transactional(rollbackFor = Exception.class) +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class ImTalkController { + + private final ImTalkService imTalkService; + + @GetMapping(value = "/{id}") + @ApiOperation(value = "查看聊天详情") + public ResultMessage get(@PathVariable String id) { + + ImTalk imTalk = imTalkService.getById(id); + return new ResultUtil().setData(imTalk); + } + + @GetMapping(value = "/user/{uid}") + @ApiOperation(value = "查看与某人聊天详情") + public ResultMessage getUser(@PathVariable String uid) { + AuthUser authUser = UserContext.getAuthUser(); + if (Long.parseLong(uid) > Long.parseLong(authUser.getId())) { + return ResultUtil.data(imTalkService.getTalkByUser(authUser.getId(), uid)); + } else { + return ResultUtil.data(imTalkService.getTalkByUser(uid, authUser.getId())); + } + } + + @GetMapping(value = "/top") + @ApiOperation(value = "查看与某人聊天详情") + public ResultMessage top(String id, Boolean top) { + imTalkService.top(id, top); + return ResultUtil.success(); + } + + @GetMapping("/list") + @ApiOperation(value = "分页获取聊天") + public ResultMessage> getByPage() { + AuthUser authUser = UserContext.getAuthUser(); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(ImTalk::getUserId1, authUser.getId()).or().eq(ImTalk::getUserId2, authUser.getId()); + List imTalks = imTalkService.list(queryWrapper); + + List results = imTalks.stream().map(imTalk -> { + return new ImTalkVO(imTalk, authUser.getId()); + }).collect(Collectors.toList()); + + return ResultUtil.data(results); + } + + @DeleteMapping(value = "/{id}") + @ApiOperation(value = "删除聊天") + public ResultMessage disable(@PathVariable String id) { + imTalkService.disable(id); + return ResultUtil.success(ResultCode.SUCCESS); + } +} diff --git a/src/main/java/cn/lili/controller/im/ImUserController.java b/src/main/java/cn/lili/controller/im/ImUserController.java new file mode 100644 index 0000000000000000000000000000000000000000..02fbaff398d550e0feca737101be2cb57d460a8d --- /dev/null +++ b/src/main/java/cn/lili/controller/im/ImUserController.java @@ -0,0 +1,36 @@ +package cn.lili.controller.im; + + +import cn.lili.modules.im.entity.ImUser; +import cn.lili.modules.im.service.ImUserService; +import cn.lili.security.AuthUser; +import cn.lili.security.UserContext; +import cn.lili.result.*; +import lombok.RequiredArgsConstructor; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import org.springframework.transaction.annotation.Transactional; + + +/** + * @author Chopper + */ +@RestController +@Api(tags = "Im消息接口") +@RequestMapping("/lili/imUser") +@Transactional(rollbackFor = Exception.class) +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class ImUserController { + + private final ImUserService imUserService; + + @GetMapping + @ApiOperation(value = "获取用户信息") + public ResultMessage get() { + AuthUser authUser = UserContext.getAuthUser(); + return ResultUtil.data(imUserService.getById(UserContext.getAuthUser().getId())); + } + +} diff --git a/src/main/java/cn/lili/controller/manager/SeatStoreManagerController.java b/src/main/java/cn/lili/controller/manager/SeatStoreManagerController.java new file mode 100644 index 0000000000000000000000000000000000000000..1369ef6233071e40d38b00a804c272fb48bdbe2f --- /dev/null +++ b/src/main/java/cn/lili/controller/manager/SeatStoreManagerController.java @@ -0,0 +1,39 @@ +package cn.lili.controller.manager; + +import cn.lili.modules.seat.service.SeatService; +import cn.lili.modules.seat.vo.SeatVO; +import cn.lili.result.ResultMessage; +import cn.lili.result.ResultUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * SeatController + * + * @author Chopper + * @version v1.0 + * 2022-02-10 11:50 + */ +@RestController +@Api(tags = "店铺端,坐席管理") +@RequestMapping("/manager/seat/setting") +@Transactional(rollbackFor = Exception.class) +public class SeatStoreManagerController { + + @Autowired + private SeatService seatService; + + @ApiOperation(value = "查看店铺坐席列表") + @GetMapping("/list") + public ResultMessage> getSeats(String storeId) { + return ResultUtil.data(seatService.seatVoList(storeId)); + } + +} diff --git a/src/main/java/cn/lili/controller/seat/SeatLogin.java b/src/main/java/cn/lili/controller/seat/SeatLogin.java new file mode 100644 index 0000000000000000000000000000000000000000..06347c5d6bbfc69be9908a3e2fad2dec68dd071d --- /dev/null +++ b/src/main/java/cn/lili/controller/seat/SeatLogin.java @@ -0,0 +1,57 @@ +package cn.lili.controller.seat; + +import cn.lili.modules.seat.service.SeatService; +import cn.lili.result.ResultMessage; +import cn.lili.result.ResultUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +/** + * 坐席登录接口 + * + * @author Chopper + * @version v1.0 + * 2022-02-10 16:40 + */ +@Slf4j +@RestController +@Api(tags = "坐席端") +@RequestMapping("/seat/login") +public class SeatLogin { + + @Autowired + private SeatService seatService; + + @ApiOperation(value = "登录接口") + @ApiImplicitParams({ + @ApiImplicitParam(name = "username", value = "用户名", required = true, paramType = "query"), + @ApiImplicitParam(name = "password", value = "密码", required = true, paramType = "query") + }) + @PostMapping("/userLogin") + public ResultMessage userLogin(String username, String password) { + return ResultUtil.data(this.seatService.usernameLogin(username, password)); + } + + @ApiOperation(value = "商家快捷登录客服") + @PostMapping("/quicklogin") + public ResultMessage quickLogin(String code) { + return ResultUtil.data(this.seatService.quickLogin(code)); + } + + + @ApiOperation(value = "登出") + @PostMapping("/logout") + public ResultMessage logout() { + //todo +// UserContext.getCurrentUser().getId() +// verificationServiceClient.check(uuid); + return ResultUtil.success(); + } + + +} diff --git a/src/main/java/cn/lili/controller/tenant/QAStoreController.java b/src/main/java/cn/lili/controller/tenant/QAStoreController.java new file mode 100644 index 0000000000000000000000000000000000000000..3e72f1a57ac96f92488374b22ec28decaff01834 --- /dev/null +++ b/src/main/java/cn/lili/controller/tenant/QAStoreController.java @@ -0,0 +1,62 @@ +package cn.lili.controller.tenant; + +import cn.lili.modules.seat.dos.QA; +import cn.lili.modules.seat.service.QAService; +import cn.lili.result.PageVO; +import cn.lili.result.ResultMessage; +import cn.lili.result.ResultUtil; +import com.baomidou.mybatisplus.core.metadata.IPage; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; + +/** + * 管理端,自定义分词接口 + * + * @author paulG + * @since 2020/10/16 + **/ +@Slf4j +@RestController +@Api(tags = "管理端,自定义分词接口") +@RequestMapping("/store/qa") +public class QAStoreController { + + @Autowired + private QAService qaService; + + @ApiOperation(value = "添加问答") + @PostMapping + public ResultMessage addCustomWords(@Valid QA qa) { + qaService.save(qa); + return ResultUtil.data(qa); + } + + @ApiOperation(value = "修改自定义问答") + @PutMapping + public ResultMessage updateCustomWords(@Valid QA qa) { + qaService.updateById(qa); + return ResultUtil.data(qa); + } + + @ApiOperation(value = "删除自定义分词") + @DeleteMapping("/{id}") + public ResultMessage deleteCustomWords(@NotNull @PathVariable String id) { + qaService.removeById(id); + return ResultUtil.success(); + } + + @ApiOperation(value = "分页获取自定义分词") + @ApiImplicitParam(name = "word", value = "问题", required = true, dataType = "String", paramType = "query") + @GetMapping("/page") + public ResultMessage> getCustomWords(@RequestParam String word, PageVO pageVo) { + return ResultUtil.data(qaService.getStoreQA(word, pageVo)); + } + +} diff --git a/src/main/java/cn/lili/controller/tenant/SeatSettingStoreController.java b/src/main/java/cn/lili/controller/tenant/SeatSettingStoreController.java new file mode 100644 index 0000000000000000000000000000000000000000..3debc8025c164732b88a0feb3a14ed1b2318066f --- /dev/null +++ b/src/main/java/cn/lili/controller/tenant/SeatSettingStoreController.java @@ -0,0 +1,41 @@ +package cn.lili.controller.tenant; + +import cn.lili.modules.seat.dos.SeatSetting; +import cn.lili.modules.seat.service.SeatSettingService; +import cn.lili.security.UserContext; +import cn.lili.result.ResultMessage; +import cn.lili.result.ResultUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; + +/** + * 店铺端,分类绑定参数组管理接口 + * + * @author pikachu + * @since 2020-02-18 15:18:56 + */ +@RestController +@Api(tags = "店铺端,坐席设置") +@RequestMapping("/store/seat/setting") +@Transactional(rollbackFor = Exception.class) +public class SeatSettingStoreController { + + @Autowired + private SeatSettingService seatSettingService; + + @ApiOperation(value = "查询坐席设置") + @GetMapping + public ResultMessage getSetting() { + return ResultUtil.data(seatSettingService.getSetting(UserContext.getCurrentUser().getTenantId())); + } + + @ApiOperation(value = "更新坐席设置") + @PutMapping + public void update(SeatSetting seatSetting) { + seatSetting.setTenantId(UserContext.getCurrentUser().getTenantId()); + seatSettingService.updateByStore(seatSetting); + } +} diff --git a/src/main/java/cn/lili/controller/tenant/SeatStoreController.java b/src/main/java/cn/lili/controller/tenant/SeatStoreController.java new file mode 100644 index 0000000000000000000000000000000000000000..cf9085cb7345edfdd093c3b82a4a408b00aa08f5 --- /dev/null +++ b/src/main/java/cn/lili/controller/tenant/SeatStoreController.java @@ -0,0 +1,42 @@ +package cn.lili.controller.tenant; + +import cn.lili.modules.seat.service.SeatService; +import cn.lili.modules.seat.vo.SeatVO; +import cn.lili.security.UserContext; +import cn.lili.result.ResultMessage; +import cn.lili.result.ResultUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * SeatController + * + * @author Chopper + * @version v1.0 + * 2022-02-10 11:50 + */ +@RestController +@Api(tags = "店铺端,坐席管理") +@RequestMapping("/store/seat/setting") +@Transactional(rollbackFor = Exception.class) +public class SeatStoreController { + + + @Autowired + private SeatService seatService; + + @ApiOperation(value = "分页获取坐席") + @GetMapping("/list") + public ResultMessage> getSeats() { + return ResultUtil.data(seatService.seatVoList(UserContext.getCurrentUser().getTenantId())); + } + + +} diff --git a/src/main/java/cn/lili/elasticsearch/BaseElasticsearchService.java b/src/main/java/cn/lili/elasticsearch/BaseElasticsearchService.java new file mode 100644 index 0000000000000000000000000000000000000000..5cc50ec41bc0e49f35cc1d62a0da7b4037a88502 --- /dev/null +++ b/src/main/java/cn/lili/elasticsearch/BaseElasticsearchService.java @@ -0,0 +1,461 @@ +package cn.lili.elasticsearch; + +import cn.hutool.core.bean.BeanUtil; +import cn.lili.elasticsearch.config.ElasticsearchProperties; +import lombok.extern.slf4j.Slf4j; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; +import org.elasticsearch.action.delete.DeleteRequest; +import org.elasticsearch.action.index.IndexRequest; +import org.elasticsearch.action.search.SearchRequest; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.action.update.UpdateRequest; +import org.elasticsearch.client.HttpAsyncResponseConsumerFactory; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.client.indices.CreateIndexRequest; +import org.elasticsearch.client.indices.CreateIndexResponse; +import org.elasticsearch.client.indices.GetIndexRequest; +import org.elasticsearch.client.indices.PutMappingRequest; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author paulG + * @since 2020/10/14 + **/ +@Slf4j +public abstract class BaseElasticsearchService { + + protected static final RequestOptions COMMON_OPTIONS; + + static { + RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder(); + + //默认缓冲限制为100MB,此处修改为30MB。 + builder.setHttpAsyncResponseConsumerFactory(new HttpAsyncResponseConsumerFactory.HeapBufferedResponseConsumerFactory(30 * 1024 * 1024)); + COMMON_OPTIONS = builder.build(); + } + + @Autowired + @Qualifier("elasticsearchClient") + protected RestHighLevelClient client; + + @Autowired + private ElasticsearchProperties elasticsearchProperties; + + /** + * build DeleteIndexRequest + * + * @param index elasticsearch index name + * @author fxbin + */ + private static DeleteIndexRequest buildDeleteIndexRequest(String index) { + return new DeleteIndexRequest(index); + } + + /** + * build IndexRequest + * + * @param index elasticsearch index name + * @param id request object id + * @param object request object + * @return {@link IndexRequest} + * @author fxbin + */ + protected static IndexRequest buildIndexRequest(String index, String id, Object object) { + return new IndexRequest(index).id(id).source(BeanUtil.beanToMap(object), XContentType.JSON); + } + + /** + * create elasticsearch index (asyc) + * + * @param index elasticsearch index + * @author fxbin + */ + protected void createIndexRequest(String index) { + try { + CreateIndexRequest request = new CreateIndexRequest(index); + //Settings for this index + request.settings(Settings.builder() + .put("index.number_of_shards", elasticsearchProperties.getIndex().getNumberOfShards()) + .put("index.number_of_replicas", elasticsearchProperties.getIndex().getNumberOfReplicas()) + .put("index.mapping.total_fields.limit", 2000)); + + //创建索引 + CreateIndexResponse createIndexResponse = client.indices().create(request, COMMON_OPTIONS); + createMapping(index); + log.info(" whether all of the nodes have acknowledged the request : {}", createIndexResponse.isAcknowledged()); + log.info(" Indicates whether the requisite number of shard copies were started for each shard in the index before timing out :{}", createIndexResponse.isShardsAcknowledged()); + } catch (Exception e) { + log.error("创建索引错误",e); + throw new ElasticsearchException("创建索引 {" + index + "} 失败:" + e.getMessage()); + } + } + + public void createMapping(String index) throws Exception { + String source = + " {\n" + + " \"properties\": {\n" + + " \"_class\": {\n" + + " \"type\": \"text\",\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " },\n" + + " \"attrList\": {\n" + + " \"type\": \"nested\",\n" + + " \"properties\": {\n" + + " \"name\": {\n" + + " \"type\": \"keyword\"\n" + + " },\n" + + " \"type\": {\n" + + " \"type\": \"long\"\n" + + " },\n" + + " \"value\": {\n" + + " \"type\": \"keyword\"\n" + + " }\n" + + " }\n" + + " },\n" + + " \"brandId\": {\n" + + " \"type\": \"text\",\n" + + " \"fielddata\": true,\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " },\n" + + " \"brandName\": {\n" + + " \"type\": \"text\",\n" + + " \"fielddata\": true,\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " },\n" + + " \"brandUrl\": {\n" + + " \"type\": \"text\",\n" + + " \"fielddata\": true,\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " },\n" + + " \"buyCount\": {\n" + + " \"type\": \"long\"\n" + + " },\n" + + " \"releaseTime\": {\n" + + " \"type\": \"text\",\n" + + " \"fielddata\": true, \n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " },\n" + + " \"categoryPath\": {\n" + + " \"type\": \"text\",\n" + + " \"fielddata\": true,\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " },\n" + + " \"categoryNamePath\": {\n" + + " \"type\": \"text\",\n" + + " \"fielddata\": true,\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " },\n" + + " \"commentNum\": {\n" + + " \"type\": \"long\"\n" + + " },\n" + + " \"skuSource\": {\n" + + " \"type\": \"long\"\n" + + " },\n" + + " \"goodsId\": {\n" + + " \"type\": \"text\",\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " },\n" + + " \"goodsName\": {\n" + + " \"type\": \"text\",\n" + + " \"fielddata\": true, \n" + + " \"analyzer\": \"ik_max_word\",\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " },\n" + + " \"grade\": {\n" + + " \"type\": \"float\"\n" + + " },\n" + + " \"highPraiseNum\": {\n" + + " \"type\": \"long\"\n" + + " },\n" + + " \"id\": {\n" + + " \"type\": \"text\",\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " },\n" + + " \"intro\": {\n" + + " \"type\": \"text\",\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " },\n" + + " \"authFlag\": {\n" + + " \"type\": \"text\",\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " },\n" + + " \"marketEnable\": {\n" + + " \"type\": \"text\",\n" + + " \"fielddata\": true, \n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " },\n" + + " \"mobileIntro\": {\n" + + " \"type\": \"text\",\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " },\n" + + " \"point\": {\n" + + " \"type\": \"long\"\n" + + " },\n" + + " \"price\": {\n" + + " \"type\": \"float\"\n" + + " },\n" + + " \"salesModel\": {\n" + + " \"type\": \"text\",\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " },\n" + + " \"recommend\": {\n" + + " \"type\": \"boolean\"\n" + + " },\n" + + " \"selfOperated\": {\n" + + " \"type\": \"boolean\"\n" + + " },\n" + + " \"sellerId\": {\n" + + " \"type\": \"text\",\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " },\n" + + " \"sellerName\": {\n" + + " \"type\": \"text\",\n" + + " \"fielddata\": true, \n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " },\n" + + " \"shopCategoryPath\": {\n" + + " \"type\": \"text\",\n" + + " \"fielddata\": true, \n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " },\n" + + " \"sn\": {\n" + + " \"type\": \"text\",\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " },\n" + + " \"promotionMapJson\": {\n" + + " \"type\": \"text\"\n" + + " },\n" + + " \"thumbnail\": {\n" + + " \"type\": \"text\",\n" + + " \"fields\": {\n" + + " \"keyword\": {\n" + + " \"type\": \"keyword\",\n" + + " \"ignore_above\": 256\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n"; + + PutMappingRequest request = new PutMappingRequest(index) + .source(source, XContentType.JSON); + CountDownLatch latch = new CountDownLatch(1); + AtomicReference response = new AtomicReference<>(); + client.indices().putMappingAsync( + request, + RequestOptions.DEFAULT, + new ActionListener() { + @Override + public void onResponse(AcknowledgedResponse r) { + response.set(r); + latch.countDown(); + } + + @Override + public void onFailure(Exception e) { + latch.countDown(); + } + }); + latch.await(10, TimeUnit.SECONDS); + } + + /** + * Description: 判断某个index是否存在 + * + * @param index index名 + * @return boolean + * @author fanxb + * @since 2019/7/24 14:57 + */ + public boolean indexExist(String index) { + try { + GetIndexRequest request = new GetIndexRequest(index); + request.local(false); + request.humanReadable(true); + request.includeDefaults(false); + return client.indices().exists(request, RequestOptions.DEFAULT); + } catch (Exception e) { + throw new ElasticsearchException("获取索引 {" + index + "} 是否存在失败:" + e.getMessage()); + } + } + + /** + * delete elasticsearch index + * + * @param index elasticsearch index name + * @author fxbin + */ + protected void deleteIndexRequest(String index) { + DeleteIndexRequest deleteIndexRequest = buildDeleteIndexRequest(index); + try { + client.indices().delete(deleteIndexRequest, COMMON_OPTIONS); + } catch (IOException e) { + throw new ElasticsearchException("删除索引 {" + index + "} 失败:" + e.getMessage()); + } + } + + /** + * exec updateRequest + * + * @param index elasticsearch index name + * @param id Document id + * @param object request object + * @author fxbin + */ + protected void updateRequest(String index, String id, Object object) { + try { + UpdateRequest updateRequest = new UpdateRequest(index, id).doc(BeanUtil.beanToMap(object), XContentType.JSON); + client.update(updateRequest, COMMON_OPTIONS); + } catch (IOException e) { + throw new ElasticsearchException("更新索引 {" + index + "} 数据 {" + object + "} 失败: " + e.getMessage()); + } + } + + /** + * exec deleteRequest + * + * @param index elasticsearch index name + * @param id Document id + * @author fxbin + */ + protected void deleteRequest(String index, String id) { + try { + DeleteRequest deleteRequest = new DeleteRequest(index, id); + client.delete(deleteRequest, COMMON_OPTIONS); + } catch (IOException e) { + throw new ElasticsearchException("删除索引 {" + index + "} 数据id {" + id + "} 失败: " + e.getMessage()); + } + } + + /** + * search all + * + * @param index elasticsearch index name + * @return {@link SearchResponse} + * @author fxbin + */ + protected SearchResponse search(String index) { + SearchRequest searchRequest = new SearchRequest(index); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(QueryBuilders.matchAllQuery()); + searchRequest.source(searchSourceBuilder); + SearchResponse searchResponse = null; + try { + searchResponse = client.search(searchRequest, COMMON_OPTIONS); + } catch (IOException e) { + log.error("es 搜索错误",e); + } + return searchResponse; + } + + +} diff --git a/src/main/java/cn/lili/elasticsearch/EsSuffix.java b/src/main/java/cn/lili/elasticsearch/EsSuffix.java new file mode 100644 index 0000000000000000000000000000000000000000..9b6e4a9b15f8734676b1239d6356bc740ad6e5c7 --- /dev/null +++ b/src/main/java/cn/lili/elasticsearch/EsSuffix.java @@ -0,0 +1,21 @@ +package cn.lili.elasticsearch; + +/** + * elasticsearch 索引后缀 + * + * @author paulG + * @since 2020/10/13 + **/ +public class EsSuffix { + + /** + * 商品索引后缀 + */ + public static final String GOODS_INDEX_NAME = "goods"; + + /** + * 日志索引后缀 + */ + public static final String LOGS_INDEX_NAME = "logs"; + +} diff --git a/src/main/java/cn/lili/elasticsearch/config/ElasticsearchConfig.java b/src/main/java/cn/lili/elasticsearch/config/ElasticsearchConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..38b63496836adb41a13a4a201c3c9aea0de84ba3 --- /dev/null +++ b/src/main/java/cn/lili/elasticsearch/config/ElasticsearchConfig.java @@ -0,0 +1,107 @@ +package cn.lili.elasticsearch.config; + +import cn.hutool.core.convert.Convert; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.conn.ConnectionKeepAliveStrategy; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.nio.reactor.IOReactorConfig; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestClientBuilder; +import org.elasticsearch.client.RestHighLevelClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration; +import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; + +import javax.annotation.PreDestroy; +import java.io.IOException; +import java.util.List; + +/** + * elasticsearch 配置 + * + * @author paulG + * @since 2020/10/13 + **/ +@Slf4j +@Configuration +public class ElasticsearchConfig extends AbstractElasticsearchConfiguration { + + @Autowired + private ElasticsearchProperties elasticsearchProperties; + + private RestHighLevelClient client; + + @Override + @Bean + public RestHighLevelClient elasticsearchClient() { + RestClientBuilder restBuilder = RestClient + .builder(this.getHttpHosts()); + restBuilder.setHttpClientConfigCallback(httpClientBuilder -> + httpClientBuilder + .setKeepAliveStrategy(getConnectionKeepAliveStrategy()) + .setMaxConnPerRoute(10). + setDefaultIOReactorConfig(IOReactorConfig.custom().setIoThreadCount(1).build())); + String username = elasticsearchProperties.getAccount().getUsername(); + String password = elasticsearchProperties.getAccount().getPassword(); + if (username != null && password != null) { + final CredentialsProvider credential = new BasicCredentialsProvider(); + credential.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password)); + restBuilder.setHttpClientConfigCallback(httpClientBuilder -> + httpClientBuilder + .setDefaultCredentialsProvider(credential) + .setKeepAliveStrategy(getConnectionKeepAliveStrategy()) + .setMaxConnPerRoute(10) + .setDefaultIOReactorConfig(IOReactorConfig.custom().setIoThreadCount(Runtime.getRuntime().availableProcessors()).build())); + } + + restBuilder.setRequestConfigCallback(requestConfigBuilder -> + requestConfigBuilder.setConnectTimeout(1000) //time until a connection with the server is established. + .setSocketTimeout(12 * 1000) //time of inactivity to wait for packets[data] to receive. + .setConnectionRequestTimeout(-1)); //time to fetch a connection from the connection pool 0 for infinite. + + client = new RestHighLevelClient(restBuilder); + return client; + } + + @Bean("elasticsearchRestTemplate") + public ElasticsearchRestTemplate elasticsearchRestTemplate() { + return new ElasticsearchRestTemplate(this.client); + } + + private HttpHost[] getHttpHosts() { + List clusterNodes = elasticsearchProperties.getClusterNodes(); + HttpHost[] httpHosts = new HttpHost[clusterNodes.size()]; + for (int i = 0; i < clusterNodes.size(); i++) { + String[] node = clusterNodes.get(i).split(":"); + httpHosts[i] = new HttpHost(node[0], Convert.toInt(node[1]), elasticsearchProperties.getSchema()); + } + return httpHosts; + } + + private ConnectionKeepAliveStrategy getConnectionKeepAliveStrategy() { + return (response, context) -> 2 * 60 * 1000; + } + + /** + * it gets called when bean instance is getting removed from the context if + * scope is not a prototype + * If there is a method named shutdown or close then spring container will try + * to automatically configure them as callback methods when bean is being + * destroyed + */ + @PreDestroy + public void clientClose() { + try { + this.client.close(); + } catch (IOException e) { + log.error("es clientClose错误", e); + } + } + +} diff --git a/src/main/java/cn/lili/elasticsearch/config/ElasticsearchProperties.java b/src/main/java/cn/lili/elasticsearch/config/ElasticsearchProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..e6fdbda2f7753550c14f83419a767286f01dfeca --- /dev/null +++ b/src/main/java/cn/lili/elasticsearch/config/ElasticsearchProperties.java @@ -0,0 +1,119 @@ +package cn.lili.elasticsearch.config; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import javax.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.List; + +/** + * @author paulG + * @since 2020/10/13 + **/ +@Data +@Builder +@Component("elasticsearchProperties") +@NoArgsConstructor +@AllArgsConstructor +@ConfigurationProperties(prefix = "lili.data.elasticsearch") +public class ElasticsearchProperties { + + /** + * 请求协议 + */ + private String schema = "https"; + + /** + * 集群名称 + */ + private String clusterName = "elasticsearch"; + + /** + * 集群节点 + */ + @NotNull(message = "集群节点不允许为空") + private List clusterNodes = new ArrayList<>(); + + /** + * 索引前缀 + */ + private String indexPrefix; + + /** + * 连接超时时间(毫秒) + */ + private Integer connectTimeout = 1000; + + /** + * socket 超时时间 + */ + private Integer socketTimeout = 30000; + + /** + * 连接请求超时时间 + */ + private Integer connectionRequestTimeout = 500; + + /** + * 每个路由的最大连接数量 + */ + private Integer maxConnectPerRoute = 10; + + /** + * 最大连接总数量 + */ + private Integer maxConnectTotal = 30; + + /** + * 索引配置信息 + */ + private Index index = new Index(); + + /** + * 认证账户 + */ + private Account account = new Account(); + + /** + * 索引配置信息 + */ + @Data + public static class Index { + + /** + * 分片数量 + */ + private Integer numberOfShards = 3; + + /** + * 副本数量 + */ + private Integer numberOfReplicas = 2; + + } + + /** + * 认证账户 + */ + @Data + public static class Account { + + /** + * 认证用户 + */ + private String username; + + /** + * 认证密码 + */ + private String password; + + } + + +} diff --git a/src/main/java/cn/lili/exception/GlobalControllerExceptionHandler.java b/src/main/java/cn/lili/exception/GlobalControllerExceptionHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..957e5781a9b6b794f4f9bb80fc8ba4ecc3589883 --- /dev/null +++ b/src/main/java/cn/lili/exception/GlobalControllerExceptionHandler.java @@ -0,0 +1,140 @@ +package cn.lili.exception; + +import cn.lili.result.ResultCode; +import cn.lili.result.ResultMessage; +import cn.lili.result.ResultUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.validation.BindException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.ConstraintViolationException; +import java.util.List; + +/** + * 全局异常异常处理 + * + * @author Chopper + */ +@RestControllerAdvice +@Slf4j +public class GlobalControllerExceptionHandler { + + /** + * 如果超过长度,则前后段交互体验不佳,使用默认错误消息 + */ + static Integer MAX_LENGTH = 200; + + /** + * 自定义异常 + * + * @param e + * @return + */ + @ExceptionHandler(ServiceException.class) + @ResponseStatus(value = HttpStatus.BAD_REQUEST) + public ResultMessage handleServiceException(HttpServletRequest request, final Exception e, HttpServletResponse response) { + + + //如果是自定义异常,则获取异常,返回自定义错误消息 + if (e instanceof ServiceException) { + ServiceException serviceException = ((ServiceException) e); + ResultCode resultCode = serviceException.getResultCode(); + + Integer code = null; + String message = null; + + if (resultCode != null) { + code = resultCode.code(); + message = resultCode.message(); + } + //如果有扩展消息,则输出异常中,跟随补充异常 + if (!serviceException.getMsg().equals(ServiceException.DEFAULT_MESSAGE)) { + message += ":" + serviceException.getMsg(); + } + + log.error("全局异常[ServiceException]:{}-{}", serviceException.getResultCode().code(), serviceException.getResultCode().message(), e); + return ResultUtil.error(code, message); + + } else { + + log.error("全局异常[ServiceException]:", e); + } + + //默认错误消息 + String errorMsg = "服务器异常,请稍后重试"; + if (e != null && e.getMessage() != null && e.getMessage().length() < MAX_LENGTH) { + errorMsg = e.getMessage(); + } + return ResultUtil.error(ResultCode.ERROR.code(), errorMsg); + } + + @ExceptionHandler(RuntimeException.class) + @ResponseStatus(value = HttpStatus.BAD_REQUEST) + public ResultMessage runtimeExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) { + + log.error("全局异常[RuntimeException]:", e); + + return ResultUtil.error(ResultCode.ERROR); + } + +// /** +// * 通用的接口映射异常处理方 +// */ +// @Override +// protected ResponseEntity handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) { +// if (ex instanceof MethodArgumentNotValidException) { +// MethodArgumentNotValidException exception = (MethodArgumentNotValidException) ex; +// return new ResponseEntity<>(new ResultUtil<>().setErrorMsg(exception.getBindingResult().getAllErrors().get(0).getDefaultMessage()), status); +// } +// if (ex instanceof MethodArgumentTypeMismatchException) { +// MethodArgumentTypeMismatchException exception = (MethodArgumentTypeMismatchException) ex; +// logger.error("参数转换失败,方法:" + exception.getParameter().getMethod().getName() + ",参数:" + exception.getName() +// + ",信息:" + exception.getLocalizedMessage()); +// return new ResponseEntity<>(new ResultUtil<>().setErrorMsg("参数转换失败"), status); +// } +// ex.printStackTrace(); +// return new ResponseEntity<>(new ResultUtil<>().setErrorMsg("未知异常,请联系管理员"), status); +// } + + /** + * bean校验未通过异常 + * + * @see javax.validation.Valid + * @see org.springframework.validation.Validator + * @see org.springframework.validation.DataBinder + */ + @ExceptionHandler(BindException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseBody + public ResultMessage validExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) { + + BindException exception = (BindException) e; + List fieldErrors = exception.getBindingResult().getFieldErrors(); + for (FieldError error : fieldErrors) { + return ResultUtil.error(ResultCode.PARAMS_ERROR.code(), error.getDefaultMessage()); + } + return ResultUtil.error(ResultCode.PARAMS_ERROR); + } + + /** + * bean校验未通过异常 + * + * @see javax.validation.Valid + * @see org.springframework.validation.Validator + * @see org.springframework.validation.DataBinder + */ + @ExceptionHandler(ConstraintViolationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseBody + public ResultMessage constraintViolationExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) { + ConstraintViolationException exception = (ConstraintViolationException) e; + return ResultUtil.error(ResultCode.PARAMS_ERROR.code(), exception.getMessage()); + } +} diff --git a/src/main/java/cn/lili/exception/ServiceException.java b/src/main/java/cn/lili/exception/ServiceException.java new file mode 100644 index 0000000000000000000000000000000000000000..323d2904431a1d464af07abf631ab8b1043d4b6b --- /dev/null +++ b/src/main/java/cn/lili/exception/ServiceException.java @@ -0,0 +1,48 @@ +package cn.lili.exception; + +import cn.lili.result.ResultCode; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 全局业务异常类 + * + * @author Chopper + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class ServiceException extends RuntimeException { + + private static final long serialVersionUID = 3447728300174142127L; + + public static final String DEFAULT_MESSAGE = "网络错误,请稍后重试!"; + + /** + * 异常消息 + */ + private String msg = DEFAULT_MESSAGE; + + /** + * 错误码 + */ + private ResultCode resultCode; + + public ServiceException(String msg) { + this.resultCode = ResultCode.ERROR; + this.msg = msg; + } + + public ServiceException() { + super(); + } + + public ServiceException(ResultCode resultCode) { + this.resultCode = resultCode; + } + + public ServiceException(ResultCode resultCode, String message) { + this.resultCode = resultCode; + this.msg = message; + } + +} diff --git a/src/main/java/cn/lili/po/Message.java b/src/main/java/cn/lili/modules/im/entity/ImMessage.java similarity index 38% rename from src/main/java/cn/lili/po/Message.java rename to src/main/java/cn/lili/modules/im/entity/ImMessage.java index c702ba84c7dcf44956e9b54f9257396ab16fad84..8206a4148b6abdbf9b919dad9e5fb0891c4fab97 100644 --- a/src/main/java/cn/lili/po/Message.java +++ b/src/main/java/cn/lili/modules/im/entity/ImMessage.java @@ -1,38 +1,52 @@ -package cn.lili.po; - -import java.util.Date; -import java.util.List; +package cn.lili.modules.im.entity; +import cn.lili.modules.im.entity.enums.MessageType; +import cn.lili.mybatis.BaseEntity; import com.alibaba.fastjson.annotation.JSONField; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; import lombok.Data; +import java.util.Date; + +/** + * @author Chopper + */ @Data -public class Message { +@TableName("li_im_message") +@ApiModel(value = "Im消息") +public class ImMessage extends BaseEntity { + + private static final long serialVersionUID = 1L; /** * 发送者 */ - private String from; + private String fromUser; + /** * 接收者 */ - private String to; + private String toUser; /** - * 消息类型 + * 已阅 */ - private MessageType messageType; + private Boolean isRead; + /** - * 消息实体 + * 消息类型 */ - private String text; + private MessageType messageType; + /** - * 在线用户 + * 聊天id */ - private Object users; + private String talkId; + /** - * 发送时间 + * 消息实体 */ - @JSONField(format = "yyyy-MM-dd HH:mm:ss") - private Date date; -} + private String text; + +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/im/entity/ImTalk.java b/src/main/java/cn/lili/modules/im/entity/ImTalk.java new file mode 100644 index 0000000000000000000000000000000000000000..64c1d4538d12f107b8279bc31c6d8ad9eed272be --- /dev/null +++ b/src/main/java/cn/lili/modules/im/entity/ImTalk.java @@ -0,0 +1,96 @@ +package cn.lili.modules.im.entity; + +import cn.lili.mybatis.BaseTenantEntity; +import cn.lili.utils.SnowFlake; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.util.Date; + +/** + * @author Chopper + */ +@Data +@TableName("li_im_talk") +@ApiModel(value = "聊天") +public class ImTalk extends BaseTenantEntity { + + private static final long serialVersionUID = 1L; + + /** + * 用户1 id小的排在1 + */ + private String userId1; + /** + * 用户1 id大的排在2 + */ + private String userId2; + + /** + * 用户1置顶 + */ + private Boolean top1; + + /** + * 用户2置顶 + */ + private Boolean top2; + /** + * 用户1 不可见 + */ + private Boolean disable1; + + /** + * 用户2 不可见 + */ + private Boolean disable2; + /** + * 用户1名字 + */ + private String name1; + + /** + * 用户2名字 + */ + private String name2; + /** + * 用户1头像 + */ + private String face1; + + /** + * 用户2头像 + */ + private String face2; + + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty(value = "最后聊天时间", hidden = true) + private Date lastTalkTime; + + public ImTalk() { + + } + + public ImTalk(String userId1, String userId2, + String face1, String face2, + String name1, String name2 + ) { + this.userId1 = userId1; + this.userId2 = userId2; + this.top1 = false; + this.top2 = false; + this.disable1 = false; + this.disable2 = false; + this.setId(SnowFlake.getIdStr()); + this.lastTalkTime = new Date(); + this.face1 = face1; + this.face2 = face2; + this.name1 = name1; + this.name2 = name2; + } +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/im/entity/ImUser.java b/src/main/java/cn/lili/modules/im/entity/ImUser.java new file mode 100644 index 0000000000000000000000000000000000000000..8db57b7c111740ee05bf010a456d8f531a597533 --- /dev/null +++ b/src/main/java/cn/lili/modules/im/entity/ImUser.java @@ -0,0 +1,26 @@ +package cn.lili.modules.im.entity; + + +import cn.lili.mybatis.BaseTenantEntity; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import lombok.Data; + +/** + * @author Chopper + */ +@Data +@TableName("li_im_users") +@ApiModel(value = "Im消息") +public class ImUser extends BaseTenantEntity { + + private static final long serialVersionUID = 1L; + /** + * 头像 + */ + private String face; + /** + * 昵称 + */ + private String name; +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/im/entity/dto/MessageQueryParams.java b/src/main/java/cn/lili/modules/im/entity/dto/MessageQueryParams.java new file mode 100644 index 0000000000000000000000000000000000000000..2a643b4ec70fe1bc95a3f09d98480d8980a5fe76 --- /dev/null +++ b/src/main/java/cn/lili/modules/im/entity/dto/MessageQueryParams.java @@ -0,0 +1,50 @@ +package cn.lili.modules.im.entity.dto; + +import cn.lili.exception.ServiceException; +import cn.lili.modules.im.entity.ImMessage; +import cn.lili.result.ResultCode; +import cn.lili.utils.StringUtils; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.Data; + +/** + * MessageQueryParams + * + * @author Chopper + * @version v1.0 + * 2022-01-20 17:16 + */ +@Data +public class MessageQueryParams { + /** + * 聊天窗口 + */ + private String talkId; + /** + * 最后一个消息 + */ + private String lastMessageId; + /** + * 获取消息数量 + */ + private Integer num; + + public LambdaQueryWrapper initQueryWrapper() { + if (StringUtils.isEmpty(talkId)) { + throw new ServiceException(ResultCode.ERROR); + } + if (num == null || num > 50) { + num = 50; + } + + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(ImMessage::getTalkId, talkId); + if (StringUtils.isNotEmpty(lastMessageId)) { + lambdaQueryWrapper.lt(ImMessage::getId, lastMessageId); + } + lambdaQueryWrapper.orderByDesc(ImMessage::getId); + lambdaQueryWrapper.last("limit " + num); + return lambdaQueryWrapper; + } +} diff --git a/src/main/java/cn/lili/modules/im/entity/enums/MessageResultType.java b/src/main/java/cn/lili/modules/im/entity/enums/MessageResultType.java new file mode 100644 index 0000000000000000000000000000000000000000..ba54b5d52d4e7385e07fae8d19e5b76440c6d80b --- /dev/null +++ b/src/main/java/cn/lili/modules/im/entity/enums/MessageResultType.java @@ -0,0 +1,28 @@ +package cn.lili.modules.im.entity.enums; + +/** + * 返回消息类型枚举 + * + * @author liushuai + */ +public enum MessageResultType { + /** + * 返回消息类型枚举 + *

+ * 好友列表 + * 增加好友 + * 消息 + * 阅读消息 + * 未读消息 + * 历史消息 + * 系统提示 + */ + FRIENDS, + ADD_FRIENDS, + MESSAGE, + READ_MESSAGE, + UN_READ, + HISTORY, + SYSTEM_TIPS + +} diff --git a/src/main/java/cn/lili/modules/im/entity/enums/MessageType.java b/src/main/java/cn/lili/modules/im/entity/enums/MessageType.java new file mode 100644 index 0000000000000000000000000000000000000000..fae06d0faca4c21091f30eb1671ef1ae74da7dc7 --- /dev/null +++ b/src/main/java/cn/lili/modules/im/entity/enums/MessageType.java @@ -0,0 +1,21 @@ +package cn.lili.modules.im.entity.enums; + +/** + * 消息类型 + * + * @author liushuai + */ +public enum MessageType { + /** + * 消息类型枚举 + *

+ * 普通消息 + * 图片 + * 语音 + * 视频 + */ + MESSAGE, + PICTURE, + VOICE, + VIDEO +} diff --git a/src/main/java/cn/lili/modules/im/entity/enums/OperationType.java b/src/main/java/cn/lili/modules/im/entity/enums/OperationType.java new file mode 100644 index 0000000000000000000000000000000000000000..5909222e928457c8b61bbdbe201fc965b1e80f0e --- /dev/null +++ b/src/main/java/cn/lili/modules/im/entity/enums/OperationType.java @@ -0,0 +1,26 @@ +package cn.lili.modules.im.entity.enums; + +/** + * 操作类型枚举 + * + * @author liushuai + */ +public enum OperationType { + /** + * 消息类型枚举 + *

+ * 心跳检测 + * 发起聊天 + * 发起消息 + * 查询历史消息 + * 阅读消息 + * 查询未读消息 + */ + PING, + CREATE, + MESSAGE, + HISTORY, + READ, + UNREAD, + +} diff --git a/src/main/java/cn/lili/modules/im/entity/vo/ImTalkVO.java b/src/main/java/cn/lili/modules/im/entity/vo/ImTalkVO.java new file mode 100644 index 0000000000000000000000000000000000000000..2d65e25979e661e0ad64cab52a4f0a99c74cb365 --- /dev/null +++ b/src/main/java/cn/lili/modules/im/entity/vo/ImTalkVO.java @@ -0,0 +1,78 @@ +package cn.lili.modules.im.entity.vo; + +import cn.lili.modules.im.entity.ImTalk; +import cn.lili.mybatis.BaseTenantEntity; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.util.Date; + +/** + * @author Chopper + */ +@Data +@ApiModel(value = "聊天") +public class ImTalkVO extends BaseTenantEntity { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + private String id; + /** + * 用户 id + */ + private String userId; + + /** + * 置顶 + */ + private Boolean top; + + /** + * 用户 不可见 + */ + private Boolean disable; + + /** + * 用户名字 + */ + private String name; + + /** + * 用户头像 + */ + private String face; + + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty(value = "最后聊天时间", hidden = true) + private Date lastTalkTime; + + public ImTalkVO() { + + } + + public ImTalkVO(ImTalk imTalk, String currentUser) { + if (imTalk.getUserId2().equals(currentUser)) { + userId = imTalk.getUserId1(); + top = imTalk.getTop1(); + disable = imTalk.getDisable1(); + name = imTalk.getName1(); + face = imTalk.getFace1(); + } else { + userId = imTalk.getUserId2(); + top = imTalk.getTop2(); + disable = imTalk.getDisable2(); + name = imTalk.getName2(); + face = imTalk.getFace2(); + } + + lastTalkTime = imTalk.getLastTalkTime(); + id = imTalk.getId(); + } +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/im/entity/vo/MessageOperation.java b/src/main/java/cn/lili/modules/im/entity/vo/MessageOperation.java new file mode 100644 index 0000000000000000000000000000000000000000..01eea96b2c67beb3961ef6e54bef153503a0ea48 --- /dev/null +++ b/src/main/java/cn/lili/modules/im/entity/vo/MessageOperation.java @@ -0,0 +1,48 @@ +package cn.lili.modules.im.entity.vo; + +import cn.lili.modules.im.entity.enums.MessageType; +import cn.lili.modules.im.entity.enums.OperationType; +import cn.lili.utils.StringUtils; +import lombok.Data; + +/** + * @author liushuai + */ +@Data +public class MessageOperation { + + /** + * 消息类型 + */ + private OperationType operationType; + /** + * 与某人聊天记录 + */ + private String to; + + /** + * 聊天id + */ + private String talkId; + + /** + * 消息类型 + */ + private MessageType messageType; + /** + * 消息内容 + */ + private String context; + + public void setOperationType(String operationType) { + if (!StringUtils.isEmpty(operationType)) { + this.operationType = OperationType.valueOf(operationType); + } + } + + public void setMessageType(String messageType) { + if (!StringUtils.isEmpty(messageType)) { + this.messageType = MessageType.valueOf(messageType); + } + } +} diff --git a/src/main/java/cn/lili/modules/im/entity/vo/MessageVO.java b/src/main/java/cn/lili/modules/im/entity/vo/MessageVO.java new file mode 100644 index 0000000000000000000000000000000000000000..4821583ec375360c1bd4c28219fedf4ee26ce4fe --- /dev/null +++ b/src/main/java/cn/lili/modules/im/entity/vo/MessageVO.java @@ -0,0 +1,27 @@ +package cn.lili.modules.im.entity.vo; + +import cn.lili.modules.im.entity.enums.MessageResultType; +import cn.lili.modules.im.entity.enums.OperationType; +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * MessageVO + * + * @author Chopper + * @version v1.0 + * 2021-12-30 15:51 + */ +@Data +@AllArgsConstructor +public class MessageVO { + + /** + * 消息类型 + */ + private MessageResultType messageResultType; + /** + * 消息内容 + */ + private Object result; +} diff --git a/src/main/java/cn/lili/modules/im/entity/vo/ReadMessage.java b/src/main/java/cn/lili/modules/im/entity/vo/ReadMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..bbeb7d11ad81cbad512bb97a7e6f65de942a6f88 --- /dev/null +++ b/src/main/java/cn/lili/modules/im/entity/vo/ReadMessage.java @@ -0,0 +1,17 @@ +package cn.lili.modules.im.entity.vo; + +import lombok.Data; + +import java.util.List; + +/** + * ReadMessage + * + * @author Chopper + * @version v1.0 + * 2021-12-31 11:13 + */ +@Data +public class ReadMessage { + private List readMessageList; +} diff --git a/src/main/java/cn/lili/modules/im/mapper/ImMessageMapper.java b/src/main/java/cn/lili/modules/im/mapper/ImMessageMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..2d045b88201ef30c21004bb59abae29af30dc95c --- /dev/null +++ b/src/main/java/cn/lili/modules/im/mapper/ImMessageMapper.java @@ -0,0 +1,14 @@ +package cn.lili.modules.im.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import cn.lili.modules.im.entity.ImMessage; + +import java.util.List; + +/** + * Im消息 Dao层 + * @author Chopper + */ +public interface ImMessageMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/im/mapper/ImTalkMapper.java b/src/main/java/cn/lili/modules/im/mapper/ImTalkMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..537175e5162e165c17fe19b9f3bdc50e678369a1 --- /dev/null +++ b/src/main/java/cn/lili/modules/im/mapper/ImTalkMapper.java @@ -0,0 +1,14 @@ +package cn.lili.modules.im.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import cn.lili.modules.im.entity.ImTalk; + +import java.util.List; + +/** + * 聊天 Dao层 + * @author Chopper + */ +public interface ImTalkMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/im/mapper/ImUserMapper.java b/src/main/java/cn/lili/modules/im/mapper/ImUserMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..4525fba67170239d42956c13a73ea1698fd984d8 --- /dev/null +++ b/src/main/java/cn/lili/modules/im/mapper/ImUserMapper.java @@ -0,0 +1,13 @@ +package cn.lili.modules.im.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import cn.lili.modules.im.entity.ImUser; + +/** + * Im消息 Dao层 + * + * @author Chopper + */ +public interface ImUserMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/im/service/ImMessageService.java b/src/main/java/cn/lili/modules/im/service/ImMessageService.java new file mode 100644 index 0000000000000000000000000000000000000000..923699d8bd33bac1f945a3912bcd4e1ebec008ce --- /dev/null +++ b/src/main/java/cn/lili/modules/im/service/ImMessageService.java @@ -0,0 +1,37 @@ +package cn.lili.modules.im.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import cn.lili.modules.im.entity.ImMessage; + +import java.util.List; + +/** + * Im消息 业务层 + * + * @author Chopper + */ +public interface ImMessageService extends IService { + + /** + * 阅读消息 + * + * @param talkId + * @param messageId + */ + void read(String talkId, String messageId); + + /** + * 未读消息列表 + * + * @param accessToken + */ + List unReadMessages(String accessToken); + + /** + * 历史消息 + * + * @param accessToken + * @param to + */ + List historyMessage(String accessToken, String to); +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/im/service/ImTalkService.java b/src/main/java/cn/lili/modules/im/service/ImTalkService.java new file mode 100644 index 0000000000000000000000000000000000000000..2733f80f89e3648aff2f9a86a94604b0d9e67f60 --- /dev/null +++ b/src/main/java/cn/lili/modules/im/service/ImTalkService.java @@ -0,0 +1,39 @@ +package cn.lili.modules.im.service; + +import cn.lili.result.ResultMessage; +import com.baomidou.mybatisplus.extension.service.IService; +import cn.lili.modules.im.entity.ImTalk; + +import java.util.List; + +/** + * 聊天 业务层 + * + * @author Chopper + */ +public interface ImTalkService extends IService { + + /** + * 获取与某人的聊天框 + * + * @param userId1 + * @param userId2 + * @return + */ + ImTalk getTalkByUser(String userId1, String userId2); + + /** + * 置顶消息 + * + * @param id + * @param top + */ + void top(String id, Boolean top); + + /** + * 禁用(前端不做展示)聊天 + * + * @param id + */ + void disable(String id); +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/im/service/ImUserService.java b/src/main/java/cn/lili/modules/im/service/ImUserService.java new file mode 100644 index 0000000000000000000000000000000000000000..29c4e3b3b68beb3d1d9348fcef0e80833f097c7a --- /dev/null +++ b/src/main/java/cn/lili/modules/im/service/ImUserService.java @@ -0,0 +1,21 @@ +package cn.lili.modules.im.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import cn.lili.modules.im.entity.ImUser; + +/** + * Im消息 业务层 + * + * @author Chopper + */ +public interface ImUserService extends IService { + + /** + * 注册用户 + * + * @param accessToken + * @return + */ + ImUser register(String accessToken); + +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/im/serviceimpl/ImMessageServiceImpl.java b/src/main/java/cn/lili/modules/im/serviceimpl/ImMessageServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..bd980aaf119c8c9104b4783962876f53523d0e00 --- /dev/null +++ b/src/main/java/cn/lili/modules/im/serviceimpl/ImMessageServiceImpl.java @@ -0,0 +1,55 @@ +package cn.lili.modules.im.serviceimpl; + +import cn.lili.modules.im.mapper.ImMessageMapper; +import cn.lili.modules.im.entity.ImMessage; +import cn.lili.modules.im.service.ImMessageService; +import cn.lili.security.UserContext; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * Im消息 业务实现 + * + * @author Chopper + */ +@Service +@Transactional(rollbackFor = Exception.class) +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class ImMessageServiceImpl extends ServiceImpl implements ImMessageService { + + @Override + public void read(String talkId, String messageId) { + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(ImMessage::getTalkId, talkId); + updateWrapper.le(ImMessage::getId, messageId); + updateWrapper.eq(ImMessage::getIsRead, false); + updateWrapper.set(ImMessage::getIsRead, true); + this.update(updateWrapper); + } + + @Override + public List unReadMessages(String accessToken) { + String userId = UserContext.getAuthUser(accessToken).getId(); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(ImMessage::getToUser, userId); + queryWrapper.eq(ImMessage::getIsRead, false); + return this.list(queryWrapper); + } + + @Override + public List historyMessage(String accessToken, String to) { + String userId = UserContext.getAuthUser(accessToken).getId(); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.and(i -> i.eq(ImMessage::getToUser, userId).and(j -> j.eq(ImMessage::getFromUser, to))); + queryWrapper.or(i -> i.eq(ImMessage::getToUser, to).and(j -> j.eq(ImMessage::getFromUser, userId))); + queryWrapper.orderByDesc(ImMessage::getCreateTime); + return this.list(queryWrapper); + } +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/im/serviceimpl/ImTalkServiceImpl.java b/src/main/java/cn/lili/modules/im/serviceimpl/ImTalkServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..9fbdffe6d534a8d936732c89f4a442f78205a31f --- /dev/null +++ b/src/main/java/cn/lili/modules/im/serviceimpl/ImTalkServiceImpl.java @@ -0,0 +1,92 @@ +package cn.lili.modules.im.serviceimpl; + +import cn.lili.exception.ServiceException; +import cn.lili.modules.im.entity.ImUser; +import cn.lili.modules.im.mapper.ImTalkMapper; +import cn.lili.modules.im.entity.ImTalk; +import cn.lili.modules.im.service.ImTalkService; +import cn.lili.modules.im.service.ImUserService; +import cn.lili.security.UserContext; +import cn.lili.result.ResultCode; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * 聊天 业务实现 + * + * @author Chopper + */ +@Service +@Transactional(rollbackFor = Exception.class) +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class ImTalkServiceImpl extends ServiceImpl implements ImTalkService { + + @Autowired + private ImUserService imUserService; + + @Override + public ImTalk getTalkByUser(String userId1, String userId2) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(ImTalk::getUserId2, userId2); + queryWrapper.eq(ImTalk::getUserId1, userId1); + ImTalk imTalk = this.getOne(queryWrapper); + ImUser imUser1 = imUserService.getById(userId1); + ImUser imUser2 = imUserService.getById(userId2); + + //如果没有聊天,则创建聊天 + if (imTalk == null) { + if (imUser1 == null || imUser2 == null) { + return null; + } + imTalk = new ImTalk(userId1, userId2, imUser1.getFace(), imUser2.getFace(), imUser1.getName(), imUser2.getName()); + this.save(imTalk); + } else { + imTalk = check(imTalk); + } + return imTalk; + } + + /** + * 发起聊天后,如果聊天不可见为true,则需要修正 + * + * @param imTalk + */ + private ImTalk check(ImTalk imTalk) { + if (imTalk.getDisable1() || imTalk.getDisable2()) { + imTalk.setDisable1(false); + imTalk.setDisable2(false); + this.updateById(imTalk); + + } + return imTalk; + } + + @Override + public void top(String id, Boolean top) { + ImTalk imTalk = this.getById(id); + if (imTalk.getUserId1().equals(UserContext.getAuthUser().getId())) { + imTalk.setTop1(top); + } else if (imTalk.getUserId2().equals(UserContext.getAuthUser().getId())) { + imTalk.setTop2(top); + } else { + throw new ServiceException(ResultCode.ERROR); + } + this.updateById(imTalk); + } + + @Override + public void disable(String id) { + ImTalk imTalk = this.getById(id); + if (imTalk.getUserId1().equals(UserContext.getCurrentUser().getId())) { + imTalk.setDisable1(true); + this.updateById(imTalk); + } else if (imTalk.getUserId2().equals(UserContext.getCurrentUser().getId())) { + imTalk.setDisable2(true); + this.updateById(imTalk); + } + } +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/im/serviceimpl/ImUserServiceImpl.java b/src/main/java/cn/lili/modules/im/serviceimpl/ImUserServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..48645a105f3d52b88f98647295791d2e11d3550f --- /dev/null +++ b/src/main/java/cn/lili/modules/im/serviceimpl/ImUserServiceImpl.java @@ -0,0 +1,40 @@ +package cn.lili.modules.im.serviceimpl; + +import cn.lili.modules.im.mapper.ImUserMapper; +import cn.lili.modules.im.entity.ImUser; +import cn.lili.modules.im.service.ImUserService; +import cn.lili.security.AuthUser; +import cn.lili.security.UserContext; +import cn.lili.security.enums.UserEnums; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * Im消息 业务实现 + * + * @author Chopper + */ +@Service +@Transactional(rollbackFor = Exception.class) +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class ImUserServiceImpl extends ServiceImpl implements ImUserService { + + @Override + public ImUser register(String accessToken) { + AuthUser authUser = UserContext.getAuthUser(accessToken); + ImUser imUser; + //如果用户存在 + imUser = this.getById(authUser.getId()); + if (imUser == null) { + imUser = new ImUser(); + imUser.setId(authUser.getId()); + imUser.setName(authUser.getNickName()); + this.save(imUser); + } + return imUser; + } + +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/permission/controller/AdminUserManagerController.java b/src/main/java/cn/lili/modules/permission/controller/AdminUserManagerController.java new file mode 100644 index 0000000000000000000000000000000000000000..fe51522a6d336b41502d73dc29a1940df3a81f61 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/controller/AdminUserManagerController.java @@ -0,0 +1,178 @@ +package cn.lili.modules.permission.controller; + +import cn.lili.exception.ServiceException; +import cn.lili.modules.permission.entity.dos.AdminUser; +import cn.lili.modules.permission.entity.dto.AdminUserDTO; +import cn.lili.modules.permission.entity.vo.AdminUserVO; +import cn.lili.modules.permission.service.AdminUserService; +import cn.lili.modules.permission.service.DepartmentService; +import cn.lili.mybatis.util.PageUtil; +import cn.lili.result.*; +import cn.lili.security.AuthUser; +import cn.lili.security.UserContext; +import cn.lili.security.enums.UserEnums; +import cn.lili.security.token.Token; +import cn.lili.utils.StringUtils; +import com.baomidou.mybatisplus.core.metadata.IPage; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import java.util.List; + + +/** + * 管理员接口 + * + * @author Chopper + * @since 2020/11/16 10:57 + */ +@Slf4j +@RestController +@Api(tags = "管理员") +@RequestMapping("/manager/passport/user") +@Validated +public class AdminUserManagerController { + @Autowired + private AdminUserService adminUserService; + @Autowired + private DepartmentService departmentService; + + @PostMapping(value = "/login") + @ApiOperation(value = "登录管理员") + public ResultMessage login(@NotNull(message = "用户名不能为空") String username, + @NotNull(message = "密码不能为空") String password) { + return ResultUtil.data(adminUserService.login(username, password)); + } + + @ApiOperation(value = "注销接口") + @PostMapping("/logout") + public ResultMessage logout() { + this.adminUserService.logout(UserEnums.MANAGER); + return ResultUtil.success(); + } + + @ApiOperation(value = "刷新token") + @GetMapping("/refresh/{refreshToken}") + public ResultMessage refreshToken(@NotNull(message = "刷新token不能为空") @PathVariable String refreshToken) { + return ResultUtil.data(this.adminUserService.refreshToken(refreshToken)); + } + + + @GetMapping(value = "/info") + @ApiOperation(value = "获取当前登录用户接口") + public ResultMessage getUserInfo() { + AuthUser tokenUser = UserContext.getCurrentUser(); + if (tokenUser != null) { + AdminUserVO adminUser = new AdminUserVO(adminUserService.findByUsername(tokenUser.getUsername())); + if (StringUtils.isNotEmpty(adminUser.getDepartmentId())) { + adminUser.setDepartmentTitle(departmentService.getById(adminUser.getDepartmentId()).getTitle()); + } + adminUser.setPassword(null); + return ResultUtil.data(adminUser); + } + throw new ServiceException(ResultCode.USER_NOT_LOGIN); + } + + @PutMapping(value = "/edit") + @ApiOperation(value = "修改用户自己资料", notes = "用户名密码不会修改") + public ResultMessage editOwner(AdminUser adminUser) { + + AuthUser tokenUser = UserContext.getCurrentUser(); + if (tokenUser != null) { + //查询当前管理员 + AdminUser oldAdminUser = adminUserService.findByUsername(tokenUser.getUsername()); + oldAdminUser.setAvatar(adminUser.getAvatar()); + oldAdminUser.setNickName(adminUser.getNickName()); + if (!adminUserService.updateById(oldAdminUser)) { + throw new ServiceException(ResultCode.USER_EDIT_ERROR); + } + return ResultUtil.success(ResultCode.USER_EDIT_SUCCESS); + } + throw new ServiceException(ResultCode.USER_NOT_LOGIN); + } + + @PutMapping(value = "/admin/edit") + @ApiOperation(value = "超级管理员修改其他管理员资料") + public ResultMessage edit(@Valid AdminUser adminUser, + @RequestParam(required = false) List roles) { + if (!adminUserService.updateAdminUser(adminUser, roles)) { + throw new ServiceException(ResultCode.USER_EDIT_ERROR); + } + return ResultUtil.success(ResultCode.USER_EDIT_SUCCESS); + } + + /** + * 修改密码 + * + * @param password + * @param newPassword + * @return + */ + @PutMapping(value = "/editPassword") + @ApiOperation(value = "修改密码") + public ResultMessage editPassword(String password, String newPassword) { + adminUserService.editPassword(password, newPassword); + return ResultUtil.success(ResultCode.USER_EDIT_SUCCESS); + } + + @PostMapping(value = "/resetPassword/{ids}") + @ApiOperation(value = "重置密码") + public ResultMessage resetPassword(@PathVariable List ids) { + adminUserService.resetPassword(ids); + return ResultUtil.success(ResultCode.USER_EDIT_SUCCESS); + } + + @GetMapping + @ApiOperation(value = "多条件分页获取用户列表") + public ResultMessage> getByCondition(AdminUserDTO user, + SearchVO searchVo, + PageVO pageVo) { + IPage page = adminUserService.adminUserPage(PageUtil.initPage(pageVo), PageUtil.initWrapper(user, searchVo)); + + return ResultUtil.data(page); + } + + + @PostMapping + @ApiOperation(value = "添加用户") + public ResultMessage register(@Valid AdminUserDTO adminUser, + @RequestParam(required = false) List roles) { + int rolesMaxSize = 10; + try { + if (roles != null && roles.size() >= rolesMaxSize) { + throw new ServiceException(ResultCode.PERMISSION_BEYOND_TEN); + } + adminUserService.saveAdminUser(adminUser, roles); + } catch (Exception e) { + log.error("添加用户错误", e); + } + return ResultUtil.success(); + } + + @PutMapping(value = "/enable/{userId}") + @ApiOperation(value = "禁/启 用 用户") + public ResultMessage disable(@ApiParam("用户唯一id标识") @PathVariable String userId, Boolean status) { + AdminUser user = adminUserService.getById(userId); + if (user == null) { + throw new ServiceException(ResultCode.USER_NOT_EXIST); + } + user.setStatus(status); + adminUserService.updateById(user); + return ResultUtil.success(); + } + + @DeleteMapping(value = "/{ids}") + @ApiOperation(value = "批量通过ids删除") + public ResultMessage delAllByIds(@PathVariable List ids) { + adminUserService.deleteCompletely(ids); + return ResultUtil.success(); + } + +} diff --git a/src/main/java/cn/lili/modules/permission/entity/dos/AdminUser.java b/src/main/java/cn/lili/modules/permission/entity/dos/AdminUser.java new file mode 100644 index 0000000000000000000000000000000000000000..92977e0f876d4fbebb37c98b71bbd60442b87dee --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/entity/dos/AdminUser.java @@ -0,0 +1,63 @@ +package cn.lili.modules.permission.entity.dos; + +import cn.lili.mybatis.BaseEntity; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + + +/** + * 管理员类 + * + * @author Chopper + * @since 2020/11/19 11:42 + */ +@Data +@TableName("li_admin_user") +@ApiModel(value = "管理员") +public class AdminUser extends BaseEntity { + + private static final long serialVersionUID = 2918352800205024873L; + + @ApiModelProperty(value = "用户名") + @Length(max = 20,message = "用户名长度不能超过20个字符") + private String username; + + @ApiModelProperty(value = "密码") + private String password; + + @ApiModelProperty(value = "昵称") + @Length(max = 10,message = "昵称长度不能超过10个字符") + private String nickName; + + @ApiModelProperty(value = "手机") + @Length(max = 11,message = "手机号长度不能超过11") + private String mobile; + + @ApiModelProperty(value = "邮件") + @Length(max = 100,message = "邮箱长度不能超过100") + private String email; + + @ApiModelProperty(value = "用户头像") + private String avatar = "https://i.loli.net/2020/11/19/LyN6JF7zZRskdIe.png"; + + @ApiModelProperty(value = "是否是超级管理员 超级管理员/普通管理员") + private Boolean isSuper = false; + + @ApiModelProperty(value = "状态 默认true正常 false禁用") + private Boolean status = true; + + @ApiModelProperty(value = "描述/详情/备注") + private String description; + + @ApiModelProperty(value = "所属部门id") + private String departmentId; + /** + * 冗余字段 + */ + @ApiModelProperty(value = "角色id集合") + private String roleIds; + +} diff --git a/src/main/java/cn/lili/modules/permission/entity/dos/Department.java b/src/main/java/cn/lili/modules/permission/entity/dos/Department.java new file mode 100644 index 0000000000000000000000000000000000000000..61ab9ffca255351d85b5eedbb4834f92a5c8cd57 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/entity/dos/Department.java @@ -0,0 +1,31 @@ +package cn.lili.modules.permission.entity.dos; + +import cn.lili.mybatis.BaseEntity; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + + +/** + * 部门 + * + * @author Chopper + * @since 2020/11/19 11:57 + */ +@Data +@TableName("li_department") +@ApiModel(value = "部门") +public class Department extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "部门名称") + private String title; + + @ApiModelProperty(value = "父id") + private String parentId; + + @ApiModelProperty(value = "排序值") + private Double sortOrder; +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/permission/entity/dos/DepartmentRole.java b/src/main/java/cn/lili/modules/permission/entity/dos/DepartmentRole.java new file mode 100644 index 0000000000000000000000000000000000000000..ba182b03f685f0abc1d28a29d236fcc7cd7b404b --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/entity/dos/DepartmentRole.java @@ -0,0 +1,34 @@ +package cn.lili.modules.permission.entity.dos; + +import cn.lili.mybatis.BaseEntity; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * 角色部门绑定关系 + * + * @author Chopper + * @since 2020/11/19 12:18 + */ +@Data +@TableName("li_department_role") +@ApiModel(value = "角色部门") +@NoArgsConstructor +@AllArgsConstructor +public class DepartmentRole extends BaseEntity { + + + private static final long serialVersionUID = 2342812932116647050L; + + @ApiModelProperty(value = "角色id") + private String roleId; + + @ApiModelProperty(value = "部门id") + private String departmentId; + +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/permission/entity/dos/Menu.java b/src/main/java/cn/lili/modules/permission/entity/dos/Menu.java new file mode 100644 index 0000000000000000000000000000000000000000..e4ea8137df8d9c0ff3968e79d597fbf161beb36f --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/entity/dos/Menu.java @@ -0,0 +1,48 @@ +package cn.lili.modules.permission.entity.dos; + +import cn.lili.mybatis.BaseEntity; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 菜单权限 + * + * @author Chopper + * @since 2020/11/19 12:12 + */ +@Data +@TableName("li_menu") +@ApiModel(value = "菜单权限") +public class Menu extends BaseEntity { + + private static final long serialVersionUID = 7050744476203495207L; + + + @ApiModelProperty(value = "菜单标题") + private String title; + + @ApiModelProperty(value = "路由名称") + private String name; + + @ApiModelProperty(value = "路径") + private String path; + + @ApiModelProperty(value = "菜单层级") + private Integer level; + + @ApiModelProperty(value = "前端目录文件") + private String frontRoute; + + @ApiModelProperty(value = "父id") + private String parentId = "0"; + + @ApiModelProperty(value = "排序值") + private Double sortOrder; + + @ApiModelProperty(value = "权限URL,*号模糊匹配,逗号分割") + private String permission; + + +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/permission/entity/dos/Role.java b/src/main/java/cn/lili/modules/permission/entity/dos/Role.java new file mode 100644 index 0000000000000000000000000000000000000000..4057bcde77bfe0a48e50af5dc17186e4380b77c2 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/entity/dos/Role.java @@ -0,0 +1,34 @@ +package cn.lili.modules.permission.entity.dos; + +import cn.lili.mybatis.BaseEntity; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +/** + * 角色 + * + * @author Chopper + * @since 2020/11/19 11:57 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("li_role") +@ApiModel(value = "角色") +public class Role extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "角色名") + private String name; + + @ApiModelProperty(value = "是否为注册默认角色") + private Boolean defaultRole; + + @ApiModelProperty(value = "备注") + private String description; + +} diff --git a/src/main/java/cn/lili/modules/permission/entity/dos/RoleMenu.java b/src/main/java/cn/lili/modules/permission/entity/dos/RoleMenu.java new file mode 100644 index 0000000000000000000000000000000000000000..571e62f566efa0ffaccbf8bc0d7c4a13d953d96d --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/entity/dos/RoleMenu.java @@ -0,0 +1,32 @@ +package cn.lili.modules.permission.entity.dos; + +import cn.lili.mybatis.BaseEntity; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + + +/** + * 角色权限绑定关系 + * + * @author Chopper + * @since 2020/11/19 12:18 + */ +@Data +@TableName("li_role_menu") +@ApiModel(value = "角色权限") +public class RoleMenu extends BaseEntity { + + private static final long serialVersionUID = -4680260092546996026L; + + @ApiModelProperty(value = "角色id") + private String roleId; + + @ApiModelProperty(value = "菜单") + private String menuId; + + @ApiModelProperty(value = "是否拥有操作数据权限,为否则只有查看权限") + private Boolean isSuper; + +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/permission/entity/dos/UserRole.java b/src/main/java/cn/lili/modules/permission/entity/dos/UserRole.java new file mode 100644 index 0000000000000000000000000000000000000000..13c9e60624d2ec71000646960e607f113a6dd2e1 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/entity/dos/UserRole.java @@ -0,0 +1,34 @@ +package cn.lili.modules.permission.entity.dos; + +import cn.lili.mybatis.BaseEntity; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 用户角色 + * + * @author Chopper + * @since 2020/11/19 12:18 + */ +@Data +@TableName("li_user_role") +@ApiModel(value = "用户角色") +public class UserRole extends BaseEntity { + + @ApiModelProperty(value = "用户唯一id") + private String userId; + + @ApiModelProperty(value = "角色唯一id") + private String roleId; + + public UserRole(String userId, String roleId) { + this.userId = userId; + this.roleId = roleId; + } + + public UserRole() { + + } +} diff --git a/src/main/java/cn/lili/modules/permission/entity/dto/AdminUserDTO.java b/src/main/java/cn/lili/modules/permission/entity/dto/AdminUserDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..3f363f806eae815720c261149a38d583968ed43b --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/entity/dto/AdminUserDTO.java @@ -0,0 +1,52 @@ +package cn.lili.modules.permission.entity.dto; + +import cn.lili.mybatis.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +/** + * 管理员入库dto + * + * @author Chopper + * @since 2020/11/16 19:55 + */ +@Data +@ApiModel(value = "管理员入库dto") +public class AdminUserDTO extends BaseEntity { + + private static final long serialVersionUID = 1L; + + + @ApiModelProperty(value = "用户名") + @Length(max = 20,message = "用户名长度不能超过20个字符") + private String username; + + @ApiModelProperty(value = "密码") + private String password; + + @ApiModelProperty(value = "昵称") + @Length(max = 10,message = "昵称长度不能超过10个字符") + private String nickName; + + @ApiModelProperty(value = "手机") + @Length(max = 11,message = "手机号长度不能超过11") + private String mobile; + + @ApiModelProperty(value = "邮件") + @Length(max = 100,message = "邮箱长度不能超过100") + private String email; + + @ApiModelProperty(value = "头像") + private String avatar; + + @ApiModelProperty(value = "描述/详情/备注") + private String description; + + @ApiModelProperty(value = "所属部门id") + private String departmentId; + + @ApiModelProperty(value = "是否为超级管理员") + private Boolean isSuper; +} diff --git a/src/main/java/cn/lili/modules/permission/entity/dto/MenuSearchParams.java b/src/main/java/cn/lili/modules/permission/entity/dto/MenuSearchParams.java new file mode 100644 index 0000000000000000000000000000000000000000..e4fb54aea44d66441bb34029810c6b4aa51e1a2b --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/entity/dto/MenuSearchParams.java @@ -0,0 +1,33 @@ +package cn.lili.modules.permission.entity.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 权限搜索参数 + * + * @author Chopper + * @since 2020-11-19 16:46 + */ +@Data +public class MenuSearchParams { + + @ApiModelProperty(value = "菜单/权限名称") + private String name; + + @ApiModelProperty(value = "层级") + private Integer level; + + @ApiModelProperty(value = "菜单标题") + private String title; + + @ApiModelProperty(value = "赋权API地址,正则表达式") + private String path; + + @ApiModelProperty(value = "前端路由") + private String frontRoute; + + @ApiModelProperty(value = "图标") + private String icon; + +} diff --git a/src/main/java/cn/lili/modules/permission/entity/enums/PermissionEnum.java b/src/main/java/cn/lili/modules/permission/entity/enums/PermissionEnum.java new file mode 100644 index 0000000000000000000000000000000000000000..1c819b5a1274199c56cc6a31f46d2c889a4eed6c --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/entity/enums/PermissionEnum.java @@ -0,0 +1,18 @@ +package cn.lili.modules.permission.entity.enums; + +/** + * 权限枚举值 + * + * @author Chopper + * @version v4.0 + * @since 2020/11/25 09:21 + */ + +public enum PermissionEnum { + + /** + * 超级权限,查看权限 + */ + SUPER, QUERY + +} diff --git a/src/main/java/cn/lili/modules/permission/entity/vo/AdminUserVO.java b/src/main/java/cn/lili/modules/permission/entity/vo/AdminUserVO.java new file mode 100644 index 0000000000000000000000000000000000000000..5b8018d906c7b57a1729b9ec201eeb1d5498e517 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/entity/vo/AdminUserVO.java @@ -0,0 +1,37 @@ +package cn.lili.modules.permission.entity.vo; + +import cn.lili.modules.permission.entity.dos.AdminUser; +import cn.lili.modules.permission.entity.dos.Menu; +import cn.lili.modules.permission.entity.dos.Role; +import cn.lili.utils.BeanUtil; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + * 管理员VO + * + * @author Chopper + * @since 2020-11-22 09:17 + */ +@Data +public class AdminUserVO extends AdminUser { + + private static final long serialVersionUID = -2378384199695839312L; + + @ApiModelProperty(value = "所属部门名称") + private String departmentTitle; + + @ApiModelProperty(value = "用户拥有角色") + private List roles; + + @ApiModelProperty(value = "用户拥有的权限") + private List menus; + + + public AdminUserVO(AdminUser user) { + BeanUtil.copyProperties(user, this); + } + +} diff --git a/src/main/java/cn/lili/modules/permission/entity/vo/DepartmentVO.java b/src/main/java/cn/lili/modules/permission/entity/vo/DepartmentVO.java new file mode 100644 index 0000000000000000000000000000000000000000..9cffc6343f0a9943aaed3cdb8ec7b3f51364cecd --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/entity/vo/DepartmentVO.java @@ -0,0 +1,27 @@ +package cn.lili.modules.permission.entity.vo; + +import cn.lili.modules.permission.entity.dos.Department; +import cn.lili.utils.BeanUtil; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 部门VO + * + * @author Chopper + * @since 2020-11-23 18:48 + */ +@Data +public class DepartmentVO extends Department { + + private List children = new ArrayList<>(); + + public DepartmentVO() { + } + + public DepartmentVO(Department department) { + BeanUtil.copyProperties(department, this); + } +} diff --git a/src/main/java/cn/lili/modules/permission/entity/vo/MenuVO.java b/src/main/java/cn/lili/modules/permission/entity/vo/MenuVO.java new file mode 100644 index 0000000000000000000000000000000000000000..f7f72b84a14dc2f2f5e51e883af61095097bea3f --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/entity/vo/MenuVO.java @@ -0,0 +1,45 @@ +package cn.lili.modules.permission.entity.vo; + +import cn.lili.modules.permission.entity.dos.Menu; +import cn.lili.utils.BeanUtil; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * 菜单VO 展示模型 + * + * @author Chopper + * @since 2020/11/20 15:38 + */ + +@Data +public class MenuVO extends Menu { + + @ApiModelProperty(value = "子菜单") + private List children = new ArrayList<>(); + + public MenuVO() { + + } + + public MenuVO(Menu menu) { + BeanUtil.copyProperties(menu, this); + } + + public List getChildren() { + if (children != null) { + children.sort(new Comparator() { + @Override + public int compare(MenuVO o1, MenuVO o2) { + return o1.getSortOrder().compareTo(o2.getSortOrder()); + } + }); + return children; + } + return null; + } +} diff --git a/src/main/java/cn/lili/modules/permission/entity/vo/RoleVO.java b/src/main/java/cn/lili/modules/permission/entity/vo/RoleVO.java new file mode 100644 index 0000000000000000000000000000000000000000..d86352b57ddd2c58ab82d4a8306f3a70180ec6dc --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/entity/vo/RoleVO.java @@ -0,0 +1,23 @@ +package cn.lili.modules.permission.entity.vo; + +import cn.lili.modules.permission.entity.dos.Role; +import cn.lili.modules.permission.entity.dos.RoleMenu; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + * RoleVO + * + * @author Chopper + * @since 2020-11-22 17:42 + */ +@Data +public class RoleVO extends Role { + + private static final long serialVersionUID = 8625345346785692513L; + + @ApiModelProperty(value = "拥有权限") + private List roleMenus; +} diff --git a/src/main/java/cn/lili/modules/permission/entity/vo/SystemLogVO.java b/src/main/java/cn/lili/modules/permission/entity/vo/SystemLogVO.java new file mode 100644 index 0000000000000000000000000000000000000000..783b02285c8cfd276a962c1f499462c0aa98d183 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/entity/vo/SystemLogVO.java @@ -0,0 +1,104 @@ +package cn.lili.modules.permission.entity.vo; + +import cn.lili.elasticsearch.EsSuffix; +import cn.lili.utils.ObjectUtil; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.experimental.Accessors; +import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.annotations.DateFormat; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; + +import java.io.Serializable; +import java.util.Date; +import java.util.Map; + + +/** + * 日志 + * + * @author Chopper + * @since 2020/12/2 17:50 + */ +@Data +@Document(indexName = "#{@elasticsearchProperties.indexPrefix}_" + EsSuffix.LOGS_INDEX_NAME) +@ToString +@NoArgsConstructor +@Accessors(chain = true) +public class SystemLogVO implements Serializable { + + + private static final long serialVersionUID = -8995552592401630086L; + + @Id + @ApiModelProperty(value = "id") + private String id; + + + @ApiModelProperty(value = "日志记录时间") + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") + @Field(type = FieldType.Date, format = DateFormat.basic_date_time) + private Date createTime = new Date(); + + @ApiModelProperty(value = "请求用户") + @Field(type = FieldType.Text) + private String username; + + @ApiModelProperty(value = "请求路径") + @Field(type = FieldType.Text) + private String requestUrl; + + @ApiModelProperty(value = "请求参数") + @Field(type = FieldType.Text) + private String requestParam; + + @ApiModelProperty(value = "响应参数") + @Field(type = FieldType.Text) + private String responseBody; + + @ApiModelProperty(value = "ip") + @Field(type = FieldType.Keyword) + private String ip; + + @ApiModelProperty(value = "方法操作名称") + @Field(type = FieldType.Keyword) + private String name; + + + @ApiModelProperty(value = "请求类型") + @Field(type = FieldType.Keyword) + private String requestType; + + + @ApiModelProperty(value = "自定义日志内容") + @Field(type = FieldType.Text) + private String customerLog; + + + @ApiModelProperty(value = "ip信息") + @Field(type = FieldType.Text) + private String ipInfo; + + @ApiModelProperty(value = "花费时间") + @Field(type = FieldType.Integer) + private Integer costTime; + + @ApiModelProperty(value = "商家") + @Field(type = FieldType.Long) + private Long storeId = -1L; + + /** + * 转换请求参数为Json + * + * @param paramMap + */ + public void setMapToParams(Map paramMap) { + + this.requestParam = ObjectUtil.mapToString(paramMap); + } +} diff --git a/src/main/java/cn/lili/modules/permission/entity/vo/UserMenuVO.java b/src/main/java/cn/lili/modules/permission/entity/vo/UserMenuVO.java new file mode 100644 index 0000000000000000000000000000000000000000..228dd19d247ef005427794a6120d43c4363b64c0 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/entity/vo/UserMenuVO.java @@ -0,0 +1,28 @@ +package cn.lili.modules.permission.entity.vo; + +import cn.lili.modules.permission.entity.dos.Menu; +import lombok.Data; + +/** + * RoleMenuVO + * + * @author Chopper + * @since 2020-11-24 11:45 + */ +@Data +public class UserMenuVO extends Menu { + + private static final long serialVersionUID = -7478870595109016162L; + + /** + * 是否是超级管理员 + */ + private Boolean isSuper; + + public Boolean getSuper() { + if (this.isSuper == null) { + return false; + } + return isSuper; + } +} diff --git a/src/main/java/cn/lili/modules/permission/mapper/AdminUserMapper.java b/src/main/java/cn/lili/modules/permission/mapper/AdminUserMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..d3ff44ebb1e06d11353f02601835a585b68bb7e9 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/mapper/AdminUserMapper.java @@ -0,0 +1,38 @@ +package cn.lili.modules.permission.mapper; + +import cn.lili.modules.permission.entity.dos.AdminUser; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import java.util.List; + +/** + * 用户数据处理层 + * + * @author Chopper + * @since 2020-11-22 09:17 + */ +public interface AdminUserMapper extends BaseMapper { + + /** + * 通过用户名获取用户 + * @param username + * @return + */ + AdminUser findByUsername(String username); + + + /** + * 通过部门id获取 + * @param departmentId + * @return + */ + List findByDepartmentId(String departmentId); + + /** + * 通过用户名模糊搜索 + * @param username + * @param status + * @return + */ + List findByUsernameLikeAndStatus(String username, Integer status); +} diff --git a/src/main/java/cn/lili/modules/permission/mapper/DepartmentMapper.java b/src/main/java/cn/lili/modules/permission/mapper/DepartmentMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..3881824d4e9261423d93ea8122db28a84cd95bd9 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/mapper/DepartmentMapper.java @@ -0,0 +1,14 @@ +package cn.lili.modules.permission.mapper; + +import cn.lili.modules.permission.entity.dos.Department; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 部门数据处理层 + * + * @author Chopper + * @since 2020-11-22 09:17 + */ +public interface DepartmentMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/permission/mapper/DepartmentRoleMapper.java b/src/main/java/cn/lili/modules/permission/mapper/DepartmentRoleMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..5d0c97fc7c5c6df7fecb17fc320b434712d18dcd --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/mapper/DepartmentRoleMapper.java @@ -0,0 +1,14 @@ +package cn.lili.modules.permission.mapper; + +import cn.lili.modules.permission.entity.dos.DepartmentRole; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 部门角色数据处理层 + * + * @author Chopper + * @since 2020-11-22 09:17 + */ +public interface DepartmentRoleMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/permission/mapper/MenuMapper.java b/src/main/java/cn/lili/modules/permission/mapper/MenuMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..bd3c7235619b5b6703a41589226abfe9ed08bd82 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/mapper/MenuMapper.java @@ -0,0 +1,42 @@ +package cn.lili.modules.permission.mapper; + +import cn.lili.modules.permission.entity.dos.Menu; +import cn.lili.modules.permission.entity.vo.UserMenuVO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/** + * 菜单数据处理层 + * + * @author Chopper + * @since 2020-11-22 09:17 + */ +public interface MenuMapper extends BaseMapper { + + /** + * 根据用户获取菜单列表 + * + * @param userId 用户ID + * @return 菜单列表 + */ + @Select("SELECT menu.* FROM li_menu AS menu WHERE menu.id IN (" + + "SELECT rm.menu_id FROM li_role_menu AS rm WHERE rm.role_id IN (" + + "SELECT ur.role_id FROM li_user_role AS ur WHERE ur.user_id=#{userId}) OR rm.role_id IN (" + + "SELECT dr.role_id FROM li_department_role AS dr WHERE dr.id=(" + + "SELECT department_id FROM li_admin_user AS au WHERE au.id = #{userId})))") + List findByUserId(String userId); + + /** + * 根据用户获取菜单权限 + * + * @param userId 用户ID + * @return 用户菜单VO列表 + */ + @Select("SELECT rm.is_super as is_super,m.*FROM li_menu AS m INNER JOIN li_role_menu AS rm ON rm.menu_id=m.id WHERE rm.role_id IN (" + + "SELECT ur.role_id FROM li_user_role AS ur WHERE ur.user_id=#{userId}) OR rm.role_id IN (" + + "SELECT dr.role_id FROM li_department_role AS dr INNER JOIN li_admin_user AS au ON au.department_id=dr.department_id " + + "WHERE au.id=#{userId}) GROUP BY m.id,rm.is_super ORDER BY rm.is_super desc") + List getUserRoleMenu(String userId); +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/permission/mapper/RoleDepartmentMapper.java b/src/main/java/cn/lili/modules/permission/mapper/RoleDepartmentMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..3cfd4eb469fca11064a36535efd1d1b2b80e6406 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/mapper/RoleDepartmentMapper.java @@ -0,0 +1,14 @@ +package cn.lili.modules.permission.mapper; + +import cn.lili.modules.permission.entity.dos.DepartmentRole; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 角色部门数据处理层 + * + * @author Chopper + * @since 2020-11-22 09:17 + */ +public interface RoleDepartmentMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/permission/mapper/RoleMapper.java b/src/main/java/cn/lili/modules/permission/mapper/RoleMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..5d16cffc48f5055eb753014452cb5096364cdbee --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/mapper/RoleMapper.java @@ -0,0 +1,14 @@ +package cn.lili.modules.permission.mapper; + +import cn.lili.modules.permission.entity.dos.Role; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 角色数据处理层 + * + * @author Chopper + * @since 2020-11-22 09:17 + */ +public interface RoleMapper extends BaseMapper { + +} diff --git a/src/main/java/cn/lili/modules/permission/mapper/RoleMenuMapper.java b/src/main/java/cn/lili/modules/permission/mapper/RoleMenuMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..d08e6579106892f4614eaf778139308348cc4783 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/mapper/RoleMenuMapper.java @@ -0,0 +1,14 @@ +package cn.lili.modules.permission.mapper; + +import cn.lili.modules.permission.entity.dos.RoleMenu; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 角色菜单数据处理层 + * + * @author Chopper + * @since 2020-11-22 09:17 + */ +public interface RoleMenuMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/permission/mapper/RolePermissionMapper.java b/src/main/java/cn/lili/modules/permission/mapper/RolePermissionMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..af075a9cbf21de454564561ab2ba5a98d61c9a03 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/mapper/RolePermissionMapper.java @@ -0,0 +1,13 @@ +package cn.lili.modules.permission.mapper; + +import cn.lili.modules.permission.entity.dos.RoleMenu; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 角色权限数据处理层 + * + * @author Chopper + * @since 2020-11-22 09:17 + */ +public interface RolePermissionMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/permission/mapper/UserRoleMapper.java b/src/main/java/cn/lili/modules/permission/mapper/UserRoleMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..f513905af87a7c75364d1a44e2fa3f5951c3c5f2 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/mapper/UserRoleMapper.java @@ -0,0 +1,13 @@ +package cn.lili.modules.permission.mapper; + +import cn.lili.modules.permission.entity.dos.UserRole; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 角色权限数据处理层 + * @author Chopper + * @since 2020-11-22 09:17 + */ +public interface UserRoleMapper extends BaseMapper { + +} diff --git a/src/main/java/cn/lili/modules/permission/repository/SystemLogRepository.java b/src/main/java/cn/lili/modules/permission/repository/SystemLogRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..49e221ecec875d2110c1cf280ab7c87b3884bd10 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/repository/SystemLogRepository.java @@ -0,0 +1,14 @@ +package cn.lili.modules.permission.repository; + +import cn.lili.modules.permission.entity.vo.SystemLogVO; +import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; + +/** + * 日志 + * + * @author paulG + * @since 2021/12/13 + **/ +public interface SystemLogRepository extends ElasticsearchRepository { + +} diff --git a/src/main/java/cn/lili/modules/permission/service/AdminUserService.java b/src/main/java/cn/lili/modules/permission/service/AdminUserService.java new file mode 100644 index 0000000000000000000000000000000000000000..8214e0854552faaff3c10c7417e2b9d4d920c060 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/service/AdminUserService.java @@ -0,0 +1,109 @@ +package cn.lili.modules.permission.service; + + +import cn.lili.modules.permission.entity.dos.AdminUser; +import cn.lili.modules.permission.entity.dto.AdminUserDTO; +import cn.lili.modules.permission.entity.vo.AdminUserVO; +import cn.lili.security.enums.UserEnums; +import cn.lili.security.token.Token; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import org.springframework.cache.annotation.CacheConfig; + +import java.util.List; + +/** + * 用户业务层 + * + * @author Chopper + * @since 2020/11/17 3:42 下午 + */ +@CacheConfig(cacheNames = "{adminuser}") +public interface AdminUserService extends IService { + + + /** + * 获取管理员分页 + * + * @param initPage + * @param initWrapper + * @return + */ + IPage adminUserPage(Page initPage, QueryWrapper initWrapper); + + /** + * 通过用户名获取用户 + * + * @param username + * @return + */ + AdminUser findByUsername(String username); + + + /** + * 更新管理员 + * + * @param adminUser + * @param roles + * @return + */ + boolean updateAdminUser(AdminUser adminUser, List roles); + + + /** + * 修改管理员密码 + * + * @param password + * @param newPassword + */ + void editPassword(String password, String newPassword); + + /** + * 重置密码 + * + * @param ids id集合 + */ + void resetPassword(List ids); + + /** + * 新增管理员 + * + * @param adminUser + * @param roles + */ + void saveAdminUser(AdminUserDTO adminUser, List roles); + + /** + * 彻底删除 + * + * @param ids + */ + void deleteCompletely(List ids); + + + /** + * 用户登录 + * + * @param username 用户名 + * @param password 密码 + * @return token + */ + Token login(String username, String password); + + /** + * 刷新token + * + * @param refreshToken + * @return token + */ + Token refreshToken(String refreshToken); + + /** + * 登出 + * + * @param userEnums token角色类型 + */ + void logout(UserEnums userEnums); +} diff --git a/src/main/java/cn/lili/modules/permission/service/DepartmentRoleService.java b/src/main/java/cn/lili/modules/permission/service/DepartmentRoleService.java new file mode 100644 index 0000000000000000000000000000000000000000..bd95b17c1949767acc728463bbfd3501500d1070 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/service/DepartmentRoleService.java @@ -0,0 +1,39 @@ +package cn.lili.modules.permission.service; + +import cn.lili.modules.permission.entity.dos.DepartmentRole; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +/** + * 部门角色业务层 + * + * @author Chopper + * @since 2020/11/22 12:08 + */ +public interface DepartmentRoleService extends IService { + + /** + * 根据部门获取角色集合 + * + * @param departmentId + * @return + */ + List listByDepartmentId(String departmentId); + + + /** + * 更新部门角色关联 + * + * @param departmentId + * @param departmentRoles + */ + void updateByDepartmentId(String departmentId, List departmentRoles); + + /** + * 根据部门id删除部门与角色关联 + * + * @param ids id集合 + */ + void deleteByDepartment(List ids); +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/permission/service/DepartmentService.java b/src/main/java/cn/lili/modules/permission/service/DepartmentService.java new file mode 100644 index 0000000000000000000000000000000000000000..38ff2e3ac888f82309402866559e4be9b4e2ed11 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/service/DepartmentService.java @@ -0,0 +1,32 @@ +package cn.lili.modules.permission.service; + +import cn.lili.modules.permission.entity.dos.Department; +import cn.lili.modules.permission.entity.vo.DepartmentVO; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +/** + * 部门业务层 + * + * @author Chopper + * @since 2020/11/17 3:43 下午 + */ +public interface DepartmentService extends IService { + + /** + * 获取部门树 + * + * @param initWrapper + * @return + */ + List tree(QueryWrapper initWrapper); + + /** + * 删除部门 + * + * @param ids + */ + void deleteByIds(List ids); +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/permission/service/MenuService.java b/src/main/java/cn/lili/modules/permission/service/MenuService.java new file mode 100644 index 0000000000000000000000000000000000000000..1d91e5103ddd557a717632680a627f18e5befa37 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/service/MenuService.java @@ -0,0 +1,74 @@ +package cn.lili.modules.permission.service; + +import cn.lili.modules.permission.entity.dos.Menu; +import cn.lili.modules.permission.entity.dto.MenuSearchParams; +import cn.lili.modules.permission.entity.vo.MenuVO; +import com.baomidou.mybatisplus.extension.service.IService; +import org.springframework.cache.annotation.CacheConfig; + +import java.util.List; + +/** + * 权限业务层 + * + * @author Chopper + * @since 2020/11/17 3:45 下午 + */ +@CacheConfig(cacheNames = "{menu}") +public interface MenuService extends IService { + + /** + * 通过用户的菜单权限 + * + * @return + */ + List findUserTree(); + + /** + * 通过用户id获取 + * + * @param userId + * @return + */ + List findUserList(String userId); + + + /** + * 根据角色id获取菜单集合 + * + * @param roleIds + * @return + */ + List findByRoleIds(String roleIds); + + /** + * 树形结构 + * + * @return + */ + List tree(); + + /** + * 查询列表 + * + * @param menuSearchParams + * @return + */ + List searchList(MenuSearchParams menuSearchParams); + + /** + * 批量删除 + * + * @param ids + */ + void deleteIds(List ids); + + /** + * 添加更新菜单 + * + * @param menu 菜单数据 + * @return 是否成功 + */ + boolean saveOrUpdateMenu(Menu menu); + +} diff --git a/src/main/java/cn/lili/modules/permission/service/RoleMenuService.java b/src/main/java/cn/lili/modules/permission/service/RoleMenuService.java new file mode 100644 index 0000000000000000000000000000000000000000..f2261c03eac452bfac3a46c799e7aea28767de86 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/service/RoleMenuService.java @@ -0,0 +1,57 @@ +package cn.lili.modules.permission.service; + +import cn.lili.modules.permission.entity.dos.RoleMenu; +import cn.lili.modules.permission.entity.vo.UserMenuVO; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +/** + * 角色菜单接口 + * + * @author Chopper + * @since 2020/11/22 11:43 + */ +public interface RoleMenuService extends IService { + + /** + * 通过角色获取菜单权限列表 + * + * @param roleId + * @return + */ + List findByRoleId(String roleId); + + + /** + * 根据角色集合获取拥有的菜单具体权限 + * + * @param userId + * @return + */ + List findAllMenu(String userId); + + + /** + * 更新某角色拥有到菜单 + * + * @param roleId + * @param roleMenus + */ + void updateRoleMenu(String roleId, List roleMenus); + + /** + * 根据角色id 删除数据 + * + * @param roleId + */ + void deleteRoleMenu(String roleId); + + /** + * 根据角色id 删除数据 + * + * @param roleId + */ + void deleteRoleMenu(List roleId); + +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/permission/service/RoleService.java b/src/main/java/cn/lili/modules/permission/service/RoleService.java new file mode 100644 index 0000000000000000000000000000000000000000..e133420834029e94bf7527d66839e21d8a9799d9 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/service/RoleService.java @@ -0,0 +1,31 @@ +package cn.lili.modules.permission.service; + + +import cn.lili.modules.permission.entity.dos.Role; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +/** + * 角色业务层 + * + * @author Chopper + * @since 2020/11/17 3:45 下午 + */ +public interface RoleService extends IService { + + /** + * 获取默认角色 + * + * @param defaultRole + * @return + */ + List findByDefaultRole(Boolean defaultRole); + + + /** + * 批量删除角色 + * @param roleIds + */ + void deleteRoles(List roleIds); +} diff --git a/src/main/java/cn/lili/modules/permission/service/SystemLogService.java b/src/main/java/cn/lili/modules/permission/service/SystemLogService.java new file mode 100644 index 0000000000000000000000000000000000000000..dc3ac6e770b688f210b163dfb0eabd4ae4859e8d --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/service/SystemLogService.java @@ -0,0 +1,49 @@ +package cn.lili.modules.permission.service; + +import cn.lili.modules.permission.entity.vo.SystemLogVO; +import cn.lili.result.PageVO; +import cn.lili.result.SearchVO; +import com.baomidou.mybatisplus.core.metadata.IPage; + +import java.util.List; + +/** + * 系统日志业务层 + * + * @author Chopper + * @since 2020/11/17 3:45 下午 + */ +public interface SystemLogService { + + /** + * 添加日志 + * + * @param systemLogVO + * @return + */ + void saveLog(SystemLogVO systemLogVO); + + /** + * 通过id删除日志 + * + * @param id + */ + void deleteLog(List id); + + /** + * 删除全部日志 + */ + void flushAll(); + + /** + * 分页搜索获取日志 + * + * @param key 关键字 + * @param searchVo 查询VO + * @param pageVO 分页 + * @param operatorName 操作人 + * @param storeId 店铺ID + * @return 日志分页 + */ + IPage queryLog(String storeId, String operatorName, String key, SearchVO searchVo, PageVO pageVO); +} diff --git a/src/main/java/cn/lili/modules/permission/service/UserRoleService.java b/src/main/java/cn/lili/modules/permission/service/UserRoleService.java new file mode 100644 index 0000000000000000000000000000000000000000..0cb376998fd5184e7085e303d3b2e4e153d72471 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/service/UserRoleService.java @@ -0,0 +1,41 @@ +package cn.lili.modules.permission.service; + +import cn.lili.modules.permission.entity.dos.UserRole; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +/** + * 管理员业务层 + * + * @author Chopper + * @since 2020/11/17 3:46 下午 + */ +public interface UserRoleService extends IService { + + /** + * 根据用户查看拥有的角色 + * + * @param userId + * @return + */ + List listByUserId(String userId); + + /** + * 根据用户查看拥有的角色 + * + * @param userId + * @return + */ + List listId(String userId); + + /** + * 更新用户拥有的角色 + * + * @param userId 角色 + * @param userRoles 角色权限 + */ + void updateUserRole(String userId, List userRoles); + + +} diff --git a/src/main/java/cn/lili/modules/permission/serviceimpl/AdminUserServiceImpl.java b/src/main/java/cn/lili/modules/permission/serviceimpl/AdminUserServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..9caf4ebde2f743610329796c591a870ca70748f4 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/serviceimpl/AdminUserServiceImpl.java @@ -0,0 +1,269 @@ +package cn.lili.modules.permission.serviceimpl; + +import cn.hutool.core.text.CharSequenceUtil; +import cn.lili.aspect.annotation.SystemLogPoint; +import cn.lili.cache.Cache; +import cn.lili.cache.CachePrefix; +import cn.lili.exception.ServiceException; +import cn.lili.modules.permission.entity.dos.AdminUser; +import cn.lili.modules.permission.entity.dos.Department; +import cn.lili.modules.permission.entity.dos.Role; +import cn.lili.modules.permission.entity.dos.UserRole; +import cn.lili.modules.permission.entity.dto.AdminUserDTO; +import cn.lili.modules.permission.entity.vo.AdminUserVO; +import cn.lili.modules.permission.mapper.AdminUserMapper; +import cn.lili.modules.permission.service.*; +import cn.lili.modules.permission.token.ManagerTokenGenerate; +import cn.lili.result.ResultCode; +import cn.lili.security.AuthUser; +import cn.lili.security.UserContext; +import cn.lili.security.enums.UserEnums; +import cn.lili.security.token.Token; +import cn.lili.utils.BeanUtil; +import cn.lili.utils.StringUtils; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 用户业务层实现 + * + * @author Chopper + * @since 2020/11/17 3:46 下午 + */ +@Slf4j +@Service +public class AdminUserServiceImpl extends ServiceImpl implements AdminUserService { + @Autowired + private UserRoleService userRoleService; + @Autowired + private RoleService roleService; + @Autowired + private DepartmentService departmentService; + @Autowired + private MenuService menuService; + @Autowired + private ManagerTokenGenerate managerTokenGenerate; + + @Autowired + private Cache cache; + /** + * 角色长度 + */ + private final int rolesMaxSize = 10; + + @Override + public IPage adminUserPage(Page initPage, QueryWrapper initWrapper) { + Page adminUserPage = page(initPage, initWrapper); + List roles = roleService.list(); + List departments = departmentService.list(); + + List result = new ArrayList<>(); + + adminUserPage.getRecords().forEach(adminUser -> { + AdminUserVO adminUserVO = new AdminUserVO(adminUser); + if (!CharSequenceUtil.isEmpty(adminUser.getDepartmentId())) { + try { + adminUserVO.setDepartmentTitle( + departments.stream().filter + (department -> department.getId().equals(adminUser.getDepartmentId())) + .collect(Collectors.toList()) + .get(0) + .getTitle() + ); + } catch (Exception e) { + log.error("填充部门信息异常", e); + } + } + if (!CharSequenceUtil.isEmpty(adminUser.getRoleIds())) { + try { + List memberRoles = Arrays.asList(adminUser.getRoleIds().split(",")); + adminUserVO.setRoles( + roles.stream().filter + (role -> memberRoles.contains(role.getId())) + .collect(Collectors.toList()) + ); + } catch (Exception e) { + log.error("填充部门信息异常", e); + } + } + result.add(adminUserVO); + }); + Page pageResult = new Page(adminUserPage.getCurrent(), adminUserPage.getSize(), adminUserPage.getTotal()); + pageResult.setRecords(result); + return pageResult; + + } + + @Autowired + private void setManagerTokenGenerate(ManagerTokenGenerate managerTokenGenerate) { + this.managerTokenGenerate = managerTokenGenerate; + } + + @Override + @SystemLogPoint(description = "管理员登录", customerLog = "'管理员登录请求:'+#username") + public Token login(String username, String password) { + AdminUser adminUser = this.findByUsername(username); + + if (adminUser == null || !adminUser.getStatus()) { + throw new ServiceException(ResultCode.USER_PASSWORD_ERROR); + } + if (!new BCryptPasswordEncoder().matches(password, adminUser.getPassword())) { + throw new ServiceException(ResultCode.USER_PASSWORD_ERROR); + } + try { + return managerTokenGenerate.createToken(adminUser, false); + } catch (Exception e) { + log.error("管理员登录错误", e); + } + return null; + + } + + + @Override + public Token refreshToken(String refreshToken) { + return managerTokenGenerate.refreshToken(refreshToken); + } + + /** + * 登出 + */ + @Override + public void logout(UserEnums userEnums) { + String currentUserToken = UserContext.getCurrentUserToken(); + if (CharSequenceUtil.isNotEmpty(currentUserToken)) { + cache.remove(CachePrefix.ACCESS_TOKEN.getPrefix(userEnums) + currentUserToken); + } + } + + + @Override + public AdminUser findByUsername(String username) { + + AdminUser user = getOne(new LambdaQueryWrapper().eq(AdminUser::getUsername, username)); + + if (user == null) { + return null; + } + AdminUserVO adminUserVO = new AdminUserVO(user); + //关联部门 + if (user.getDepartmentId() != null) { + Department department = departmentService.getById(user.getDepartmentId()); + if (department != null) { + adminUserVO.setDepartmentTitle(department.getTitle()); + } + } + adminUserVO.setMenus(menuService.findUserList(user.getId())); + return user; + } + + + @Override + @SystemLogPoint(description = "修改管理员", customerLog = "'修改管理员:'+#adminUser.username") + @Transactional(rollbackFor = Exception.class) + public boolean updateAdminUser(AdminUser adminUser, List roles) { + + if (roles != null && !roles.isEmpty()) { + + if (roles.size() > rolesMaxSize) { + throw new ServiceException(ResultCode.PERMISSION_BEYOND_TEN); + } + adminUser.setRoleIds(CharSequenceUtil.join(",", roles)); + + } else { + adminUser.setRoleIds(""); + } + + updateRole(adminUser.getId(), roles); + this.updateById(adminUser); + return true; + } + + + @Override + public void editPassword(String password, String newPassword) { + AuthUser tokenUser = UserContext.getCurrentUser(); + AdminUser user = this.getById(tokenUser.getId()); + if (!new BCryptPasswordEncoder().matches(password, user.getPassword())) { + throw new ServiceException(ResultCode.USER_OLD_PASSWORD_ERROR); + } + String newEncryptPass = new BCryptPasswordEncoder().encode(newPassword); + user.setPassword(newEncryptPass); + this.updateById(user); + } + + @Override + public void resetPassword(List ids) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper(); + lambdaQueryWrapper.in(AdminUser::getId, ids); + List adminUsers = this.list(lambdaQueryWrapper); + String password = StringUtils.md5("123456"); + String newEncryptPass = new BCryptPasswordEncoder().encode(password); + if (null != adminUsers && !adminUsers.isEmpty()) { + adminUsers.forEach(item -> item.setPassword(newEncryptPass)); + this.updateBatchById(adminUsers); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveAdminUser(AdminUserDTO adminUser, List roles) { + AdminUser dbUser = new AdminUser(); + BeanUtil.copyProperties(adminUser, dbUser); + dbUser.setPassword(new BCryptPasswordEncoder().encode(dbUser.getPassword())); + if (roles.size() > rolesMaxSize) { + throw new ServiceException(ResultCode.PERMISSION_BEYOND_TEN); + } + if (!roles.isEmpty()) { + dbUser.setRoleIds(CharSequenceUtil.join(",", roles)); + } + this.save(dbUser); + dbUser = this.findByUsername(dbUser.getUsername()); + updateRole(dbUser.getId(), roles); + } + + + @Override + public void deleteCompletely(List ids) { + //彻底删除超级管理员 + this.removeByIds(ids); + + //删除管理员角色 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.in("user_id", ids); + userRoleService.remove(queryWrapper); + } + + /** + * 更新用户角色 + * + * @param userId 用户id + * @param roles 角色id集合 + */ + private void updateRole(String userId, List roles) { + + QueryWrapper queryWrapper = new QueryWrapper(); + queryWrapper.eq("user_id", userId); + userRoleService.remove(queryWrapper); + + if (roles.isEmpty()) { + return; + } + List userRoles = new ArrayList<>(roles.size()); + roles.forEach(id -> userRoles.add(new UserRole(userId, id))); + userRoleService.updateUserRole(userId, userRoles); + } +} diff --git a/src/main/java/cn/lili/modules/permission/serviceimpl/DepartmentRoleServiceImpl.java b/src/main/java/cn/lili/modules/permission/serviceimpl/DepartmentRoleServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..10e484de9db5adfb09fb7016bb421312c913e517 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/serviceimpl/DepartmentRoleServiceImpl.java @@ -0,0 +1,46 @@ +package cn.lili.modules.permission.serviceimpl; + +import cn.lili.modules.permission.entity.dos.DepartmentRole; +import cn.lili.modules.permission.mapper.DepartmentRoleMapper; +import cn.lili.modules.permission.service.DepartmentRoleService; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * 部门角色业务层实现 + * + * @author Chopper + * @since 2020/11/22 12:08 + */ +@Service +public class DepartmentRoleServiceImpl extends ServiceImpl implements DepartmentRoleService { + + + @Override + public List listByDepartmentId(String departmentId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("department_id", departmentId); + return this.baseMapper.selectList(queryWrapper); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateByDepartmentId(String departmentId, List departmentRoles) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("department_id", departmentId); + this.remove(queryWrapper); + + this.saveBatch(departmentRoles); + } + + @Override + public void deleteByDepartment(List ids) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.in("department_id", ids); + this.remove(queryWrapper); + } +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/permission/serviceimpl/DepartmentServiceImpl.java b/src/main/java/cn/lili/modules/permission/serviceimpl/DepartmentServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..8de3fe9b16906294f14d4152f2db95965cbc7aab --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/serviceimpl/DepartmentServiceImpl.java @@ -0,0 +1,96 @@ +package cn.lili.modules.permission.serviceimpl; + +import cn.lili.exception.ServiceException; +import cn.lili.modules.permission.entity.dos.AdminUser; +import cn.lili.modules.permission.entity.dos.Department; +import cn.lili.modules.permission.entity.vo.DepartmentVO; +import cn.lili.modules.permission.mapper.DepartmentMapper; +import cn.lili.modules.permission.service.AdminUserService; +import cn.lili.modules.permission.service.DepartmentRoleService; +import cn.lili.modules.permission.service.DepartmentService; +import cn.lili.result.ResultCode; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +/** + * 部门业务层实现 + * + * @author Chopper + * @since 2020/11/17 3:47 下午 + */ +@Slf4j +@Service +public class DepartmentServiceImpl extends ServiceImpl implements DepartmentService { + + /** + * 管理员 + */ + @Autowired + private AdminUserService adminUserService; + /** + * 部门角色 + */ + @Autowired + private DepartmentRoleService departmentRoleService; + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteByIds(List ids) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.in("department_id", ids); + if (adminUserService.count(queryWrapper) > 0) { + throw new ServiceException(ResultCode.PERMISSION_DEPARTMENT_DELETE_ERROR); + } + this.removeByIds(ids); + departmentRoleService.deleteByDepartment(ids); + } + + @Override + public List tree(QueryWrapper initWrapper) { + try { + List departments = this.list(initWrapper); + + List all = new ArrayList<>(); + departments.forEach(item -> all.add(new DepartmentVO(item))); + + List tree = new ArrayList<>(); + all.forEach(item -> { + if ("0".equals(item.getParentId())) { + initChild(item, all); + tree.add(item); + } + }); + + return tree; + } catch (Exception e) { + log.error("部门业务错误", e); + return null; + } + } + + + /** + * 递归初始化子树 + * + * @param tree 树结构 + * @param departmentVOS 数据库对象集合 + */ + private void initChild(DepartmentVO tree, List departmentVOS) { + departmentVOS.stream() + .filter(item -> (item.getParentId().equals(tree.getId()))) + .forEach(child -> { + DepartmentVO childTree = new DepartmentVO(child); + initChild(childTree, departmentVOS); + tree.getChildren().add(childTree); + }); + } + + +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/permission/serviceimpl/MenuServiceImpl.java b/src/main/java/cn/lili/modules/permission/serviceimpl/MenuServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..46fc859cc0540744b8a78ea3391530040c7e9e22 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/serviceimpl/MenuServiceImpl.java @@ -0,0 +1,167 @@ +package cn.lili.modules.permission.serviceimpl; + +import cn.hutool.core.text.CharSequenceUtil; +import cn.lili.cache.Cache; +import cn.lili.cache.CachePrefix; +import cn.lili.exception.ServiceException; +import cn.lili.modules.permission.entity.dos.Menu; +import cn.lili.modules.permission.entity.dos.RoleMenu; +import cn.lili.modules.permission.entity.dto.MenuSearchParams; +import cn.lili.modules.permission.entity.vo.MenuVO; +import cn.lili.modules.permission.mapper.MenuMapper; +import cn.lili.modules.permission.service.MenuService; +import cn.lili.modules.permission.service.RoleMenuService; +import cn.lili.mybatis.util.PageUtil; +import cn.lili.result.ResultCode; +import cn.lili.result.SearchVO; +import cn.lili.security.AuthUser; +import cn.lili.security.UserContext; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.*; + +/** + * 权限业务层实现 + * + * @author Chopper + * @since 2020/11/17 3:49 下午 + */ +@Slf4j +@Service +public class MenuServiceImpl extends ServiceImpl implements MenuService { + /** + * 菜单角色 + */ + @Autowired + private RoleMenuService roleMenuService; + + @Autowired + private Cache> cache; + + @Override + public void deleteIds(List ids) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.in("menu_id", ids); + //如果已有角色绑定菜单,则不能直接删除 + if (roleMenuService.count(queryWrapper) > 0) { + throw new ServiceException(ResultCode.PERMISSION_MENU_ROLE_ERROR); + } + this.removeByIds(ids); + } + + + @Override + public List findUserTree() { + AuthUser authUser = Objects.requireNonNull(UserContext.getCurrentUser()); + if (Boolean.TRUE.equals(authUser.getIsSuper())) { + return this.tree(); + } + List userMenus = this.baseMapper.findByUserId(authUser.getId()); + return this.tree(userMenus); + } + + @Override + public List findUserList(String userId) { + String cacheKey = CachePrefix.MENU_USER_ID.getPrefix() + userId; + List menuList = cache.get(cacheKey); + if (menuList == null) { + menuList = this.baseMapper.findByUserId(userId); + cache.put(cacheKey, menuList); + } + return menuList; + } + + /** + * 添加更新菜单 + * + * @param menu 菜单数据 + * @return 是否成功 + */ + @Override + public boolean saveOrUpdateMenu(Menu menu) { + if (CharSequenceUtil.isNotEmpty(menu.getId())) { + cache.vagueDel(CachePrefix.MENU_USER_ID.getPrefix()); + cache.vagueDel(CachePrefix.USER_MENU.getPrefix()); + } + return this.saveOrUpdate(menu); + } + + @Override + public List findByRoleIds(String roleId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("role_id", roleId); + return this.list(queryWrapper); + } + + @Override + public List searchList(MenuSearchParams menuSearchParams) { + //title 需要特殊处理 + String title = null; + if (CharSequenceUtil.isNotEmpty(menuSearchParams.getTitle())) { + title = menuSearchParams.getTitle(); + menuSearchParams.setTitle(null); + } + QueryWrapper queryWrapper = PageUtil.initWrapper(menuSearchParams, new SearchVO()); + if (CharSequenceUtil.isNotEmpty(title)) { + queryWrapper.like("title", title); + } + queryWrapper.orderByDesc("sort_order"); + return this.baseMapper.selectList(queryWrapper); + } + + + @Override + public List tree() { + try { + List menus = this.list(); + return tree(menus); + } catch (Exception e) { + log.error("菜单树错误", e); + } + return Collections.emptyList(); + } + + /** + * 传入自定义菜单集合 + * + * @param menus 自定义菜单集合 + * @return 修改后的自定义菜单集合 + */ + private List tree(List menus) { + List tree = new ArrayList<>(); + menus.forEach(item -> { + if (item.getLevel() == 0) { + MenuVO treeItem = new MenuVO(item); + initChild(treeItem, menus); + tree.add(treeItem); + } + }); + //对一级菜单排序 + tree.sort(Comparator.comparing(Menu::getSortOrder)); + return tree; + } + + /** + * 递归初始化子树 + * + * @param tree 树结构 + * @param menus 数据库对象集合 + */ + private void initChild(MenuVO tree, List menus) { + if (menus == null) { + return; + } + menus.stream() + .filter(item -> (item.getParentId().equals(tree.getId()))) + .forEach(child -> { + MenuVO childTree = new MenuVO(child); + initChild(childTree, menus); + tree.getChildren().add(childTree); + }); + } + +} diff --git a/src/main/java/cn/lili/modules/permission/serviceimpl/RoleMenuServiceImpl.java b/src/main/java/cn/lili/modules/permission/serviceimpl/RoleMenuServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..8966c190af49677311fe3990a7680f1d3cf44305 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/serviceimpl/RoleMenuServiceImpl.java @@ -0,0 +1,94 @@ +package cn.lili.modules.permission.serviceimpl; + +import cn.lili.cache.Cache; +import cn.lili.cache.CachePrefix; +import cn.lili.modules.permission.entity.dos.RoleMenu; +import cn.lili.modules.permission.entity.vo.UserMenuVO; +import cn.lili.modules.permission.mapper.MenuMapper; +import cn.lili.modules.permission.mapper.RoleMenuMapper; +import cn.lili.modules.permission.service.RoleMenuService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import groovy.util.logging.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 角色菜单业务层实现 + * + * @author Chopper + * @since 2020/11/22 11:43 + */ +@Slf4j +@Service +public class RoleMenuServiceImpl extends ServiceImpl implements RoleMenuService { + + /** + * 菜单 + */ + @Resource + private MenuMapper menuMapper; + + + @Autowired + private Cache cache; + + @Override + public List findByRoleId(String roleId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(RoleMenu::getRoleId, roleId); + return this.baseMapper.selectList(queryWrapper); + } + + @Override + public List findAllMenu(String userId) { + String cacheKey = CachePrefix.USER_MENU.getPrefix() + userId; + List menuList = (List) cache.get(cacheKey); + if (menuList == null) { + menuList = menuMapper.getUserRoleMenu(userId); + cache.put(cacheKey, menuList); + } + return menuList; + } + + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateRoleMenu(String roleId, List roleMenus) { + try { + //删除角色已经绑定的菜单 + this.deleteRoleMenu(roleId); + //重新保存角色菜单关系 + this.saveBatch(roleMenus); + cache.vagueDel(CachePrefix.MENU_USER_ID.getPrefix()); + cache.vagueDel(CachePrefix.USER_MENU.getPrefix()); + } catch (Exception e) { + log.error("修改用户权限错误", e); + } + } + + @Override + public void deleteRoleMenu(String roleId) { + //删除 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("role_id", roleId); + this.remove(queryWrapper); + cache.vagueDel(CachePrefix.MENU_USER_ID.getPrefix()); + cache.vagueDel(CachePrefix.USER_MENU.getPrefix()); + } + + @Override + public void deleteRoleMenu(List roleId) { + //删除 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.in("role_id", roleId); + this.remove(queryWrapper); + cache.vagueDel(CachePrefix.MENU_USER_ID.getPrefix()); + cache.vagueDel(CachePrefix.USER_MENU.getPrefix()); + } +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/permission/serviceimpl/RoleServiceImpl.java b/src/main/java/cn/lili/modules/permission/serviceimpl/RoleServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..54ee6604e06e812cc1727c1bc1dd30969302cd17 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/serviceimpl/RoleServiceImpl.java @@ -0,0 +1,65 @@ +package cn.lili.modules.permission.serviceimpl; + +import cn.lili.exception.ServiceException; +import cn.lili.modules.permission.entity.dos.Role; +import cn.lili.modules.permission.mapper.RoleMapper; +import cn.lili.modules.permission.service.DepartmentRoleService; +import cn.lili.modules.permission.service.RoleMenuService; +import cn.lili.modules.permission.service.RoleService; +import cn.lili.modules.permission.service.UserRoleService; +import cn.lili.result.ResultCode; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * 角色业务层实现 + * + * @author Chopper + * @since 2020/11/17 3:50 下午 + */ +@Service +public class RoleServiceImpl extends ServiceImpl implements RoleService { + + /** + * 部门角色 + */ + @Autowired + private DepartmentRoleService departmentRoleService; + /** + * 用户权限 + */ + @Autowired + private UserRoleService userRoleService; + + @Autowired + private RoleMenuService roleMenuService; + + @Override + public List findByDefaultRole(Boolean defaultRole) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("default_role", true); + return baseMapper.selectList(queryWrapper); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteRoles(List roleIds) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.in("role_id", roleIds); + if (departmentRoleService.count(queryWrapper) > 0) { + throw new ServiceException(ResultCode.PERMISSION_DEPARTMENT_ROLE_ERROR); + } + if (userRoleService.count(queryWrapper) > 0) { + throw new ServiceException(ResultCode.PERMISSION_USER_ROLE_ERROR); + } + //删除角色 + this.removeByIds(roleIds); + //删除角色与菜单关联 + roleMenuService.remove(queryWrapper); + } +} diff --git a/src/main/java/cn/lili/modules/permission/serviceimpl/SystemLogServiceImpl.java b/src/main/java/cn/lili/modules/permission/serviceimpl/SystemLogServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..5fb886d14492948e6158933009fdb090cc0ae2ca --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/serviceimpl/SystemLogServiceImpl.java @@ -0,0 +1,126 @@ +package cn.lili.modules.permission.serviceimpl; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.text.CharSequenceUtil; +import cn.lili.modules.permission.entity.vo.SystemLogVO; +import cn.lili.modules.permission.repository.SystemLogRepository; +import cn.lili.modules.permission.service.SystemLogService; +import cn.lili.result.PageVO; +import cn.lili.result.SearchVO; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.search.sort.SortBuilders; +import org.elasticsearch.search.sort.SortOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; +import org.springframework.data.elasticsearch.core.SearchHits; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.stereotype.Service; + +import org.springframework.data.elasticsearch.core.SearchHit; +import org.springframework.data.elasticsearch.core.SearchHits; +import javax.swing.*; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 系统日志 + * + * @author Chopper + * @since 2020/11/17 3:45 下午 + */ +@Service +public class SystemLogServiceImpl implements SystemLogService { + + @Autowired + private SystemLogRepository systemLogRepository; + + /** + * ES + */ + @Autowired + @Qualifier("elasticsearchRestTemplate") + private ElasticsearchRestTemplate restTemplate; + + @Override + public void saveLog(SystemLogVO systemLogVO) { + systemLogRepository.save(systemLogVO); + } + + @Override + public void deleteLog(List id) { + for (String s : id) { + systemLogRepository.deleteById(s); + } + } + + @Override + public void flushAll() { + systemLogRepository.deleteAll(); + } + + @Override + public IPage queryLog(String storeId, String operatorName, String key, SearchVO searchVo, PageVO pageVO) { + pageVO.setNotConvert(true); + IPage iPage = new Page<>(); + NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder(); + if (pageVO.getPageNumber() != null && pageVO.getPageSize() != null) { + int pageNumber = pageVO.getPageNumber() - 1; + if (pageNumber < 0) { + pageNumber = 0; + } + Pageable pageable = PageRequest.of(pageNumber, pageVO.getPageSize()); + //分页 + nativeSearchQueryBuilder.withPageable(pageable); + iPage.setCurrent(pageVO.getPageNumber()); + iPage.setSize(pageVO.getPageSize()); + } + + if (CharSequenceUtil.isNotEmpty(storeId)) { + nativeSearchQueryBuilder.withFilter(QueryBuilders.matchQuery("storeId", storeId)); + } + + if (CharSequenceUtil.isNotEmpty(operatorName)) { + nativeSearchQueryBuilder.withFilter(QueryBuilders.wildcardQuery("username", "*" + operatorName + "*")); + } + + if (CharSequenceUtil.isNotEmpty(key)) { + BoolQueryBuilder filterBuilder = new BoolQueryBuilder(); + filterBuilder.should(QueryBuilders.wildcardQuery("requestUrl", "*" + key + "*")) + .should(QueryBuilders.wildcardQuery("requestParam", "*" + key + "*")) + .should(QueryBuilders.wildcardQuery("responseBody", "*" + key + "*")) + .should(QueryBuilders.wildcardQuery("name", "*" + key + "*")); + nativeSearchQueryBuilder.withFilter(filterBuilder); + } + //时间有效性判定 + if (searchVo.getConvertStartDate() != null && searchVo.getConvertEndDate() != null) { + BoolQueryBuilder filterBuilder = new BoolQueryBuilder(); + //大于方法 + filterBuilder.must( + QueryBuilders.rangeQuery("createTime") + .gte(DateUtil.format(searchVo.getConvertStartDate(), "dd/MM/yyyy")) + .lte(DateUtil.format(searchVo.getConvertEndDate(), "dd/MM/yyyy")).format("dd/MM/yyyy||yyyy")); + + nativeSearchQueryBuilder.withFilter(filterBuilder); + } + + if (CharSequenceUtil.isNotEmpty(pageVO.getOrder()) && CharSequenceUtil.isNotEmpty(pageVO.getSort())) { + nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(pageVO.getSort()).order(SortOrder.valueOf(pageVO.getOrder().toUpperCase()))); + } else { + nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC)); + } + + SearchHits searchResult = restTemplate.search(nativeSearchQueryBuilder.build(), SystemLogVO.class); + + iPage.setTotal(searchResult.getTotalHits()); + + iPage.setRecords(searchResult.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList())); + return iPage; + } + +} diff --git a/src/main/java/cn/lili/modules/permission/serviceimpl/UserRoleServiceImpl.java b/src/main/java/cn/lili/modules/permission/serviceimpl/UserRoleServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..e3e84d4e4496cc1101584afc63a740a897c3fb1a --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/serviceimpl/UserRoleServiceImpl.java @@ -0,0 +1,51 @@ +package cn.lili.modules.permission.serviceimpl; + +import cn.lili.modules.permission.entity.dos.UserRole; +import cn.lili.modules.permission.mapper.UserRoleMapper; +import cn.lili.modules.permission.service.UserRoleService; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +/** + * 用户权限业务层实现 + * + * @author Chopper + * @since 2020/11/17 3:52 下午 + */ +@Service +public class UserRoleServiceImpl extends ServiceImpl implements UserRoleService { + + @Override + public List listByUserId(String userId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("user_id", userId); + return this.baseMapper.selectList(queryWrapper); + } + + @Override + public List listId(String userId) { + List userRoleList = this.listByUserId(userId); + List strings = new ArrayList<>(); + userRoleList.forEach(item -> strings.add(item.getRoleId())); + return strings; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateUserRole(String userId, List userRoles) { + + //删除 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("user_id", userId); + this.remove(queryWrapper); + + //保存 + this.saveBatch(userRoles); + } + +} diff --git a/src/main/java/cn/lili/modules/permission/token/ManagerTokenGenerate.java b/src/main/java/cn/lili/modules/permission/token/ManagerTokenGenerate.java new file mode 100644 index 0000000000000000000000000000000000000000..7da17e00ec36dba6816147f0a4a954ad97550e19 --- /dev/null +++ b/src/main/java/cn/lili/modules/permission/token/ManagerTokenGenerate.java @@ -0,0 +1,135 @@ +package cn.lili.modules.permission.token; + +import cn.hutool.core.text.CharSequenceUtil; +import cn.lili.cache.Cache; +import cn.lili.cache.CachePrefix; +import cn.lili.modules.permission.entity.dos.AdminUser; +import cn.lili.modules.permission.entity.enums.PermissionEnum; +import cn.lili.modules.permission.entity.vo.UserMenuVO; +import cn.lili.modules.permission.service.RoleMenuService; +import cn.lili.security.AuthUser; +import cn.lili.security.enums.UserEnums; +import cn.lili.security.token.Token; +import cn.lili.security.token.TokenUtil; +import cn.lili.security.token.base.AbstractTokenGenerate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 管理员token生成 + * + * @author Chopper + * @version v4.0 + * @since 2020/11/16 10:51 + */ +@Component +public class ManagerTokenGenerate extends AbstractTokenGenerate { + + @Autowired + private TokenUtil tokenUtil; + @Autowired + private RoleMenuService roleMenuService; + @Autowired + private Cache cache; + + + @Override + public Token createToken(AdminUser adminUser, Boolean longTerm) { + AuthUser authUser = new AuthUser(adminUser.getUsername(), + adminUser.getId(), + adminUser.getAvatar(), + UserEnums.MANAGER, + adminUser.getNickName(), + adminUser.getIsSuper() + ); + + + List userMenuVOList = roleMenuService.findAllMenu(authUser.getId()); + //缓存权限列表 + cache.put(CachePrefix.PERMISSION_LIST.getPrefix(UserEnums.MANAGER) + authUser.getId(), this.permissionList(userMenuVOList)); + + return tokenUtil.createToken(adminUser.getUsername(), authUser, longTerm, UserEnums.MANAGER); + } + + @Override + public Token refreshToken(String refreshToken) { + return tokenUtil.refreshToken(refreshToken, UserEnums.MANAGER); + } + + /** + * 获取用户权限 + * + * @param userMenuVOList + * @return + */ + private Map> permissionList(List userMenuVOList) { + Map> permission = new HashMap<>(2); + + List superPermissions = new ArrayList<>(); + List queryPermissions = new ArrayList<>(); + initPermission(superPermissions, queryPermissions); + + //循环权限菜单 + if (userMenuVOList != null && !userMenuVOList.isEmpty()) { + userMenuVOList.forEach(menu -> { + //循环菜单,赋予用户权限 + if (CharSequenceUtil.isNotEmpty(menu.getPermission())) { + //获取路径集合 + String[] permissionUrl = menu.getPermission().split(","); + //for循环路径集合 + for (String url : permissionUrl) { + //如果是超级权限 则计入超级权限 + if (Boolean.TRUE.equals(menu.getSuper())) { + //如果已有超级权限,则这里就不做权限的累加 + if (!superPermissions.contains(url)) { + superPermissions.add(url); + } + } + //否则计入浏览权限 + else { + //没有权限,则累加。 + if (!queryPermissions.contains(url)) { + queryPermissions.add(url); + } + } + } + } + //去除重复的权限 + queryPermissions.removeAll(superPermissions); + }); + } + permission.put(PermissionEnum.SUPER.name(), superPermissions); + permission.put(PermissionEnum.QUERY.name(), queryPermissions); + return permission; + } + + /** + * 初始赋予的权限,查看权限包含首页流量统计权限, + * 超级权限包含个人信息维护,密码修改权限 + * + * @param superPermissions 超级权限 + * @param queryPermissions 查询权限 + */ + void initPermission(List superPermissions, List queryPermissions) { + //用户信息维护 + superPermissions.add("/manager/user/info*"); + superPermissions.add("/manager/user/edit*"); + superPermissions.add("/manager/user/editPassword*"); + + //统计查看权限 + queryPermissions.add("/manager/statistics*"); + //菜单查看权限 + queryPermissions.add("/manager/menu*"); + //商品分类查看权限 + queryPermissions.add("/manager/goods/category*"); + //查看地区接口 + queryPermissions.add("/manager/region*"); + + } + +} diff --git a/src/main/java/cn/lili/modules/seat/dos/QA.java b/src/main/java/cn/lili/modules/seat/dos/QA.java new file mode 100644 index 0000000000000000000000000000000000000000..4e4a2031b7029c86c0f6a400b6949fdf05b6e0da --- /dev/null +++ b/src/main/java/cn/lili/modules/seat/dos/QA.java @@ -0,0 +1,31 @@ +package cn.lili.modules.seat.dos; + +import cn.lili.mybatis.BaseEntity; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 问题答案 + * + * @author Chopper + * @version v1.0 + * 2022-02-09 17:59 + */ +@Data +@TableName("li_qa") +@ApiModel(value = "租户问答") +@NoArgsConstructor +public class QA extends BaseEntity { + + @ApiModelProperty(value = "租户id") + private Integer tenantId; + + @ApiModelProperty(value = "问题") + private String question; + + @ApiModelProperty(value = "答案") + private String answer; +} diff --git a/src/main/java/cn/lili/modules/seat/dos/Seat.java b/src/main/java/cn/lili/modules/seat/dos/Seat.java new file mode 100644 index 0000000000000000000000000000000000000000..67a4e9958d06d57a80e8adfd3253ec69910306ad --- /dev/null +++ b/src/main/java/cn/lili/modules/seat/dos/Seat.java @@ -0,0 +1,47 @@ +package cn.lili.modules.seat.dos; + +import cn.lili.mybatis.BaseTenantEntity; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotEmpty; + +/** + * 坐席 + * + * @author Chopper + * @version v1.0 + * 2022-02-09 17:08 + */ +@Data +@TableName("li_seat") +@ApiModel(value = "坐席") +@NoArgsConstructor +public class Seat extends BaseTenantEntity { + + @ApiModelProperty(value = "租户id") + private String tenantId; + + @ApiModelProperty(value = "坐席用户名") + private String username; + + @ApiModelProperty(value = "会员头像") + private String face; + + @ApiModelProperty(value = "坐席密码") + private String password; + + @ApiModelProperty(value = "昵称") + private String nickName; + + @ApiModelProperty(value = "坐席状态") + private Boolean disabled; + + @NotEmpty(message = "手机号码不能为空") + @ApiModelProperty(value = "手机号码", required = true) + private String mobile; + +} diff --git a/src/main/java/cn/lili/modules/seat/dos/SeatSetting.java b/src/main/java/cn/lili/modules/seat/dos/SeatSetting.java new file mode 100644 index 0000000000000000000000000000000000000000..c5671e70d146921874d47176c324493edb2e6cb9 --- /dev/null +++ b/src/main/java/cn/lili/modules/seat/dos/SeatSetting.java @@ -0,0 +1,36 @@ +package cn.lili.modules.seat.dos; + +import cn.lili.mybatis.BaseTenantEntity; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 坐席设置 + * + * @author Chopper + * @version v1.0 + * 2022-02-09 17:55 + */ +@Data +@TableName("li_seat_setting") +@ApiModel(value = "坐席设置") +@NoArgsConstructor +public class SeatSetting extends BaseTenantEntity { + + @ApiModelProperty(value = "租户idid") + private String tenantId; + + @ApiModelProperty(value = "欢迎语") + private String welcome; + + @ApiModelProperty(value = "离线自动回复") + private String outLineAutoReply; + + @ApiModelProperty(value = "长时间自动回复") + private String longTermAutoReply; + + +} diff --git a/src/main/java/cn/lili/modules/seat/enums/MessageTypeEnum.java b/src/main/java/cn/lili/modules/seat/enums/MessageTypeEnum.java new file mode 100644 index 0000000000000000000000000000000000000000..7906c00a0146b62c9b74b300cbd40b8bd24d93b7 --- /dev/null +++ b/src/main/java/cn/lili/modules/seat/enums/MessageTypeEnum.java @@ -0,0 +1,31 @@ +package cn.lili.modules.seat.enums; + +/** + * 消息的类型 + * + * @author liushuai(liushuai711 @ gmail.com) + * @version v4.0 + * @Description: + * @since 2022/2/10 16:36 + */ +public enum MessageTypeEnum { + //socket刚打开时发送的消息,这个一般是是刚打开socket链接,进行登录,传入token用 + CONNECT, + //心跳类型的消息,此种类型的消息只有 type 、 text 两种属性 + HEARTBEAT, + //用户打开一个对话框,准备跟某人聊天时 + OPEN, + //客服进行自动回复。客户端发起这种类型请求,则是在拉取对方是否有自动回复,如果有,服务端就会给客户端发送过自动回复的信息 + AUTO_REPLY, + //正常收发消息沟通,文字、表情等沟通 + MSG, + //扩展。比如发送商品、发送订单 + EXTEND, + //系统提示,如提示 对方已离线 + SYSTEM, + //服务端发送到客户端,用于设置客户端的用户信息。会吧 com.xnx3.yunkefu.core.vo.bean.User 传过去 + SET_USER, + //结束服务 + CLOSE_SERVICE; + +} diff --git a/src/main/java/cn/lili/modules/seat/enums/OnlineStatusEnum.java b/src/main/java/cn/lili/modules/seat/enums/OnlineStatusEnum.java new file mode 100644 index 0000000000000000000000000000000000000000..3972ece63cd474bfa2bf179b3acb0574191420ed --- /dev/null +++ b/src/main/java/cn/lili/modules/seat/enums/OnlineStatusEnum.java @@ -0,0 +1,14 @@ +package cn.lili.modules.seat.enums; + +/** + * 坐席在线状态 + * + * @author Chopper + * @version v1.0 + * 2022-02-10 16:37 + */ +public enum OnlineStatusEnum { + // 在线/下线 + ONLINE, + OUTLINE; +} diff --git a/src/main/java/cn/lili/modules/seat/handler/SeatTokenGenerate.java b/src/main/java/cn/lili/modules/seat/handler/SeatTokenGenerate.java new file mode 100644 index 0000000000000000000000000000000000000000..64e39ed5705ccfd96853f403117ab2563e843fcb --- /dev/null +++ b/src/main/java/cn/lili/modules/seat/handler/SeatTokenGenerate.java @@ -0,0 +1,43 @@ +package cn.lili.modules.seat.handler; + +import cn.lili.modules.seat.dos.Seat; +import cn.lili.modules.seat.service.SeatService; +import cn.lili.security.AuthUser; +import cn.lili.security.enums.UserEnums; +import cn.lili.security.token.Token; +import cn.lili.security.token.TokenUtil; +import cn.lili.security.token.base.AbstractTokenGenerate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * 会员token生成 + * + * @author Chopper + * @version v4.0 + * @since 2020/11/16 10:50 + */ +@Component +public class SeatTokenGenerate extends AbstractTokenGenerate { + @Autowired + private TokenUtil tokenUtil; + + @Override + public Token createToken(Seat seat, Boolean longTerm) { + AuthUser authUser = new AuthUser( + seat.getUsername(), + seat.getId(), + seat.getNickName(), + seat.getFace(), + UserEnums.SEAT); + authUser.setTenantId(seat.getTenantId()); + //登陆成功生成token + return tokenUtil.createToken(seat.getUsername(), authUser, longTerm, UserEnums.SEAT); + } + + @Override + public Token refreshToken(String refreshToken) { + return tokenUtil.refreshToken(refreshToken, UserEnums.SEAT); + } + +} diff --git a/src/main/java/cn/lili/modules/seat/mapper/QAMapper.java b/src/main/java/cn/lili/modules/seat/mapper/QAMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..e078c74ecb5910fcfcbda913f96f3dae70d64e8d --- /dev/null +++ b/src/main/java/cn/lili/modules/seat/mapper/QAMapper.java @@ -0,0 +1,16 @@ +package cn.lili.modules.seat.mapper; + + +import cn.lili.modules.seat.dos.QA; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 问答处理层 + * + * @author pikachu + * @since 2020-02-18 15:18:56 + */ +public interface QAMapper extends BaseMapper { + + +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/seat/mapper/SeatMapper.java b/src/main/java/cn/lili/modules/seat/mapper/SeatMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..e364884b296a0fa4a7f332a778aedb19ef70ee74 --- /dev/null +++ b/src/main/java/cn/lili/modules/seat/mapper/SeatMapper.java @@ -0,0 +1,16 @@ +package cn.lili.modules.seat.mapper; + + +import cn.lili.modules.seat.dos.Seat; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 坐席处理层 + * + * @author pikachu + * @since 2020-02-18 15:18:56 + */ +public interface SeatMapper extends BaseMapper { + + +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/seat/mapper/SeatSettingMapper.java b/src/main/java/cn/lili/modules/seat/mapper/SeatSettingMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..3ccf4563879a74c0c9f87e3676f6b4a2b3a385cc --- /dev/null +++ b/src/main/java/cn/lili/modules/seat/mapper/SeatSettingMapper.java @@ -0,0 +1,16 @@ +package cn.lili.modules.seat.mapper; + + +import cn.lili.modules.seat.dos.SeatSetting; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 坐席设置处理层 + * + * @author pikachu + * @since 2020-02-18 15:18:56 + */ +public interface SeatSettingMapper extends BaseMapper { + + +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/seat/service/QAService.java b/src/main/java/cn/lili/modules/seat/service/QAService.java new file mode 100644 index 0000000000000000000000000000000000000000..0f55683dbb90623951a5986b29a96d120802812f --- /dev/null +++ b/src/main/java/cn/lili/modules/seat/service/QAService.java @@ -0,0 +1,25 @@ +package cn.lili.modules.seat.service; + + +import cn.lili.modules.seat.dos.QA; +import cn.lili.result.PageVO; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + * 问答 + * + * @author pikachu + * @since 2020-02-18 16:18:56 + */ +public interface QAService extends IService { + + /** + * 查询店铺问题 + * @param word + * @param pageVO + * @return + */ + IPage getStoreQA(String word, PageVO pageVO); + +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/seat/service/SeatService.java b/src/main/java/cn/lili/modules/seat/service/SeatService.java new file mode 100644 index 0000000000000000000000000000000000000000..fb3e90019064c463d54d3728b50ee4b146e0e437 --- /dev/null +++ b/src/main/java/cn/lili/modules/seat/service/SeatService.java @@ -0,0 +1,60 @@ +package cn.lili.modules.seat.service; + + +import cn.lili.modules.seat.dos.Seat; +import cn.lili.modules.seat.vo.SeatVO; +import cn.lili.security.token.Token; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +/** + * 坐席业务 + * + * @author pikachu + * @since 2020-02-18 16:18:56 + */ +public interface SeatService extends IService { + + + /** + * 获取坐席列表 + * + * @param storeId 店铺id + * @return + */ + List seatVoList(String storeId); + + /** + * 坐席登录 + * + * @param username + * @param password + * @return + */ + Token usernameLogin(String username, String password); + + /** + * 快捷登录code 生成 + * + * @param username 用户名 + * @return + */ + String createQuickLoginCode(String username); + + /** + * 快捷登录 + * + * @param code + * @return + */ + Token quickLogin(String code); + + /** + * 查询坐席 + * + * @param username + * @return + */ + Seat findByUsername(String username); +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/seat/service/SeatSettingService.java b/src/main/java/cn/lili/modules/seat/service/SeatSettingService.java new file mode 100644 index 0000000000000000000000000000000000000000..ef14120adebd9445f739e4235a86c0ba5cf79241 --- /dev/null +++ b/src/main/java/cn/lili/modules/seat/service/SeatSettingService.java @@ -0,0 +1,30 @@ +package cn.lili.modules.seat.service; + +import cn.lili.modules.seat.dos.SeatSetting; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + * 坐席设置业务 + * + * @author pikachu + * @since 2020-02-18 16:18:56 + */ +public interface SeatSettingService extends IService { + + + /** + * 根据店铺id获取坐席配置 + * + * @param storeId + * @return + */ + SeatSetting getSetting(String storeId); + + /** + * 根据店铺修改坐席设置 + * + * @param seatSetting 坐席设置 + * @return + */ + SeatSetting updateByStore(SeatSetting seatSetting); +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/seat/serviceimpl/QAServiceImpl.java b/src/main/java/cn/lili/modules/seat/serviceimpl/QAServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..9b1eed6ed8678c4954cbaf7221d7913430ae2c39 --- /dev/null +++ b/src/main/java/cn/lili/modules/seat/serviceimpl/QAServiceImpl.java @@ -0,0 +1,32 @@ +package cn.lili.modules.seat.serviceimpl; + +import cn.lili.modules.seat.dos.QA; +import cn.lili.modules.seat.mapper.QAMapper; +import cn.lili.modules.seat.service.QAService; +import cn.lili.security.UserContext; +import cn.lili.mybatis.util.PageUtil; +import cn.lili.result.PageVO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * 坐席业务层实现 + * + * @author pikachu + * @since 2020-02-18 16:18:56 + */ +@Service +@Transactional(rollbackFor = Exception.class) +public class QAServiceImpl extends ServiceImpl implements QAService { + + @Override + public IPage getStoreQA(String word, PageVO pageVo) { + LambdaQueryWrapper qaLambdaQueryWrapper = new LambdaQueryWrapper<>(); + qaLambdaQueryWrapper.eq(QA::getTenantId, UserContext.getCurrentUser().getTenantId()); + qaLambdaQueryWrapper.like(QA::getQuestion, word); + return this.page(PageUtil.initPage(pageVo), qaLambdaQueryWrapper); + } +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/seat/serviceimpl/SeatServiceImpl.java b/src/main/java/cn/lili/modules/seat/serviceimpl/SeatServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..6f3864a195bc484e29210aeb4fb5226ca3310fad --- /dev/null +++ b/src/main/java/cn/lili/modules/seat/serviceimpl/SeatServiceImpl.java @@ -0,0 +1,111 @@ +package cn.lili.modules.seat.serviceimpl; + +import cn.lili.exception.ServiceException; +import cn.lili.cache.Cache; +import cn.lili.modules.seat.dos.Seat; +import cn.lili.modules.seat.enums.OnlineStatusEnum; +import cn.lili.modules.seat.handler.SeatTokenGenerate; +import cn.lili.modules.seat.mapper.SeatMapper; +import cn.lili.modules.seat.service.SeatService; +import cn.lili.modules.seat.vo.SeatVO; +import cn.lili.security.token.Token; +import cn.lili.result.ResultCode; +import cn.lili.utils.StringUtils; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * 坐席业务层实现 + * + * @author pikachu + * @since 2020-02-18 16:18:56 + */ +@Service +@Transactional(rollbackFor = Exception.class) +public class SeatServiceImpl extends ServiceImpl implements SeatService { + + + @Autowired + private SeatTokenGenerate seatTokenGenerate; + + @Autowired + private Cache cache; + + /** + * 快捷登录缓存前缀 + */ + private static String prefix = "{quick_login}_"; + + + @Override + public List seatVoList(String storeId) { + + LambdaQueryWrapper seatLambdaQueryWrapper = new LambdaQueryWrapper<>(); + seatLambdaQueryWrapper.eq(Seat::getTenantId, storeId); + List list = this.list(seatLambdaQueryWrapper); + + //转换模型为VO + List results = list.stream().map(item -> (SeatVO) item).collect(Collectors.toList()); + //填充坐席当前状态 + //todo + results.forEach(item -> { + item.setOnlineStatus(OnlineStatusEnum.ONLINE.name()); + }); + return results; + } + + @Override + public Token usernameLogin(String username, String password) { + + Seat seat = this.findByUsername(username); + //判断用户是否存在 + if (seat == null || !seat.getDisabled()) { + throw new ServiceException(ResultCode.ERROR); + } + //判断密码是否输入正确 + if (!new BCryptPasswordEncoder().matches(password, seat.getPassword())) { + throw new ServiceException(ResultCode.ERROR); + } + return seatTokenGenerate.createToken(seat, true); + } + + @Override + public String createQuickLoginCode(String username) { + String code = UUID.randomUUID().toString(); + cache.put(prefix + code, username, 20L); + return code; + } + + @Override + public Token quickLogin(String code) { + String username = cache.get(prefix + code); + cache.remove(prefix + code); + if (StringUtils.isEmpty(username)) { + throw new ServiceException(ResultCode.ERROR); + } + return seatTokenGenerate.createToken(findByUsername(username), true); + } + + /** + * 查询坐席 + * + * @param username + * @return + */ + @Override + public Seat findByUsername(String username) { + LambdaQueryWrapper seatLambdaQueryWrapper = new LambdaQueryWrapper<>(); + seatLambdaQueryWrapper.eq(Seat::getUsername, username); + return this.getOne(seatLambdaQueryWrapper); + } + + +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/seat/serviceimpl/SeatSettingServiceImpl.java b/src/main/java/cn/lili/modules/seat/serviceimpl/SeatSettingServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..7196c31b4b074da3c981c72aba7a29d263b257e5 --- /dev/null +++ b/src/main/java/cn/lili/modules/seat/serviceimpl/SeatSettingServiceImpl.java @@ -0,0 +1,48 @@ +package cn.lili.modules.seat.serviceimpl; + +import cn.lili.exception.ServiceException; +import cn.lili.modules.seat.dos.SeatSetting; +import cn.lili.modules.seat.mapper.SeatSettingMapper; +import cn.lili.modules.seat.service.SeatSettingService; +import cn.lili.result.ResultCode; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * 坐席设置业务层实现 + * + * @author pikachu + * @since 2020-02-18 16:18:56 + */ +@Service +@Transactional(rollbackFor = Exception.class) +public class SeatSettingServiceImpl extends ServiceImpl implements SeatSettingService { + @Override + public SeatSetting getSetting(String storeId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SeatSetting::getTenantId, storeId); + SeatSetting seatSetting = this.baseMapper.selectOne(queryWrapper); + if (seatSetting == null) { + seatSetting = new SeatSetting(); + seatSetting.setOutLineAutoReply("您好,我现在不在线,请您留下关键内容和联系方式,我看到后会立马回电。"); + seatSetting.setLongTermAutoReply("您好,我正在查阅相关资料,请您稍等。"); + seatSetting.setWelcome("您好,请问有什么可以帮您?"); + seatSetting.setTenantId(storeId); + this.save(seatSetting); + } + return seatSetting; + } + + @Override + public SeatSetting updateByStore(SeatSetting seatSetting) { + SeatSetting oldSetting = this.baseMapper.selectById(seatSetting.getId()); + if (oldSetting.getTenantId().equals(seatSetting.getTenantId())) { + this.updateById(seatSetting); + } else { + throw new ServiceException(ResultCode.ERROR); + } + return seatSetting; + } +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/seat/vo/SeatVO.java b/src/main/java/cn/lili/modules/seat/vo/SeatVO.java new file mode 100644 index 0000000000000000000000000000000000000000..dac436c615f2052e36a135fa1717efaef7f59f1b --- /dev/null +++ b/src/main/java/cn/lili/modules/seat/vo/SeatVO.java @@ -0,0 +1,22 @@ +package cn.lili.modules.seat.vo; + +import cn.lili.modules.seat.dos.Seat; +import lombok.Data; + +/** + * 客服VO + * + * @author Chopper + * @version v1.0 + * 2022-02-10 15:02 + */ +@Data +public class SeatVO extends Seat { + + /** + * 在线状态 + */ + private String onlineStatus; + + +} diff --git a/src/main/java/cn/lili/modules/tenant/controller/TenantController.java b/src/main/java/cn/lili/modules/tenant/controller/TenantController.java new file mode 100644 index 0000000000000000000000000000000000000000..412cf7e405a646c7f57652fed961bd2394e0f2ed --- /dev/null +++ b/src/main/java/cn/lili/modules/tenant/controller/TenantController.java @@ -0,0 +1,70 @@ +package cn.lili.modules.tenant.controller; + +import cn.lili.modules.tenant.entity.Tenant; +import cn.lili.modules.tenant.service.TenantService; +import cn.lili.mybatis.util.PageUtil; +import cn.lili.result.*; +import com.baomidou.mybatisplus.core.metadata.IPage; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + + +/** + * @author Chopper + */ +@RestController +@Api(tags = "租户接口") +@RequestMapping("/lili/tenant") +public class TenantController { + + @Autowired + private TenantService tenantService; + + @GetMapping(value = "/{id}") + @ApiOperation(value = "查看租户详情") + public ResultMessage get(@PathVariable String id) { + + Tenant tenant = tenantService.getById(id); + return new ResultUtil().setData(tenant); + } + + @GetMapping + @ApiOperation(value = "分页获取租户") + public ResultMessage> getByPage(Tenant entity, + SearchVO searchVo, + PageVO page) { + IPage data = tenantService.page(PageUtil.initPage(page), PageUtil.initWrapper(entity, searchVo)); + return new ResultUtil>().setData(data); + } + + @PostMapping + @ApiOperation(value = "新增租户") + public ResultMessage save(Tenant tenant) { + + if (tenantService.save(tenant)) { + return new ResultUtil().setData(tenant); + } + return new ResultUtil().setErrorMsg(ResultCode.ERROR); + } + + @PutMapping("/{id}") + @ApiOperation(value = "更新租户") + public ResultMessage update(@PathVariable String id, Tenant tenant) { + if (tenantService.updateById(tenant)) { + return new ResultUtil().setData(tenant); + } + return new ResultUtil().setErrorMsg(ResultCode.ERROR); + } + + @DeleteMapping(value = "/{ids}") + @ApiOperation(value = "删除租户") + public ResultMessage delAllByIds(@PathVariable List ids) { + + tenantService.removeByIds(ids); + return ResultUtil.success(); + } +} diff --git a/src/main/java/cn/lili/modules/tenant/controller/TenantUserController.java b/src/main/java/cn/lili/modules/tenant/controller/TenantUserController.java new file mode 100644 index 0000000000000000000000000000000000000000..60eb731ddf376eb4fffce9e0fbd1a5908255fd9e --- /dev/null +++ b/src/main/java/cn/lili/modules/tenant/controller/TenantUserController.java @@ -0,0 +1,70 @@ +package cn.lili.modules.tenant.controller; + +import cn.lili.modules.tenant.entity.TenantUser; +import cn.lili.modules.tenant.service.TenantUserService; +import cn.lili.mybatis.util.PageUtil; +import cn.lili.result.*; +import com.baomidou.mybatisplus.core.metadata.IPage; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + + +/** + * @author Chopper + */ +@RestController +@Api(tags = "租户用户接口") +@RequestMapping("/lili/tenantUser") +public class TenantUserController { + + @Autowired + private TenantUserService tenantUserService; + + @GetMapping(value = "/{id}") + @ApiOperation(value = "查看租户用户详情") + public ResultMessage get(@PathVariable String id){ + + TenantUser tenantUser = tenantUserService.getById(id); + return new ResultUtil().setData(tenantUser); + } + + @GetMapping + @ApiOperation(value = "分页获取租户用户") + public ResultMessage> getByPage(TenantUser entity, + SearchVO searchVo, + PageVO page){ + IPage data = tenantUserService.page(PageUtil.initPage(page),PageUtil.initWrapper(entity, searchVo)); + return new ResultUtil>().setData(data); + } + + @PostMapping + @ApiOperation(value = "新增租户用户") + public ResultMessage save(TenantUser tenantUser){ + + if(tenantUserService.save(tenantUser)){ + return new ResultUtil().setData(tenantUser); + } + return new ResultUtil().setErrorMsg(ResultCode.ERROR); + } + + @PutMapping("/{id}") + @ApiOperation(value = "更新租户用户") + public ResultMessage update(@PathVariable String id, TenantUser tenantUser){ + if(tenantUserService.updateById(tenantUser)){ + return new ResultUtil().setData(tenantUser); + } + return new ResultUtil().setErrorMsg(ResultCode.ERROR); + } + + @DeleteMapping(value = "/{ids}") + @ApiOperation(value = "删除租户用户") + public ResultMessage delAllByIds(@PathVariable List ids){ + + tenantUserService.removeByIds(ids); + return ResultUtil.success(); + } +} diff --git a/src/main/java/cn/lili/modules/tenant/entity/Tenant.java b/src/main/java/cn/lili/modules/tenant/entity/Tenant.java new file mode 100644 index 0000000000000000000000000000000000000000..b8656fd28c56b2bc84a6f37c2d7e339d0f1678d3 --- /dev/null +++ b/src/main/java/cn/lili/modules/tenant/entity/Tenant.java @@ -0,0 +1,40 @@ +package cn.lili.modules.tenant.entity; + +import cn.lili.mybatis.BaseEntity; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; + +/** + * 租户 + * + * @author Chopper + * @version v1.0 + * 2022-02-14 15:56 + */ +@Data +@TableName("li_tenant") +@ApiModel(value = "租户") +@NoArgsConstructor +public class Tenant extends BaseEntity { + + @ApiModelProperty(value = "租户名称") + private String tenantName; + + @ApiModelProperty(value = "租户logo") + private String tenantLogo; + + @ApiModelProperty(value = "accessKey") + private String accessKey; + + @ApiModelProperty(value = "accessKeySecret") + private String accessKeySecret; + + @ApiModelProperty(value = "失效时间") + private Date invalidTime; + +} diff --git a/src/main/java/cn/lili/modules/tenant/entity/TenantUser.java b/src/main/java/cn/lili/modules/tenant/entity/TenantUser.java new file mode 100644 index 0000000000000000000000000000000000000000..d37208910ae1513682f66c7e42cfec01fe6f64cd --- /dev/null +++ b/src/main/java/cn/lili/modules/tenant/entity/TenantUser.java @@ -0,0 +1,37 @@ +package cn.lili.modules.tenant.entity; + +import cn.lili.mybatis.BaseEntity; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +import java.util.Date; + +/** + * 租户用户 + * + * @author Chopper + * @version v1.0 + * 2022-02-14 15:56 + */ +@Data +@TableName("li_tenant") +@ApiModel(value = "租户") +@NoArgsConstructor +public class TenantUser extends BaseEntity { + + @ApiModelProperty(value = "租户id") + private String tenantId; + + @ApiModelProperty(value = "用户名") + @Length(max = 20, message = "用户名长度不能超过20个字符") + private String username; + + @ApiModelProperty(value = "密码") + private String password; + + +} diff --git a/src/main/java/cn/lili/modules/tenant/mapper/TenantMapper.java b/src/main/java/cn/lili/modules/tenant/mapper/TenantMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..35320dd753bd085e0d344925f07dcde78e36554d --- /dev/null +++ b/src/main/java/cn/lili/modules/tenant/mapper/TenantMapper.java @@ -0,0 +1,14 @@ +package cn.lili.modules.tenant.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import cn.lili.modules.tenant.entity.Tenant; + +import java.util.List; + +/** + * 租户 Dao层 + * @author Chopper + */ +public interface TenantMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/tenant/mapper/TenantUserMapper.java b/src/main/java/cn/lili/modules/tenant/mapper/TenantUserMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..f100dc4912781cb6da9c0165f3265e00eb88581b --- /dev/null +++ b/src/main/java/cn/lili/modules/tenant/mapper/TenantUserMapper.java @@ -0,0 +1,14 @@ +package cn.lili.modules.tenant.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import cn.lili.modules.tenant.entity.TenantUser; + +import java.util.List; + +/** + * 租户用户 Dao层 + * @author Chopper + */ +public interface TenantUserMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/tenant/service/TenantService.java b/src/main/java/cn/lili/modules/tenant/service/TenantService.java new file mode 100644 index 0000000000000000000000000000000000000000..e1f26ded73b2b812f93c7b7d41f509627153b7f5 --- /dev/null +++ b/src/main/java/cn/lili/modules/tenant/service/TenantService.java @@ -0,0 +1,14 @@ +package cn.lili.modules.tenant.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import cn.lili.modules.tenant.entity.Tenant; + +import java.util.List; + +/** + * 租户 业务层 + * @author Chopper + */ +public interface TenantService extends IService { + +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/tenant/service/TenantUserService.java b/src/main/java/cn/lili/modules/tenant/service/TenantUserService.java new file mode 100644 index 0000000000000000000000000000000000000000..bf30e39bb69a9a5d6ce6347eb206617c78492cf6 --- /dev/null +++ b/src/main/java/cn/lili/modules/tenant/service/TenantUserService.java @@ -0,0 +1,14 @@ +package cn.lili.modules.tenant.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import cn.lili.modules.tenant.entity.TenantUser; + +import java.util.List; + +/** + * 租户用户 业务层 + * @author Chopper + */ +public interface TenantUserService extends IService { + +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/tenant/serviceimpl/TenantServiceImpl.java b/src/main/java/cn/lili/modules/tenant/serviceimpl/TenantServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..e7e8032e29d0c75cd3802415b8c4af81699f6ec5 --- /dev/null +++ b/src/main/java/cn/lili/modules/tenant/serviceimpl/TenantServiceImpl.java @@ -0,0 +1,21 @@ +package cn.lili.modules.tenant.serviceimpl; + +import cn.lili.modules.tenant.mapper.TenantMapper; +import cn.lili.modules.tenant.entity.Tenant; +import cn.lili.modules.tenant.service.TenantService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * 租户 业务实现 + * @author Chopper + */ +@Service +public class TenantServiceImpl extends ServiceImpl implements TenantService { + + @Autowired + private TenantMapper tenantMapper; +} \ No newline at end of file diff --git a/src/main/java/cn/lili/modules/tenant/serviceimpl/TenantUserServiceImpl.java b/src/main/java/cn/lili/modules/tenant/serviceimpl/TenantUserServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..8a6377a09a5ba596c4c62d5e033dd1654bd69370 --- /dev/null +++ b/src/main/java/cn/lili/modules/tenant/serviceimpl/TenantUserServiceImpl.java @@ -0,0 +1,21 @@ +package cn.lili.modules.tenant.serviceimpl; + +import cn.lili.modules.tenant.mapper.TenantUserMapper; +import cn.lili.modules.tenant.entity.TenantUser; +import cn.lili.modules.tenant.service.TenantUserService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * 租户用户 业务实现 + * @author Chopper + */ +@Service +public class TenantUserServiceImpl extends ServiceImpl implements TenantUserService { + + @Autowired + private TenantUserMapper tenantUserMapper; +} \ No newline at end of file diff --git a/src/main/java/cn/lili/mybatis/BaseEntity.java b/src/main/java/cn/lili/mybatis/BaseEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..48ca8aa30c95d01f7ef6884c8bea85062c8cadb8 --- /dev/null +++ b/src/main/java/cn/lili/mybatis/BaseEntity.java @@ -0,0 +1,46 @@ +package cn.lili.mybatis; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serializable; +import java.util.Date; + + +/** + * 基础实体类 + * + * @author Chopper + * @version v1.0 + * @since 2020/8/20 14:34 + */ +@Data +@JsonIgnoreProperties(value = {"handler", "fieldHandler"}) +@AllArgsConstructor +@NoArgsConstructor +public abstract class BaseEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + + @TableId + @ApiModelProperty(value = "唯一标识", hidden = true) + private String id; + + @CreatedDate + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @TableField(fill = FieldFill.INSERT) + @ApiModelProperty(value = "创建时间", hidden = true) + private Date createTime; + +} diff --git a/src/main/java/cn/lili/mybatis/BaseTenantEntity.java b/src/main/java/cn/lili/mybatis/BaseTenantEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..c0fc647c168ccc76ab348cbcf5b5c359ade056de --- /dev/null +++ b/src/main/java/cn/lili/mybatis/BaseTenantEntity.java @@ -0,0 +1,24 @@ +package cn.lili.mybatis; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * 租户超级类 + * + * @author Chopper + * @version v1.0 + * @since 2020/8/20 14:34 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public abstract class BaseTenantEntity extends BaseEntity { + + @ApiModelProperty(value = "租户id", hidden = true) + private String tenantId; + +} diff --git a/src/main/java/cn/lili/mybatis/mybatisplus/MyMetaObjectHandler.java b/src/main/java/cn/lili/mybatis/mybatisplus/MyMetaObjectHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..a056f4d113728f4e849e1994fa35f80fd2ede8e2 --- /dev/null +++ b/src/main/java/cn/lili/mybatis/mybatisplus/MyMetaObjectHandler.java @@ -0,0 +1,52 @@ +package cn.lili.mybatis.mybatisplus; + +import cn.lili.security.AuthUser; +import cn.lili.security.UserContext; +import cn.lili.utils.SnowFlake; +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; + +import java.util.Date; + +/** + * 字段填充审计 + * + * @author lili + */ +@Component +public class MyMetaObjectHandler implements MetaObjectHandler { + + @Override + public void insertFill(MetaObject metaObject) { + AuthUser authUser = UserContext.getCurrentUser(); + if (authUser != null) { + this.setFieldValByName("createBy", authUser.getUsername(), metaObject); + }else{ + + this.setFieldValByName("createBy", "SYSTEM", metaObject); + } + this.setFieldValByName("createTime", new Date(), metaObject); + //有值,则写入 + if (metaObject.hasGetter("deleteFlag")) { + this.setFieldValByName("deleteFlag", false, metaObject); + } + if (metaObject.hasGetter("id")) { + //如果已经配置id,则不再写入 + if (metaObject.getValue("id") == null) { + this.setFieldValByName("id", String.valueOf(SnowFlake.getId()), metaObject); + } + } + } + + @Override + public void updateFill(MetaObject metaObject) { + + AuthUser authUser = UserContext.getCurrentUser(); + if (authUser != null) { + this.setFieldValByName("updateBy", authUser.getUsername(), metaObject); + } + this.setFieldValByName("updateTime", new Date(), metaObject); + } +} + diff --git a/src/main/java/cn/lili/mybatis/mybatisplus/MybatisPlusConfig.java b/src/main/java/cn/lili/mybatis/mybatisplus/MybatisPlusConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..87bb2f2c98e6d23b8eb0d15a8e2b53d7aae338a3 --- /dev/null +++ b/src/main/java/cn/lili/mybatis/mybatisplus/MybatisPlusConfig.java @@ -0,0 +1,34 @@ +package cn.lili.mybatis.mybatisplus; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Chopper + */ +@Configuration +@MapperScan({"cn.lili.modules.*.*.mapper", "cn.lili.modules.*.mapper"}) +public class MybatisPlusConfig { + /** + * 分页插件,自动识别数据库类型 + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return interceptor; + + //阻断解析器,测试环境使用 +// PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); +// +// List sqlParserList = new ArrayList<>(); +// //攻击 SQL 阻断解析器、加入解析链 +// sqlParserList.add(new BlockAttackSqlParser()); +// paginationInterceptor.setSqlParserList(sqlParserList); +// return paginationInterceptor; + } +} diff --git a/src/main/java/cn/lili/mybatis/util/PageUtil.java b/src/main/java/cn/lili/mybatis/util/PageUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..e5fcf2667f2bcf20adb37834bd20fb6e28acf590 --- /dev/null +++ b/src/main/java/cn/lili/mybatis/util/PageUtil.java @@ -0,0 +1,175 @@ +package cn.lili.mybatis.util; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import cn.lili.result.PageVO; +import cn.lili.result.SearchVO; +import cn.lili.utils.BeanUtil; +import cn.lili.utils.StringUtils; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.metadata.OrderItem; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * 分页工具 + * + * @author Chopper + * @version v4.0 + * @since 2020/11/26 15:23 + */ +public class PageUtil { + + + /** + * Mybatis-Plus分页封装 + * + * @param page 分页VO + * @param 范型 + * @return 分页响应 + */ + public static Page initPage(PageVO page) { + + Page p; + int pageNumber = page.getPageNumber(); + int pageSize = page.getPageSize(); + String sort = page.getSort(); + String order = page.getOrder(); + + if (pageNumber < 1) { + pageNumber = 1; + } + if (pageSize < 1) { + pageSize = 10; + } + if (pageSize > 100) { + pageSize = 100; + } + if (StrUtil.isNotBlank(sort)) { + Boolean isAsc = false; + if (StrUtil.isBlank(order)) { + isAsc = false; + } else { + if ("desc".equals(order.toLowerCase())) { + isAsc = false; + } else if ("asc".equals(order.toLowerCase())) { + isAsc = true; + } + } + p = new Page<>(pageNumber, pageSize); + if (isAsc) { + p.addOrder(OrderItem.asc(sort)); + } else { + p.addOrder(OrderItem.desc(sort)); + } + + } else { + p = new Page<>(pageNumber, pageSize); + } + return p; + } + + /** + * 生成条件搜索 全对象对比 equals + * 如果需要like 需要另行处理 + * + * @param object 对象(根据对象构建查询条件) + * @return 查询wrapper + */ + public static QueryWrapper initWrapper(Object object) { + return initWrapper(object, null); + } + + /** + * 生成条件搜索 全对象对比 + * + * @param object 对象 + * @param searchVo 查询条件 + * @return 查询wrapper + */ + public static QueryWrapper initWrapper(Object object, SearchVO searchVo) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + //创建时间区间判定 + if (searchVo != null && StrUtil.isNotBlank(searchVo.getStartDate()) && StrUtil.isNotBlank(searchVo.getEndDate())) { + Date start = cn.hutool.core.date.DateUtil.parse(searchVo.getStartDate()); + Date end = cn.hutool.core.date.DateUtil.parse(searchVo.getEndDate()); + queryWrapper.between("create_time", start, DateUtil.endOfDay(end)); + } + if (object != null) { + String[] fieldNames = BeanUtil.getFiledName(object); + //遍历所有属性 + for (int j = 0; j < fieldNames.length; j++) { + //获取属性的名字 + String key = fieldNames[j]; + //获取值 + Object value = BeanUtil.getFieldValueByName(key, object); + //如果值不为空才做查询处理 + if (value != null && !"".equals(value)) { + //字段数据库中,驼峰转下划线 + queryWrapper.eq(StringUtils.camel2Underline(key), value); + } + } + } + return queryWrapper; + } + + + /** + * List 手动分页 + * + * @param page 分页对象 + * @param list 分页集合 + * @return 范型结果 + */ + public static List listToPage(PageVO page, List list) { + + int pageNumber = page.getPageNumber() - 1; + int pageSize = page.getPageSize(); + + if (pageNumber < 0) { + pageNumber = 0; + } + if (pageSize < 1) { + pageSize = 10; + } + if (pageSize > 100) { + pageSize = 100; + } + + int fromIndex = pageNumber * pageSize; + int toIndex = pageNumber * pageSize + pageSize; + + if (fromIndex > list.size()) { + return new ArrayList<>(); + } else if (toIndex >= list.size()) { + return list.subList(fromIndex, list.size()); + } else { + return list.subList(fromIndex, toIndex); + } + } + + /** + * 转换分页类型 + * + * @param originPage 原分页 + * @param records 新分页数据 + * @param 新类型 + * @return 新类型分页 + */ + public static IPage convertPage(IPage originPage, List records) { + IPage resultPage = new Page<>(); + if (originPage != null) { + resultPage.setCurrent(originPage.getCurrent()); + resultPage.setPages(originPage.getPages()); + resultPage.setTotal(originPage.getTotal()); + resultPage.setSize(originPage.getSize()); + resultPage.setRecords(records); + } + return resultPage; + } + +} diff --git a/src/main/java/cn/lili/po/MessageType.java b/src/main/java/cn/lili/po/MessageType.java deleted file mode 100644 index 621c3f1b69e89b7e907d48cf9c99e87209b6ca5a..0000000000000000000000000000000000000000 --- a/src/main/java/cn/lili/po/MessageType.java +++ /dev/null @@ -1,8 +0,0 @@ -package cn.lili.po; - -public enum MessageType { - MESSAGE, - TIPS, - SYSTEM, - ONLINE -} diff --git a/src/main/java/cn/lili/result/PageVO.java b/src/main/java/cn/lili/result/PageVO.java new file mode 100644 index 0000000000000000000000000000000000000000..9d1ed57b879aaaf622a2938ee1cf092b9e3783ed --- /dev/null +++ b/src/main/java/cn/lili/result/PageVO.java @@ -0,0 +1,46 @@ +package cn.lili.result; + +import cn.hutool.core.text.CharSequenceUtil; +import cn.lili.utils.StringUtils; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 查询参数 + * + * @author Chopper + */ +@Data +public class PageVO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "页号") + private Integer pageNumber = 1; + + @ApiModelProperty(value = "页面大小") + private Integer pageSize = 10; + + @ApiModelProperty(value = "排序字段") + private String sort; + + @ApiModelProperty(value = "排序方式 asc/desc") + private String order; + + @ApiModelProperty(value = "需要驼峰转换蛇形", notes = "一般不做处理,如果数据库中就是蛇形,则这块需要处理。") + private Boolean notConvert; + + public String getSort() { + if (CharSequenceUtil.isNotEmpty(sort)) { + if (notConvert == null || Boolean.FALSE.equals(notConvert)) { + return StringUtils.camel2Underline(sort); + } else { + return sort; + } + } + return sort; + } + +} diff --git a/src/main/java/cn/lili/result/ResultCode.java b/src/main/java/cn/lili/result/ResultCode.java index ad753ab322a9a23acd4dae22bb63d70bca5e8368..735bc24bc57ee7847c74d55a958614452df4de25 100644 --- a/src/main/java/cn/lili/result/ResultCode.java +++ b/src/main/java/cn/lili/result/ResultCode.java @@ -16,7 +16,457 @@ public enum ResultCode { /** * 失败返回码 */ - ERROR(400, "服务器繁忙,请稍后重试"); + ERROR(400, "服务器繁忙,请稍后重试"), + + /** + * 失败返回码 + */ + DEMO_SITE_EXCEPTION(4001, "演示站点禁止使用"), + /** + * 参数异常 + */ + PARAMS_ERROR(4002, "参数异常"), + + + /** + * 系统异常 + */ + WECHAT_CONNECT_NOT_EXIST(1001, "微信联合登录未配置"), + VERIFICATION_EXIST(1002, "验证码服务异常"), + LIMIT_ERROR(1003, "访问过于频繁,请稍后再试"), + ILLEGAL_REQUEST_ERROR(1004, "非法请求,请重新刷新页面操作"), + IMAGE_FILE_EXT_ERROR(1005, "不支持图片格式"), + FILE_TYPE_NOT_SUPPORT(1010, "不支持上传的文件类型!"), + PLATFORM_NOT_SUPPORTED_IM(1006, "平台未开启IM"), + STORE_NOT_SUPPORTED_IM(1007, "店铺未开启IM"), + /** + * 分类 + */ + CATEGORY_NOT_EXIST(10001, "商品分类不存在"), + CATEGORY_NAME_IS_EXIST(10002, "该分类名称已存在"), + CATEGORY_PARENT_NOT_EXIST(10003, "该分类名称已存在"), + CATEGORY_BEYOND_THREE(10004, "最多为三级分类,添加失败"), + CATEGORY_HAS_CHILDREN(10005, "此类别下存在子类别不能删除"), + CATEGORY_HAS_GOODS(10006, "此类别下存在商品不能删除"), + CATEGORY_SAVE_ERROR(10007, "此类别下存在商品不能删除"), + CATEGORY_PARAMETER_NOT_EXIST(10012, "分类绑定参数组不存在"), + CATEGORY_PARAMETER_SAVE_ERROR(10008, "分类绑定参数组添加失败"), + CATEGORY_PARAMETER_UPDATE_ERROR(10009, "分类绑定参数组添加失败"), + CATEGORY_DELETE_FLAG_ERROR(10010, "子类状态不能与父类不一致!"), + CATEGORY_COMMISSION_RATE_ERROR(10011, "分类的佣金不正确!"), + + /** + * 商品 + */ + GOODS_ERROR(11001, "商品异常,请稍后重试"), + GOODS_NOT_EXIST(11001, "商品已下架"), + GOODS_NAME_ERROR(11002, "商品名称不正确,名称应为2-50字符"), + GOODS_UNDER_ERROR(11003, "商品下架失败"), + GOODS_UPPER_ERROR(11004, "商品上架失败"), + GOODS_AUTH_ERROR(11005, "商品审核失败"), + POINT_GOODS_ERROR(11006, "积分商品业务异常,请稍后重试"), + POINT_GOODS_NOT_EXIST(11020, "积分商品不存在"), + POINT_GOODS_CATEGORY_EXIST(11021, "当前积分商品分类已存在"), + GOODS_SKU_SN_ERROR(11007, "商品SKU货号不能为空"), + GOODS_SKU_PRICE_ERROR(11008, "商品SKU价格不能小于等于0"), + GOODS_SKU_COST_ERROR(11009, "商品SKU成本价不能小于等于0"), + GOODS_SKU_WEIGHT_ERROR(11010, "商品重量不能为负数"), + GOODS_SKU_QUANTITY_ERROR(11011, "商品库存数量不能为负数"), + GOODS_SKU_QUANTITY_NOT_ENOUGH(11011, "商品库存不足"), + MUST_HAVE_GOODS_SKU(11012, "规格必须要有一个!"), + GOODS_PARAMS_ERROR(11013, "商品参数错误,刷新后重试"), + PHYSICAL_GOODS_NEED_TEMP(11014, "实物商品需选择配送模板"), + VIRTUAL_GOODS_NOT_NEED_TEMP(11015, "实物商品需选择配送模板"), + GOODS_NOT_EXIST_STORE(11017, "当前用户无权操作此商品"), + GOODS_TYPE_ERROR(11016, "需选择商品类型"), + + /** + * 参数 + */ + PARAMETER_SAVE_ERROR(12001, "参数添加失败"), + PARAMETER_UPDATE_ERROR(12002, "参数编辑失败"), + + /** + * 规格 + */ + SPEC_SAVE_ERROR(13001, "规格修改失败"), + SPEC_UPDATE_ERROR(13002, "规格修改失败"), + SPEC_DELETE_ERROR(13003, "分类已经绑定此规格,请先解除关联"), + + /** + * 品牌 + */ + BRAND_SAVE_ERROR(14001, "品牌添加失败"), + BRAND_UPDATE_ERROR(14002, "品牌修改失败"), + BRAND_DISABLE_ERROR(14003, "品牌禁用失败"), + BRAND_DELETE_ERROR(14004, "品牌删除失败"), + BRAND_NAME_EXIST_ERROR(20002, "品牌名称重复!"), + BRAND_USE_DISABLE_ERROR(20003, "分类已经绑定品牌,请先解除关联"), + BRAND_BIND_GOODS_ERROR(20005, "品牌已经绑定商品,请先解除关联"), + BRAND_NOT_EXIST(20004, "品牌不存在"), + + /** + * 用户 + */ + USER_EDIT_SUCCESS(20001, "用户修改成功"), + USER_NOT_EXIST(20002, "用户不存在"), + USER_NOT_LOGIN(20003, "用户未登录"), + USER_AUTH_EXPIRED(20004, "用户已退出,请重新登录"), + USER_AUTHORITY_ERROR(20005, "权限不足"), + USER_CONNECT_LOGIN_ERROR(20006, "未找到登录信息"), + USER_NAME_EXIST(20007, "该用户名已被注册"), + USER_PHONE_EXIST(20008, "该手机号已被注册"), + USER_PHONE_NOT_EXIST(20009, "手机号不存在"), + USER_PASSWORD_ERROR(20010, "密码不正确"), + USER_NOT_PHONE(20011, "非当前用户的手机号"), + USER_CONNECT_ERROR(20012, "联合第三方登录,授权信息错误"), + USER_RECEIPT_REPEAT_ERROR(20013, "会员发票信息重复"), + USER_RECEIPT_NOT_EXIST(20014, "会员发票信息不存在"), + USER_EDIT_ERROR(20015, "用户修改失败"), + USER_OLD_PASSWORD_ERROR(20016, "旧密码不正确"), + USER_COLLECTION_EXIST(20017, "无法重复收藏"), + USER_GRADE_IS_DEFAULT(20018, "会员等级为默认会员等级"), + USER_NOT_BINDING(20020, "未绑定用户"), + USER_AUTO_REGISTER_ERROR(20021, "自动注册失败,请稍后重试"), + USER_OVERDUE_CONNECT_ERROR(20022, "授权信息已过期,请重新授权/登录"), + USER_CONNECT_BANDING_ERROR(20023, "当前联合登陆方式,已绑定其他账号,需进行解绑操作"), + USER_CONNECT_NOT_EXIST_ERROR(20024, "暂无联合登陆信息,无法实现一键注册功能,请点击第三方登录进行授权"), + USER_POINTS_ERROR(20024, "用户积分不足"), + + /** + * 权限 + */ + PERMISSION_DEPARTMENT_ROLE_ERROR(21001, "角色已绑定部门,请逐个删除"), + PERMISSION_USER_ROLE_ERROR(21002, "角色已绑定管理员,请逐个删除"), + PERMISSION_MENU_ROLE_ERROR(21003, "菜单已绑定角色,请先删除或编辑角色"), + PERMISSION_DEPARTMENT_DELETE_ERROR(21004, "部门已经绑定管理员,请先删除或编辑管理员"), + PERMISSION_BEYOND_TEN(21005, "最多可以设置10个角色"), + + /** + * 分销 + */ + DISTRIBUTION_CLOSE(22000, "分销功能关闭"), + DISTRIBUTION_NOT_EXIST(22001, "分销员不存在"), + DISTRIBUTION_IS_APPLY(22002, "分销员已申请,无需重复提交"), + DISTRIBUTION_AUDIT_ERROR(22003, "审核分销员失败"), + DISTRIBUTION_RETREAT_ERROR(22004, "分销员清退失败"), + DISTRIBUTION_CASH_NOT_EXIST(22005, "分销员提现记录不存在"), + DISTRIBUTION_GOODS_DOUBLE(22006, "不能重复添加分销商品"), + + /** + * 购物车 + */ + CART_ERROR(30001, "读取结算页的购物车异常"), + CART_NUM_ERROR(30010, "购买数量必须大于0"), + CART_PINTUAN_NOT_EXIST_ERROR(30002, "拼团活动已关闭,请稍后重试"), + CART_PINTUAN_LIMIT_ERROR(30003, "购买数量超过拼团活动限制数量"), + SHIPPING_NOT_APPLY(30005, "购物商品不支持当前收货地址配送"), + + /** + * 订单 + */ + ORDER_ERROR(31001, "创建订单异常,请稍后重试"), + ORDER_NOT_EXIST(31002, "订单不存在"), + ORDER_DELIVERED_ERROR(31003, "订单状态错误,无法进行确认收货"), + ORDER_UPDATE_PRICE_ERROR(31004, "已支付的订单不能修改金额"), + ORDER_LOGISTICS_ERROR(31005, "物流错误"), + ORDER_DELIVER_ERROR(31006, "物流错误"), + ORDER_NOT_USER(31007, "非当前会员的订单"), + ORDER_TAKE_ERROR(31008, "当前订单无法核销"), + MEMBER_ADDRESS_NOT_EXIST(31009, "订单无收货地址,请先配置收货地址"), + ORDER_DELIVER_NUM_ERROR(31010, "没有待发货的订单"), + ORDER_NOT_SUPPORT_DISTRIBUTION(31011, "购物车中包含不支持配送的商品,请重新选择收货地址,或者重新选择商品"), + ORDER_CAN_NOT_CANCEL(31012, "当前订单状态不可取消"), + ORDER_BATCH_DELIVER_ERROR(31013, "批量发货,文件读取失败"), + ORDER_ITEM_NOT_EXIST(31014, "当前订单项不存在!"), + POINT_NOT_ENOUGH(31015, "当前会员积分不足购买当前积分商品!"), + + + /** + * 支付 + */ + PAY_UN_WANTED(32000, "当前订单不需要付款,返回订单列表等待系统订单出库即可"), + PAY_SUCCESS(32001, "支付成功"), + PAY_INCONSISTENT_ERROR(32002, "付款金额和应付金额不一致"), + PAY_DOUBLE_ERROR(32003, "订单已支付,不能再次进行支付"), + PAY_CASHIER_ERROR(32004, "收银台信息获取错误"), + PAY_ERROR(32005, "支付业务异常,请稍后重试"), + PAY_BAN(32006, "当前订单不需要付款,请返回订单列表重新操作"), + PAY_PARTIAL_ERROR(32007, "该订单已部分支付,请前往订单中心进行支付"), + PAY_NOT_SUPPORT(32008, "支付暂不支持"), + PAY_CLIENT_TYPE_ERROR(32009, "错误的客户端"), + PAY_POINT_ENOUGH(32010, "积分不足,不能兑换"), + PAY_NOT_EXIST_ORDER(32011, "支付订单不存在"), + CAN_NOT_RECHARGE_WALLET(32012, "不能使用余额进行充值"), + + + /** + * 售后 + */ + AFTER_SALES_NOT_PAY_ERROR(33001, "当前订单未支付,不能申请售后"), + AFTER_SALES_CANCEL_ERROR(33002, "当前售后单无法取消"), + AFTER_SALES_BAN(33003, "订单状态不允许申请售后,请联系平台或商家"), + AFTER_SALES_DOUBLE_ERROR(33004, "售后已审核,无法重复操作"), + AFTER_SALES_LOGISTICS_ERROR(33005, "物流公司错误,请重新选择"), + AFTER_STATUS_ERROR(33006, "售后状态错误,请刷新页面"), + RETURN_MONEY_OFFLINE_BANK_ERROR(33007, "当账号类型为银行转账时,银行信息不能为空"), + AFTER_SALES_PRICE_ERROR(33004, "申请退款金额错误"), + AFTER_GOODS_NUMBER_ERROR(33008, "申请售后商品数量错误"), + + /** + * 投诉 + */ + COMPLAINT_ORDER_ITEM_EMPTY_ERROR(33100, "订单不存在"), + COMPLAINT_SKU_EMPTY_ERROR(33101, "商品已下架,如需投诉请联系平台客服"), + COMPLAINT_ERROR(33102, "投诉异常,请稍后重试"), + COMPLAINT_NOT_EXIT(33103, "当前投诉记录不存在"), + COMPLAINT_ARBITRATION_RESULT_ERROR(33104, "结束订单投诉时,仲裁结果不能为空"), + COMPLAINT_APPEAL_CONTENT_ERROR(33105, "商家申诉时,申诉内容不能为空"), + COMPLAINT_CANCEL_ERROR(33106, "申诉已完成,不需要进行取消申诉操作"), + + + /** + * 余额 + */ + WALLET_NOT_EXIT_ERROR(34000, "钱包不存在,请联系管理员"), + WALLET_INSUFFICIENT(34001, "余额不足以支付订单,请充值!"), + WALLET_WITHDRAWAL_INSUFFICIENT(34002, "可提现金额不足!"), + WALLET_WITHDRAWAL_FROZEN_AMOUNT_INSUFFICIENT(34006, "冻结金额不足,无法处理提现申请请求!"), + WALLET_ERROR_INSUFFICIENT(34003, "零钱提现失败!"), + WALLET_REMARK_ERROR(34004, "请填写审核备注!"), + WALLET_EXIT_ERROR(34000, "钱包已存在,无法重复创建"), + WALLET_APPLY_ERROR(34005, "提现申请异常!"), + + /** + * 评价 + */ + EVALUATION_DOUBLE_ERROR(35001, "无法重复提交评价"), + + /** + * 活动 + */ + PROMOTION_GOODS_NOT_EXIT(40000, "当前促销商品不存在!"), + PROMOTION_SAME_ACTIVE_EXIST(40001, "活动时间内已存在同类活动,请选择关闭、删除当前时段的活动"), + PROMOTION_START_TIME_ERROR(40002, "活动起始时间不能小于当前时间"), + PROMOTION_END_TIME_ERROR(40003, "活动结束时间不能小于当前时间"), + PROMOTION_TIME_ERROR(40004, "活动起始时间必须大于结束时间"), + PROMOTION_TIME_NOT_EXIST(40011, "活动起始时间和活动结束时间不能为空"), + PROMOTION_SAME_ERROR(40005, "当前时间段已存在相同活动!"), + PROMOTION_GOODS_ERROR(40006, "请选择要参与活动的商品"), + PROMOTION_STATUS_END(40007, "当前活动已停止"), + PROMOTION_UPDATE_ERROR(40008, "当前活动已开始/结束,无法编辑!"), + PROMOTION_ACTIVITY_GOODS_ERROR(40009, "当前活动已经开始无法添加商品"), + PROMOTION_ACTIVITY_ERROR(400010, "当前促销活动不存在"), + PROMOTION_LOG_EXIST(40011, "活动已参加,已发重复参加"), + + /** + * 优惠券 + */ + COUPON_LIMIT_ERROR(41000, "超出领取限制"), + COUPON_EDIT_STATUS_SUCCESS(41001, "修改状态成功!"), + COUPON_CANCELLATION_SUCCESS(41002, "会员优惠券作废成功"), + COUPON_EXPIRED(41003, "优惠券已使用/已过期,不能使用"), + COUPON_EDIT_STATUS_ERROR(41004, "优惠券修改状态失败!"), + COUPON_RECEIVE_ERROR(41005, "当前优惠券已经被领取完了,下次要早点来哦"), + COUPON_NUM_INSUFFICIENT_ERROR(41006, "优惠券剩余领取数量不足"), + COUPON_NOT_EXIST(41007, "当前优惠券不存在"), + COUPON_DO_NOT_RECEIVER(41030, "当前优惠券不允许主动领取"), + COUPON_ACTIVITY_NOT_EXIST(410022, "当前优惠券活动不存在"), + COUPON_SAVE_ERROR(41020, "保存优惠券失败"), + COUPON_ACTIVITY_SAVE_ERROR(41023, "保存优惠券活动失败"), + COUPON_DELETE_ERROR(41021, "删除优惠券失败"), + COUPON_LIMIT_NUM_LESS_THAN_0(41008, "领取限制数量不能为负数"), + COUPON_LIMIT_GREATER_THAN_PUBLISH(41009, "领取限制数量超出发行数量"), + COUPON_DISCOUNT_ERROR(41010, "优惠券折扣必须小于10且大于0"), + COUPON_SCOPE_TYPE_GOODS_ERROR(41011, "当前关联范围类型为指定商品时,商品列表不能为空"), + COUPON_SCOPE_TYPE_CATEGORY_ERROR(41012, "当前关联范围类型为部分商品分类时,范围关联的id不能为空"), + COUPON_SCOPE_TYPE_STORE_ERROR(41013, "当前关联范围类型为部分店铺分类时,范围关联的id不能为空"), + COUPON_SCOPE_ERROR(41014, "指定商品范围关联id无效!"), + COUPON_MEMBER_NOT_EXIST(41015, "没有当前会员优惠券"), + COUPON_MEMBER_STATUS_ERROR(41016, "当前会员优惠券已过期/作废无法变更状态!"), + + + /** + * 拼团 + */ + PINTUAN_MANUAL_OPEN_SUCCESS(42001, "手动开启拼团活动成功"), + PINTUAN_MANUAL_CLOSE_SUCCESS(42002, "手动关闭拼团活动成功"), + PINTUAN_ADD_SUCCESS(42003, "添加拼团活动成功"), + PINTUAN_EDIT_SUCCESS(42004, "修改拼团活动成功"), + PINTUAN_DELETE_SUCCESS(42005, "删除拼团活动成功"), + PINTUAN_MANUAL_OPEN_ERROR(42006, "手动开启拼团活动失败"), + PINTUAN_MANUAL_CLOSE_ERROR(42007, "手动关闭拼团活动失败"), + PINTUAN_ADD_ERROR(42008, "添加拼团活动失败"), + PINTUAN_EDIT_ERROR(42009, "修改拼团活动失败"), + PINTUAN_EDIT_ERROR_ITS_OPEN(42019, "拼团活动已开启,无法修改拼团活动!"), + PINTUAN_DELETE_ERROR(42010, "删除拼团活动失败"), + PINTUAN_JOIN_ERROR(42011, "不能参与自己发起的拼团活动!"), + PINTUAN_LIMIT_NUM_ERROR(42012, "购买数量超过拼团活动限制数量!"), + PINTUAN_NOT_EXIST_ERROR(42013, "当前拼团活动不存在!"), + PINTUAN_GOODS_NOT_EXIST_ERROR(42014, "当前拼团商品不存在!"), + + /** + * 满额活动 + */ + FULL_DISCOUNT_EDIT_SUCCESS(43001, "修改满优惠活动成功"), + FULL_DISCOUNT_EDIT_DELETE(43002, "删除满优惠活动成功"), + FULL_DISCOUNT_MODIFY_ERROR(43003, "当前编辑的满优惠活动已经开始或者已经结束,无法修改"), + FULL_DISCOUNT_NOT_EXIST_ERROR(43004, "当前要操作的满优惠活动不存在!"), + FULL_DISCOUNT_WAY_ERROR(43005, "请选择一种优惠方式!"), + FULL_DISCOUNT_GIFT_ERROR(43006, "请选择赠品!"), + FULL_DISCOUNT_COUPON_TIME_ERROR(43007, "赠送的优惠券有效时间必须在活动时间之内"), + FULL_DISCOUNT_MONEY_ERROR(43008, "请填写满减金额"), + FULL_DISCOUNT_MONEY_GREATER_THAN_MINUS(43009, "满减金额不能大于优惠门槛"), + FULL_RATE_NUM_ERROR(43010, "请填写打折数值"), + + /** + * 直播 + */ + STODIO_GOODS_EXIST_ERROR(44001, "直播商品已存在"), + COMMODITY_ERROR(44002, "添加直播商品失败"), + + /** + * 秒杀 + */ + SECKILL_NOT_START_ERROR(45000, "今日没有限时抢购活动,请明天再来看看吧。"), + SECKILL_NOT_EXIST_ERROR(45001, "当前参与的秒杀活动不存在!"), + SECKILL_APPLY_NOT_EXIST_ERROR(45010, "当前参与的秒杀活动不存在!"), + SECKILL_UPDATE_ERROR(45002, "当前秒杀活动活动已经开始,无法修改!"), + SECKILL_PRICE_ERROR(45003, "活动价格不能大于商品原价"), + SECKILL_TIME_ERROR(45004, "时刻参数异常"), + SECKILL_DELETE_ERROR(45005, "该秒杀活动活动的状态不能删除"), + SECKILL_OPEN_ERROR(45010, "该秒杀活动活动的状态不能删除"), + SECKILL_CLOSE_ERROR(45006, "该秒杀活动活动的状态不能关闭"), + + + /** + * 优惠券活动 + */ + COUPON_ACTIVITY_START_TIME_ERROR(46001, "活动时间小于当前时间,不能进行编辑删除操作"), + COUPON_ACTIVITY_MEMBER_ERROR(46002, "指定精准发券则必须指定会员,会员不可以为空"), + COUPON_ACTIVITY_ITEM_ERROR(46003, "优惠券活动必须指定优惠券,不能为空"), + COUPON_ACTIVITY_ITEM_MUST_NUM_ERROR(46004, "优惠券活动最多指定10个优惠券"), + COUPON_ACTIVITY_ITEM_NUM_ERROR(46005, "赠券数量必须大于0"), + + /** + * 其他促销 + */ + MEMBER_SIGN_REPEAT(47001, "请勿重复签到"), + POINT_GOODS_ACTIVE_STOCK_ERROR(47002, "活动库存数量不能高于商品库存"), + POINT_GOODS_ACTIVE_STOCK_INSUFFICIENT(47003, "积分商品库存不足"), + + /** + * 砍价活动 + */ + KANJIA_GOODS_ACTIVE_STOCK_ERROR(48001, "活动库存数量不能高于商品库存"), + KANJIA_GOODS_ACTIVE_PRICE_ERROR(48002, "最低购买金额不能高于商品金额"), + KANJIA_GOODS_ACTIVE_HIGHEST_PRICE_ERROR(48003, "最高砍价金额不能为0且不能超过商品金额"), + KANJIA_GOODS_ACTIVE_LOWEST_PRICE_ERROR(48004, "最低砍价金额不能为0且不能超过商品金额"), + KANJIA_GOODS_ACTIVE_HIGHEST_LOWEST_PRICE_ERROR(48005, "最低砍价金额不能高于最高砍价金额"), + KANJIA_GOODS_ACTIVE_SETTLEMENT_PRICE_ERROR(48006, "结算金额不能高于商品金额"), + KANJIA_GOODS_DELETE_ERROR(48007, "删除砍价商品异常"), + KANJIA_GOODS_UPDATE_ERROR(48012, "更新砍价商品异常"), + KANJIA_ACTIVITY_NOT_FOUND_ERROR(48008, "砍价记录不存在"), + KANJIA_ACTIVITY_LOG_MEMBER_ERROR(48009, "当前会员已经帮砍"), + KANJIA_ACTIVITY_MEMBER_ERROR(48010, "当前会员已经发起此砍价商品活动"), + KANJIA_ACTIVITY_NOT_PASS_ERROR(48011, "当前砍价未满足条件,不能进行购买"), + KANJIA_NUM_BUY_ERROR(48012, "砍价商品购买数量不正确"), + /** + * 店铺 + */ + + STORE_NOT_EXIST(50001, "此店铺不存在"), + STORE_NAME_EXIST_ERROR(50002, "店铺名称已存在!"), + STORE_APPLY_DOUBLE_ERROR(50003, "已有店铺,无需重复申请!"), + STORE_NOT_OPEN(50004, "该会员未开通店铺"), + STORE_NOT_LOGIN_ERROR(50005, "未登录店铺"), + STORE_CLOSE_ERROR(50006, "店铺关闭,请联系管理员"), + FREIGHT_TEMPLATE_NOT_EXIST(50010, "当前模版不存在"), + + /** + * 结算单 + */ + BILL_CHECK_ERROR(51001, "只有已出账结算单可以核对"), + BILL_COMPLETE_ERROR(51002, "只有已审核结算单可以支付"), + + /** + * 文章 + */ + ARTICLE_CATEGORY_NAME_EXIST(60001, "文章分类名称已存在"), + ARTICLE_CATEGORY_PARENT_NOT_EXIST(60002, "文章分类父分类不存在"), + ARTICLE_CATEGORY_BEYOND_TWO(60003, "最多为二级分类,操作失败"), + ARTICLE_CATEGORY_DELETE_ERROR(60004, "该文章分类下存在子分类,不能删除"), + ARTICLE_CATEGORY_HAS_ARTICLE(60005, "该文章分类下存在文章,不能删除"), + ARTICLE_CATEGORY_NO_DELETION(60007, "默认文章分类不能进行删除"), + ARTICLE_NO_DELETION(60008, "默认文章不能进行删除"), + + + /** + * 页面 + */ + PAGE_NOT_EXIST(61001, "页面不存在"), + PAGE_OPEN_DELETE_ERROR(61002, "当前页面为开启状态,无法删除"), + PAGE_DELETE_ERROR(61003, "当前页面为唯一页面,无法删除"), + PAGE_RELEASE_ERROR(61004, "页面已发布,无需重复提交"), + + /** + * 设置 + */ + SETTING_NOT_TO_SET(70001, "该参数不需要设置"), + ALIPAY_NOT_SETTING(70002, "支付宝支付未配置"), + ALIPAY_EXCEPTION(70003, "支付宝支付错误,请稍后重试"), + ALIPAY_PARAMS_EXCEPTION(70004, "支付宝参数异常"), + LOGISTICS_NOT_SETTING(70005, "您还未配置快递查询"), + ORDER_SETTING_ERROR(70006, "系统订单配置异常"), + ALI_SMS_SETTING_ERROR(70007, "您还未配置阿里云短信"), + SMS_SIGN_EXIST_ERROR(70008, "短信签名已存在"), + + /** + * 站内信 + */ + NOTICE_NOT_EXIST(80001, "当前消息模板不存在"), + NOTICE_ERROR(80002, "修改站内信异常,请稍后重试"), + NOTICE_SEND_ERROR(80003, "发送站内信异常,请检查系统日志"), + + + /** + * OSS + */ + OSS_NOT_EXIST(80101, "OSS未配置"), + OSS_EXCEPTION_ERROR(80102, "文件上传失败,请稍后重试"), + OSS_DELETE_ERROR(80103, "图片删除失败"), + + /** + * 验证码 + */ + VERIFICATION_SEND_SUCCESS(80201, "短信验证码,发送成功"), + VERIFICATION_ERROR(80202, "验证失败"), + VERIFICATION_CODE_INVALID(80204, "验证码已失效,请重新校验"), + VERIFICATION_SMS_CHECKED_ERROR(80210, "短信验证码错误,请重新校验"), + + /** + * 微信相关异常 + */ + WECHAT_CONNECT_NOT_SETTING(80300, "微信联合登陆信息未配置"), + WECHAT_PAYMENT_NOT_SETTING(80301, "微信支付信息未配置"), + WECHAT_QRCODE_ERROR(80302, "微信二维码生成异常"), + WECHAT_MP_MESSAGE_ERROR(80303, "微信小程序小消息订阅异常"), + WECHAT_JSAPI_SIGN_ERROR(80304, "微信JsApi签名异常"), + WECHAT_CERT_ERROR(80305, "证书获取失败"), + WECHAT_MP_MESSAGE_TMPL_ERROR(80306, "未能获取到微信模版消息id"), + WECHAT_ERROR(80307, "微信接口异常"), + APP_VERSION_EXIST(80307, "APP版本已存在"), + + /** + * 其他 + */ + CUSTOM_WORDS_EXIST_ERROR(90000, "当前自定义分词已存在!"), + CUSTOM_WORDS_NOT_EXIST_ERROR(90001, "当前自定义分词不存在!"), + CUSTOM_WORDS_SECRET_KEY_ERROR(90002, "秘钥验证失败!"), + CONNECT_NOT_EXIST(90000, "登录方式不存在!"), + ELASTICSEARCH_INDEX_INIT_ERROR(90003, "索引初始化失败!"), + PURCHASE_ORDER_DEADLINE_ERROR(90004, "供求单,已超过报名截止时间"), + INDEX_BUILDING(90005, "索引正在生成"); private final Integer code; private final String message; diff --git a/src/main/java/cn/lili/result/SearchVO.java b/src/main/java/cn/lili/result/SearchVO.java new file mode 100644 index 0000000000000000000000000000000000000000..4f5df956439c417b5d528ee10d2d01c6470efa10 --- /dev/null +++ b/src/main/java/cn/lili/result/SearchVO.java @@ -0,0 +1,50 @@ +package cn.lili.result; + +import cn.lili.utils.DateUtil; +import cn.lili.utils.StringUtils; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.Calendar; +import java.util.Date; + +/** + * 日期搜索参数 + * + * @author Chopper + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SearchVO implements Serializable { + + @ApiModelProperty(value = "起始日期") + private String startDate; + + @ApiModelProperty(value = "结束日期") + private String endDate; + + public Date getConvertStartDate() { + if (StringUtils.isEmpty(startDate)) { + return null; + } + Date date = DateUtil.toDate(startDate, DateUtil.STANDARD_DATE_FORMAT); + return date; + } + + public Date getConvertEndDate() { + if (StringUtils.isEmpty(endDate)) { + return null; + } + //结束时间等于结束日期+1天 -1秒, + Date date = DateUtil.toDate(endDate, DateUtil.STANDARD_DATE_FORMAT); + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.set(Calendar.DAY_OF_MONTH, calendar.get(Calendar.DAY_OF_MONTH) + 1); + calendar.set(Calendar.SECOND, -1); + return calendar.getTime(); + } +} diff --git a/src/main/java/cn/lili/security/AuthUser.java b/src/main/java/cn/lili/security/AuthUser.java new file mode 100644 index 0000000000000000000000000000000000000000..067b8b1c8e5aab79e2d06ce2579cf0310438a9bd --- /dev/null +++ b/src/main/java/cn/lili/security/AuthUser.java @@ -0,0 +1,94 @@ +package cn.lili.security; + +import cn.lili.security.enums.UserEnums; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.io.Serializable; + +/** + * @author Chopper + */ +@Data +@AllArgsConstructor +public class AuthUser implements Serializable { + + private static final long serialVersionUID = 582441893336003319L; + + /** + * 用户名 + */ + private String username; + + /** + * 昵称 + */ + private String nickName; + + /** + * 头像 + */ + private String face; + + /** + * id + */ + private String id; + + /** + * 长期有效(用于手机app登录场景或者信任场景等) + */ + private Boolean longTerm = false; + + /** + * @see UserEnums + * 角色 + */ + private UserEnums role; + + /** + * 商家id + */ + private String storeId; + + /** + * 租户id + */ + private String tenantId; + + /** + * 可以展示租户名称 + */ + private String tenantName; + + /** + * 是否是超级管理员 + */ + private Boolean isSuper = false; + + public AuthUser(String username, String id, String nickName, String face, UserEnums role) { + this.username = username; + this.face = face; + this.id = id; + this.role = role; + this.nickName = nickName; + } + + public AuthUser(String username, String id, String face, UserEnums manager, String nickName, Boolean isSuper) { + this.username = username; + this.id = id; + this.face = face; + this.role = manager; + this.isSuper = isSuper; + this.nickName = nickName; + } + + public String getId() { + if (role != null && role.equals(UserEnums.MEMBER)) { + return id; + } else { + return storeId; + } + + } +} diff --git a/src/main/java/cn/lili/security/JWTTokenProperties.java b/src/main/java/cn/lili/security/JWTTokenProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..1c31f1c1871cc97c359ee3f779d19d2bdd88e4f7 --- /dev/null +++ b/src/main/java/cn/lili/security/JWTTokenProperties.java @@ -0,0 +1,22 @@ +package cn.lili.security; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * token过期配置 + * + * @author Chopper + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "lili.jwt-setting") +public class JWTTokenProperties { + + + /** + * token默认过期时间 + */ + private long tokenExpireTime = 60; +} diff --git a/src/main/java/cn/lili/security/SecretKeyUtil.java b/src/main/java/cn/lili/security/SecretKeyUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..a32ad17919276409bba5df5f49a12331db803d33 --- /dev/null +++ b/src/main/java/cn/lili/security/SecretKeyUtil.java @@ -0,0 +1,28 @@ +package cn.lili.security; + +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import org.apache.commons.codec.binary.Base64; + +import javax.crypto.SecretKey; + +/** + * SignWithUtil + * + * @author Chopper + * @version v1.0 + * 2020-11-18 17:30 + */ +public class SecretKeyUtil { + public static SecretKey generalKey() { + //自定义 + byte[] encodedKey = Base64.decodeBase64("cuAihCz53DZRjZwbsGcZJ2Ai6At+T142uphtJMsk7iQ="); + SecretKey key = Keys.hmacShaKeyFor(encodedKey); + return key; + } + + public static SecretKey generalKeyByDecoders() { + return Keys.hmacShaKeyFor(Decoders.BASE64.decode("cuAihCz53DZRjZwbsGcZJ2Ai6At+T142uphtJMsk7iQ=")); + + } +} diff --git a/src/main/java/cn/lili/security/SecurityBean.java b/src/main/java/cn/lili/security/SecurityBean.java new file mode 100644 index 0000000000000000000000000000000000000000..dfa59ecd91de7a8d104b21621025fcc479caa86b --- /dev/null +++ b/src/main/java/cn/lili/security/SecurityBean.java @@ -0,0 +1,40 @@ +package cn.lili.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +/** + * SecurityBean + * + * @author Chopper + * @version v1.0 + * 2020-11-14 15:03 + */ +@Configuration +public class SecurityBean { + /** + * 开启跨域访问拦截器 + * + * @date 2021/4/29 9:50 + */ + @Bean + public CorsFilter corsFilter() { + //创建CorsConfiguration对象后添加配置 + CorsConfiguration corsConfiguration = new CorsConfiguration(); + //设置放行哪些原始域 + corsConfiguration.addAllowedOrigin("*"); + //放行哪些原始请求头部信息 + corsConfiguration.addAllowedHeader("*"); + //放行哪些请求方式 + corsConfiguration.addAllowedMethod("*"); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + //2. 添加映射路径 + source.registerCorsConfiguration("/**", corsConfiguration); + return new CorsFilter(source); + } + +} diff --git a/src/main/java/cn/lili/security/UserContext.java b/src/main/java/cn/lili/security/UserContext.java new file mode 100644 index 0000000000000000000000000000000000000000..abeb15456e650b3c75fe906a85441a983c3881ab --- /dev/null +++ b/src/main/java/cn/lili/security/UserContext.java @@ -0,0 +1,100 @@ +package cn.lili.security; + + +import cn.lili.exception.ServiceException; +import cn.lili.cache.Cache; +import cn.lili.security.enums.SecurityEnum; +import cn.lili.result.ResultCode; +import com.google.gson.Gson; +import io.jsonwebtoken.*; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; + +/** + * 用户上下文 + * + * @author Chopper + * @version v4.0 + * @since 2020/11/14 20:27 + */ +public class UserContext { + + /** + * 根据request获取用户信息 + * + * @return 授权用户 + */ + public static AuthUser getCurrentUser() { + if (RequestContextHolder.getRequestAttributes() != null) { + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + String accessToken = request.getHeader(SecurityEnum.HEADER_TOKEN.getValue()); + return getAuthUser(accessToken); + } + return null; + } + + + /** + * 根据jwt获取token重的用户信息 + * + * @param cache 缓存 + * @param accessToken token + * @return 授权用户 + */ + public static AuthUser getAuthUser(Cache cache, String accessToken) { + try { + if (cache.keys("*" + accessToken).isEmpty()) { + throw new ServiceException(ResultCode.ERROR); + } + return getAuthUser(accessToken); + } catch (Exception e) { + return null; + } + } + + public static String getCurrentUserToken() { + if (RequestContextHolder.getRequestAttributes() != null) { + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + return request.getHeader(SecurityEnum.HEADER_TOKEN.getValue()); + } + return null; + } + + /** + * 根据jwt获取token重的用户信息 + * + * @return 授权用户 + */ + public static AuthUser getAuthUser() { + return getAuthUser(getCurrentUserToken()); + } + + /** + * 根据jwt获取token重的用户信息 + * + * @param accessToken token + * @return 授权用户 + */ + public static AuthUser getAuthUser(String accessToken) { + try { + //获取token的信息 + Claims claims = null; + try { + claims + = Jwts.parser() + .setSigningKey(SecretKeyUtil.generalKeyByDecoders()) + .parseClaimsJws(accessToken).getBody(); + } catch (ExpiredJwtException e) { + claims = e.getClaims(); + } + //获取存储在claims中的用户信息 + String json = claims.get(SecurityEnum.USER_CONTEXT.getValue()).toString(); + return new Gson().fromJson(json, AuthUser.class); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/src/main/java/cn/lili/security/enums/SecurityEnum.java b/src/main/java/cn/lili/security/enums/SecurityEnum.java new file mode 100644 index 0000000000000000000000000000000000000000..55b438409e4fb6d05fddb1f40ed1ea27a385b447 --- /dev/null +++ b/src/main/java/cn/lili/security/enums/SecurityEnum.java @@ -0,0 +1,24 @@ +package cn.lili.security.enums; + +/** + * 安全相关常量 + * + * @author Chopper + */ +public enum SecurityEnum { + + /** + * 存在与header中的token参数头 名 + */ + HEADER_TOKEN("accessToken"), USER_CONTEXT("userContext"), JWT_SECRET("secret"); + + String value; + + SecurityEnum(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/src/main/java/cn/lili/security/enums/UserEnums.java b/src/main/java/cn/lili/security/enums/UserEnums.java new file mode 100644 index 0000000000000000000000000000000000000000..1404b7afc4cc7f2817392f277217f0dfe498203f --- /dev/null +++ b/src/main/java/cn/lili/security/enums/UserEnums.java @@ -0,0 +1,29 @@ +package cn.lili.security.enums; + +/** + * token角色类型 + * + * @author Chopper + * @version v1.0 + * @since 2020/8/18 15:23 + */ +public enum UserEnums { + /** + * 角色 + */ + MANAGER("管理员"), + MEMBER("会员"), + SELLER("租户"), + TENANT("租户"), + SEAT("坐席"); + + private final String role; + + UserEnums(String role) { + this.role = role; + } + + public String getRole() { + return role; + } +} diff --git a/src/main/java/cn/lili/security/token/Token.java b/src/main/java/cn/lili/security/token/Token.java new file mode 100644 index 0000000000000000000000000000000000000000..5399934d42a967da2a52d6cf6c0e55b750ec7c7a --- /dev/null +++ b/src/main/java/cn/lili/security/token/Token.java @@ -0,0 +1,24 @@ +package cn.lili.security.token; + +import lombok.Data; + +/** + * Token 实体类 + * + * @author Chopper + * @version v1.0 + * 2020-11-13 10:02 + */ +@Data +public class Token { + /** + * 访问token + */ + private String accessToken; + + /** + * 刷新token + */ + private String refreshToken; + +} diff --git a/src/main/java/cn/lili/security/token/TokenUtil.java b/src/main/java/cn/lili/security/token/TokenUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..ea50226e4d11f15ebbaf8f961689842c941a5948 --- /dev/null +++ b/src/main/java/cn/lili/security/token/TokenUtil.java @@ -0,0 +1,139 @@ +package cn.lili.security.token; + +import cn.lili.exception.ServiceException; +import cn.lili.cache.Cache; +import cn.lili.cache.CachePrefix; +import cn.lili.security.AuthUser; +import cn.lili.security.SecretKeyUtil; +import cn.lili.security.enums.SecurityEnum; +import cn.lili.security.enums.UserEnums; +import cn.lili.result.ResultCode; +import cn.lili.security.JWTTokenProperties; +import com.google.gson.Gson; +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.SignatureException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.concurrent.TimeUnit; + +/** + * TokenUtil + * + * @author Chopper + * @version v1.0 + * 2020-11-12 18:44 + */ +@Component +public class TokenUtil { + @Autowired + private JWTTokenProperties tokenProperties; + @Autowired + private Cache cache; + + /** + * 构建token + * + * @param username 主体 + * @param claim 私有声明 + * @param longTerm 长时间特殊token 如:移动端,微信小程序等 + * @param userEnums 用户枚举 + * @return TOKEN + */ + public Token createToken(String username, Object claim, boolean longTerm, UserEnums userEnums) { + Token token = new Token(); + //访问token + String accessToken = createToken(username, claim, tokenProperties.getTokenExpireTime()); + + cache.put(CachePrefix.ACCESS_TOKEN.getPrefix(userEnums) + accessToken, 1, + tokenProperties.getTokenExpireTime(), TimeUnit.MINUTES); + //刷新token生成策略:如果是长时间有效的token(用于app),则默认15天有效期刷新token。如果是普通用户登录,则刷新token为普通token2倍数 + Long expireTime = longTerm ? 15 * 24 * 60L : tokenProperties.getTokenExpireTime() * 2; + String refreshToken = createToken(username, claim, expireTime); + + cache.put(CachePrefix.REFRESH_TOKEN.getPrefix(userEnums) + refreshToken, 1, expireTime, TimeUnit.MINUTES); + + token.setAccessToken(accessToken); + token.setRefreshToken(refreshToken); + return token; + } + + /** + * 刷新token + * + * @param oldRefreshToken 刷新token + * @param userEnums 用户枚举 + * @return token + */ + public Token refreshToken(String oldRefreshToken, UserEnums userEnums) { + + Claims claims; + try { + claims = Jwts.parser() + .setSigningKey(SecretKeyUtil.generalKeyByDecoders()) + .parseClaimsJws(oldRefreshToken).getBody(); + } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException e) { + //token 过期 认证失败等 + throw new ServiceException(ResultCode.USER_AUTH_EXPIRED); + } + + //获取存储在claims中的用户信息 + String json = claims.get(SecurityEnum.USER_CONTEXT.getValue()).toString(); + AuthUser authUser = new Gson().fromJson(json, AuthUser.class); + + + String username = authUser.getUsername(); + //获取是否长期有效的token + boolean longTerm = authUser.getLongTerm(); + + + //如果缓存中有刷新token && + if (cache.hasKey(CachePrefix.REFRESH_TOKEN.getPrefix(userEnums) + oldRefreshToken)) { + Token token = new Token(); + //访问token + String accessToken = createToken(username, authUser, tokenProperties.getTokenExpireTime()); + cache.put(CachePrefix.ACCESS_TOKEN.getPrefix(userEnums) + accessToken, 1, tokenProperties.getTokenExpireTime(), TimeUnit.MINUTES); + + //如果是信任登录设备,则刷新token长度继续延长 + Long expirationTime = tokenProperties.getTokenExpireTime() * 2; + if (longTerm) { + expirationTime = 60 * 24 * 15L; + } + + //刷新token生成策略:如果是长时间有效的token(用于app),则默认15天有效期刷新token。如果是普通用户登录,则刷新token为普通token2倍数 + String refreshToken = createToken(username, authUser, expirationTime); + + cache.put(CachePrefix.REFRESH_TOKEN.getPrefix(userEnums) + refreshToken, 1, expirationTime, TimeUnit.MINUTES); + token.setAccessToken(accessToken); + token.setRefreshToken(refreshToken); + cache.remove(CachePrefix.REFRESH_TOKEN.getPrefix(userEnums) + oldRefreshToken); + return token; + } else { + throw new ServiceException(ResultCode.USER_AUTH_EXPIRED); + } + + } + + /** + * 生成token + * + * @param username 主体 + * @param claim 私有神明内容 + * @param expirationTime 过期时间(分钟) + * @return token字符串 + */ + private String createToken(String username, Object claim, Long expirationTime) { + //JWT 生成 + return Jwts.builder() + //jwt 私有声明 + .claim(SecurityEnum.USER_CONTEXT.getValue(), new Gson().toJson(claim)) + //JWT的主体 + .setSubject(username) + //失效时间 当前时间+过期分钟 + .setExpiration(new Date(System.currentTimeMillis() + expirationTime * 60 * 1000)) + //签名算法和密钥 + .signWith(SecretKeyUtil.generalKey()) + .compact(); + } +} diff --git a/src/main/java/cn/lili/security/token/base/AbstractTokenGenerate.java b/src/main/java/cn/lili/security/token/base/AbstractTokenGenerate.java new file mode 100644 index 0000000000000000000000000000000000000000..85c79757db9f4c59f668af346183575c22f2ae77 --- /dev/null +++ b/src/main/java/cn/lili/security/token/base/AbstractTokenGenerate.java @@ -0,0 +1,40 @@ +package cn.lili.security.token.base; + + +import cn.lili.security.enums.UserEnums; +import cn.lili.security.token.Token; + +/** + * AbstractToken + * 抽象token,定义生成token类 + * + * @author Chopper + * @version v1.0 + * 2020-11-13 10:13 + */ +public abstract class AbstractTokenGenerate { + + /** + * 生成token + * + * @param user 用户名 + * @param longTerm 是否长时间有效 + * @return TOKEN对象 + */ + public abstract Token createToken(T user, Boolean longTerm); + + + /** + * 刷新token + * + * @param refreshToken 刷新token + * @return token + */ + public abstract Token refreshToken(String refreshToken); + + /** + * 默认role + */ + public UserEnums role = UserEnums.MANAGER; + +} diff --git a/src/main/java/cn/lili/server/WebSocketServer.java b/src/main/java/cn/lili/server/WebSocketServer.java new file mode 100644 index 0000000000000000000000000000000000000000..f6cc220dea39ec959ba03be6285834e4ccfc982a --- /dev/null +++ b/src/main/java/cn/lili/server/WebSocketServer.java @@ -0,0 +1,175 @@ +package cn.lili.server; + +import java.io.IOException; +import java.util.Date; +import java.util.concurrent.ConcurrentHashMap; +import javax.websocket.*; +import javax.websocket.server.PathParam; +import javax.websocket.server.ServerEndpoint; + +import cn.lili.config.CustomSpringConfigurator; +import cn.lili.cache.Cache; +import cn.lili.modules.im.entity.ImMessage; +import cn.lili.modules.im.entity.ImUser; +import cn.lili.modules.im.entity.enums.MessageResultType; +import cn.lili.modules.im.entity.vo.MessageVO; +import cn.lili.modules.im.service.ImMessageService; +import cn.lili.modules.im.service.ImUserService; +import cn.lili.security.AuthUser; +import cn.lili.security.UserContext; +import cn.lili.security.enums.UserEnums; +import cn.lili.utils.SnowFlake; +import com.alibaba.druid.util.StringUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import com.alibaba.fastjson.JSON; +import cn.lili.modules.im.entity.vo.MessageOperation; + +/** + * @author liushuai + */ +@Component +@ServerEndpoint(value = "/lili/webSocket/{accessToken}", configurator = CustomSpringConfigurator.class) +@Slf4j +public class WebSocketServer { + /** + * 消息服务 + */ + @Autowired + private ImMessageService imMessageService; + + /** + * im用户服务 + */ + @Autowired + private ImUserService imUserService; + + + @Autowired + private Cache cache; + /** + * 在线人数 + * PS 注意,只能单节点,如果多节点部署需要自行寻找方案 + */ + private static ConcurrentHashMap sessionPools = new ConcurrentHashMap<>(); + + /** + * 建立连接 + * + * @param session + */ + @OnOpen + public void onOpen(@PathParam("accessToken") String accessToken, Session session) throws IOException { + + ImUser imUser = imUserService.register(accessToken); + AuthUser authUser = UserContext.getAuthUser(accessToken); + sessionPools.put(authUser.getId(), session); + MessageVO messageVO = new MessageVO(MessageResultType.FRIENDS, imUser); + sendMessage(authUser.getId(), messageVO); + } + + /** + * 关闭连接 + */ + @OnClose + public void onClose(@PathParam("accessToken") String accessToken) { + log.error("断开连接"); + sessionPools.remove(UserContext.getAuthUser(accessToken).getId()); + } + + /** + * 发送消息 + * + * @param msg + * @throws IOException + */ + @OnMessage + public void onMessage(@PathParam("accessToken") String accessToken, String msg) { + log.error(msg); + MessageOperation messageOperation = JSON.parseObject(msg, MessageOperation.class); + operation(accessToken, messageOperation); + } + + /** + * IM操作 + * + * @param accessToken + * @param messageOperation + */ + private void operation(String accessToken, MessageOperation messageOperation) { + + AuthUser authUser = UserContext.getAuthUser(accessToken); + switch (messageOperation.getOperationType()) { + case PING: + break; + case MESSAGE: + //保存消息 + ImMessage imMessage = new ImMessage(); + imMessage.setFromUser(authUser.getId()); + imMessage.setMessageType(messageOperation.getMessageType()); + imMessage.setIsRead(false); + imMessage.setText(messageOperation.getContext()); + imMessage.setTalkId(messageOperation.getTalkId()); + imMessage.setCreateTime(new Date()); + imMessage.setToUser(messageOperation.getTo()); + imMessage.setId(SnowFlake.getIdStr()); + imMessageService.save(imMessage); + //发送消息 + sendMessage(messageOperation.getTo(), new MessageVO(MessageResultType.MESSAGE, imMessage)); + break; + case READ: + if (!StringUtils.isEmpty(messageOperation.getContext())) { + imMessageService.read(messageOperation.getTalkId(), messageOperation.getContext()); + sendMessage(messageOperation.getTo(), new MessageVO(MessageResultType.READ_MESSAGE, messageOperation.getContext().split(","))); + } + break; + case UNREAD: + sendMessage(authUser.getId(), new MessageVO(MessageResultType.UN_READ, imMessageService.unReadMessages(accessToken))); + break; + case HISTORY: + sendMessage(authUser.getId(), new MessageVO(MessageResultType.HISTORY, imMessageService.historyMessage(accessToken, messageOperation.getTo()))); + break; + default: + break; + } + } + + /** + * 发送消息 + * + * @param key 密钥 + * @param message 消息对象 + */ + private void sendMessage(String key, MessageVO message) { + Session session = sessionPools.get(key); + if (session != null) { + try { + session.getBasicRemote().sendText(JSON.toJSONString(message, true)); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * socket exception + * + * @param session + * @param throwable + */ + @OnError + public void onError(Session session, Throwable throwable) { + throwable.printStackTrace(); + } + + /** + * 获取店铺id + * + * @return + */ + private String storeKey(String storeId) { + return "STORE_" + storeId; + } + +} diff --git a/src/main/java/cn/lili/swagger/Swagger2Config.java b/src/main/java/cn/lili/swagger/Swagger2Config.java new file mode 100644 index 0000000000000000000000000000000000000000..c2878ee11e9765dadbf26c0de7ba5bc904a4b3fc --- /dev/null +++ b/src/main/java/cn/lili/swagger/Swagger2Config.java @@ -0,0 +1,93 @@ +package cn.lili.swagger; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +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.*; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.service.contexts.SecurityContext; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Chopper + */ +@Slf4j +@Configuration +@EnableSwagger2WebMvc +public class Swagger2Config { + + @Value("${swagger.title}") + private String title; + + @Value("${swagger.description}") + private String description; + + @Value("${swagger.version}") + private String version; + + @Value("${swagger.termsOfServiceUrl}") + private String termsOfServiceUrl; + + @Value("${swagger.contact.name}") + private String name; + + @Value("${swagger.contact.url}") + private String url; + + @Value("${swagger.contact.email}") + private String email; + + private List securitySchemes() { + List apiKeys = new ArrayList<>(); + apiKeys.add(new ApiKey("Authorization", "accessToken", "header")); + return apiKeys; + } + + private List securityContexts() { + List securityContexts = new ArrayList<>(); + securityContexts.add(SecurityContext.builder() + .securityReferences(defaultAuth()) + .forPaths(PathSelectors.regex("^(?!auth).*$")).build()); + return securityContexts; + } + + private List defaultAuth() { + AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); + AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; + authorizationScopes[0] = authorizationScope; + List securityReferences = new ArrayList<>(); + securityReferences.add(new SecurityReference("Authorization", authorizationScopes)); + return securityReferences; + } + + @Bean + public Docket memberRestApi() { + return new Docket(DocumentationType.SWAGGER_2) + .groupName("im") + .apiInfo(apiInfo()).select() + //扫描所有有注解的api,用这种方式更灵活 + .apis(RequestHandlerSelectors.basePackage("cn.lili.controller.im")) + .paths(PathSelectors.any()) + .build() + .securitySchemes(securitySchemes()) + .securityContexts(securityContexts()); + } + + private ApiInfo apiInfo() { + return new ApiInfoBuilder() + .title(title) + .description(description) + .termsOfServiceUrl(termsOfServiceUrl) + .contact(new Contact(name, url, email)) + .version(version) + .build(); + } +} diff --git a/src/main/java/cn/lili/utils/BeanUtil.java b/src/main/java/cn/lili/utils/BeanUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..aa61d0f431b881b2760deeb633a8490091373074 --- /dev/null +++ b/src/main/java/cn/lili/utils/BeanUtil.java @@ -0,0 +1,137 @@ +package cn.lili.utils; + +import org.springframework.beans.BeanUtils; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * 对象属性复制 + * + * @author Chopper + */ +public class BeanUtil { + + /** + * 复制属性 + * + * @param objectFrom 源自对象 + * @param objectTo 复制给对象 + */ + public static void copyProperties(Object objectFrom, Object objectTo) { + BeanUtils.copyProperties(objectFrom, objectTo); + } + + + /** + * 获取属性名数组 + * + * @param o 获取字段的对象 + * @return 返回各个字段 + */ + public static String[] getFiledName(Object o) { + Field[] fields = o.getClass().getDeclaredFields(); + Field[] superFields = o.getClass().getSuperclass().getDeclaredFields(); + String[] fieldNames = new String[fields.length + superFields.length]; + int index = 0; + for (int i = 0; i < fields.length; i++) { + fieldNames[index] = fields[i].getName(); + index++; + } + for (int i = 0; i < superFields.length; i++) { + if ("id".equals(superFields[i].getName())) { + continue; + } + fieldNames[index] = superFields[i].getName(); + index++; + } + return fieldNames; + } + + /** + * 根据属性名获取属性值 + * + * @param fieldName 属性名 + * @param o 对象 + * @return 属性值 + */ + public static Object getFieldValueByName(String fieldName, Object o) { + try { + String firstLetter = fieldName.substring(0, 1).toUpperCase(); + String getter = "get" + firstLetter + fieldName.substring(1); + Method method = o.getClass().getMethod(getter, new Class[]{}); + Object value = method.invoke(o, new Object[]{}); + return value; + } catch (Exception e) { + return null; + } + } + + + /** + * 将对象转换为key value + * A=a&B=b&C=c 格式 + * + * @param object 对象 + * @return 格式化结果 + */ + public static String formatKeyValuePair(Object object) { + //准备接受的字符串 + StringBuilder stringBuffer = new StringBuilder(); + //获取对象字段 + String[] fieldNames = BeanUtil.getFiledName(object); + //遍历所有属性 + for (int j = 0; j < fieldNames.length; j++) { + //不是第一个并且不是最后一个,拼接& + if (j != 0) { + stringBuffer.append("&"); + } + //获取属性的名字 + String key = fieldNames[j]; + //获取值 + Object value = BeanUtil.getFieldValueByName(key, object); + assert value != null; + stringBuffer.append(key).append("=").append(value.toString()); + } + return stringBuffer.toString(); + } + + /** + * key value键值对 转换为 对象 + * A=a&B=b&C=c 格式 转换为对象 + * + * @param str 对象字符串 + * @param t 范型 + * @param 范型 + * @return 格式化结果 + */ + public static T formatKeyValuePair(String str, T t) { + //填写对参数键值对 + String[] params = str.split("&"); + + //获取对象字段 + String[] fieldNames = BeanUtil.getFiledName(t); + + try { + //循环每个参数 + for (String param : params) { + String[] keyValues = param.split("="); + for (int i = 0; i < fieldNames.length; i++) { + if (fieldNames[i].equals(keyValues[0])) { + Field f = t.getClass().getDeclaredField(fieldNames[i]); + f.setAccessible(true); + //长度为2 才转换,否则不转 + if (keyValues.length == 2) { + f.set(t, keyValues[1]); + } + } + } + + } + } catch (Exception e) { + e.printStackTrace(); + } + return t; + } + +} diff --git a/src/main/java/cn/lili/utils/DateUtil.java b/src/main/java/cn/lili/utils/DateUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..2fbc3f280aa65eb1864dca82324dbe09455942e3 --- /dev/null +++ b/src/main/java/cn/lili/utils/DateUtil.java @@ -0,0 +1,398 @@ +package cn.lili.utils; + +import java.text.SimpleDateFormat; +import java.time.*; +import java.util.*; + +/** + * 日期相关的操作 + * + * @author Chopper + */ +public class DateUtil { + + public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss"; + + public static final String STANDARD_DATE_FORMAT = "yyyy-MM-dd"; + + public static final String STANDARD_DATE_NO_UNDERLINE_FORMAT = "yyyyMMdd"; + + public static final String FULL_DATE = "yyyyMMddHHmmss"; + + + /** + * 当天的开始时间 + * + * @return 今天开始时间 + */ + public static Date startOfTodDayTime() { + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + return calendar.getTime(); + } + + /** + * 当天的开始时间 + * + * @param date 时间 + * @return 根据传入的时间获取开始时间 + */ + public static Date startOfTodDayTime(Date date) { + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + return calendar.getTime(); + } + + /** + * 当天的开始时间 + * + * @return 今天开始时间 + */ + public static long startOfTodDay() { + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + Date date = calendar.getTime(); + return date.getTime() / 1000; + } + + /** + * 当天的结束时间 + * + * @return 今天结束时间 + */ + public static Date endOfDate() { + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.HOUR_OF_DAY, 23); + calendar.set(Calendar.MINUTE, 59); + calendar.set(Calendar.SECOND, 59); + calendar.set(Calendar.MILLISECOND, 999); + return calendar.getTime(); + } + + /** + * 当天的结束时间 + * + * @param date 传入日期 + * @return 获得传入日期当天结束时间 + */ + public static Date endOfDate(Date date) { + if (date == null) { + date = new Date(); + } + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.set(Calendar.HOUR_OF_DAY, 23); + calendar.set(Calendar.MINUTE, 59); + calendar.set(Calendar.SECOND, 59); + calendar.set(Calendar.MILLISECOND, 999); + return calendar.getTime(); + } + + /** + * 某天的年月日 + * + * @param dayUntilNow 距今多少天以前 + * @return 年月日map key为 year month day + */ + public static Map getYearMonthAndDay(int dayUntilNow) { + + Map map = new HashMap(3); + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + calendar.add(Calendar.DATE, -dayUntilNow); + map.put("year", calendar.get(Calendar.YEAR)); + map.put("month", calendar.get(Calendar.MONTH) + 1); + map.put("day", calendar.get(Calendar.DAY_OF_MONTH)); + return map; + } + + /** + * 将一个字符串转换成日期格式 + * + * @param date 字符串日期 + * @param pattern 日期格式 + * @return date + */ + public static Date toDate(String date, String pattern) { + if ("".equals("" + date)) { + return null; + } + if (pattern == null) { + pattern = STANDARD_DATE_FORMAT; + } + SimpleDateFormat sdf = new SimpleDateFormat(pattern, Locale.ENGLISH); + Date newDate = new Date(); + try { + newDate = sdf.parse(date); + + } catch (Exception ex) { + ex.printStackTrace(); + } + return newDate; + } + + /** + * 获取上个月的开始结束时间 + * + * @return 上个月的开始结束时间 + */ + public static Long[] getLastMonth() { + //取得系统当前时间 + Calendar cal = Calendar.getInstance(); + int year = cal.get(Calendar.YEAR); + int month = cal.get(Calendar.MONTH) + 1; + + //取得系统当前时间所在月第一天时间对象 + cal.set(Calendar.DAY_OF_MONTH, 1); + + //日期减一,取得上月最后一天时间对象 + cal.add(Calendar.DAY_OF_MONTH, -1); + + //输出上月最后一天日期 + int day = cal.get(Calendar.DAY_OF_MONTH); + + String months = ""; + String days = ""; + + if (month > 1) { + month--; + } else { + year--; + month = 12; + } + if (String.valueOf(month).length() <= 1) { + months = "0" + month; + } else { + months = String.valueOf(month); + } + if (String.valueOf(day).length() <= 1) { + days = "0" + day; + } else { + days = String.valueOf(day); + } + String firstDay = "" + year + "-" + months + "-01"; + String lastDay = "" + year + "-" + months + "-" + days + " 23:59:59"; + + Long[] lastMonth = new Long[2]; + lastMonth[0] = DateUtil.getDateline(firstDay); + lastMonth[1] = DateUtil.getDateline(lastDay, STANDARD_FORMAT); + + return lastMonth; + } + + /** + * 把日期转换成字符串型 + * + * @param date 日期 + * @return 字符串时间 + */ + public static String toString(Date date) { + return toString(date, STANDARD_FORMAT); + } + + /** + * 把日期转换成字符串型 + * + * @param date 日期 + * @return 字符串时间 + */ + public static String toString(Long date) { + return toString(date, STANDARD_FORMAT); + } + + /** + * 把日期转换成字符串型 + * + * @param date 日期 + * @param pattern 类型 + * @return 字符串时间 + */ + public static String toString(Date date, String pattern) { + if (date == null) { + return ""; + } + if (pattern == null) { + pattern = STANDARD_DATE_FORMAT; + } + String dateString = ""; + SimpleDateFormat sdf = new SimpleDateFormat(pattern); + try { + dateString = sdf.format(date); + } catch (Exception ex) { + ex.printStackTrace(); + } + return dateString; + } + + /** + * 时间戳转换成时间类型 + * + * @param time 时间戳 + * @param pattern 格式 + * @return 字符串时间 + */ + public static String toString(Long time, String pattern) { + if (time > 0) { + if (time.toString().length() == 10) { + time = time * 1000; + } + Date date = new Date(time); + return DateUtil.toString(date, pattern); + } + return ""; + } + + /** + * 判断当前时间是否在某个时间范围 + * + * @param start 开始时间,以秒为单位的时间戳 + * @param end 结束时间,以秒为单位的时间戳 + * @return 是否在范围内 + */ + public static boolean inRangeOf(long start, long end) { + long now = getDateline(); + return start <= now && end >= now; + } + + /** + * 获取指定日期的时间戳 + * + * @param date 指定日期 + * @return 时间戳 + */ + public static long getDateline(String date) { + return Objects.requireNonNull(toDate(date, STANDARD_DATE_FORMAT)).getTime() / 1000; + } + + /** + * 获取当前时间的时间戳 + * + * @return 时间戳 + */ + public static long getDateline() { + return System.currentTimeMillis() / 1000; + } + + /** + * 获取当前时间格式化字符串 + * + * @return 时间戳 + */ + public static String getCurrentDateStr(String format) { + return toString(new Date(), format); + } + + /** + * 获取当前时间格式化字符串 + * + * @return 格式化的时间 + */ + public static String getCurrentDateStr() { + return toString(new Date(), FULL_DATE); + } + + /** + * 根据日期格式及日期获取时间戳 + * + * @param date 日期 + * @param pattern 日期格式 + * @return 时间戳 + */ + public static long getDateline(String date, String pattern) { + return Objects.requireNonNull(toDate(date, pattern)).getTime() / 1000; + } + + /** + * 获取几个月之前的日期时间戳 + * + * @param beforeMonth 几个月之前 + * @return 时间戳 + */ + public static long getBeforeMonthDateline(int beforeMonth) { + SimpleDateFormat format = new SimpleDateFormat(STANDARD_FORMAT); + Calendar c = Calendar.getInstance(); + + //过去一月 + c.setTime(new Date()); + c.add(Calendar.MONTH, (0 - beforeMonth)); + Date m = c.getTime(); + String mon = format.format(m); + return getDateline(mon, STANDARD_FORMAT); + } + + /** + * 获取当前天的结束时间 + * + * @return 当前天的结束时间 + */ + public static Date getCurrentDayEndTime() { + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + cal.set(Calendar.DATE, cal.get(Calendar.DATE) + 1); + cal.set(Calendar.SECOND, cal.get(Calendar.SECOND) - 1); + return cal.getTime(); + } + + /** + * 获取延时时间(秒) + * + * @param startTime 开始时间 + * @return 延时时间(秒) + */ + public static Integer getDelayTime(Long startTime) { + int time = Math.toIntExact((startTime - System.currentTimeMillis()) / 1000); + //如果时间为负数则改为一秒后执行 + if (time <= 0) { + time = 1; + } + return time; + } + + /** + * 获取某年某月开始时间 + * + * @param year 年 + * @param month 月 + * @return 开始时间 + */ + public static Date getBeginTime(int year, int month) { + YearMonth yearMonth = YearMonth.of(year, month); + LocalDate localDate = yearMonth.atDay(1); + LocalDateTime startOfDay = localDate.atStartOfDay(); + ZonedDateTime zonedDateTime = startOfDay.atZone(ZoneId.of("Asia/Shanghai")); + + return Date.from(zonedDateTime.toInstant()); + + } + + /** + * 获取某年某月结束时间 + * + * @param year 年 + * @param month 月 + * @return 结束时间 + */ + public static Date getEndTime(int year, int month) { + YearMonth yearMonth = YearMonth.of(year, month); + LocalDate endOfMonth = yearMonth.atEndOfMonth(); + LocalDateTime localDateTime = endOfMonth.atTime(23, 59, 59, 999); + ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of("Asia/Shanghai")); + return Date.from(zonedDateTime.toInstant()); + } +} diff --git a/src/main/java/cn/lili/utils/IpHelper.java b/src/main/java/cn/lili/utils/IpHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..82df045ecffff5fc5657ac8ea5728118d34d6cb1 --- /dev/null +++ b/src/main/java/cn/lili/utils/IpHelper.java @@ -0,0 +1,78 @@ +package cn.lili.utils; + + +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.http.HttpUtil; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; + + +/** + * ip工具 + * + * @author Chopper + */ +@Slf4j +@Component +public class IpHelper { + + /** + * qq lbs 地区查询key + */ + @Value("${lili.lbs.key:''}") + private String key; + /** + * qq lbs 地区查询key + */ + @Value("${lili.lbs.sk:''}") + private String sk; + + private static final String API = "https://apis.map.qq.com"; + + + /** + * 获取IP返回地理信息 + * + * @param request 请求参数 + * @return 城市信息 + */ + public String getIpCity(HttpServletRequest request) { + + String url = "/ws/location/v1/ip?key=" + key + "&ip=" + IpUtils.getIpAddress(request); + String sign = SecureUtil.md5(url + sk); + url = API + url + "&sign=" + sign; + String result = "未知"; + try { + String json = HttpUtil.get(url, 3000); + JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject(); + String status = jsonObject.get("status").getAsString(); + if ("0".equals(status)) { + JsonObject address = jsonObject.get("result").getAsJsonObject().get("ad_info").getAsJsonObject(); + String nation = address.get("nation").getAsString(); + String province = address.get("province").getAsString(); + String city = address.get("city").getAsString(); + String district = address.get("district").getAsString(); + if (StrUtil.isNotBlank(nation) && StrUtil.isBlank(province)) { + result = nation; + } else { + result = province; + if (StrUtil.isNotBlank(city)) { + result += " " + city; + } + if (StrUtil.isNotBlank(district)) { + result += " " + district; + } + } + } + } catch (Exception e) { + log.info("获取IP地理信息失败"); + } + return result; + } +} diff --git a/src/main/java/cn/lili/utils/IpUtils.java b/src/main/java/cn/lili/utils/IpUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..238382b85cdc0c557526fff214e44ee05f0654ff --- /dev/null +++ b/src/main/java/cn/lili/utils/IpUtils.java @@ -0,0 +1,67 @@ +package cn.lili.utils; + +import lombok.extern.slf4j.Slf4j; + +import javax.servlet.http.HttpServletRequest; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * IpUtils + * + * @author Chopper + * @version v1.0 + * 2020-12-08 15:32 + */ +@Slf4j +public class IpUtils { + + /** + * 获取本机IP + * + * @return ip + */ + public static String getLocalIp() { + try { + return InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException e) { + log.error("获取本机IP错误",e); + return null; + } + } + + /** + * 获取客户端IP地址 + * + * @param request 请求 + * @return + */ + public static String getIpAddress(HttpServletRequest request) { + + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + //对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 + if (ip != null && ip.length() > 15) { + if (ip.indexOf(",") > 0) { + ip = ip.substring(0, ip.indexOf(",")); + } + } + if ("0:0:0:0:0:0:0:1".equals(ip)) { + ip = "127.0.0.1"; + } + return ip; + } + + + public static void main(String[] args) { + System.out.println(IpUtils.getLocalIp()); + } +} diff --git a/src/main/java/cn/lili/utils/ObjectUtil.java b/src/main/java/cn/lili/utils/ObjectUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..f00c765c00969d793a69b984df6454080ee4e41a --- /dev/null +++ b/src/main/java/cn/lili/utils/ObjectUtil.java @@ -0,0 +1,57 @@ +package cn.lili.utils; + +import cn.hutool.core.util.StrUtil; +import com.google.gson.Gson; +import org.springframework.cglib.beans.BeanMap; + +import java.util.HashMap; +import java.util.Map; + +/** + * 对象转换工具 + * @author Chopper + */ +public class ObjectUtil { + + public static String mapToString(Map paramMap){ + + if (paramMap == null) { + return ""; + } + Map params = new HashMap<>(16); + for (Map.Entry param : paramMap.entrySet()) { + + String key = param.getKey(); + String paramValue = (param.getValue() != null && param.getValue().length > 0 ? param.getValue()[0] : ""); + String obj = StrUtil.endWithIgnoreCase(param.getKey(), "password") ? "******" : paramValue; + params.put(key,obj); + } + return new Gson().toJson(params); + } + + public static String mapToStringAll(Map paramMap){ + + if (paramMap == null) { + return ""; + } + Map params = new HashMap<>(16); + for (Map.Entry param : paramMap.entrySet()) { + + String key = param.getKey(); + String paramValue = (param.getValue() != null && param.getValue().length > 0 ? param.getValue()[0] : ""); + params.put(key, paramValue); + } + return new Gson().toJson(params); + } + + public static Map beanToMap(T bean) { + Map map = new HashMap<>(16); + if (bean != null) { + BeanMap beanMap = BeanMap.create(bean); + for (Object key : beanMap.keySet()) { + map.put(key+"", beanMap.get(key)); + } + } + return map; + } +} diff --git a/src/main/java/cn/lili/utils/RSAUtils.java b/src/main/java/cn/lili/utils/RSAUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..85b5974b67e04e782937d4c4d1705f9c314816c1 --- /dev/null +++ b/src/main/java/cn/lili/utils/RSAUtils.java @@ -0,0 +1,401 @@ +package cn.lili.utils; + +import org.apache.tomcat.util.codec.binary.Base64; + +import javax.crypto.Cipher; +import java.io.ByteArrayOutputStream; +import java.io.UnsupportedEncodingException; +import java.security.*; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.HashMap; +import java.util.Map; + +/** + * 加密算法 + * Created by Chopper + */ +public class RSAUtils { + /** + * 签名算法 + */ + public static final String SIGNATURE_ALGORITHM = "SHA1withRSA"; + /** + * 加密算法RSA + */ + public static final String KEY_ALGORITHM = "RSA"; + /** + * RSA最大加密明文大小 + */ + private static final int MAX_ENCRYPT_BLOCK = 117; + + /** + * RSA最大解密密文大小 + */ + private static final int MAX_DECRYPT_BLOCK = 128; + + /** + * 获取公钥的key + */ + private static final String PUBLIC_KEY = "RSAPublicKey"; + + /** + * 获取私钥的key + */ + private static final String PRIVATE_KEY = "RSAPrivateKey"; + + + public static void main(String[] args) throws Exception { + Map genKeyPair = genKeyPair(); + + String base64publicKey = getPublicKey(genKeyPair); + System.out.println("公钥 \n" + base64publicKey); + String base64privateKey = getPrivateKey(genKeyPair); + System.out.println("私钥\n" + base64privateKey); + + String passwd = "cat123113"; + String charsetName = "utf-8"; + + String encryptByPublicKey = Base64.encodeBase64String((encryptByPublicKey( + passwd.getBytes(charsetName), base64publicKey))); + System.out.println("加密\n" + encryptByPublicKey); + + byte[] decryptByPrivateKey = decryptByPrivateKey(Base64.decodeBase64(encryptByPublicKey), + base64privateKey); + String string = new String(decryptByPrivateKey, "utf-8"); + System.out.println("解密后\n" + string); + + } + + /** + *

+ * 生成密钥对(公钥和私钥) + *

+ * + * @return + * @throws Exception + */ + public static Map genKeyPair() throws Exception { + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); + keyPairGen.initialize(1024); + KeyPair keyPair = keyPairGen.generateKeyPair(); + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); + Map keyMap = new HashMap(2); + keyMap.put(PUBLIC_KEY, publicKey); + keyMap.put(PRIVATE_KEY, privateKey); + return keyMap; + + } + /** + * 签名字符串 + * + * @param text 需要签名的字符串 + * @param privateKey 私钥(BASE64编码) + * @param charset 编码格式 + * @return 签名结果(BASE64编码) + */ + public static String sign(String text, String privateKey, String charset) throws Exception { + + byte[] keyBytes = Base64.decodeBase64(privateKey); + PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec); + Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); + signature.initSign(privateK); + signature.update(getContentBytes(text, charset)); + byte[] result = signature.sign(); + return Base64.encodeBase64String(result); + + } + + /** + * 签名字符串 + * + * @param text 需要签名的字符串 + * @param privateKey 私钥 + * @param charset 编码格式 + * @return 签名结果(BASE64编码) + */ + public static String sign(String text, PrivateKey privateKey, String charset) + throws SignatureException, + InvalidKeyException { + try { + Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); + signature.initSign(privateKey); + signature.update(getContentBytes(text, charset)); + byte[] result = signature.sign(); + return Base64.encodeBase64String(result); + } catch (NoSuchAlgorithmException e) { + //不可能发生, + return null; + } + } + + /** + * 签名字符串 + * + * @param text 需要签名的字符串 + * @param sign 客户签名结果 + * @param publicKey 公钥(BASE64编码) + * @param charset 编码格式 + * @return 验签结果 + */ + public static boolean verify(String text, String sign, String publicKey, String charset) + throws Exception { + byte[] keyBytes = Base64.decodeBase64(publicKey); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + PublicKey publicK = keyFactory.generatePublic(keySpec); + Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); + signature.initVerify(publicK); + signature.update(getContentBytes(text, charset)); + return signature.verify(Base64.decodeBase64(sign)); + + } + + /** + *

+ * 私钥解密 + *

+ * + * @param encryptedData 已加密数据 + * @param privateKey 私钥(BASE64编码) + * @return + * @throws Exception + */ + public static byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey) + throws Exception { + byte[] keyBytes = Base64.decodeBase64(privateKey); + PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + Key privateK = keyFactory.generatePrivate(pkcs8KeySpec); + Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); + cipher.init(Cipher.DECRYPT_MODE, privateK); + int inputLen = encryptedData.length; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offSet = 0; + byte[] cache; + int i = 0; + // 对数据分段解密 + while (inputLen - offSet > 0) { + if (inputLen - offSet > MAX_DECRYPT_BLOCK) { + cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK); + } else { + cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet); + } + out.write(cache, 0, cache.length); + i++; + offSet = i * MAX_DECRYPT_BLOCK; + } + byte[] decryptedData = out.toByteArray(); + out.close(); + return decryptedData; + + } + + /** + *

+ * 公钥解密 + *

+ * + * @param encryptedData 已加密数据 + * @param publicKey 公钥(BASE64编码) + * @return + * @throws Exception + */ + public static byte[] decryptByPublicKey(byte[] encryptedData, String publicKey) + throws Exception { + byte[] keyBytes = Base64.decodeBase64(publicKey); + X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + Key publicK = keyFactory.generatePublic(x509KeySpec); + Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); + cipher.init(Cipher.DECRYPT_MODE, publicK); + int inputLen = encryptedData.length; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offSet = 0; + byte[] cache; + int i = 0; + // 对数据分段解密 + while (inputLen - offSet > 0) { + if (inputLen - offSet > MAX_DECRYPT_BLOCK) { + cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK); + } else { + cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet); + } + out.write(cache, 0, cache.length); + i++; + offSet = i * MAX_DECRYPT_BLOCK; + } + byte[] decryptedData = out.toByteArray(); + out.close(); + return decryptedData; + + } + + /** + *

+ * 公钥加密 + *

+ * + * @param data 源数据 + * @param publicKey 公钥(BASE64编码) + * @return + * @throws Exception + */ + public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception { + byte[] keyBytes = Base64.decodeBase64(publicKey); + X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + Key publicK = keyFactory.generatePublic(x509KeySpec); + // 对数据加密 + Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); + cipher.init(Cipher.ENCRYPT_MODE, publicK); + int inputLen = data.length; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offSet = 0; + byte[] cache; + int i = 0; + // 对数据分段加密 + while (inputLen - offSet > 0) { + if (inputLen - offSet > MAX_ENCRYPT_BLOCK) { + cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK); + } else { + cache = cipher.doFinal(data, offSet, inputLen - offSet); + } + out.write(cache, 0, cache.length); + i++; + offSet = i * MAX_ENCRYPT_BLOCK; + } + byte[] encryptedData = out.toByteArray(); + out.close(); + return encryptedData; + + } + + /** + *

+ * 公钥加密 + *

+ * + * @param data 源数据 + * @param cert 证书 + * @return + * @throws Exception + */ + public static byte[] encryptByPublicKey(byte[] data, Certificate cert) throws Exception { + + // 对数据加密 + PublicKey uk = cert.getPublicKey(); + Cipher cipher = Cipher.getInstance(uk.getAlgorithm()); + cipher.init(Cipher.ENCRYPT_MODE, uk); + int inputLen = data.length; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offSet = 0; + byte[] cache; + int i = 0; + // 对数据分段加密 + while (inputLen - offSet > 0) { + if (inputLen - offSet > MAX_ENCRYPT_BLOCK) { + cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK); + } else { + cache = cipher.doFinal(data, offSet, inputLen - offSet); + } + out.write(cache, 0, cache.length); + i++; + offSet = i * MAX_ENCRYPT_BLOCK; + } + byte[] encryptedData = out.toByteArray(); + out.close(); + return encryptedData; + + } + + /** + *

+ * 私钥加密 + *

+ * + * @param data 源数据 + * @param privateKey 私钥(BASE64编码) + * @return + * @throws Exception + */ + public static byte[] encryptByPrivateKey(byte[] data, String privateKey) throws Exception { + byte[] keyBytes = Base64.decodeBase64(privateKey); + PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); + Key privateK = keyFactory.generatePrivate(pkcs8KeySpec); + Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); + cipher.init(Cipher.ENCRYPT_MODE, privateK); + int inputLen = data.length; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int offSet = 0; + byte[] cache; + int i = 0; + // 对数据分段加密 + while (inputLen - offSet > 0) { + if (inputLen - offSet > MAX_ENCRYPT_BLOCK) { + cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK); + } else { + cache = cipher.doFinal(data, offSet, inputLen - offSet); + } + out.write(cache, 0, cache.length); + i++; + offSet = i * MAX_ENCRYPT_BLOCK; + } + byte[] encryptedData = out.toByteArray(); + out.close(); + return encryptedData; + + } + + /** + * @param content + * @param charset + * @return + * @throws SignatureException + * @throws UnsupportedEncodingException + */ + private static byte[] getContentBytes(String content, String charset) { + if (charset == null || "".equals(charset)) { + return content.getBytes(); + } + try { + return content.getBytes(charset); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset); + } + } + + /** + *

+ * 获取私钥 + *

+ * + * @param keyMap 密钥对 + * @return + * @throws Exception + */ + public static String getPrivateKey(Map keyMap) throws Exception { + Key key = (Key) keyMap.get(PRIVATE_KEY); + return Base64.encodeBase64String(key.getEncoded()); + } + + /** + *

+ * 获取公钥 + *

+ * + * @param keyMap 密钥对 + * @return + * @throws Exception + */ + public static String getPublicKey(Map keyMap) throws Exception { + Key key = (Key) keyMap.get(PUBLIC_KEY); + return Base64.encodeBase64String(key.getEncoded()); + } +} diff --git a/src/main/java/cn/lili/utils/SnowFlake.java b/src/main/java/cn/lili/utils/SnowFlake.java new file mode 100644 index 0000000000000000000000000000000000000000..e5bfba485bb24996f7870c5dc53f441e439ecf62 --- /dev/null +++ b/src/main/java/cn/lili/utils/SnowFlake.java @@ -0,0 +1,43 @@ +package cn.lili.utils; + +import cn.hutool.core.lang.Snowflake; +import cn.hutool.core.util.IdUtil; + +import java.util.Date; + +/** + * 雪花分布式id获取 + * + * @author Chopper + */ +public class SnowFlake { + + /** + * 机器id + */ + private static long workerId = 0L; + /** + * 机房id + */ + private static long datacenterId = 0L; + + private static Snowflake snowflake = IdUtil.createSnowflake(workerId, datacenterId); + + public static long getId() { + return snowflake.nextId(); + } + + /** + * 生成字符,带有前缀 + * + * @param prefix + * @return + */ + public static String createStr(String prefix) { + return prefix + DateUtil.toString(new Date(), "yyyyMMdd") + SnowFlake.getId(); + } + + public static String getIdStr() { + return snowflake.nextId() + ""; + } +} diff --git a/src/main/java/cn/lili/utils/SpelUtil.java b/src/main/java/cn/lili/utils/SpelUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..6ab2dd37b318d3af18750fc5255b69e0e5e10916 --- /dev/null +++ b/src/main/java/cn/lili/utils/SpelUtil.java @@ -0,0 +1,81 @@ +package cn.lili.utils; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +/** + * SpelUtil + * + * @author Chopper + * @version v1.0 + * 2021-01-11 10:45 + */ +public class SpelUtil { + + + /** + * spel表达式解析器 + */ + private static SpelExpressionParser spelExpressionParser = new SpelExpressionParser(); + /** + * 参数名发现器 + */ + private static DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); + + /** + * 转换 jspl参数 + * + * @param joinPoint + * @param spel + * @return + */ + public static String compileParams(JoinPoint joinPoint, String spel) { //Spel表达式解析日志信息 + //获得方法参数名数组 + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + + String[] parameterNames = parameterNameDiscoverer.getParameterNames(signature.getMethod()); + if (parameterNames != null && parameterNames.length > 0) { + EvaluationContext context = new StandardEvaluationContext(); + + //获取方法参数值 + Object[] args = joinPoint.getArgs(); + for (int i = 0; i < args.length; i++) { + //替换spel里的变量值为实际值, 比如 #user --> user对象 + context.setVariable(parameterNames[i], args[i]); + } + return spelExpressionParser.parseExpression(spel).getValue(context).toString(); + } + return ""; + } + + /** + * 转换 jspl参数 + * + * @param joinPoint + * @param spel + * @return + */ + public static String compileParams(JoinPoint joinPoint, Object rvt, String spel) { //Spel表达式解析日志信息 + //获得方法参数名数组 + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + + String[] parameterNames = parameterNameDiscoverer.getParameterNames(signature.getMethod()); + if (parameterNames != null && parameterNames.length > 0) { + EvaluationContext context = new StandardEvaluationContext(); + + //获取方法参数值 + Object[] args = joinPoint.getArgs(); + for (int i = 0; i < args.length; i++) { + //替换spel里的变量值为实际值, 比如 #user --> user对象 + context.setVariable(parameterNames[i], args[i]); + } + context.setVariable("rvt", rvt); + return spelExpressionParser.parseExpression(spel).getValue(context).toString(); + } + return ""; + } +} diff --git a/src/main/java/cn/lili/utils/SpringContextUtil.java b/src/main/java/cn/lili/utils/SpringContextUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..03236312dd79767ee03e8d94800ad00e5865f523 --- /dev/null +++ b/src/main/java/cn/lili/utils/SpringContextUtil.java @@ -0,0 +1,62 @@ +package cn.lili.utils; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * @author Chopper + */ +@Component +public class SpringContextUtil implements ApplicationContextAware { + + /** + * 上下文对象实例 + */ + private static ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext appContext) throws BeansException { + applicationContext = appContext; + } + + /** + * 获取applicationContext + * @return spring applicationContext + */ + public static ApplicationContext getApplicationContext() { + return applicationContext; + } + + /** + * 通过name获取 Bean. + * @param name bean的名字 + * @return bean实例 + */ + public static Object getBean(String name){ + return getApplicationContext().getBean(name); + } + + /** + * 通过class获取Bean. + * @param clazz bean的类型 + * @param bean的类型 + * @return bean实例 + */ + public static T getBean(Class clazz){ + return getApplicationContext().getBean(clazz); + } + + /** + * 通过name,以及Clazz返回指定的Bean + * @param name bean的名字 + * @param clazz bean的类型 + * @param bean的类型 + * @return bean实例 + */ + public static T getBean(String name,Class clazz){ + return getApplicationContext().getBean(name, clazz); + } + +} diff --git a/src/main/java/cn/lili/utils/StringUtils.java b/src/main/java/cn/lili/utils/StringUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..7ae370441d1fe17ea2480e20843826b289ed3bd6 --- /dev/null +++ b/src/main/java/cn/lili/utils/StringUtils.java @@ -0,0 +1,181 @@ +package cn.lili.utils; + +import cn.hutool.core.util.StrUtil; + +import java.beans.BeanInfo; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 字串工具类 + * + * @author pikachu + */ +public class StringUtils extends StrUtil { + + /** + * MD5加密方法 + * + * @param str String + * @return String + */ + public static String md5(String str) { + MessageDigest messageDigest = null; + try { + messageDigest = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException ex) { + ex.printStackTrace(); + return null; + } + byte[] resultByte = messageDigest.digest(str.getBytes()); + StringBuffer result = new StringBuffer(); + for (int i = 0; i < resultByte.length; ++i) { + int v = 0xFF & resultByte[i]; + if (v < 16) { + result.append("0"); + } + result.append(Integer.toHexString(v)); + } + return result.toString(); + } + + /** + * 获取随机数 + * + * @param n 随机次数 + * @return + */ + public static String getRandStr(int n) { + Random random = new Random(); + String sRand = ""; + for (int i = 0; i < n; i++) { + String rand = String.valueOf(random.nextInt(10)); + sRand += rand; + } + return sRand; + } + + /** + * 切个字符串,如果超出长度则切割 + * + * @param var + * @param size + * @return + */ + public static String subStringLength(String var, Integer size) { + if (var.length() > size) { + return var.substring(0, (size - 4)).toString() + "..."; + } + return var; + } + + /** + * 对象转map + * + * @param obj + * @return + * @throws Exception + */ + public static Map objectToMap(Object obj) throws Exception { + if (obj == null) { + return null; + } + Map map = new HashMap(16); + + BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass()); + PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); + for (PropertyDescriptor property : propertyDescriptors) { + String key = property.getName(); + if (key.compareToIgnoreCase("class") == 0) { + continue; + } + Method getter = property.getReadMethod(); + Object value = getter != null ? getter.invoke(obj) : null; + map.put(key, value); + } + + return map; + } + + + /** + * 驼峰法转下划线 + */ + public static String camel2Underline(String str) { + + if (StrUtil.isBlank(str)) { + return ""; + } + if (str.length() == 1) { + return str.toLowerCase(); + } + StringBuffer sb = new StringBuffer(); + for (int i = 1; i < str.length(); i++) { + if (Character.isUpperCase(str.charAt(i))) { + sb.append("_" + Character.toLowerCase(str.charAt(i))); + } else { + sb.append(str.charAt(i)); + } + } + return (str.charAt(0) + sb.toString()).toLowerCase(); + } + + /** + * 如果给定字符串{@code str}中不包含{@code appendStr},则在{@code str}后追加{@code appendStr}; + * 如果已包含{@code appendStr},则在{@code str}后追加{@code otherwise} + * + * @param str 给定的字符串 + * @param appendStr 需要追加的内容 + * @param otherwise 当{@code appendStr}不满足时追加到{@code str}后的内容 + * @return 追加后的字符串 + */ + public static String appendIfNotContain(String str, String appendStr, String otherwise) { + if (isEmpty(str) || isEmpty(appendStr)) { + return str; + } + if (str.contains(appendStr)) { + return str.concat(otherwise); + } + return str.concat(appendStr); + } + + /** + * 过滤特殊字符串 + * + * @param str + * @return + */ + public static String filterSpecialChart(String str) { + String regEx = "[`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]"; + Pattern p = Pattern.compile(regEx); + Matcher m = p.matcher(str); + return m.replaceAll("").trim(); + } + + /** + * double 转价格字符串 + * + * @return + */ + public static String toFen(Double doubleValue) { + String str = doubleValue.toString(); + + if (!str.contains(".")) { + str = str + ".00"; + } else if (str.substring(str.indexOf(".")).length() == 2) { + str = str + "0"; + } + return str; + } + +} + + diff --git a/src/main/java/cn/lili/utils/ThreadPoolUtil.java b/src/main/java/cn/lili/utils/ThreadPoolUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..21622e4c778ea1c8218897ca5f89ce589a7f913f --- /dev/null +++ b/src/main/java/cn/lili/utils/ThreadPoolUtil.java @@ -0,0 +1,78 @@ +package cn.lili.utils; + +import java.util.concurrent.*; + +/** + * @author Chopper + */ +public class ThreadPoolUtil { + + /** + * 核心线程数,会一直存活,即使没有任务,线程池也会维护线程的最少数量 + */ + private static final int SIZE_CORE_POOL = 5; + /** + * 线程池维护线程的最大数量 + */ + private static final int SIZE_MAX_POOL = 10; + /** + * 线程池维护线程所允许的空闲时间 + */ + private static final long ALIVE_TIME = 2000; + /** + * 线程缓冲队列 + */ + private static final BlockingQueue BQUEUE = new ArrayBlockingQueue(100); + private static final ThreadPoolExecutor POOL = new ThreadPoolExecutor(SIZE_CORE_POOL, SIZE_MAX_POOL, ALIVE_TIME, TimeUnit.MILLISECONDS, BQUEUE, new ThreadPoolExecutor.CallerRunsPolicy()); + /** + * volatile禁止指令重排 + */ + public static volatile ThreadPoolExecutor threadPool; + + static { + POOL.prestartAllCoreThreads(); + } + + /** + * 无返回值直接执行, 管他娘的 + * + * @param runnable + */ + public static void execute(Runnable runnable) { + getThreadPool().execute(runnable); + } + + /** + * 返回值直接执行, 管他娘的 + * + * @param callable + */ + public static Future submit(Callable callable) { + return getThreadPool().submit(callable); + } + + /** + * DCL获取线程池 + * + * @return 线程池对象 + */ + public static ThreadPoolExecutor getThreadPool() { + if (threadPool != null) { + return threadPool; + } + synchronized (ThreadPoolUtil.class) { + if (threadPool == null) { + threadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool(); + } + } + return threadPool; + } + + public static ThreadPoolExecutor getPool() { + return POOL; + } + + public static void main(String[] args) { + System.out.println(POOL.getPoolSize()); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index a7afc92b751b6cd5dc7916261d5cf5d3478b4148..e03e95e37a565da8e0bc0f06243a0540de5a08be 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,2 +1,103 @@ server: - port: 8080 + port: 8088 +spring: + # Redis + redis: + host: 192.168.0.116 + port: 6379 + password: lilishop + lettuce: + pool: + # 连接池最大连接数(使用负值表示没有限制) 默认 8 + max-active: 200 + # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1 + max-wait: 20 + # 连接池中的最大空闲连接 默认 8 + max-idle: 10 + # 连接池中的最小空闲连接 默认 8 + min-idle: 8 + jackson: + time-zone: GMT+8 + serialization: + #关闭jackson 对json做解析 + fail-on-empty-beans: false + shardingsphere: + datasource: + # 数据库名称,可自定义,可以为多个,以逗号隔开,每个在这里定义的库,都要在下面定义连接属性 + names: default-datasource + default-datasource: + type: com.alibaba.druid.pool.DruidDataSource + driverClassName: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://192.168.0.116:3306/lilishop?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai + username: root + password: lilishop + maxActive: 20 + initialSize: 5 + maxWait: 60000 + minIdle: 5 + timeBetweenEvictionRunsMillis: 60000 + minEvictableIdleTimeMillis: 300000 + validationQuery: SELECT 1 FROM DUAL + testWhileIdle: true + testOnBorrow: false + testOnReturn: false + #是否缓存preparedStatement,也就是PSCache。在mysql下建议关闭。 PSCache对支持游标的数据库性能提升巨大,比如说oracle。 + poolPreparedStatements: false + #要启用PSCache,-1为关闭 必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true 可以把这个数值配置大一些,比如说100 + maxOpenPreparedStatements: -1 + #配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙 + filters: stat,wall,log4j2 + #通过connectProperties属性来打开mergeSql功能;慢SQL记录 + connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 + #合并多个DruidDataSource的监控数据 + useGlobalDataSourceStat: true + loginUsername: druid + loginPassword: druid + # sharding: + # default-data-source-name: default-datasource + # #需要拆分的表,可以设置多个 在 li_order 级别即可 + # tables: + # #需要进行分表的逻辑表名 + # li_order: + # #实际的表结点,下面代表的是li_order_为开头的所有表,如果能确定表的范围例如按月份分表,这里的写法是data2020.li_order_$->{2020..2021}_$->{01..12} 表示例如 li_order_2020_01 li_order_2020_03 li_order_2021_01 + # actual-data-nodes: data2020.li_order_$->{2019..2021}_$->{01..12} + # table-strategy: + # # 分表策略,根据创建日期 + # standard: + # sharding-column: create_time + # #分表策略 + # precise-algorithm-class-name: cn.lili.mybatis.sharding.CreateTimeShardingTableAlgorithm + # #范围查询实现 + # range-algorithm-class-name: cn.lili.mybatis.sharding.CreateTimeShardingTableAlgorithm + props: + #是否打印逻辑SQL语句和实际SQL语句,建议调试时打印,在生产环境关闭 + sql: + show: true +lili: + #多线程配置 + thread: + corePoolSize: 5 + maxPoolSize: 50 + queueCapacity: 50 + data: + elasticsearch: + cluster-name: elasticsearch + cluster-nodes: 127.0.0.1:9200 + index: + number-of-replicas: 0 + number-of-shards: 3 + index-prefix: lili + schema: http + # account: + # username: elastic + # password: LiLiShopES +# Swagger界面内容配置 +swagger: + title: lili API接口文档 + description: lili Api Documentation + version: 1.0.0 + termsOfServiceUrl: https://pickmall.cn + contact: + name: lili + url: https://pickmall.cn + email: admin@pickmall.com diff --git a/src/main/resources/templates/chatroom.html b/src/main/resources/templates/chatroom.html deleted file mode 100644 index 7681bc2bcc3ccfe3441ef4369f28e5d19b7584fb..0000000000000000000000000000000000000000 --- a/src/main/resources/templates/chatroom.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - 聊天室 - - - - - - -
-
-
-
-
-

当前登录用户

-
-
-
- -
-
-
-
-
-

当前在线的其他用户

-
-
-
-
-
-
-
-
-
-
-

-
-
-
- -
-
- -
-
-
-
-
- - - -