├── .gitattributes ├── .gitignore ├── .mvn └── wrapper │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml ├── screenshot ├── chatroom.jpg ├── githubpage.jpg ├── newChatRoom.PNG └── xpllyn1.png └── src ├── main ├── java │ └── com │ │ └── xpllyn │ │ ├── XpllynApplication.java │ │ ├── configurer │ │ ├── AppContext.java │ │ ├── LoginInterceptor.java │ │ ├── MyShiroRealm.java │ │ ├── MyWebConfigurer.java │ │ ├── RedisConfig.java │ │ ├── ShiroConfig.java │ │ └── WebMvcConfigurer.java │ │ ├── controller │ │ ├── ChatRoomController.java │ │ ├── GitHubSearchController.java │ │ ├── HomeController.java │ │ ├── LoginController.java │ │ └── MessageController.java │ │ ├── im │ │ ├── handler │ │ │ ├── HttpRequestHandler.java │ │ │ └── WebSocketServerHandler.java │ │ └── server │ │ │ └── NettyServer.java │ │ ├── mapper │ │ ├── ChatMapper.java │ │ ├── GroupMapper.java │ │ ├── MessageMapper.java │ │ ├── ReadMessageMapper.java │ │ └── UserMapper.java │ │ ├── pojo │ │ ├── Blog.java │ │ ├── Book.java │ │ ├── ChatMessage.java │ │ ├── GitHubRepository.java │ │ ├── GitHubRepositoryOwner.java │ │ ├── Group.java │ │ ├── GroupMessage.java │ │ ├── Message.java │ │ ├── ReadMessage.java │ │ └── User.java │ │ ├── service │ │ ├── IChatService.java │ │ ├── IGroupService.java │ │ ├── IMRedisService.java │ │ ├── IMessageService.java │ │ ├── IReadMessageService.java │ │ ├── IUserService.java │ │ └── impl │ │ │ ├── ChatService.java │ │ │ ├── GroupService.java │ │ │ ├── MessageService.java │ │ │ ├── ReadMessageService.java │ │ │ └── UserService.java │ │ └── utils │ │ ├── BlogUtils.java │ │ ├── BookUtils.java │ │ ├── EncryptionUtils.java │ │ ├── MessageInfoUtils.java │ │ ├── githubpageutil │ │ └── SearchUtil.java │ │ ├── im │ │ ├── ChatType.java │ │ ├── Constant.java │ │ ├── IMRedisUtils.java │ │ └── ResponseJson.java │ │ └── schedule │ │ └── ChatMessageSaveTask.java └── resources │ ├── logback-spring.xml │ ├── static │ ├── bootstrap-3.3.7-dist │ │ ├── css │ │ │ ├── bootstrap-theme.css │ │ │ ├── bootstrap-theme.css.map │ │ │ ├── bootstrap-theme.min.css │ │ │ ├── bootstrap-theme.min.css.map │ │ │ ├── bootstrap.css │ │ │ ├── bootstrap.css.map │ │ │ ├── bootstrap.min.css │ │ │ └── bootstrap.min.css.map │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ └── glyphicons-halflings-regular.woff2 │ │ └── js │ │ │ ├── bootstrap.js │ │ │ ├── bootstrap.min.js │ │ │ └── npm.js │ ├── chat │ │ ├── css │ │ │ ├── reset.min.css │ │ │ └── style.css │ │ ├── img │ │ │ ├── 1.jpg │ │ │ ├── 2.jpg │ │ │ ├── 3.jpg │ │ │ ├── 4.jpg │ │ │ ├── 5.jpg │ │ │ ├── 6.jpg │ │ │ ├── attachment.png │ │ │ ├── groupicon.jpg │ │ │ ├── image.jpg │ │ │ ├── name-type.png │ │ │ ├── send.png │ │ │ └── smiley.png │ │ └── js │ │ │ ├── chatroom.js │ │ │ └── index.js │ ├── css │ │ ├── LoginCSS.css │ │ ├── chatroom.css │ │ ├── githubPageSearch.css │ │ └── homepage.css │ ├── font-awesome-4.7.0 │ │ ├── HELP-US-OUT.txt │ │ ├── css │ │ │ ├── font-awesome.css │ │ │ └── font-awesome.min.css │ │ ├── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ │ ├── less │ │ │ ├── animated.less │ │ │ ├── bordered-pulled.less │ │ │ ├── core.less │ │ │ ├── fixed-width.less │ │ │ ├── font-awesome.less │ │ │ ├── icons.less │ │ │ ├── larger.less │ │ │ ├── list.less │ │ │ ├── mixins.less │ │ │ ├── path.less │ │ │ ├── rotated-flipped.less │ │ │ ├── screen-reader.less │ │ │ ├── stacked.less │ │ │ └── variables.less │ │ └── scss │ │ │ ├── _animated.scss │ │ │ ├── _bordered-pulled.scss │ │ │ ├── _core.scss │ │ │ ├── _fixed-width.scss │ │ │ ├── _icons.scss │ │ │ ├── _larger.scss │ │ │ ├── _list.scss │ │ │ ├── _mixins.scss │ │ │ ├── _path.scss │ │ │ ├── _rotated-flipped.scss │ │ │ ├── _screen-reader.scss │ │ │ ├── _stacked.scss │ │ │ ├── _variables.scss │ │ │ └── font-awesome.scss │ ├── images │ │ ├── bg.jpg │ │ ├── bg2.jpg │ │ ├── bg3.jpg │ │ ├── bg4.jpg │ │ ├── bg5.jpg │ │ ├── githubpagesearch.png │ │ ├── githubpagesearch_25.png │ │ ├── githubpagesearch_40.png │ │ ├── githubpagesearchbg.jpg │ │ ├── icon.jpg │ │ ├── icon150.jpg │ │ ├── lynbg.jpg │ │ ├── lynbg1.jpg │ │ ├── newtitle.png │ │ ├── pic1.jpg │ │ ├── pic2.jpg │ │ ├── pic5.jpg │ │ ├── title.png │ │ ├── title1.png │ │ └── title150_70.png │ └── js │ │ ├── JQuery │ │ └── jquery-3.4.1.min.js │ │ ├── functions.js │ │ └── jq-paginator.js │ └── templates │ ├── chat_room.html │ ├── commons │ ├── footer.html │ └── navigation.html │ ├── github_page_search.html │ ├── homepage.html │ ├── login.html │ └── userpage.html └── test └── java └── com └── xpllyn └── XpllynApplicationTests.java /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | 33 | ### my configurations ### 34 | book 35 | application.properties 36 | logs/ -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | import java.io.File; 21 | import java.io.FileInputStream; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.net.URL; 25 | import java.nio.channels.Channels; 26 | import java.nio.channels.ReadableByteChannel; 27 | import java.util.Properties; 28 | 29 | public class MavenWrapperDownloader { 30 | 31 | /** 32 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 33 | */ 34 | private static final String DEFAULT_DOWNLOAD_URL = 35 | "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; 36 | 37 | /** 38 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 39 | * use instead of the default one. 40 | */ 41 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 42 | ".mvn/wrapper/maven-wrapper.properties"; 43 | 44 | /** 45 | * Path where the maven-wrapper.jar will be saved to. 46 | */ 47 | private static final String MAVEN_WRAPPER_JAR_PATH = 48 | ".mvn/wrapper/maven-wrapper.jar"; 49 | 50 | /** 51 | * Name of the property which should be used to override the default download url for the wrapper. 52 | */ 53 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 54 | 55 | public static void main(String args[]) { 56 | System.out.println("- Downloader started"); 57 | File baseDirectory = new File(args[0]); 58 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 59 | 60 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 61 | // wrapperUrl parameter. 62 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 63 | String url = DEFAULT_DOWNLOAD_URL; 64 | if (mavenWrapperPropertyFile.exists()) { 65 | FileInputStream mavenWrapperPropertyFileInputStream = null; 66 | try { 67 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 68 | Properties mavenWrapperProperties = new Properties(); 69 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 70 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 71 | } catch (IOException e) { 72 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 73 | } finally { 74 | try { 75 | if (mavenWrapperPropertyFileInputStream != null) { 76 | mavenWrapperPropertyFileInputStream.close(); 77 | } 78 | } catch (IOException e) { 79 | // Ignore ... 80 | } 81 | } 82 | } 83 | System.out.println("- Downloading from: : " + url); 84 | 85 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 86 | if (!outputFile.getParentFile().exists()) { 87 | if (!outputFile.getParentFile().mkdirs()) { 88 | System.out.println( 89 | "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 90 | } 91 | } 92 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 93 | try { 94 | downloadFileFromURL(url, outputFile); 95 | System.out.println("Done"); 96 | System.exit(0); 97 | } catch (Throwable e) { 98 | System.out.println("- Error downloading"); 99 | e.printStackTrace(); 100 | System.exit(1); 101 | } 102 | } 103 | 104 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 105 | URL website = new URL(urlString); 106 | ReadableByteChannel rbc; 107 | rbc = Channels.newChannel(website.openStream()); 108 | FileOutputStream fos = new FileOutputStream(destination); 109 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 110 | fos.close(); 111 | rbc.close(); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xpllyn 2 | 我的个人小站~ 3 | 博客和资源将持续更新,敬请关注~ 4 | 传送门:http://www.xpllyn.com 5 | --- 6 | 使用SpringBoot搭建,集合了博客、书籍下载、留言、GitHubPage查询、Netty网页聊天室等功能。 7 | 其中GitHubPage查询工具使用了GitHub的API,聊天室使用Netty+WebSocket搭建服务,使用Shiro安全框架实现身份验证/登录,采用MySQL作为网站的数据库,使用Redis作为缓存,并用其作为辅助实现聊天记录的保存。另外 8 | ,每当用户点击打开了好友的聊天窗口,都会向好友发送一条已读回执,用于更新好友那边的已读未读。同时已读回执会保存在Redis中,和聊天记录一起凌晨三点持久化到MySQL中。详细逻辑见博客https://xiepl1997.github.io/。 9 | 总的来说实现了群聊、单聊、查询用户、添加好友、聊天记录、聊天消息已读未读等功能。 10 | 11 | 个人网站中目前已开发的功能: 12 | * 资源下载 13 | * 个人博客 14 | * 访客留言 15 | * GitHubPage博客搜索 16 | * 利用了Github的开放API实现了GitHubPage博客搜索工具,给使用者提供了一种,能够找到并访问那些没有被常用搜索引擎收录的优质个人博客和站点的方式。 17 | * 聊天室(Netty + WebSocket + Redis)http://www.xpllyn.com/chatroom 18 | * Shiro登录认证 19 | * 群聊 20 | * 私聊 21 | * 加好友 22 | * 账号互踢机制 23 | * 聊天记录 24 | * 聊天消息已读未读功能 25 | 26 | 正在开发的功能: 27 | * 资源共享 28 | 29 | 主页截图: 30 | ![image](https://raw.githubusercontent.com/xiepl1997/xpllyn/master/screenshot/xpllyn1.png) 31 | 32 | github page搜索截图: 33 | ![image](https://raw.githubusercontent.com/xiepl1997/xpllyn/master/screenshot/githubpage.jpg) 34 | 35 | 聊天室截图: 36 | ![image](https://raw.githubusercontent.com/xiepl1997/xpllyn/master/screenshot/newChatRoom.PNG) -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | ########################################################################################## 204 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 205 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 206 | ########################################################################################## 207 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 208 | if [ "$MVNW_VERBOSE" = true ]; then 209 | echo "Found .mvn/wrapper/maven-wrapper.jar" 210 | fi 211 | else 212 | if [ "$MVNW_VERBOSE" = true ]; then 213 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 214 | fi 215 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 216 | while IFS="=" read key value; do 217 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 218 | esac 219 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 220 | if [ "$MVNW_VERBOSE" = true ]; then 221 | echo "Downloading from: $jarUrl" 222 | fi 223 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 224 | 225 | if command -v wget > /dev/null; then 226 | if [ "$MVNW_VERBOSE" = true ]; then 227 | echo "Found wget ... using wget" 228 | fi 229 | wget "$jarUrl" -O "$wrapperJarPath" 230 | elif command -v curl > /dev/null; then 231 | if [ "$MVNW_VERBOSE" = true ]; then 232 | echo "Found curl ... using curl" 233 | fi 234 | curl -o "$wrapperJarPath" "$jarUrl" 235 | else 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Falling back to using Java to download" 238 | fi 239 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 240 | if [ -e "$javaClass" ]; then 241 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 242 | if [ "$MVNW_VERBOSE" = true ]; then 243 | echo " - Compiling MavenWrapperDownloader.java ..." 244 | fi 245 | # Compiling the Java class 246 | ("$JAVA_HOME/bin/javac" "$javaClass") 247 | fi 248 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 249 | # Running the downloader 250 | if [ "$MVNW_VERBOSE" = true ]; then 251 | echo " - Running MavenWrapperDownloader.java ..." 252 | fi 253 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 254 | fi 255 | fi 256 | fi 257 | fi 258 | ########################################################################################## 259 | # End of extension 260 | ########################################################################################## 261 | 262 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 263 | if [ "$MVNW_VERBOSE" = true ]; then 264 | echo $MAVEN_PROJECTBASEDIR 265 | fi 266 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 267 | 268 | # For Cygwin, switch paths to Windows format before running java 269 | if $cygwin; then 270 | [ -n "$M2_HOME" ] && 271 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 272 | [ -n "$JAVA_HOME" ] && 273 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 274 | [ -n "$CLASSPATH" ] && 275 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 276 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 277 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 278 | fi 279 | 280 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 281 | 282 | exec "$JAVACMD" \ 283 | $MAVEN_OPTS \ 284 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 285 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 286 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 287 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 124 | FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( 125 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | echo Found %WRAPPER_JAR% 132 | ) else ( 133 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 134 | echo Downloading from: %DOWNLOAD_URL% 135 | powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" 136 | echo Finished downloading %WRAPPER_JAR% 137 | ) 138 | @REM End of extension 139 | 140 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 141 | if ERRORLEVEL 1 goto error 142 | goto end 143 | 144 | :error 145 | set ERROR_CODE=1 146 | 147 | :end 148 | @endlocal & set ERROR_CODE=%ERROR_CODE% 149 | 150 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 151 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 152 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 153 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 154 | :skipRcPost 155 | 156 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 157 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 158 | 159 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 160 | 161 | exit /B %ERROR_CODE% 162 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.6.RELEASE 9 | 10 | 11 | com 12 | xpllyn 13 | 0.0.1-SNAPSHOT 14 | war 15 | xpllyn 16 | Demo project for Spring Boot 17 | 18 | 19 | 1.8 20 | 21 | 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-jdbc 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-thymeleaf 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-web 35 | 36 | 37 | org.mybatis.spring.boot 38 | mybatis-spring-boot-starter 39 | 2.1.0 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-devtools 45 | runtime 46 | true 47 | 48 | 49 | mysql 50 | mysql-connector-java 51 | runtime 52 | 53 | 54 | org.springframework.boot 55 | spring-boot-starter-test 56 | test 57 | 58 | 59 | org.springframework.boot 60 | spring-boot-starter-data-redis 61 | 62 | 63 | com.alibaba 64 | fastjson 65 | 1.2.73 66 | 67 | 68 | org.apache.shiro 69 | shiro-spring 70 | 1.6.0 71 | 72 | 73 | io.netty 74 | netty-all 75 | 4.1.2.Final 76 | 77 | 78 | org.apache.commons 79 | commons-pool2 80 | 81 | 82 | org.projectlombok 83 | lombok 84 | true 85 | 86 | 87 | 88 | 89 | 90 | 91 | org.springframework.boot 92 | spring-boot-maven-plugin 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /screenshot/chatroom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/screenshot/chatroom.jpg -------------------------------------------------------------------------------- /screenshot/githubpage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/screenshot/githubpage.jpg -------------------------------------------------------------------------------- /screenshot/newChatRoom.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/screenshot/newChatRoom.PNG -------------------------------------------------------------------------------- /screenshot/xpllyn1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/screenshot/xpllyn1.png -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/XpllynApplication.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.boot.builder.SpringApplicationBuilder; 7 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 8 | import org.springframework.cache.annotation.EnableCaching; 9 | 10 | @SpringBootApplication 11 | @MapperScan("com.xpllyn.mapper") 12 | @EnableCaching 13 | public class XpllynApplication extends SpringBootServletInitializer{ 14 | 15 | @Override 16 | protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { 17 | return builder.sources(XpllynApplication.class); 18 | } 19 | 20 | public static void main(String[] args) { 21 | SpringApplication.run(XpllynApplication.class, args); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/configurer/AppContext.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.configurer; 2 | 3 | import com.xpllyn.im.server.NettyServer; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.annotation.Scope; 6 | import org.springframework.stereotype.Component; 7 | 8 | import javax.annotation.PostConstruct; 9 | import javax.annotation.PreDestroy; 10 | 11 | @Component 12 | @Scope("singleton") 13 | public class AppContext { 14 | @Autowired 15 | private NettyServer nettyServer; 16 | 17 | private Thread nettyThread; 18 | 19 | /** 20 | * 该方法在Constructor,Autowired之后执行 21 | * 启动netty websocket服务器 22 | */ 23 | @PostConstruct 24 | public void init() { 25 | nettyThread = new Thread(nettyServer); 26 | nettyThread.start(); 27 | } 28 | 29 | @SuppressWarnings("deprecation") 30 | @PreDestroy 31 | public void close() { 32 | nettyServer.close(); 33 | nettyThread.stop(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/configurer/LoginInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.configurer; 2 | 3 | import org.springframework.stereotype.Component; 4 | import org.springframework.web.servlet.HandlerInterceptor; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | 9 | /** 10 | * 拦截器 11 | * 2020-6-26 Peiliang Xie 12 | */ 13 | @Component 14 | public class LoginInterceptor implements HandlerInterceptor { 15 | 16 | /** 17 | * 在controller调用之前进行调用 18 | */ 19 | // @Override 20 | // public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 21 | // response.sendRedirect(request.getContextPath() + "/"); 22 | // return false; 23 | // } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/configurer/MyShiroRealm.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.configurer; 2 | 3 | import com.xpllyn.pojo.User; 4 | import com.xpllyn.service.impl.UserService; 5 | import com.xpllyn.utils.im.Constant; 6 | import com.xpllyn.utils.EncryptionUtils; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import org.apache.shiro.SecurityUtils; 9 | import org.apache.shiro.authc.AuthenticationException; 10 | import org.apache.shiro.authc.AuthenticationInfo; 11 | import org.apache.shiro.authc.AuthenticationToken; 12 | import org.apache.shiro.authc.SimpleAuthenticationInfo; 13 | import org.apache.shiro.authz.AuthorizationInfo; 14 | import org.apache.shiro.authz.SimpleAuthorizationInfo; 15 | import org.apache.shiro.realm.AuthorizingRealm; 16 | import org.apache.shiro.session.Session; 17 | import org.apache.shiro.subject.PrincipalCollection; 18 | import org.apache.shiro.subject.SimplePrincipalCollection; 19 | import org.apache.shiro.subject.support.DefaultSubjectContext; 20 | import org.apache.shiro.util.ByteSource; 21 | import org.apache.shiro.web.mgt.DefaultWebSecurityManager; 22 | import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; 23 | import org.springframework.beans.factory.annotation.Autowired; 24 | 25 | import java.util.Collection; 26 | 27 | public class MyShiroRealm extends AuthorizingRealm { 28 | 29 | @Autowired 30 | private UserService userService; 31 | 32 | /** 33 | * 角色权限和对应权限添加 34 | * @param principalCollection 35 | * @return 36 | */ 37 | @Override 38 | protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { 39 | SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); 40 | return authorizationInfo; 41 | } 42 | 43 | /** 44 | * 用户认证 45 | * @param authenticationToken 46 | * @return 47 | * @throws AuthenticationException 48 | */ 49 | @Override 50 | protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { 51 | // 加这一步的目的是在post请求的时候会先进行认证,然后再到请求 52 | if (authenticationToken.getPrincipal() == null) { 53 | return null; 54 | } 55 | // 获取用户信息 56 | String email = authenticationToken.getPrincipal().toString(); 57 | User user = userService.findByEmail(email); 58 | User tempUser = null; 59 | if (user == null) { 60 | // 这里返回后会报出对应异常 61 | return null; 62 | } else { 63 | // 处理一个账号异地登录的问题,后期用户数量上来之后需要做优化 64 | DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager(); 65 | DefaultWebSessionManager sessionManager = (DefaultWebSessionManager) securityManager.getSessionManager(); 66 | // 获取当前已登录的session列表 67 | Collection sessions = sessionManager.getSessionDAO().getActiveSessions(); 68 | for (Session session : sessions) { 69 | //查找是否有当前登录账户的记录,有就清除该用户以前登录时保存的session 70 | Object obj = session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY); 71 | if (obj != null) { 72 | if (obj instanceof SimplePrincipalCollection) { 73 | // 强转 74 | SimplePrincipalCollection spc = (SimplePrincipalCollection) obj; 75 | tempUser = (User) spc.getPrimaryPrincipal(); 76 | if (user.getId() == tempUser.getId()) { 77 | ctxDisconnect(String.valueOf(user.getId())); 78 | sessionManager.getSessionDAO().delete(session); 79 | } 80 | } 81 | } 82 | } 83 | 84 | } 85 | 86 | SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user, user.getUser_pw(), ByteSource.Util.bytes(EncryptionUtils.salt), getName()); 87 | return simpleAuthenticationInfo; 88 | } 89 | 90 | // 发送一条关闭websocket连接的消息,让客户端断开连接 91 | public void ctxDisconnect(String id) { 92 | ChannelHandlerContext ctx = Constant.onlineUser.get(id); 93 | if (ctx != null) { 94 | ctx.channel().disconnect(); 95 | ctx.channel().close(); 96 | ctx.disconnect(); 97 | ctx.close(); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/configurer/MyWebConfigurer.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.configurer; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 5 | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 7 | 8 | /** 9 | * 过滤器 10 | * 2020-6-26 Peiliang Xie 11 | */ 12 | @Configuration 13 | public class MyWebConfigurer implements WebMvcConfigurer { 14 | 15 | /** 16 | * 配置视图映射 17 | * @param registry 18 | */ 19 | @Override 20 | public void addViewControllers(ViewControllerRegistry registry) { 21 | // registry.addViewController("/login.html").setViewName("login"); 22 | registry.addViewController("/homepage.html").setViewName("homepage"); 23 | } 24 | // 25 | // /** 26 | // * 配置拦截路径,拦截controller中的请求路径 27 | // * @param registry 28 | // */ 29 | // @Override 30 | // public void addInterceptors(InterceptorRegistry registry) { 31 | // registry.addInterceptor(new LoginInterceptor()) 32 | // .addPathPatterns("/userInfo") 33 | // .excludePathPatterns("/", "/login", "/login/**", "/register", "static/**", "bootstrap-3.3.7-dist/**", 34 | // "css/**", "font-awesome-4.7.0/**", "images/**", "js/**", "templates/**"); 35 | // } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/configurer/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.configurer; 2 | 3 | import org.springframework.cache.annotation.CachingConfigurerSupport; 4 | import org.springframework.cache.annotation.EnableCaching; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; 8 | import org.springframework.data.redis.core.RedisTemplate; 9 | import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; 10 | import org.springframework.data.redis.serializer.RedisSerializer; 11 | import org.springframework.data.redis.serializer.StringRedisSerializer; 12 | 13 | @EnableCaching 14 | @Configuration 15 | public class RedisConfig extends CachingConfigurerSupport { 16 | 17 | @Bean 18 | public RedisTemplate redisTemplate(LettuceConnectionFactory connectionFactory) { 19 | RedisTemplate redisTemplate = new RedisTemplate<>(); 20 | redisTemplate.setConnectionFactory(connectionFactory); 21 | redisTemplate.setKeySerializer(new StringRedisSerializer()); 22 | redisTemplate.setValueSerializer(valueSerializer()); 23 | redisTemplate.setHashKeySerializer(new StringRedisSerializer()); 24 | redisTemplate.setHashValueSerializer(valueSerializer()); 25 | return redisTemplate; 26 | } 27 | 28 | /** 29 | * 使用jackson序列化器 30 | * @return 31 | */ 32 | private RedisSerializer valueSerializer() { 33 | return new GenericJackson2JsonRedisSerializer(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/configurer/ShiroConfig.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.configurer; 2 | 3 | import org.apache.shiro.authc.credential.HashedCredentialsMatcher; 4 | import org.apache.shiro.codec.Base64; 5 | import org.apache.shiro.mgt.SecurityManager; 6 | import org.apache.shiro.spring.web.ShiroFilterFactoryBean; 7 | import org.apache.shiro.util.ThreadContext; 8 | import org.apache.shiro.web.mgt.CookieRememberMeManager; 9 | import org.apache.shiro.web.mgt.DefaultWebSecurityManager; 10 | import org.apache.shiro.web.servlet.SimpleCookie; 11 | import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.context.annotation.Configuration; 14 | 15 | import javax.servlet.Filter; 16 | import java.util.LinkedHashMap; 17 | import java.util.Map; 18 | 19 | @Configuration 20 | public class ShiroConfig { 21 | 22 | @Bean 23 | public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { 24 | ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); 25 | shiroFilterFactoryBean.setSecurityManager(securityManager); 26 | 27 | // 拦截器 28 | // 1.一个URL可以设置多个Filter,用逗号隔开 29 | // 2.当设置多个过滤器时,全部验证通过,才视为通过 30 | // 3.部分过滤器可以指定参数,如perms,roles 31 | // 4.anon表示可以匿名访问不过滤;authc表示所有url都必须通过认证才可以访问;user表示记住我或者通过认证可以访问 32 | Map filterChainDefinitionMap = new LinkedHashMap<>(); 33 | filterChainDefinitionMap.put("static/**", "anon"); 34 | filterChainDefinitionMap.put("/bootstrap-3.3.7-dist/**", "anon"); 35 | filterChainDefinitionMap.put("/chat/**", "anon"); 36 | filterChainDefinitionMap.put("/css/**", "anon"); 37 | filterChainDefinitionMap.put("/font-awesome-4.7.0/**", "anon"); 38 | filterChainDefinitionMap.put("/images/**", "anon"); 39 | filterChainDefinitionMap.put("/js/**", "anon"); 40 | filterChainDefinitionMap.put("/homepage", "anon"); 41 | filterChainDefinitionMap.put("/homepage.html", "anon"); 42 | filterChainDefinitionMap.put("/book/**", "anon"); 43 | filterChainDefinitionMap.put("/GitHubPageSearch/**", "anon"); 44 | filterChainDefinitionMap.put("/", "anon"); 45 | filterChainDefinitionMap.put("/login", "anon"); 46 | filterChainDefinitionMap.put("/loginpage", "anon"); 47 | filterChainDefinitionMap.put("/insertMessage", "anon"); 48 | filterChainDefinitionMap.put("/getAllMessages", "anon"); 49 | filterChainDefinitionMap.put("/register", "anon"); 50 | filterChainDefinitionMap.put("/user/**", "anon"); 51 | filterChainDefinitionMap.put("/chatroom/**", "user"); 52 | // 配置登出过滤器 53 | filterChainDefinitionMap.put("/logout", "logout"); 54 | // 过滤链定义,从上到下顺序执行,一般将/**放在最下面 55 | // authc:所有url都必须认证通过才可以访问;anon:所有url都可以匿名访问 56 | filterChainDefinitionMap.put("/**", "user"); 57 | 58 | // 如果不设置,会自动寻找web工程目录下的index 59 | shiroFilterFactoryBean.setLoginUrl("/loginpage"); 60 | shiroFilterFactoryBean.setUnauthorizedUrl("/loginpage"); 61 | shiroFilterFactoryBean.setSuccessUrl("/chatroom"); 62 | 63 | shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); 64 | return shiroFilterFactoryBean; 65 | } 66 | 67 | /** 68 | * 凭证匹配器,密码校验给予shiro的SimpleAuthenticationInfo处理 69 | * @return 70 | */ 71 | @Bean 72 | public HashedCredentialsMatcher hashedCredentialsMatcher() { 73 | HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); 74 | hashedCredentialsMatcher.setHashAlgorithmName("md5"); //散列算法 75 | hashedCredentialsMatcher.setHashIterations(2); // 散列次数,相当于md5(md5("")) 76 | return hashedCredentialsMatcher; 77 | } 78 | 79 | @Bean 80 | public MyShiroRealm myShiroRealm() { 81 | MyShiroRealm myShiroRealm = new MyShiroRealm(); 82 | myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); 83 | return myShiroRealm; 84 | } 85 | 86 | @Bean 87 | public SecurityManager securityManager() { 88 | DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); 89 | securityManager.setSessionManager(sessionManager()); 90 | securityManager.setRealm(myShiroRealm()); 91 | //将cookie管理器交给SecurityManager进行管理 92 | securityManager.setRememberMeManager(rememberMeManager()); 93 | ThreadContext.bind(securityManager); 94 | return securityManager; 95 | } 96 | 97 | @Bean 98 | public DefaultWebSessionManager sessionManager() { 99 | DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); 100 | sessionManager.setSessionIdUrlRewritingEnabled(false); 101 | return sessionManager; 102 | } 103 | 104 | /** 105 | * 设置cookie 106 | * @return 107 | */ 108 | @Bean 109 | public SimpleCookie rememberMeCookie() { 110 | //这个参数是cookie的名称,对应前端的checkbox的name=rememberMe 111 | SimpleCookie cookie = new SimpleCookie("rememberMe"); 112 | //如果httyOnly设置为true,则客户端不会暴露给客户端脚本代码,使用HttpOnly cookie有助于减少某些类型的跨站点脚本攻击; 113 | cookie.setHttpOnly(true); 114 | //有效期为两天 115 | cookie.setMaxAge(2*24*60*60); 116 | return cookie; 117 | } 118 | 119 | /** 120 | * cookie管理对象,记住我的功能 121 | * @return 122 | */ 123 | @Bean 124 | public CookieRememberMeManager rememberMeManager() { 125 | CookieRememberMeManager manager = new CookieRememberMeManager(); 126 | manager.setCookie(rememberMeCookie()); 127 | //rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位) 128 | manager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag==")); 129 | return manager; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/configurer/WebMvcConfigurer.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.configurer; 2 | 3 | import com.alibaba.fastjson.serializer.SerializerFeature; 4 | import com.alibaba.fastjson.support.config.FastJsonConfig; 5 | import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.http.converter.HttpMessageConverter; 8 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 9 | 10 | import java.util.List; 11 | 12 | @Configuration 13 | public class WebMvcConfigurer extends WebMvcConfigurerAdapter { 14 | @Override 15 | public void configureMessageConverters(List> converters) { 16 | FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); 17 | FastJsonConfig fastJsonConfig = new FastJsonConfig(); 18 | fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat); 19 | converter.setFastJsonConfig(fastJsonConfig); 20 | converters.add(0, converter); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/controller/GitHubSearchController.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.controller; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONArray; 5 | import com.alibaba.fastjson.JSONObject; 6 | import com.xpllyn.pojo.GitHubRepository; 7 | import com.xpllyn.utils.githubpageutil.SearchUtil; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 11 | import org.springframework.stereotype.Controller; 12 | import org.springframework.web.bind.annotation.PathVariable; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RequestParam; 15 | import org.springframework.web.bind.annotation.ResponseBody; 16 | import org.springframework.web.servlet.ModelAndView; 17 | 18 | import javax.servlet.http.Cookie; 19 | import java.io.IOException; 20 | import java.util.*; 21 | 22 | @Controller 23 | @RequestMapping("/GitHubPageSearch") 24 | @Slf4j 25 | public class GitHubSearchController { 26 | 27 | @Autowired 28 | SearchUtil searchUtil = null; 29 | 30 | @RequestMapping("") 31 | public ModelAndView gotoGitHubPageSearch() { 32 | log.info("【GitHubPage Search】 用户进入。"); 33 | ModelAndView mv = new ModelAndView(); 34 | mv.addObject("tab_index", 4); 35 | mv.setViewName("github_page_search"); 36 | return mv; 37 | } 38 | 39 | @RequestMapping("/search") 40 | @ResponseBody 41 | public Map search(@RequestParam("q") String q, 42 | @RequestParam("page") int page, 43 | @RequestParam("otherConditions") String otherConditions) { 44 | JSONObject jsonObject = searchUtil.readJsonFromUrl(q, page, otherConditions); 45 | 46 | // 获取所有的GitHubProject 47 | JSONArray repositoryArray = jsonObject.getJSONArray("items"); 48 | int repository_count = jsonObject.getInteger("total_count"); 49 | List repositoryList = JSON.parseArray(repositoryArray.toJSONString(), GitHubRepository.class); 50 | 51 | for (int i = 0; i < repositoryList.size(); i++) { 52 | String name = repositoryList.get(i).getName(); 53 | String description = repositoryList.get(i).getDescription(); 54 | if (name.length() >= 9 && name.substring(name.length() - 9).equals("github.io")) { 55 | repositoryList.get(i).setIo_url("http://" + name); 56 | } 57 | if (description != null && description.length() > 200) { 58 | repositoryList.get(i).setDescription(description.substring(0, 200) + "..."); 59 | } 60 | 61 | } 62 | 63 | // // 创建cookie,存储当前查询 64 | // Cookie cookie = new Cookie("q_history", q); 65 | // //保存一天 66 | // cookie.setMaxAge(24*60*60); 67 | 68 | // 总页数,每页10个结果 69 | int page_count = (int)Math.ceil(repository_count / 10.0); 70 | 71 | Map map = new HashMap<>(); 72 | map.put("repositoryList", repositoryList); 73 | map.put("repository_count", repository_count); 74 | map.put("page_count", page_count); 75 | 76 | log.info("【GitHubPage Search】 用户发起查询:" + q); 77 | 78 | return map; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/controller/HomeController.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.controller; 2 | 3 | import com.xpllyn.pojo.Message; 4 | import com.xpllyn.service.impl.MessageService; 5 | import com.xpllyn.utils.BlogUtils; 6 | import com.xpllyn.utils.BookUtils; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.servlet.ModelAndView; 12 | 13 | import javax.servlet.http.HttpServletRequest; 14 | import java.io.FileNotFoundException; 15 | import java.util.List; 16 | 17 | @Controller 18 | @Slf4j 19 | public class HomeController { 20 | 21 | @Autowired 22 | BookUtils bookUtil = null; 23 | 24 | @Autowired 25 | BlogUtils blogUtils = null; 26 | 27 | @Autowired 28 | MessageService messageService = null; 29 | 30 | //首页 31 | @RequestMapping("/") 32 | public ModelAndView home(HttpServletRequest request){ 33 | 34 | log.info("a new visitor coming."); 35 | 36 | ModelAndView mv = new ModelAndView(); 37 | 38 | //获取书本 39 | // List bookList = null; 40 | List bookList = null; 41 | try { 42 | // bookList = bookUtil.getFileName("C:\\Users\\xiepl\\Desktop"); 43 | bookList = bookUtil.getFileName("/opt/book"); 44 | } catch (FileNotFoundException e) { 45 | e.printStackTrace(); 46 | } 47 | 48 | //获取最近文章标题和发表时间和url 49 | List blogList = null; 50 | blogList = blogUtils.getBlogInfo(); 51 | 52 | //若blog数量小于10,则显示blog实际的篇数,否则显示10篇 53 | int blogCount = 0; 54 | if(blogList.size() < 10){ 55 | blogCount = blogList.size(); 56 | } 57 | else{ 58 | blogCount = 10; 59 | } 60 | 61 | //获取最近10条评论 62 | List messageList = messageService.getAllMessages(); 63 | int messageCount = messageList.size(); 64 | if(messageCount > 10){ 65 | messageCount = 10; 66 | } 67 | 68 | mv.addObject("blogCount",blogCount); 69 | mv.addObject("blogList",blogList); 70 | mv.addObject("bookList", bookList); 71 | mv.addObject("messageList",messageList); 72 | mv.addObject("messageCount" ,messageCount); 73 | mv.addObject("tab_index", 0); 74 | mv.setViewName("homepage"); 75 | return mv; 76 | } 77 | 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/controller/LoginController.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 登陆控制 3 | * author:Xie Peiliang 4 | * date:2020/2/5 5 | */ 6 | package com.xpllyn.controller; 7 | 8 | import com.xpllyn.service.impl.ChatService; 9 | import com.xpllyn.service.impl.UserService; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.apache.shiro.SecurityUtils; 12 | import org.apache.shiro.authc.IncorrectCredentialsException; 13 | import org.apache.shiro.authc.UnknownAccountException; 14 | import org.apache.shiro.authc.UsernamePasswordToken; 15 | import org.apache.shiro.subject.Subject; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.stereotype.Controller; 18 | import org.springframework.ui.Model; 19 | import org.springframework.web.bind.annotation.RequestMapping; 20 | import org.springframework.web.bind.annotation.RequestParam; 21 | 22 | import javax.servlet.http.HttpServletRequest; 23 | import java.util.Random; 24 | 25 | @Controller 26 | @Slf4j 27 | public class LoginController { 28 | @Autowired 29 | private UserService userService; 30 | 31 | @Autowired 32 | private ChatService chatService; 33 | 34 | @RequestMapping("/loginpage") 35 | public String loginPage(){ 36 | // mv.addObject("tab_index", 5); 37 | // mv.setViewName("login"); 38 | return "login"; 39 | } 40 | 41 | @RequestMapping("/register") 42 | public String register(HttpServletRequest request, 43 | Model model) { 44 | String email = request.getParameter("email"); 45 | String name = request.getParameter("name"); 46 | String pwd = request.getParameter("password"); 47 | String sex = request.getParameter("sex"); 48 | String icon = "chat/img/" + String.valueOf((int)(Math.random() * 6 + 1)) + ".jpg"; 49 | boolean flag = userService.addNewUser(email, name, pwd, sex, icon); 50 | if (!flag) { 51 | model.addAttribute("msg", "该email已经使用!"); 52 | return "login"; 53 | } 54 | 55 | //获取subject 56 | Subject subject = SecurityUtils.getSubject(); 57 | //封装用户数据 58 | UsernamePasswordToken token = new UsernamePasswordToken(email, pwd, false); 59 | subject.login(token); 60 | 61 | log.info("【注册】 新用户注册:" + name); 62 | return "redirect:/chatroom"; 63 | } 64 | 65 | @RequestMapping("/login") 66 | public String login(@RequestParam("email") String email, 67 | @RequestParam("password") String password, 68 | @RequestParam(value = "rememberMe", required = false) boolean rememberMe, 69 | Model model) { 70 | //获取subject 71 | Subject subject = SecurityUtils.getSubject(); 72 | //封装用户数据 73 | UsernamePasswordToken token = new UsernamePasswordToken(email, password, rememberMe); 74 | try { 75 | subject.login(token); 76 | } catch (UnknownAccountException e) { 77 | model.addAttribute("msg", "用户名不存在!"); 78 | return "login"; 79 | } catch (IncorrectCredentialsException e) { 80 | model.addAttribute("msg", "密码错误!"); 81 | return "login"; 82 | } 83 | return "redirect:/chatroom"; 84 | } 85 | 86 | // @RequestMapping("/logout") 87 | // public String logout() { 88 | // // 通知在线好友我已下线 89 | // User user = (User) SecurityUtils.getSubject().getPrincipal(); 90 | // List friends = userService.findFriends(user.getId()); 91 | // List onlineFriendIds = userService.findOnlineFriendIds(friends); 92 | // for (int id : onlineFriendIds) { 93 | // chatService.offlineNotify(user.getId(), Constant.onlineUser.get(id)); 94 | // } 95 | // 96 | // Subject subject = getSubject(request, response); 97 | // try { 98 | // subject.logout(); 99 | // } catch (SessionException e) { 100 | // e.printStackTrace(); 101 | // } 102 | // 103 | // } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/controller/MessageController.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.controller; 2 | 3 | import com.xpllyn.pojo.Message; 4 | import com.xpllyn.service.IMessageService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | import org.springframework.web.bind.annotation.ResponseBody; 11 | 12 | import javax.servlet.http.HttpServletRequest; 13 | import java.util.List; 14 | 15 | /** 16 | * created by xiepl1997 at 2019-8-20 17 | * 获取用户ip地址,将用户评论保存 18 | */ 19 | @Controller 20 | public class MessageController { 21 | 22 | @Autowired 23 | IMessageService messageService; 24 | 25 | @PostMapping("/insertMessage") 26 | @ResponseBody 27 | public List insertMessage(HttpServletRequest request){ 28 | 29 | //获取用户输入留言 30 | String str = request.getParameter("message"); 31 | //得到Message 32 | Message message = messageService.assembleMessageObject(str,null, request); 33 | //将数据插入数据库中 34 | messageService.insertMessage(message); 35 | //重新获取所有的message 36 | List messageList = messageService.getAllMessages(); 37 | //String messageList = JSONArray.fromObject(mList).toString(); 38 | return messageList; 39 | } 40 | 41 | @GetMapping("/getAllMessages") 42 | @ResponseBody 43 | public List getAllMessage(){ 44 | List mList = messageService.getAllMessages(); 45 | return mList; 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/im/handler/HttpRequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.im.handler; 2 | 3 | import com.xpllyn.utils.im.Constant; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.Unpooled; 6 | import io.netty.channel.*; 7 | import io.netty.handler.codec.http.*; 8 | import io.netty.handler.codec.http.websocketx.WebSocketFrame; 9 | import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker; 10 | import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory; 11 | import io.netty.util.CharsetUtil; 12 | import org.springframework.stereotype.Component; 13 | 14 | @Component 15 | @ChannelHandler.Sharable 16 | public class HttpRequestHandler extends SimpleChannelInboundHandler { 17 | 18 | /** 19 | * 读取完连接的消息后,对消息进行处理。 20 | * 这里仅处理HTTP请求,WebSocket请求交给下一个处理器处理。 21 | * @param ctx 22 | * @param o 23 | * @throws Exception 24 | */ 25 | @Override 26 | protected void channelRead0(ChannelHandlerContext ctx, Object o) throws Exception { 27 | if (o instanceof FullHttpRequest) { 28 | handleHttpRequest(ctx, (FullHttpRequest) o); 29 | } else if (o instanceof WebSocketFrame) { 30 | ctx.fireChannelRead(((WebSocketFrame) o).retain()); 31 | } 32 | } 33 | 34 | /** 35 | * 处理http请求,主要是完成http协议到websocket协议的升级 36 | * @param ctx 37 | * @param req 38 | */ 39 | private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) { 40 | if (!req.decoderResult().isSuccess()) { 41 | sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST)); 42 | return; 43 | } 44 | 45 | WebSocketServerHandshakerFactory wsFactory = 46 | new WebSocketServerHandshakerFactory("ws:/" + ctx.channel() + "/websocket", null, false); 47 | WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(req); 48 | Constant.webSocketServerHandshakerMap.put(ctx.channel().id().asLongText(), handshaker); 49 | 50 | if (handshaker == null) { 51 | WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel()); 52 | } else { 53 | handshaker.handshake(ctx.channel(), req); 54 | } 55 | } 56 | 57 | private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, DefaultFullHttpResponse res) { 58 | // 返回应答给客户端 59 | if (res.status().code() != 200) { 60 | ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8); 61 | res.content().writeBytes(buf); 62 | buf.release(); 63 | } 64 | // 如果是非keep-alive,关闭连接 65 | boolean keepAlive = HttpUtil.isKeepAlive(req); 66 | ChannelFuture f = ctx.channel().writeAndFlush(res); 67 | if (!keepAlive) { 68 | f.addListener(ChannelFutureListener.CLOSE); 69 | } 70 | } 71 | 72 | /** 73 | * 异常处理,关闭channel 74 | * @param ctx 75 | * @param cause 76 | * @throws Exception 77 | */ 78 | @Override 79 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 80 | cause.printStackTrace(); 81 | ctx.close(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/im/handler/WebSocketServerHandler.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.im.handler; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.xpllyn.service.impl.ChatService; 5 | import com.xpllyn.utils.im.Constant; 6 | import com.xpllyn.utils.im.ResponseJson; 7 | import io.netty.channel.ChannelHandler; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import io.netty.channel.SimpleChannelInboundHandler; 10 | import io.netty.handler.codec.http.websocketx.*; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.stereotype.Component; 13 | 14 | @Component 15 | @ChannelHandler.Sharable 16 | public class WebSocketServerHandler extends SimpleChannelInboundHandler { 17 | 18 | @Autowired 19 | private ChatService chatService; 20 | 21 | /** 22 | * 读取完连接的消息后,对消息进行处理 23 | * @param ctx 24 | * @param msg 25 | * @throws Exception 26 | */ 27 | @Override 28 | protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame msg) throws Exception { 29 | handlerWebSocketFrame(ctx, msg); 30 | } 31 | 32 | /** 33 | * 处理websocketframe 34 | * @param ctx 35 | * @param frame 36 | */ 37 | private void handlerWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { 38 | if (frame instanceof CloseWebSocketFrame) { 39 | WebSocketServerHandshaker handshaker = 40 | Constant.webSocketServerHandshakerMap.get(ctx.channel().id().asLongText()); 41 | if (handshaker == null) { 42 | sendErrorMessage(ctx, "不存在的客户端连接!"); 43 | } else { 44 | handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain()); 45 | } 46 | return; 47 | } 48 | //ping消息 49 | if (frame instanceof PingWebSocketFrame) { 50 | ctx.channel().write(new PongWebSocketFrame(frame.content().retain())); 51 | return; 52 | } 53 | if (!(frame instanceof TextWebSocketFrame)) { 54 | sendErrorMessage(ctx, "仅支持文本格式,不支持二进制消息!"); 55 | return; 56 | } 57 | 58 | // 客户端传来的消息 59 | String request = ((TextWebSocketFrame)frame).text(); 60 | JSONObject param = null; 61 | try { 62 | param = JSONObject.parseObject(request); 63 | } catch (Exception e) { 64 | sendErrorMessage(ctx, "JSON字符串转换出错!"); 65 | e.printStackTrace(); 66 | } 67 | if (param == null) { 68 | sendErrorMessage(ctx, "参数为空!"); 69 | return; 70 | } 71 | 72 | String type = (String) param.get("type"); 73 | switch (type) { 74 | case "SINGLE_SENDING": 75 | chatService.singleSend(param, ctx); 76 | break; 77 | case "GROUP_SENDING": 78 | chatService.groupSend(param, ctx); 79 | break; 80 | case "GROUP_SENDING_ALL": 81 | chatService.groupSendAll(param, ctx); 82 | break; 83 | case "REGISTER": 84 | chatService.register(param, ctx); 85 | break; 86 | case "AGREE_FRIEND_REQUEST": 87 | chatService.agreeResponse(param, ctx); 88 | break; 89 | case "READ_REPLY_SENDING": 90 | chatService.readReplySend(param, ctx); 91 | break; 92 | default: 93 | chatService.typeError(ctx); 94 | break; 95 | } 96 | } 97 | 98 | /** 99 | * 客户端断开连接 100 | * @param ctx 101 | * @throws Exception 102 | */ 103 | @Override 104 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 105 | // // 通知在线好友我已下线 106 | // User user = (User) SecurityUtils.getSubject().getPrincipal(); 107 | // List friends = userService.findFriends(user.getId()); 108 | // List onlineFriendIds = userService.findOnlineFriendIds(friends); 109 | // for (int i : onlineFriendIds) { 110 | // chatService.offlineNotify(user.getId(), Constant.onlineUser.get(String.valueOf(i))); 111 | // } 112 | // 去除客户端ctx 113 | chatService.remove(ctx); 114 | } 115 | 116 | /** 117 | * 异常处理,关闭channel 118 | * @param ctx 119 | * @param cause 120 | * @throws Exception 121 | */ 122 | @Override 123 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 124 | cause.printStackTrace(); 125 | ctx.close(); 126 | } 127 | 128 | private void sendErrorMessage(ChannelHandlerContext ctx, String errorMsg) { 129 | String responseJson = new ResponseJson().error(errorMsg).toString(); 130 | ctx.channel().writeAndFlush(new TextWebSocketFrame(responseJson)); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/im/server/NettyServer.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.im.server; 2 | 3 | import com.xpllyn.im.handler.HttpRequestHandler; 4 | import com.xpllyn.im.handler.WebSocketServerHandler; 5 | import io.netty.bootstrap.ServerBootstrap; 6 | import io.netty.channel.ChannelFuture; 7 | import io.netty.channel.ChannelInitializer; 8 | import io.netty.channel.ChannelOption; 9 | import io.netty.channel.FixedRecvByteBufAllocator; 10 | import io.netty.channel.nio.NioEventLoopGroup; 11 | import io.netty.channel.socket.SocketChannel; 12 | import io.netty.channel.socket.nio.NioServerSocketChannel; 13 | import io.netty.handler.codec.http.HttpObjectAggregator; 14 | import io.netty.handler.codec.http.HttpServerCodec; 15 | import io.netty.handler.stream.ChunkedWriteHandler; 16 | import io.netty.util.concurrent.Future; 17 | import org.springframework.beans.factory.annotation.Autowired; 18 | import org.springframework.stereotype.Component; 19 | 20 | /** 21 | * netty websocket 服务器 22 | * 使用独立线程启动 23 | */ 24 | @Component 25 | public class NettyServer implements Runnable { 26 | private int port = 3333; 27 | 28 | private ServerBootstrap bootstrap; 29 | 30 | private ChannelFuture serverChannelFuture; 31 | 32 | private NioEventLoopGroup boss; 33 | 34 | private NioEventLoopGroup worker; 35 | 36 | @Autowired 37 | private HttpRequestHandler httpRequestHandler; 38 | 39 | @Autowired 40 | private WebSocketServerHandler webSocketServerHandler; 41 | 42 | public NettyServer() {} 43 | 44 | public void start() { 45 | 46 | // 创建boss和worker两个线程组 47 | // boss只是处理连接请求,真正和客户端业务处理,会交给worker完成 48 | boss = new NioEventLoopGroup(); 49 | worker = new NioEventLoopGroup(); 50 | 51 | try { 52 | // 创建服务器端的启动对象,配置参数 53 | bootstrap = new ServerBootstrap(); 54 | bootstrap.group(boss, worker) 55 | .channel(NioServerSocketChannel.class) 56 | .option(ChannelOption.SO_BACKLOG, 1024) // 设置线程队列等待连接个数 57 | .option(ChannelOption.SO_KEEPALIVE, true) 58 | .childOption(ChannelOption.TCP_NODELAY, true)//开启心跳包活机制,就是客户端、服务端建立连接处于ESTABLISHED状态,超过2小时没有交流,机制会被启动 59 | .childOption(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(592048))//配置固定长度接收缓存区分配器 60 | .childHandler(new ChannelInitializer() { 61 | // 给pipeline设置处理器 62 | @Override 63 | protected void initChannel(SocketChannel ch) throws Exception { 64 | // web基于http协议的解码器 65 | ch.pipeline().addLast(new HttpServerCodec()); 66 | // 对大数据流的支持 67 | ch.pipeline().addLast(new ChunkedWriteHandler()); 68 | //对http message进行聚合,聚合成FullHttpRequest或FullHttpResponse 69 | ch.pipeline().addLast(new HttpObjectAggregator(1024 * 64)); 70 | ch.pipeline().addLast(httpRequestHandler); 71 | // 添加我们自定义的channel处理器 72 | ch.pipeline().addLast(webSocketServerHandler); 73 | } 74 | }); 75 | // 绑定一个端口并且同步,生成一个channelfuture 76 | serverChannelFuture = bootstrap.bind(port).sync(); 77 | // 对关闭通道进行监听 78 | serverChannelFuture.channel().closeFuture().sync(); 79 | } catch (Exception e) { 80 | boss.shutdownGracefully(); 81 | worker.shutdownGracefully(); 82 | e.printStackTrace(); 83 | } finally { 84 | // 关闭主从线程池 85 | boss.shutdownGracefully(); 86 | worker.shutdownGracefully(); 87 | } 88 | 89 | 90 | } 91 | 92 | /** 93 | * 描述:关闭Netty Websocket服务器,主要是释放连接 94 | * 连接包括:服务器连接serverChannel, 95 | * 客户端TCP处理连接bossGroup, 96 | * 客户端I/O操作连接workerGroup 97 | * 98 | * 若只使用 99 | * bossGroupFuture = bossGroup.shutdownGracefully(); 100 | * workerGroupFuture = workerGroup.shutdownGracefully(); 101 | * 会造成内存泄漏。 102 | */ 103 | public void close() { 104 | serverChannelFuture.channel().close(); 105 | Future bossGroupFuture = boss.shutdownGracefully(); 106 | Future workerGroupFuture = worker.shutdownGracefully(); 107 | 108 | try { 109 | bossGroupFuture.await(); 110 | workerGroupFuture.await(); 111 | } catch (InterruptedException e) { 112 | e.printStackTrace(); 113 | } 114 | } 115 | 116 | public void setPort(int port) { 117 | this.port = port; 118 | } 119 | 120 | public int getPort(int port) { 121 | return this.port; 122 | } 123 | 124 | @Override 125 | public void run() { 126 | start(); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/mapper/ChatMapper.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.mapper; 2 | 3 | import com.xpllyn.pojo.ChatMessage; 4 | import com.xpllyn.pojo.GroupMessage; 5 | import org.apache.ibatis.annotations.Insert; 6 | import org.apache.ibatis.annotations.Mapper; 7 | import org.apache.ibatis.annotations.Select; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.List; 11 | 12 | @Mapper 13 | @Component 14 | public interface ChatMapper { 15 | 16 | @Insert("insert into groupmessage(group_id, user_id, content, create_time) values(#{group_id}, #{user_id}, #{content}, #{create_time})") 17 | boolean insertGroupMessage(GroupMessage groupmessage); 18 | 19 | @Insert("insert into chatmessage(from_user_id, to_user_id, content, create_time) values(#{from_user_id},#{to_user_id},#{content},#{create_time})") 20 | boolean insertChatMessage(ChatMessage cm); 21 | 22 | @Select("select * from groupmessage order by create_time") 23 | List getGroupMessage(); 24 | 25 | @Select("select * from chatmessage order by create_time") 26 | List getChatMessage(); 27 | 28 | /** 29 | * 获取世界频道的聊天记录(最近十五条) 30 | * @return 31 | */ 32 | @Select("select * from (select * from groupmessage t where t.group_id = 1 order by create_time desc limit 0,15) m order by m.create_time") 33 | List getGlobalGroupMessage15(); 34 | 35 | /** 36 | * 获取两个用户的聊天记录(最近十五条) 37 | * @param id1 38 | * @param id2 39 | * @return 40 | */ 41 | @Select("select * from (select * from chatmessage t where t.from_user_id = #{id1} and t.to_user_id = #{id2} or t.from_user_id = #{id2} and t.to_user_id = #{id1} order by t.create_time desc limit 0,15) m order by m.create_time") 42 | List getChatMessage15ByIds(int id1, int id2); 43 | 44 | /** 45 | * 获取群消息最近count条消息记录 46 | * @param groupId 47 | * @param start 48 | * @param count 49 | * @return 50 | */ 51 | @Select("select * from (select * from groupmessage t where t.group_id = #{groupId} order by t.create_time desc limit #{start}, #{count}) m order by m.create_time") 52 | List getLatestGroupMessage(int groupId, int start, int count); 53 | 54 | /** 55 | * 获取单聊消息最近count条消息记录 56 | * @param id1 57 | * @param id2 58 | * @param start 59 | * @param count 60 | * @return 61 | */ 62 | @Select("select * from (select * from chatmessage t where t.from_user_id = #{id1} and t.to_user_id = #{id2} or t.from_user_id = #{id2} and t.to_user_id = #{id1} order by t.create_time desc limit #{start},#{count}) m order by m.create_time") 63 | List getLatestChatMessageByIds(int id1, int id2, int start, int count); 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/mapper/GroupMapper.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.mapper; 2 | 3 | import com.xpllyn.pojo.Group; 4 | import org.apache.ibatis.annotations.Mapper; 5 | import org.apache.ibatis.annotations.Select; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.List; 9 | 10 | @Mapper 11 | @Component 12 | public interface GroupMapper { 13 | 14 | @Select("select * from group where id = #{id}") 15 | Group getByGroupId(String id); 16 | 17 | @Select("select user_id from group_to_user where group_id = #{id}") 18 | List getMemberByGroupId(String id); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/mapper/MessageMapper.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.mapper; 2 | 3 | import com.xpllyn.pojo.Message; 4 | import org.apache.ibatis.annotations.Insert; 5 | import org.apache.ibatis.annotations.Mapper; 6 | import org.apache.ibatis.annotations.Select; 7 | import org.apache.ibatis.annotations.Update; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * MessageMapper 14 | * created by xiepl1997 at 2019-8-21 15 | */ 16 | @Mapper 17 | @Component 18 | public interface MessageMapper { 19 | 20 | @Insert("insert into message(ip,pre_id,name,province,city,create_time,update_time,content) values(#{ip},#{pre_id},#{name},#{province},#{city},current_timestamp,current_timestamp,#{content})") 21 | boolean insert(Message message); 22 | 23 | @Select("select * from message order by create_time desc") 24 | List selectAll(); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/mapper/ReadMessageMapper.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.mapper; 2 | 3 | import com.xpllyn.pojo.ReadMessage; 4 | import org.apache.ibatis.annotations.Insert; 5 | import org.apache.ibatis.annotations.Mapper; 6 | import org.apache.ibatis.annotations.Select; 7 | import org.apache.ibatis.annotations.Update; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.sql.Timestamp; 11 | 12 | /** 13 | * ReadMessageMapper 14 | * created by xiepl1997 at 2021-7-9 15 | */ 16 | @Mapper 17 | @Component 18 | public interface ReadMessageMapper { 19 | 20 | @Insert("insert into readmessage(read_user_id, send_user_id, read_time) values(#{read_user_id}, #{send_user_id}, #{read_time})") 21 | boolean insertReadMessage(ReadMessage rm); 22 | 23 | @Update("update readmessage set read_time = #{time} where read_user_id = #{rid} and send_user_id = #{sid}") 24 | boolean updateReadMessage(int rid, int sid, Timestamp time); 25 | 26 | @Select("select * from readmessage where read_user_id = #{rid} and send_user_id = #{sid}") 27 | ReadMessage getReadMessage(int rid, int sid); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.mapper; 2 | 3 | import com.xpllyn.pojo.User; 4 | import org.apache.ibatis.annotations.*; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.List; 8 | 9 | @Mapper 10 | @Component 11 | public interface UserMapper { 12 | 13 | @Select("select * from user where id = #{id}") 14 | User findByUserId(@Param("id") String id); 15 | 16 | @Insert("insert into user(user_email, user_name, user_pw, user_sex, user_icon, user_last_login,create_time,update_time) values(#{user_email}, #{user_name},#{user_pw},#{user_sex},#{user_icon},current_timestamp,current_timestamp,current_timestamp)") 17 | boolean addNewUser(String user_email, String user_name, String user_pw, String user_sex, String user_icon); 18 | 19 | @Update("update user set user_last_login = current_timestamp, create_time = current_timestamp where id = #{id}") 20 | boolean updateLoginTime(int id); 21 | 22 | @Update("update user set user_phone = #{user_phone}, update_time = current_timestamp where id = #{id}") 23 | boolean updatePhone(int id, String user_phone); 24 | 25 | @Select("select * from user where user_email = #{user_email} and user_password = #{user_pw}") 26 | User findByEmailAndPwd(String user_email, String user_pw); 27 | 28 | @Select("select * from user where user_email = #{user_email}") 29 | User findByEmail(String user_email); 30 | 31 | @Select("select b.* from friend a, user b where a.user_id = #{id} and a.friend_id = b.id") 32 | List findFriends(int id); 33 | 34 | @Select("select * from user where user_email = #{idOrEmail} or user_name like '%${idOrEmail}%'") 35 | List findByIdOrEmail(String idOrEmail); 36 | 37 | @Select("select b.* from add_request a, user b where a.receive_user_id = #{id} and a.status = 'send' and b.id = a.send_user_id") 38 | List getSendAddRequestUsers(int id); 39 | 40 | @Insert("insert into add_request(send_user_id, receive_user_id, status, create_time, update_time) values(#{fromId}, #{toId}, 'send', current_timestamp,current_timestamp)") 41 | boolean sendAddFriendRequest(int fromId, int toId); 42 | 43 | @Select("select count(*) from add_request where send_user_id = #{fromId} and receive_user_id = #{toId} and status = 'send'") 44 | int getAddFriendRequest(int fromId, int toId); 45 | 46 | @Select("select friend_id from friend where user_id = #{id}") 47 | List findFriendIds(int id); 48 | 49 | @Update("update add_request set status = 'agree', update_time = current_timestamp where send_user_id = #{fromId} and receive_user_id = #{toId}") 50 | boolean agreeAddRequest(int fromId, int toId); 51 | 52 | @Update("update add_request set status = 'disagree', update_time = current_timestamp where send_user_id = #{fromId} and receive_user_id = #{toId}") 53 | boolean disagreeAddRequest(int fromId, int toId); 54 | 55 | @Insert("insert into friend(friend_id, user_id, create_time, update_time) values(#{fromId}, #{toId}, current_timestamp,current_timestamp)") 56 | boolean addFriend(int fromId, int toId); 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/pojo/Blog.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.pojo; 2 | 3 | /** 4 | * Blog pojo 5 | * created by xiepl1997 at 2019-8-18 6 | */ 7 | public class Blog { 8 | private String title; //博客标题 9 | private String time; //博客发布时间 10 | private String url; //博客url 11 | 12 | public void setTitle(String title) { 13 | this.title = title; 14 | } 15 | 16 | public void setTime(String time) { 17 | this.time = time; 18 | } 19 | 20 | public void setUrl(String url) { 21 | this.url = url; 22 | } 23 | 24 | public String getTitle() { 25 | return title; 26 | } 27 | 28 | public String getTime() { 29 | return time; 30 | } 31 | 32 | public String getUrl() { 33 | return url; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/pojo/Book.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.pojo; 2 | 3 | public class Book { 4 | private String name; 5 | 6 | public void setName(String name) { 7 | this.name = name; 8 | } 9 | 10 | public String getName() { 11 | return name; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/pojo/ChatMessage.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.pojo; 2 | 3 | import java.io.Serializable; 4 | import java.sql.Timestamp; 5 | 6 | public class ChatMessage implements Serializable { 7 | private int id; 8 | private int from_user_id; 9 | private int to_user_id; 10 | private String content; 11 | private Timestamp create_time; 12 | 13 | public ChatMessage() {} 14 | 15 | public ChatMessage(int from_user_id, int to_user_id, String content, Timestamp create_time) { 16 | this.from_user_id = from_user_id; 17 | this.to_user_id = to_user_id; 18 | this.content = content; 19 | this.create_time = create_time; 20 | } 21 | 22 | public Timestamp getCreate_time() { 23 | return create_time; 24 | } 25 | 26 | public void setCreate_time(Timestamp create_time) { 27 | this.create_time = create_time; 28 | } 29 | 30 | public int getId() { 31 | return id; 32 | } 33 | 34 | public void setId(int id) { 35 | this.id = id; 36 | } 37 | 38 | public int getFrom_user_id() { 39 | return from_user_id; 40 | } 41 | 42 | public void setFrom_user_id(int from_user_id) { 43 | this.from_user_id = from_user_id; 44 | } 45 | 46 | public int getTo_user_id() { 47 | return to_user_id; 48 | } 49 | 50 | public void setTo_user_id(int to_user_id) { 51 | this.to_user_id = to_user_id; 52 | } 53 | 54 | public String getContent() { 55 | return content; 56 | } 57 | 58 | public void setContent(String content) { 59 | this.content = content; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/pojo/GitHubRepository.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.pojo; 2 | 3 | public class GitHubRepository { 4 | private String name; 5 | private String full_name; 6 | private GitHubRepositoryOwner owner; 7 | private String html_url; 8 | private String io_url; 9 | private String description; 10 | private int stargazers_count; 11 | private int watchers; 12 | private int forks; 13 | private String language; 14 | private String updated_at; 15 | 16 | public String getUpdated_at() { 17 | return updated_at; 18 | } 19 | 20 | public void setUpdated_at(String updated_at) { 21 | this.updated_at = updated_at; 22 | } 23 | 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | public void setName(String name) { 29 | this.name = name; 30 | } 31 | 32 | public String getFull_name() { 33 | return full_name; 34 | } 35 | 36 | public void setFull_name(String full_name) { 37 | this.full_name = full_name; 38 | } 39 | 40 | public GitHubRepositoryOwner getOwner() { 41 | return owner; 42 | } 43 | 44 | public void setOwner(GitHubRepositoryOwner owner) { 45 | this.owner = owner; 46 | } 47 | 48 | public String getHtml_url() { 49 | return html_url; 50 | } 51 | 52 | public void setHtml_url(String html_url) { 53 | this.html_url = html_url; 54 | } 55 | 56 | public String getIo_url() { 57 | return io_url; 58 | } 59 | 60 | public void setIo_url(String io_url) { 61 | this.io_url = io_url; 62 | } 63 | 64 | public String getDescription() { 65 | return description; 66 | } 67 | 68 | public void setDescription(String description) { 69 | this.description = description; 70 | } 71 | 72 | public int getStargazers_count() { 73 | return stargazers_count; 74 | } 75 | 76 | public void setStargazers_count(int stargazers_count) { 77 | this.stargazers_count = stargazers_count; 78 | } 79 | 80 | public int getWatchers() { 81 | return watchers; 82 | } 83 | 84 | public void setWatchers(int watchers) { 85 | this.watchers = watchers; 86 | } 87 | 88 | public int getForks() { 89 | return forks; 90 | } 91 | 92 | public void setForks(int forks) { 93 | this.forks = forks; 94 | } 95 | 96 | public String getLanguage() { 97 | return language; 98 | } 99 | 100 | public void setLanguage(String language) { 101 | this.language = language; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/pojo/GitHubRepositoryOwner.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.pojo; 2 | 3 | public class GitHubRepositoryOwner { 4 | private String login; 5 | private String avatar_url; 6 | private String html_url; 7 | 8 | public String getAvatar_url() { 9 | return avatar_url; 10 | } 11 | 12 | public void setAvatar_url(String avatar_url) { 13 | this.avatar_url = avatar_url; 14 | } 15 | 16 | public String getLogin() { 17 | return login; 18 | } 19 | 20 | public void setLogin(String login) { 21 | this.login = login; 22 | } 23 | 24 | public String getHtml_url() { 25 | return html_url; 26 | } 27 | 28 | public void setHtml_url(String html_url) { 29 | this.html_url = html_url; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/pojo/Group.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.pojo; 2 | 3 | import java.sql.Timestamp; 4 | 5 | public class Group { 6 | private int id; 7 | private String name; 8 | private int admin_id; 9 | private String icon; 10 | private String intro; 11 | private Timestamp create_time; 12 | private Timestamp update_time; 13 | 14 | public Group() {} 15 | 16 | public Group(String name, int admin_id, String icon, String intro) { 17 | this.name = name; 18 | this.admin_id = admin_id; 19 | this.icon = icon; 20 | this.intro = intro; 21 | } 22 | 23 | public int getId() { 24 | return id; 25 | } 26 | 27 | public void setId(int id) { 28 | this.id = id; 29 | } 30 | 31 | public String getName() { 32 | return name; 33 | } 34 | 35 | public void setName(String name) { 36 | this.name = name; 37 | } 38 | 39 | public int getAdmin_id() { 40 | return admin_id; 41 | } 42 | 43 | public void setAdmin_id(int admin_id) { 44 | this.admin_id = admin_id; 45 | } 46 | 47 | public String getIcon() { 48 | return icon; 49 | } 50 | 51 | public void setIcon(String icon) { 52 | this.icon = icon; 53 | } 54 | 55 | public String getIntro() { 56 | return intro; 57 | } 58 | 59 | public void setIntro(String intro) { 60 | this.intro = intro; 61 | } 62 | 63 | public Timestamp getCreate_time() { 64 | return create_time; 65 | } 66 | 67 | public void setCreate_time(Timestamp create_time) { 68 | this.create_time = create_time; 69 | } 70 | 71 | public Timestamp getUpdate_time() { 72 | return update_time; 73 | } 74 | 75 | public void setUpdate_time(Timestamp update_time) { 76 | this.update_time = update_time; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/pojo/GroupMessage.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.pojo; 2 | 3 | import java.io.Serializable; 4 | import java.sql.Timestamp; 5 | 6 | /** 7 | * 世界频道聊天记录 8 | */ 9 | public class GroupMessage implements Serializable { 10 | private int id; 11 | private int group_id; 12 | private int user_id; 13 | private String content; 14 | private Timestamp create_time; 15 | 16 | public GroupMessage() {} 17 | 18 | public GroupMessage(int group_id, int user_id, String content, Timestamp create_time) { 19 | this.group_id = group_id; 20 | this.user_id = user_id; 21 | this.content = content; 22 | this.create_time = create_time; 23 | } 24 | 25 | public Timestamp getCreate_time() { 26 | return create_time; 27 | } 28 | 29 | public void setCreate_time(Timestamp create_time) { 30 | this.create_time = create_time; 31 | } 32 | 33 | public int getId() { 34 | return id; 35 | } 36 | 37 | public void setId(int id) { 38 | this.id = id; 39 | } 40 | 41 | public int getGroup_id() { 42 | return group_id; 43 | } 44 | 45 | public void setGroup_id(int group_id) { 46 | this.group_id = group_id; 47 | } 48 | 49 | public int getUser_id() { 50 | return user_id; 51 | } 52 | 53 | public void setUser_id(int user_id) { 54 | this.user_id = user_id; 55 | } 56 | 57 | public String getContent() { 58 | return content; 59 | } 60 | 61 | public void setContent(String content) { 62 | this.content = content; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/pojo/Message.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.pojo; 2 | 3 | import java.sql.Timestamp; 4 | 5 | /** 6 | * 留言pojo 7 | * created by xiepl1997 at 2019-8-21 8 | */ 9 | public class Message { 10 | private int id; 11 | private String ip; 12 | private String pre_id; 13 | private String name; 14 | private String province; 15 | private String city; 16 | private Timestamp create_time; 17 | private Timestamp update_time; 18 | private String content; 19 | 20 | public Message() {} 21 | 22 | public Message(String ip, String pre_id, String name, String province, String city, String content) { 23 | this.ip = ip; 24 | this.pre_id = pre_id; 25 | this.name = name; 26 | this.province = province; 27 | this.city = city; 28 | this.content = content; 29 | } 30 | 31 | public void setIp(String ip) { 32 | this.ip = ip; 33 | } 34 | 35 | public void setId(int id) { 36 | this.id = id; 37 | } 38 | 39 | public void setPre_id(String pre_id) { 40 | this.pre_id = pre_id; 41 | } 42 | 43 | public void setName(String name){ 44 | this.name = name; 45 | } 46 | 47 | public void setProvince(String province) { 48 | this.province = province; 49 | } 50 | 51 | public void setCity(String city) { 52 | this.city = city; 53 | } 54 | 55 | public void setCreate_time(Timestamp create_time) { 56 | this.create_time = create_time; 57 | } 58 | 59 | public void setContent(String content) { 60 | this.content = content; 61 | } 62 | 63 | public String getIp() { 64 | return ip; 65 | } 66 | 67 | public int getId() { 68 | return id; 69 | } 70 | 71 | public String getPre_id() { 72 | return pre_id; 73 | } 74 | 75 | public String getName() {return name;} 76 | 77 | public String getProvince() { 78 | return province; 79 | } 80 | 81 | public String getCity() { 82 | return city; 83 | } 84 | 85 | public Timestamp getCreate_time() { 86 | return create_time; 87 | } 88 | 89 | public String getContent() { 90 | return content; 91 | } 92 | 93 | public Timestamp getUpdate_time() { 94 | return update_time; 95 | } 96 | 97 | public void setUpdate_time(Timestamp update_time) { 98 | this.update_time = update_time; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/pojo/ReadMessage.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.pojo; 2 | 3 | import java.io.Serializable; 4 | import java.sql.Timestamp; 5 | 6 | /** 7 | * 已读回执。 8 | * 记录消息接收人对消息发送人的消息的读取时间。 9 | * created by xiepl1997 at 2021-7-9 10 | */ 11 | public class ReadMessage implements Serializable { 12 | private int id; 13 | // 接收人id 14 | private int read_user_id; 15 | // 发送人id 16 | private int send_user_id; 17 | // 接收人读取发送人消息的最后时间 18 | private Timestamp read_time; 19 | 20 | public ReadMessage() { 21 | } 22 | 23 | public ReadMessage(int rid, int sid, Timestamp time) { 24 | this.read_user_id = rid; 25 | this.send_user_id = sid; 26 | this.read_time = time; 27 | } 28 | 29 | public int getId() { 30 | return id; 31 | } 32 | 33 | public void setId(int id) { 34 | this.id = id; 35 | } 36 | 37 | public int getRead_user_id() { 38 | return read_user_id; 39 | } 40 | 41 | public void setRead_user_id(int read_user_id) { 42 | this.read_user_id = read_user_id; 43 | } 44 | 45 | public int getSend_user_id() { 46 | return send_user_id; 47 | } 48 | 49 | public void setSend_user_id(int send_user_id) { 50 | this.send_user_id = send_user_id; 51 | } 52 | 53 | public Timestamp getTime() { 54 | return read_time; 55 | } 56 | 57 | public void setTime(Timestamp time) { 58 | this.read_time = time; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/pojo/User.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.pojo; 2 | 3 | import java.io.Serializable; 4 | import java.sql.Timestamp; 5 | 6 | /** 7 | * User pojo created at 2019/7/22 by Peiliang Xie 8 | */ 9 | public class User implements Serializable { 10 | private int id; 11 | private String user_email; 12 | private String user_pw; 13 | private Timestamp user_last_login; 14 | private String user_name; 15 | private String user_sex; 16 | private String user_icon; 17 | private String user_phone; 18 | private String user_address; 19 | private Timestamp create_time; 20 | private Timestamp update_time; 21 | 22 | public User() {} 23 | 24 | public User(String user_email, String user_pw) { 25 | this.user_email = user_email; 26 | this.user_pw = user_pw; 27 | } 28 | 29 | public User(String user_email, String user_name, String user_pw, String user_sex) { 30 | this.user_email = user_email; 31 | this.user_pw = user_pw; 32 | this.user_name = user_name; 33 | this.user_sex = user_sex; 34 | } 35 | 36 | public User(String user_email, String user_pw, Timestamp user_last_login, String user_name, String user_sex, 37 | String user_icon, String user_phone, String user_address, Timestamp create_time, Timestamp update_time) { 38 | this.user_email = user_email; 39 | this.user_pw = user_pw; 40 | this.user_last_login = user_last_login; 41 | this.user_name = user_name; 42 | this.user_sex = user_sex; 43 | this.user_icon = user_icon; 44 | this.user_phone = user_phone; 45 | this.user_address = user_address; 46 | this.create_time = create_time; 47 | this.update_time = update_time; 48 | } 49 | 50 | public String getUser_icon() { 51 | return user_icon; 52 | } 53 | 54 | public void setUser_icon(String user_icon) { 55 | this.user_icon = user_icon; 56 | } 57 | 58 | public int getId() { 59 | return id; 60 | } 61 | 62 | public void setId(int id) { 63 | this.id = id; 64 | } 65 | 66 | public String getUser_email() { 67 | return user_email; 68 | } 69 | 70 | public void setUser_email(String user_email) { 71 | this.user_email = user_email; 72 | } 73 | 74 | public String getUser_pw() { 75 | return user_pw; 76 | } 77 | 78 | public void setUser_pw(String user_pw) { 79 | this.user_pw = user_pw; 80 | } 81 | 82 | public Timestamp getUser_last_login() { 83 | return user_last_login; 84 | } 85 | 86 | public void setUser_last_login(Timestamp user_last_login) { 87 | this.user_last_login = user_last_login; 88 | } 89 | 90 | public String getUser_name() { 91 | return user_name; 92 | } 93 | 94 | public void setUser_name(String user_name) { 95 | this.user_name = user_name; 96 | } 97 | 98 | public String getUser_sex() { 99 | return user_sex; 100 | } 101 | 102 | public void setUser_sex(String user_sex) { 103 | this.user_sex = user_sex; 104 | } 105 | 106 | public String getUser_phone() { 107 | return user_phone; 108 | } 109 | 110 | public void setUser_phone(String user_phone) { 111 | this.user_phone = user_phone; 112 | } 113 | 114 | public String getUser_address() { 115 | return user_address; 116 | } 117 | 118 | public void setUser_address(String user_address) { 119 | this.user_address = user_address; 120 | } 121 | 122 | public Timestamp getCreate_time() { 123 | return create_time; 124 | } 125 | 126 | public void setCreate_time(Timestamp create_time) { 127 | this.create_time = create_time; 128 | } 129 | 130 | public Timestamp getUpdate_time() { 131 | return update_time; 132 | } 133 | 134 | public void setUpdate_time(Timestamp update_time) { 135 | this.update_time = update_time; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/service/IChatService.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.service; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.xpllyn.pojo.ChatMessage; 5 | import com.xpllyn.pojo.GroupMessage; 6 | import io.netty.channel.ChannelHandlerContext; 7 | 8 | import java.util.List; 9 | 10 | public interface IChatService { 11 | 12 | void singleSend(JSONObject param, ChannelHandlerContext ctx); 13 | 14 | void groupSend(JSONObject param, ChannelHandlerContext ctx); 15 | 16 | void groupSendAll(JSONObject param, ChannelHandlerContext ctx); 17 | 18 | void register(JSONObject param, ChannelHandlerContext ctx); 19 | 20 | /** 21 | * 发送已读回执 22 | * @param param 23 | * @param ctx 24 | */ 25 | void readReplySend(JSONObject param, ChannelHandlerContext ctx); 26 | 27 | void remove(ChannelHandlerContext ctx); 28 | 29 | void typeError(ChannelHandlerContext ctx); 30 | 31 | void offlineNotify(int fromUserId, ChannelHandlerContext ctx); 32 | 33 | void agreeResponse(JSONObject param, ChannelHandlerContext ctx); 34 | 35 | void insertGroupMessages(List groupMessages); 36 | 37 | void insertChatMessages(List chatMessages); 38 | 39 | List getGroupMessages(); 40 | 41 | List getChatMessages(); 42 | 43 | /** 44 | * 获取群消息最近count条消息记录 45 | * @param groupId 46 | * @param start 47 | * @param count 48 | * @return 49 | */ 50 | List getLatestGroupMessage(int groupId, int start, int count); 51 | 52 | /** 53 | * 获取单聊消息最近count条消息记录 54 | * @param id1 55 | * @param id2 56 | * @param start 57 | * @param count 58 | * @return 59 | */ 60 | List getLatestChatMessageByIds(int id1, int id2, int start, int count); 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/service/IGroupService.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.service; 2 | 3 | import com.xpllyn.pojo.Group; 4 | 5 | import java.util.List; 6 | 7 | public interface IGroupService { 8 | 9 | Group getByGroupId(String id); 10 | 11 | List getMemberByGroupId(String id); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/service/IMRedisService.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.service; 2 | 3 | import com.xpllyn.mapper.ChatMapper; 4 | import com.xpllyn.pojo.ChatMessage; 5 | import com.xpllyn.pojo.GroupMessage; 6 | import com.xpllyn.pojo.ReadMessage; 7 | import com.xpllyn.service.impl.ReadMessageService; 8 | import com.xpllyn.utils.im.IMRedisUtils; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.data.redis.core.RedisTemplate; 12 | import org.springframework.stereotype.Service; 13 | 14 | import java.util.List; 15 | 16 | @Service 17 | @Slf4j 18 | public class IMRedisService { 19 | @Autowired 20 | private RedisTemplate redisTemplate; 21 | 22 | @Autowired 23 | private IMRedisUtils imRedisUtils; 24 | 25 | @Autowired 26 | private ChatMapper chatMapper; 27 | 28 | @Autowired 29 | private ReadMessageService readMessageService; 30 | 31 | /** 32 | * 插入世界频道群聊新消息 33 | * @param gm 34 | * @return 35 | */ 36 | public boolean setGroupMessage(GroupMessage gm) { 37 | return imRedisUtils.setGroupMessage(gm, redisTemplate); 38 | } 39 | 40 | /** 41 | * 插入数据库中取出的消息到世界频道群聊历史记录list 42 | * @param gm 43 | * @return 44 | */ 45 | public boolean setGroupMessageHistory(GroupMessage gm) { 46 | return imRedisUtils.setGroupMessageHistory(gm, redisTemplate); 47 | } 48 | 49 | /** 50 | * 获取世界频道群聊信息 51 | * @return 52 | */ 53 | public List getGroupMessage() { 54 | List res = null; 55 | try { 56 | res = imRedisUtils.getGroupMessage(redisTemplate); 57 | } catch (Exception e) { 58 | log.error("【Redis】 redis error!"); 59 | e.printStackTrace(); 60 | } 61 | return res; 62 | } 63 | 64 | /** 65 | * 获取世界频道历史群聊历史消息 66 | * @return 67 | */ 68 | public List getGroupMessageHistory() { 69 | List gms = null; 70 | try { 71 | gms = imRedisUtils.getGroupMessageHistory(redisTemplate); 72 | } catch (Exception e) { 73 | // 如果Redis出错,则上数据库中找最近15条记录。 74 | gms = chatMapper.getGlobalGroupMessage15(); 75 | log.error("【Redis】 redis error!"); 76 | e.printStackTrace(); 77 | return gms; 78 | } 79 | if (gms == null) { 80 | // 如果缓存为null,则上数据库中找最近15条记录,放入缓存中。 81 | gms = chatMapper.getGlobalGroupMessage15(); 82 | if (gms != null) { 83 | for (GroupMessage gm : gms) { 84 | setGroupMessageHistory(gm); 85 | } 86 | } 87 | } 88 | return gms; 89 | } 90 | 91 | /** 92 | * 插入单聊消息 93 | * @param cm 94 | * @return 95 | */ 96 | public boolean setChatMessage(ChatMessage cm) { 97 | return imRedisUtils.setChatMessage(cm, redisTemplate); 98 | } 99 | 100 | /** 101 | * 插入数据库中取出的消息到单聊历史记录list 102 | * @param cm 103 | * @return 104 | */ 105 | public boolean setChatMessageHistory(ChatMessage cm) { 106 | return imRedisUtils.setChatMessageHistory(cm, redisTemplate); 107 | } 108 | 109 | /** 110 | * 111 | * @param key 112 | * @return 113 | */ 114 | public List getChatMessage(String key) { 115 | List res = null; 116 | try { 117 | res = imRedisUtils.getChatMessage(key, redisTemplate); 118 | } catch (Exception e) { 119 | log.error("【Redis】 redis error!"); 120 | e.printStackTrace(); 121 | } 122 | return res; 123 | } 124 | 125 | /** 126 | * 获取单聊历史消息 127 | * @param key 128 | * @return 129 | */ 130 | public List getChatMessageHistory(String key) { 131 | List cms = null; 132 | try { 133 | cms = imRedisUtils.getChatMessage(key, redisTemplate); 134 | } catch (Exception e) { 135 | // 如果Redis出错,则上数据库中找最近15条记录。 136 | String[] str = key.split("-"); 137 | int fId = Integer.parseInt(str[0]); 138 | int sId = Integer.parseInt(str[1]); 139 | int firstId = Math.min(fId, sId); 140 | int secondId = Math.max(fId, sId); 141 | cms = chatMapper.getChatMessage15ByIds(firstId, secondId); 142 | log.error("【Redis】 redis error!"); 143 | e.printStackTrace(); 144 | return cms; 145 | } 146 | if (cms == null) { 147 | // 如果缓存为null,则上数据库中找最近15条记录,放入缓存中。 148 | String[] str = key.split("-"); 149 | int fId = Integer.parseInt(str[0]); 150 | int sId = Integer.parseInt(str[1]); 151 | int firstId = Math.min(fId, sId); 152 | int secondId = Math.max(fId, sId); 153 | cms = chatMapper.getChatMessage15ByIds(firstId, secondId); 154 | if (cms != null) { 155 | for (ChatMessage cm : cms) { 156 | setChatMessageHistory(cm); 157 | } 158 | } 159 | } 160 | return cms; 161 | } 162 | 163 | /** 164 | * 插入或更新一条已读回执到redis中的value中 165 | * @param readMessage 166 | * @return 167 | */ 168 | public boolean setReadMessage(ReadMessage readMessage) { 169 | return imRedisUtils.setReadMessage(readMessage, redisTemplate); 170 | } 171 | 172 | public ReadMessage getReadMessage(String key) { 173 | ReadMessage readMessage = null; 174 | try { 175 | readMessage = imRedisUtils.getReadMessage(key, redisTemplate); 176 | } catch (Exception e) { 177 | log.error("【Redis】 redis error!"); 178 | e.printStackTrace(); 179 | } 180 | // 如果缓存里为空,则上数据库找,如果数据库中有的话放入缓存中 181 | if (readMessage == null) { 182 | String[] strs = key.split("-"); 183 | int rid = Integer.parseInt(strs[0]); 184 | int sid = Integer.parseInt(strs[1]); 185 | readMessage = readMessageService.getReadMessage(rid, sid); 186 | if (readMessage != null) { 187 | imRedisUtils.setReadMessage(readMessage, redisTemplate); 188 | } 189 | } 190 | return readMessage; 191 | } 192 | 193 | /** 194 | * 移除缓存 195 | * @param key 196 | */ 197 | public void remove(String key) { 198 | try { 199 | imRedisUtils.remove(key, redisTemplate); 200 | } catch (Exception e) { 201 | log.error("【Redis】 redis error!"); 202 | e.printStackTrace(); 203 | } 204 | } 205 | 206 | public RedisTemplate getRedisTemplate() { 207 | return this.redisTemplate; 208 | } 209 | 210 | 211 | } 212 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/service/IMessageService.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.service; 2 | 3 | import com.xpllyn.pojo.Message; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | import java.util.List; 7 | 8 | /** 9 | * 留言服务接口 10 | * created by xiepl1997 at 2019-8-21 11 | */ 12 | public interface IMessageService { 13 | 14 | /** 15 | * @param message 16 | * @return 插入成功返回true,否则返回false 17 | */ 18 | boolean insertMessage(Message message); 19 | 20 | /** 21 | * function:组装Message对象 22 | * @param str 将用户输入的留言传入 23 | * @param pre_id 父留言id,若没有父留言则传入null 24 | * @param request http请求 25 | * @return 返回Message对象 26 | */ 27 | Message assembleMessageObject(String str, String pre_id, HttpServletRequest request); 28 | 29 | /** 30 | * function:将所有的留言查询出来 31 | * @return 32 | */ 33 | List getAllMessages(); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/service/IReadMessageService.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.service; 2 | 3 | import com.xpllyn.pojo.ReadMessage; 4 | 5 | import java.sql.Timestamp; 6 | 7 | /** 8 | * 读取消息回执服务接口 9 | * created by xiepl1997 at 2021-7-9 10 | */ 11 | public interface IReadMessageService { 12 | 13 | /** 14 | * 插入一条读取回执 15 | * @param rm 16 | * @return 17 | */ 18 | boolean insertReadMessage(ReadMessage rm); 19 | 20 | /** 21 | * 更新一条回执 22 | * @param rid 23 | * @param sid 24 | * @param time 25 | * @return 26 | */ 27 | boolean updateReadMessage(int rid, int sid, Timestamp time); 28 | 29 | /** 30 | * 获取回执 31 | * @param rid 32 | * @param sid 33 | * @return 34 | */ 35 | ReadMessage getReadMessage(int rid, int sid); 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/service/IUserService.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.service; 2 | 3 | import com.xpllyn.pojo.User; 4 | 5 | import java.util.List; 6 | 7 | public interface IUserService { 8 | User findByUserId(String id); 9 | 10 | boolean addNewUser(String user_email, String user_name, String user_pw, String user_sex, String user_icon); 11 | 12 | boolean updateLoginTime(int id); 13 | 14 | boolean updatePhone(int id, String user_phone); 15 | 16 | User findByEmailAndPwd(String user_email, String user_pw); 17 | 18 | User findByEmail(String user_email); 19 | 20 | List findFriends(int id); 21 | 22 | List findByIdOrEmail(String idOrEmail); 23 | 24 | List findOnlineFriendIds(List users); 25 | 26 | List getSendAddRequestUsers(int id); 27 | 28 | boolean sendAddFriendRequest(int fromId, int toId); 29 | 30 | int getAddFriendRequest(int fromId, int toId); 31 | 32 | List findFriendIds(int id); 33 | 34 | boolean agreeAddRequest(int fromId, int toId); 35 | 36 | boolean disagreeAddRequest(int fromId, int toId); 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/service/impl/GroupService.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.service.impl; 2 | 3 | import com.xpllyn.mapper.GroupMapper; 4 | import com.xpllyn.pojo.Group; 5 | import com.xpllyn.service.IGroupService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.List; 10 | 11 | @Service 12 | public class GroupService implements IGroupService { 13 | 14 | @Autowired 15 | private GroupMapper groupMapper; 16 | 17 | @Override 18 | public Group getByGroupId(String id) { 19 | return groupMapper.getByGroupId(id); 20 | } 21 | 22 | @Override 23 | public List getMemberByGroupId(String id) { 24 | return groupMapper.getMemberByGroupId(id); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/service/impl/MessageService.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.service.impl; 2 | 3 | import com.xpllyn.mapper.MessageMapper; 4 | import com.xpllyn.pojo.Message; 5 | import com.xpllyn.service.IMessageService; 6 | import com.xpllyn.utils.MessageInfoUtils; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.web.context.request.RequestContextHolder; 11 | import org.springframework.web.context.request.ServletRequestAttributes; 12 | 13 | import javax.servlet.http.HttpServletRequest; 14 | import java.sql.Timestamp; 15 | import java.text.SimpleDateFormat; 16 | import java.util.Date; 17 | import java.util.List; 18 | 19 | /** 20 | * 留言服务实现类 21 | * created by xiepl1997 at 2019-8-21 22 | */ 23 | @Service 24 | @Slf4j 25 | public class MessageService implements IMessageService { 26 | 27 | @Autowired 28 | private MessageMapper messageMapper = null; 29 | 30 | @Autowired 31 | private MessageInfoUtils messageInfoUtils = null; 32 | 33 | @Override 34 | public boolean insertMessage(Message message) { 35 | boolean flag = false; 36 | try { 37 | flag = messageMapper.insert(message); 38 | log.info("【留言】 收到新留言:" + message.getContent()); 39 | } 40 | catch (Exception e){ 41 | log.error("【留言】 留言保存失败,留言内容:" + message.getContent()); 42 | e.printStackTrace(); 43 | } 44 | return flag; 45 | } 46 | 47 | @Override 48 | public Message assembleMessageObject(String str, String pre_id, HttpServletRequest request){ 49 | SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 50 | Timestamp time = new Timestamp(System.currentTimeMillis()); 51 | 52 | //HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); 53 | 54 | String ip = messageInfoUtils.getUserIp(request); 55 | 56 | String[] province_city = messageInfoUtils.getAddressByIp(ip); 57 | 58 | String name = ""; 59 | if (province_city[0] == "") { 60 | name = "未知地区"; 61 | } 62 | //如果省和市名字一样(如北京市北京市) 63 | else if(province_city[0].equals(province_city[1])){ 64 | name = province_city[0]; 65 | } else { 66 | name = province_city[0] + province_city[1]; 67 | } 68 | name += "的朋友"; 69 | 70 | Message message = new Message(ip ,pre_id ,name ,province_city[0] ,province_city[1] ,str); 71 | 72 | return message; 73 | } 74 | 75 | @Override 76 | public List getAllMessages() { 77 | List messageList = messageMapper.selectAll(); 78 | return messageList; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/service/impl/ReadMessageService.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.service.impl; 2 | 3 | import com.xpllyn.mapper.ReadMessageMapper; 4 | import com.xpllyn.pojo.ReadMessage; 5 | import com.xpllyn.service.IReadMessageService; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.sql.Timestamp; 11 | 12 | /** 13 | * 读取消息回执服务接口 14 | * created by xiepl1997 at 2021-7-9 15 | */ 16 | @Service 17 | @Slf4j 18 | public class ReadMessageService implements IReadMessageService { 19 | 20 | @Autowired 21 | private ReadMessageMapper readMessageMapper = null; 22 | 23 | @Override 24 | public boolean insertReadMessage(ReadMessage rm) { 25 | boolean result = false; 26 | try { 27 | result = readMessageMapper.insertReadMessage(rm); 28 | } catch (Exception e) { 29 | e.printStackTrace(); 30 | } 31 | return result; 32 | } 33 | 34 | @Override 35 | public boolean updateReadMessage(int rid, int sid, Timestamp time) { 36 | boolean result = false; 37 | try { 38 | result = readMessageMapper.updateReadMessage(rid, sid, time); 39 | } catch (Exception e) { 40 | e.printStackTrace(); 41 | } 42 | return result; 43 | } 44 | 45 | @Override 46 | public ReadMessage getReadMessage(int rid, int sid) { 47 | ReadMessage readMessage = null; 48 | try { 49 | readMessage = readMessageMapper.getReadMessage(rid, sid); 50 | } catch (Exception e) { 51 | e.printStackTrace(); 52 | } 53 | return readMessage; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/service/impl/UserService.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.service.impl; 2 | 3 | import com.xpllyn.mapper.UserMapper; 4 | import com.xpllyn.pojo.User; 5 | import com.xpllyn.service.IUserService; 6 | import com.xpllyn.utils.im.Constant; 7 | import com.xpllyn.utils.EncryptionUtils; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.transaction.annotation.Transactional; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | @Service 16 | public class UserService implements IUserService { 17 | @Autowired 18 | private UserMapper userMapper; 19 | 20 | @Override 21 | public User findByUserId(String id) { 22 | return userMapper.findByUserId(id); 23 | } 24 | 25 | @Override 26 | public boolean addNewUser(String user_email, String user_name, String user_pw, String user_sex, String user_icon) { 27 | User user = userMapper.findByEmail(user_email); 28 | if (user != null) { 29 | return false; 30 | } 31 | EncryptionUtils encryptionUtils = new EncryptionUtils(); 32 | String pwd = encryptionUtils.encryption(user_pw); 33 | userMapper.addNewUser(user_email, user_name, pwd, user_sex, user_icon); 34 | return true; 35 | } 36 | 37 | @Override 38 | public boolean updateLoginTime(int id) { 39 | return userMapper.updateLoginTime(id); 40 | } 41 | 42 | @Override 43 | public boolean updatePhone(int id, String user_phone) { 44 | return updatePhone(id, user_phone); 45 | } 46 | 47 | @Override 48 | public User findByEmailAndPwd(String user_email, String user_pw) { 49 | return null; 50 | } 51 | 52 | @Override 53 | public User findByEmail(String user_email) { 54 | return userMapper.findByEmail(user_email); 55 | } 56 | 57 | @Override 58 | public List findFriends(int id) { 59 | return userMapper.findFriends(id); 60 | } 61 | 62 | @Override 63 | public List findByIdOrEmail(String idOrEmail) { 64 | return userMapper.findByIdOrEmail(idOrEmail); 65 | } 66 | 67 | @Override 68 | public List findOnlineFriendIds(List users) { 69 | List onlineFriendIds = new ArrayList<>(); 70 | for (User user : users) { 71 | if (Constant.onlineUser.containsKey(String.valueOf(user.getId()))) { 72 | onlineFriendIds.add(user.getId()); 73 | } 74 | } 75 | return onlineFriendIds; 76 | } 77 | 78 | @Override 79 | public List getSendAddRequestUsers(int id) { 80 | return userMapper.getSendAddRequestUsers(id); 81 | } 82 | 83 | @Override 84 | public boolean sendAddFriendRequest(int fromId, int toId) { 85 | return userMapper.sendAddFriendRequest(fromId, toId); 86 | } 87 | 88 | @Override 89 | public int getAddFriendRequest(int fromId, int toId) { 90 | return userMapper.getAddFriendRequest(fromId, toId); 91 | } 92 | 93 | @Override 94 | @Transactional 95 | public boolean agreeAddRequest(int fromId, int toId) { 96 | userMapper.agreeAddRequest(fromId, toId); 97 | userMapper.addFriend(fromId, toId); 98 | return userMapper.addFriend(toId, fromId); 99 | } 100 | 101 | @Override 102 | public boolean disagreeAddRequest(int fromId, int toId) { 103 | return userMapper.disagreeAddRequest(fromId, toId); 104 | } 105 | 106 | @Override 107 | public List findFriendIds(int id) { 108 | return userMapper.findFriendIds(id); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/utils/BlogUtils.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.utils; 2 | 3 | import com.xpllyn.pojo.Blog; 4 | import org.springframework.stereotype.Component; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.InputStreamReader; 8 | import java.net.URL; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.regex.Matcher; 12 | import java.util.regex.Pattern; 13 | 14 | /** 15 | * bolg工具类 16 | * created by xiepl1997 at 2019-8-18 17 | */ 18 | @Component 19 | public class BlogUtils { 20 | 21 | private String path = "https://xiepl1997.github.io"; 22 | private String source = null; 23 | 24 | /** 25 | * 获取xiepl1997.github.io文章的标题,日期,url 26 | */ 27 | public List getBlogInfo(){ 28 | 29 | source = getPageSource(); 30 | 31 | List BlogList = new ArrayList<>(); 32 | 33 | Matcher matcher = null; 34 | Matcher matcher1 = null; 35 | Matcher matcher2 = null; 36 | String reg = "

(.*?)

"; //title 37 | String reg1 = ""; //time 38 | String reg2 = "(?s)"+"
.*?"+" books = new ArrayList<>(); 24 | File file = ResourceUtils.getFile(path); 25 | String[] names = file.list(); 26 | for(int i = 0; i < names.length; i++){ 27 | Book b = new Book(); 28 | b.setName(names[i]); 29 | books.add(b); 30 | } 31 | return books; 32 | } 33 | 34 | // public static void main(String[] args) { 35 | // String path = "E://book"; 36 | // String[] names = getFileName(path); 37 | // for(String name : names){ 38 | // System.out.println(name); 39 | // } 40 | // } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/utils/EncryptionUtils.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.utils; 2 | 3 | import org.apache.shiro.crypto.SecureRandomNumberGenerator; 4 | import org.apache.shiro.crypto.hash.Md5Hash; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * 加密 11 | */ 12 | public class EncryptionUtils { 13 | 14 | public static String salt = "jfaog09823gl_flan=jal"; 15 | 16 | /** 17 | * 密码加密 18 | * @param pwd 19 | * @return 20 | */ 21 | public String encryption(String pwd) { 22 | String password = new Md5Hash(pwd, salt, 2).toString(); 23 | return password; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/utils/MessageInfoUtils.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.utils; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | import java.io.BufferedReader; 7 | import java.io.InputStreamReader; 8 | import java.net.InetAddress; 9 | import java.net.URL; 10 | import java.net.UnknownHostException; 11 | import java.util.regex.Matcher; 12 | import java.util.regex.Pattern; 13 | 14 | /** 15 | * Message工具类 16 | * created by xiepl1997 at 2019-8-21 17 | */ 18 | @Component 19 | public class MessageInfoUtils { 20 | 21 | /** 22 | * @param request 23 | * @return 返回用户的IP地址 24 | */ 25 | public String getUserIp(HttpServletRequest request) { 26 | String ip = request.getHeader("X-Forwarded-For"); 27 | 28 | if (ip == null || ip.length() == 0 || "unknow".equalsIgnoreCase(ip)) { 29 | ip = request.getHeader("Proxy-Client-IP"); 30 | } 31 | if (ip == null || ip.length() == 0 || "unknow".equalsIgnoreCase(ip)) { 32 | ip = request.getHeader("WL-Proxy-Client-IP"); 33 | } 34 | if (ip == null || ip.length() == 0 || "unknow".equalsIgnoreCase(ip)) { 35 | ip = request.getHeader("HTTP_CLIENT_IP"); 36 | } 37 | if (ip == null || ip.length() == 0 || "unknow".equalsIgnoreCase(ip)) { 38 | ip = request.getHeader("HTTP_X_FORWARDED_FOR"); 39 | } 40 | if (ip == null || ip.length() == 0 || "unknow".equalsIgnoreCase(ip)) { 41 | ip = request.getRemoteAddr(); 42 | } 43 | if (ip.equals("127.0.0.1")) { 44 | //根据网卡获取ip 45 | InetAddress inet = null; 46 | try { 47 | inet = InetAddress.getLocalHost(); 48 | } catch (UnknownHostException e) { 49 | e.printStackTrace(); 50 | } 51 | ip = inet.getHostAddress(); 52 | } 53 | 54 | return ip; 55 | } 56 | 57 | /** 58 | * 通过用户ip地址获取地理位置(国家、城市) 59 | * 60 | * @param ip 61 | * @return String[0]国家、String[1]城市 62 | */ 63 | public String[] getAddressByIp(String ip) { 64 | String[] result = new String[2]; 65 | 66 | //高德地图ip定位服务key 67 | String GAODE_IP_SERVICE_KEY = "011d114ca63f41e8abbb3de3dddb1dc9"; 68 | //查询链接 69 | String urlstr = "https://restapi.amap.com/v3/ip?key=" + GAODE_IP_SERVICE_KEY + "&ip=" + ip; 70 | 71 | //获取json 72 | StringBuffer sb = new StringBuffer(); 73 | try { 74 | URL url = new URL(urlstr); 75 | BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream(), "UTF8")); 76 | String line; 77 | while ((line = br.readLine()) != null) { 78 | sb.append(line); 79 | } 80 | } catch (Exception e) { 81 | e.printStackTrace(); 82 | } 83 | 84 | //解析json,采用正则表达式的方式获取province和city 85 | String reg = ".*?\"province\":\"(.*?)\",\"city\".*?"; 86 | String reg1 = ".*?\"city\":\"(.*?)\",\"adcode\".*?"; 87 | 88 | Matcher matcher = Pattern.compile(reg).matcher(sb.toString()); 89 | Matcher matcher1 = Pattern.compile(reg1).matcher(sb.toString()); 90 | 91 | result[0] = result[1] = ""; 92 | if (matcher.find()) { 93 | result[0] = matcher.group(1); 94 | } 95 | if (matcher1.find()) { 96 | result[1] = matcher1.group(1); 97 | } 98 | 99 | return result; 100 | 101 | } 102 | 103 | } -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/utils/githubpageutil/SearchUtil.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.utils.githubpageutil; 2 | 3 | 4 | import com.alibaba.fastjson.JSON; 5 | import com.alibaba.fastjson.JSONObject; 6 | import com.alibaba.fastjson.JSONReader; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.io.BufferedReader; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.io.InputStreamReader; 13 | import java.net.URL; 14 | import java.net.URLEncoder; 15 | import java.util.Map; 16 | import java.util.PriorityQueue; 17 | 18 | /** 19 | * the util to search github page 20 | * 21 | * @author xie peiliang 22 | */ 23 | @Component 24 | public class SearchUtil { 25 | 26 | public final String u = "https://api.github.com/search/repositories?q="; 27 | 28 | public JSONObject readJsonFromUrl(String text, int page, String otherConditions) { 29 | if (!otherConditions.equals("general_match")) 30 | text = "in:name github.io in:description " + text; 31 | else { 32 | otherConditions = ""; 33 | } 34 | JSONObject jsonObject = null; 35 | try { 36 | URL url = new URL(u + URLEncoder.encode(text, "utf-8") + "&per_page=10&page=" + page + otherConditions); 37 | BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8")); 38 | String rowText = readAll(br); 39 | 40 | jsonObject = JSON.parseObject(rowText); 41 | } catch (Exception e) { 42 | e.printStackTrace(); 43 | } 44 | return jsonObject; 45 | } 46 | 47 | public String readAll(BufferedReader br) throws IOException { 48 | StringBuilder sb = new StringBuilder(); 49 | String line = ""; 50 | while ((line = br.readLine()) != null) { 51 | sb.append(line); 52 | } 53 | return sb.toString(); 54 | } 55 | 56 | public class LanguageOrder implements Comparable { 57 | String language; 58 | int count; 59 | 60 | public LanguageOrder(String language, int count) { 61 | this.language = language; 62 | this.count = count; 63 | } 64 | 65 | public String getLanguage() { 66 | return language; 67 | } 68 | 69 | public void setLanguage(String language) { 70 | this.language = language; 71 | } 72 | 73 | public int getCount() { 74 | return count; 75 | } 76 | 77 | public void setCount(int count) { 78 | this.count = count; 79 | } 80 | 81 | @Override 82 | public int compareTo(LanguageOrder o) { 83 | return o.count - this.count; 84 | } 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/utils/im/ChatType.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.utils.im; 2 | 3 | public enum ChatType { 4 | 5 | REGISTER, SINGLE_SENDING, GROUP_SENDING, GROUP_SENDING_All, FILE_MSG_SINGLE_SENDING, FILE_MSG_GROUP_SENDING, 6 | OFFLINE_NOTIFY, AGREE_FRIEND_REQUEST, READ_REPLY_SENDING; 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/utils/im/Constant.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.utils.im; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | 10 | /** 11 | * 全局常量 12 | */ 13 | public class Constant { 14 | 15 | //webSocketServerHandshaker表,用channelId为键,存放握手实例。用来响应CloseWebSocketFrame的请求 16 | public static Map webSocketServerHandshakerMap = 17 | new ConcurrentHashMap<>(); 18 | 19 | //onlineUser表,用userEmail为主键,存放在线的客户端连接上下文 20 | public static Map onlineUser = 21 | new ConcurrentHashMap<>(); 22 | 23 | //用于存储每个在线用户的在线好友id 24 | public static Map> onlineFriends = 25 | new ConcurrentHashMap<>(); 26 | 27 | //id和ctx对照表 28 | public static Map idToCtx = 29 | new ConcurrentHashMap<>(); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/utils/im/IMRedisUtils.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.utils.im; 2 | 3 | import com.xpllyn.pojo.ChatMessage; 4 | import com.xpllyn.pojo.GroupMessage; 5 | import com.xpllyn.pojo.ReadMessage; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.data.redis.core.RedisTemplate; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.sql.Timestamp; 11 | import java.util.List; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | @Component 15 | @Slf4j 16 | public class IMRedisUtils { 17 | 18 | /** 19 | * 插入世界频道群聊新消息 20 | * 两个缓存队列都会进行插入。但只有group这个list中的数据才会持久化到mysql 21 | * @param gm 22 | * @return 23 | */ 24 | public boolean setGroupMessage(GroupMessage gm, RedisTemplate redisTemplate) { 25 | boolean result = false; 26 | try { 27 | redisTemplate.opsForList().rightPush("group", gm); 28 | redisTemplate.expire("group", 25, TimeUnit.HOURS); 29 | redisTemplate.opsForList().rightPush("group-history", gm); 30 | // 历史消息list只保存15条记录 31 | redisTemplate.opsForList().trim("group-history", -15, -1); 32 | result = true; 33 | } catch (Exception e) { 34 | log.error("【redis error】saving the GroupMessage" + gm.toString()); 35 | e.printStackTrace(); 36 | } 37 | return result; 38 | } 39 | 40 | /** 41 | * 插入数据库中取出的消息到世界频道群聊历史记录list 42 | * @param gm 43 | * @param redisTemplate 44 | * @return 45 | */ 46 | public boolean setGroupMessageHistory(GroupMessage gm, RedisTemplate redisTemplate) { 47 | boolean result = false; 48 | try { 49 | redisTemplate.opsForList().rightPush("group-history", gm); 50 | result = true; 51 | } catch (Exception e) { 52 | log.error("【redis error】 saving the GroupMessage" + gm.toString()); 53 | e.printStackTrace(); 54 | } 55 | return result; 56 | } 57 | 58 | /** 59 | * 读取当前群聊消息(未持久化的记录) 60 | * @return 61 | */ 62 | public List getGroupMessage(RedisTemplate redisTemplate) { 63 | if (!exists("group", redisTemplate)) { 64 | return null; 65 | } 66 | List list = redisTemplate.opsForList().range("group", 0, -1); 67 | return list; 68 | } 69 | 70 | /** 71 | * 获取历史群聊记录 72 | * @param redisTemplate 73 | * @return 74 | */ 75 | public List getGroupMessageHistory(RedisTemplate redisTemplate) { 76 | if (!exists("group-history", redisTemplate)) { 77 | return null; 78 | } 79 | List list = redisTemplate.opsForList().range("group-history", 0, -1); 80 | return list; 81 | } 82 | 83 | /** 84 | * 插入单聊新消息到redis中。会插入到历史消息list和当前消息list中。历史消息list 85 | * 只会保存十条并且永不过期,当前消息list通过定时任务保存到MySQL之后去除。 86 | * 当前消息使用list来实现,其中key为双方的id组合,例如“1-2-chat”,小id在前,大id在后。 87 | * 历史消息list的key则为“1-2-history”. 88 | * 使用该list是为了减少数据库的操作,IM系统讲究即时性,聊天记录通过redis记 89 | * 录下来,然后再通过定时任务保存到数据库中。 90 | * 群聊的处理逻辑也是这样的。 91 | * @param cm 92 | * @return 93 | */ 94 | public boolean setChatMessage(ChatMessage cm, RedisTemplate redisTemplate) { 95 | boolean result = false; 96 | int firstId = Math.min(cm.getFrom_user_id(), cm.getTo_user_id()); 97 | int secondId = Math.max(cm.getFrom_user_id(), cm.getTo_user_id()); 98 | String key = firstId + "-" + secondId + "-chat"; 99 | String key_history = firstId + "-" + secondId + "-history"; 100 | try { 101 | redisTemplate.opsForList().rightPush(key, cm); 102 | redisTemplate.expire(key, 25, TimeUnit.HOURS); 103 | // 历史消息记录不设置过期时间。 104 | redisTemplate.opsForList().rightPush(key_history, cm); 105 | // 历史消息list只保存15条记录 106 | redisTemplate.opsForList().trim(key_history, -15, -1); 107 | result = true; 108 | } catch (Exception e) { 109 | log.error("【redis error】 saving the CharMessage" + cm.toString()); 110 | e.printStackTrace(); 111 | } 112 | return result; 113 | } 114 | 115 | /** 116 | * 插入数据库中取出的消息到单聊聊历史记录list 117 | * @param cm 118 | * @param redisTemplate 119 | * @return 120 | */ 121 | public boolean setChatMessageHistory(ChatMessage cm, RedisTemplate redisTemplate) { 122 | boolean result = false; 123 | int firstId = Math.min(cm.getFrom_user_id(), cm.getTo_user_id()); 124 | int secondId = Math.max(cm.getFrom_user_id(), cm.getTo_user_id()); 125 | String key = firstId + "-" + secondId + "-history"; 126 | try { 127 | redisTemplate.opsForList().rightPush(key, cm); 128 | result = true; 129 | } catch (Exception e) { 130 | log.error("【redis error】 saving the GroupMessage" + cm.toString()); 131 | e.printStackTrace(); 132 | } 133 | return result; 134 | } 135 | 136 | /** 137 | * 获取单聊消息 138 | * @return 139 | */ 140 | public List getChatMessage(String key, RedisTemplate redisTemplate) { 141 | if (!exists(key, redisTemplate)) { 142 | return null; 143 | } 144 | List list = redisTemplate.opsForList().range(key, 0, -1); 145 | return list; 146 | } 147 | 148 | /** 149 | * 插入或更新一条已读回执到redis中的value中 150 | * @param readMessage 151 | * @return 152 | */ 153 | public boolean setReadMessage(ReadMessage readMessage, RedisTemplate redisTemplate) { 154 | boolean result = false; 155 | int rid = readMessage.getRead_user_id(); 156 | int sid = readMessage.getSend_user_id(); 157 | String key = rid + "-" + sid + "-readTime"; 158 | try { 159 | redisTemplate.opsForValue().set(key, readMessage); 160 | redisTemplate.expire(key, 25, TimeUnit.HOURS); 161 | result = true; 162 | } catch (Exception e) { 163 | log.error("【redis error】 saving the readMessage"); 164 | e.printStackTrace(); 165 | } 166 | return result; 167 | } 168 | 169 | /** 170 | * 获取一条已读回执 171 | * @param key 172 | * @param redisTemplate 173 | * @return 174 | */ 175 | public ReadMessage getReadMessage(String key, RedisTemplate redisTemplate) { 176 | if (!exists(key, redisTemplate)) { 177 | return null; 178 | } 179 | ReadMessage readMessage = (ReadMessage) redisTemplate.opsForValue().get(key); 180 | return readMessage; 181 | } 182 | 183 | /** 184 | * 判断是否有相应的key 185 | * @param key 186 | * @return 187 | */ 188 | public boolean exists(String key, RedisTemplate redisTemplate) { 189 | return redisTemplate.hasKey(key); 190 | } 191 | 192 | /** 193 | * 删除对应的value 194 | * @param key 195 | */ 196 | public void remove(String key, RedisTemplate redisTemplate) { 197 | if (redisTemplate.hasKey(key)) { 198 | redisTemplate.delete(key); 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/utils/im/ResponseJson.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.utils.im; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import org.springframework.http.HttpStatus; 5 | 6 | import java.util.HashMap; 7 | 8 | public class ResponseJson extends HashMap { 9 | private static final long serialVersionUID = 1L; 10 | 11 | private static final Integer SUCCESS_STATUS = 200; 12 | private static final Integer ERROR_STATUS = -1; 13 | private static final String SUCCESS_MSG = "一切正常"; 14 | 15 | public ResponseJson() { 16 | super(); 17 | } 18 | 19 | public ResponseJson(int code) { 20 | super(); 21 | setStatus(code); 22 | } 23 | 24 | public ResponseJson(HttpStatus status) { 25 | super(); 26 | setStatus(status.value()); 27 | setMsg(status.getReasonPhrase()); 28 | } 29 | 30 | public ResponseJson success() { 31 | put("msg", SUCCESS_MSG); 32 | put("status", SUCCESS_STATUS); 33 | return this; 34 | } 35 | 36 | public ResponseJson success(String msg) { 37 | put("msg", msg); 38 | put("status", SUCCESS_STATUS); 39 | return this; 40 | } 41 | 42 | public ResponseJson error(String msg) { 43 | put("msg", msg); 44 | put("status", ERROR_STATUS); 45 | return this; 46 | } 47 | 48 | public ResponseJson setData(String key, Object obj) { 49 | @SuppressWarnings("unchecked") 50 | HashMap data = (HashMap) get("data"); 51 | if (data == null) { 52 | data = new HashMap(); 53 | put("data", data); 54 | } 55 | data.put(key, obj); 56 | return this; 57 | } 58 | 59 | public ResponseJson setStatus(int status) { 60 | put("status", status); 61 | return this; 62 | } 63 | 64 | public ResponseJson setMsg(String msg) { 65 | put("msg", msg); 66 | return this; 67 | } 68 | 69 | public ResponseJson setValue(String key, Object val) { 70 | put(key, val); 71 | return this; 72 | } 73 | 74 | /** 75 | * 返回JSON字符串 76 | */ 77 | @Override 78 | public String toString() { 79 | return JSONObject.toJSONString(this); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/xpllyn/utils/schedule/ChatMessageSaveTask.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn.utils.schedule; 2 | 3 | import com.xpllyn.pojo.ChatMessage; 4 | import com.xpllyn.pojo.GroupMessage; 5 | import com.xpllyn.pojo.ReadMessage; 6 | import com.xpllyn.service.IMRedisService; 7 | import com.xpllyn.service.impl.ChatService; 8 | import com.xpllyn.service.impl.ReadMessageService; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.scheduling.annotation.EnableScheduling; 13 | import org.springframework.scheduling.annotation.Scheduled; 14 | 15 | import java.sql.Timestamp; 16 | import java.util.List; 17 | import java.util.Set; 18 | 19 | @Configuration 20 | @EnableScheduling 21 | @Slf4j 22 | public class ChatMessageSaveTask { 23 | 24 | @Autowired 25 | private IMRedisService imRedisService; 26 | 27 | @Autowired 28 | private ChatService chatService; 29 | 30 | @Autowired 31 | private ReadMessageService readMessageService; 32 | 33 | /** 34 | * 定时任务,每天凌晨3点进行redis缓存的持久化,将聊天记录、已读回执记录保存到数据库 35 | */ 36 | @Scheduled(cron = "0 0 3 * * ?") 37 | public void chatMessageSave() { 38 | log.info("【定时任务】 开始!"); 39 | 40 | // 获取缓存中的群聊天缓存 41 | List gm = imRedisService.getGroupMessage(); 42 | // 聊天记录持久化 43 | if (gm != null) { 44 | try { 45 | chatService.insertGroupMessages(gm); 46 | // 清空缓存 47 | imRedisService.remove("group"); 48 | log.info("【定时任务】 " + gm.size() + "条世界频道新聊天记录持久化到MySQL。"); 49 | log.info("【定时任务】 " + gm.size() + "条世界频道新聊天记录缓存已清空。"); 50 | } catch (Exception e) { 51 | log.error("【定时任务】 Redis出错!"); 52 | } 53 | } else { 54 | log.info("【定时任务】 无世界频道新聊天记录持久化到MySQL。"); 55 | } 56 | 57 | // 获取单聊聊天缓存 58 | String pattern = "*-chat"; 59 | Set keys = imRedisService.getRedisTemplate().keys(pattern); 60 | int cnt = 0; 61 | if (keys != null) { 62 | for (String key : keys) { 63 | List cm = imRedisService.getChatMessage(key); 64 | // 聊天记录持久化 65 | if (cm != null) { 66 | try { 67 | chatService.insertChatMessages(cm); 68 | // 清空缓存 69 | imRedisService.remove(key); 70 | cnt += cm.size(); 71 | } catch (Exception e) { 72 | log.error("【定时任务】 Redis出错!"); 73 | } 74 | } 75 | } 76 | } 77 | if (cnt != 0) { 78 | log.info("【定时任务】 " + cnt + "条单聊新聊天记录从Redis新消息缓存持久化到MySQL。"); 79 | log.info("【Redis】 " + cnt + "新消息缓存已清除。"); 80 | } else { 81 | log.info("【定时任务】 无单聊新聊天记录持久化到MySQL。"); 82 | } 83 | 84 | // 获取redis中的已读回执 85 | String pattern1 = "*-readTime"; 86 | Set keys1 = imRedisService.getRedisTemplate().keys(pattern1); 87 | int cnt1 = 0; 88 | int cnt2 = 0; 89 | if (keys != null) { 90 | for (String key : keys1) { 91 | ReadMessage readMessage = imRedisService.getReadMessage(key); 92 | if (readMessage != null) { 93 | int rid = readMessage.getRead_user_id(); 94 | int sid = readMessage.getSend_user_id(); 95 | Timestamp time = readMessage.getTime(); 96 | try { 97 | ReadMessage rm = readMessageService.getReadMessage(rid, sid); 98 | if (rm != null) { 99 | if (!rm.getTime().equals(time)) { 100 | readMessageService.updateReadMessage(rid, sid, time); 101 | cnt1++; 102 | } else { 103 | cnt2++; 104 | } 105 | } else { 106 | readMessageService.insertReadMessage(readMessage); 107 | cnt1++; 108 | } 109 | // 清空缓存 110 | imRedisService.remove(key); 111 | } catch (Exception e) { 112 | log.error("【定时任务】 Redis出错!"); 113 | } 114 | } 115 | } 116 | } 117 | log.info("【定时任务】 " + cnt1 + "条已读回执已插入或更新到MySQL。"); 118 | if (cnt2 != 0) { 119 | log.info("【定时任务】 " + cnt2 + "条已读回执无需持久化到MySQL。"); 120 | } 121 | log.info("【定时任务】 结束!"); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | %d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) --- [%15.15(%thread)] %cyan(%-40.40(%logger{40})) : %msg%n 19 | 20 | UTF-8 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | logs/project_info.log 31 | 32 | true 33 | 34 | ERROR 35 | DENY 36 | ACCEPT 37 | 38 | 41 | 42 | 43 | 44 | 45 | logs/project_info.%d.%i.log 46 | 48 | 30 49 | 50 | 20GB 51 | 52 | 10MB 53 | 54 | 55 | 56 | 57 | %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level --- [%15.15(%thread)] %-40.40(%logger{40}) : %msg%n 58 | 59 | UTF-8 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | logs/project_error.log 70 | 71 | true 72 | 73 | 74 | ERROR 75 | 76 | 79 | 80 | 81 | 82 | 83 | logs/project_error.%d.%i.log 84 | 86 | 30 87 | 88 | 20GB 89 | 90 | 10MB 91 | 92 | 93 | 94 | 95 | %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level --- [%15.15(%thread)] %-40.40(%logger{40}) : %msg%n 96 | 97 | UTF-8 98 | 99 | 100 | 101 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /src/main/resources/static/bootstrap-3.3.7-dist/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/bootstrap-3.3.7-dist/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/main/resources/static/bootstrap-3.3.7-dist/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/bootstrap-3.3.7-dist/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/main/resources/static/bootstrap-3.3.7-dist/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/bootstrap-3.3.7-dist/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /src/main/resources/static/bootstrap-3.3.7-dist/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/bootstrap-3.3.7-dist/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/main/resources/static/bootstrap-3.3.7-dist/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /src/main/resources/static/chat/css/reset.min.css: -------------------------------------------------------------------------------- 1 | html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}table{border-collapse:collapse;border-spacing:0} -------------------------------------------------------------------------------- /src/main/resources/static/chat/img/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/chat/img/1.jpg -------------------------------------------------------------------------------- /src/main/resources/static/chat/img/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/chat/img/2.jpg -------------------------------------------------------------------------------- /src/main/resources/static/chat/img/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/chat/img/3.jpg -------------------------------------------------------------------------------- /src/main/resources/static/chat/img/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/chat/img/4.jpg -------------------------------------------------------------------------------- /src/main/resources/static/chat/img/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/chat/img/5.jpg -------------------------------------------------------------------------------- /src/main/resources/static/chat/img/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/chat/img/6.jpg -------------------------------------------------------------------------------- /src/main/resources/static/chat/img/attachment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/chat/img/attachment.png -------------------------------------------------------------------------------- /src/main/resources/static/chat/img/groupicon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/chat/img/groupicon.jpg -------------------------------------------------------------------------------- /src/main/resources/static/chat/img/image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/chat/img/image.jpg -------------------------------------------------------------------------------- /src/main/resources/static/chat/img/name-type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/chat/img/name-type.png -------------------------------------------------------------------------------- /src/main/resources/static/chat/img/send.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/chat/img/send.png -------------------------------------------------------------------------------- /src/main/resources/static/chat/img/smiley.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/chat/img/smiley.png -------------------------------------------------------------------------------- /src/main/resources/static/chat/js/chatroom.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/chat/js/chatroom.js -------------------------------------------------------------------------------- /src/main/resources/static/css/LoginCSS.css: -------------------------------------------------------------------------------- 1 | #j1{ 2 | margin-bottom: 0px; 3 | background: url("../images/bg.jpg"); 4 | background-position: center center; 5 | background-size: cover; 6 | background-repeat: no-repeat; 7 | 8 | } 9 | 10 | #LoginForm{ 11 | margin-left: 10px; 12 | margin-right: 10px; 13 | } 14 | 15 | #j2{ 16 | margin-bottom: 0px; 17 | background-image: url("../images/bg.JPG"); 18 | background-position: center center; 19 | background-size: cover; 20 | background-repeat: no-repeat; 21 | } 22 | 23 | #RegisterForm{ 24 | margin-left: 10px; 25 | margin-right: 10px; 26 | } 27 | 28 | .n{ 29 | 30 | } 31 | 32 | .buttom { 33 | text-align: center; 34 | padding-left: 30px; 35 | position: fixed; 36 | bottom: 0px; 37 | left: 0; 38 | height: 40px; 39 | width: 100%; 40 | line-height: 40px; 41 | text-align: center; 42 | font-size: 10px; 43 | background-color: rgba(0,0,0,0.3); 44 | } 45 | 46 | .modbg{ 47 | background: url("../images/githubpagesearchbg.jpg"); 48 | background-position: center center; 49 | background-size: cover; 50 | position: fixed; 51 | width: 100%; 52 | background-repeat: no-repeat; 53 | background-size: cover; 54 | } 55 | 56 | body{ 57 | display:inline-block; 58 | background-repeat: no-repeat; 59 | background-size: 100% 100%; 60 | background-attachment: fixed; 61 | padding-top: 40px; 62 | } 63 | 64 | html, body{ 65 | height: 100%; 66 | overflow: auto; 67 | } 68 | -------------------------------------------------------------------------------- /src/main/resources/static/css/chatroom.css: -------------------------------------------------------------------------------- 1 | .modbg{ 2 | background: url("../images/githubpagesearchbg.jpg"); 3 | background-position: center center; 4 | background-size: cover; 5 | position: fixed; 6 | width: 100%; 7 | background-repeat: no-repeat; 8 | background-size: cover; 9 | } 10 | 11 | html, body{ 12 | height: 100%; 13 | overflow: auto; 14 | } 15 | /*设置背景样式*/ 16 | body{ 17 | display:inline-block; 18 | background-repeat: no-repeat; 19 | background-size: 100% 100%; 20 | background-attachment: fixed; 21 | padding-top: 40px; 22 | } 23 | 24 | .buttom { 25 | text-align: center; 26 | padding-left: 30px; 27 | position: fixed; 28 | bottom: 0px; 29 | left: 0; 30 | height: 40px; 31 | width: 100%; 32 | line-height: 40px; 33 | font-size: 10px; 34 | background-color: rgba(0,0,0,0.3); 35 | } 36 | 37 | .chatComponent { 38 | margin: 0 auto; 39 | width: 750px; 40 | background: #444753; 41 | border-radius: 5px; 42 | } 43 | 44 | .people-list { 45 | width: 30%; 46 | float: left; 47 | } 48 | 49 | .people-list .search { 50 | padding: 20px; 51 | } 52 | 53 | .people-list input { 54 | border-radius: 3px; 55 | border: none; 56 | padding: 14px; 57 | color: white; 58 | background: #6A6C75; 59 | width: 90%; 60 | font-size: 14px; 61 | } 62 | 63 | .search_text { 64 | outline: none; 65 | border: 2px solid #a7aab5; 66 | height: 30px; 67 | width: 100%; 68 | box-sizing: border-box; 69 | text-indent: 6px; 70 | font-size: 13px; 71 | padding: 5px; 72 | border-radius: 10px 10px 10px 10px; 73 | } 74 | /* 输入框聚焦 */ 75 | input:focus { 76 | color: #000; 77 | background-color: #fff; 78 | border-color: #4e6ef2 !important; 79 | border-radius: 10px 10px 10px 10px; 80 | } 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/main/resources/static/css/githubPageSearch.css: -------------------------------------------------------------------------------- 1 | 2 | input { 3 | outline: none; 4 | border: 2px solid #a7aab5; 5 | height: 45px; 6 | width: 85%; 7 | box-sizing: border-box; 8 | text-indent: 6px; 9 | font-size: 16px; 10 | border-radius: 10px 0 0 10px; 11 | } 12 | /* 输入框聚焦 */ 13 | input:focus { 14 | color: #000; 15 | background-color: #fff; 16 | border-color: #4e6ef2 !important; 17 | border-radius: 10px 0 0 10px; 18 | } 19 | .searchbutton { 20 | font-size: 17px; 21 | } 22 | 23 | .buttonx { 24 | height: 45px; 25 | width: 15%; 26 | float: right; 27 | border: none; 28 | color: white; 29 | box-sizing: border-box; 30 | background-color: #4e6ef2; 31 | border-radius: 0 10px 10px 0; 32 | cursor: pointer; 33 | } 34 | .imges { 35 | margin: 0 auto; 36 | } 37 | .cen { 38 | text-align: center; 39 | margin-bottom: 80px; 40 | } 41 | .buttom { 42 | text-align: center; 43 | padding-left: 30px; 44 | position: fixed; 45 | bottom: 0px; 46 | left: 0; 47 | height: 40px; 48 | width: 100%; 49 | line-height: 40px; 50 | text-align: center; 51 | font-size: 10px; 52 | background-color: rgba(0,0,0,0.3); 53 | } 54 | 55 | /* 登录按钮的下拉登录图片 */ 56 | .settings { 57 | position: relative; 58 | left: 13px; 59 | background-color: #4e71f2; 60 | color: #fff; 61 | border-radius: 5px; 62 | font-size: 12px; 63 | width: 50px; 64 | height: 25px; 65 | display: inline-block; 66 | } 67 | .dropdown-settings { 68 | position: relative; 69 | display: inline-block; 70 | } 71 | 72 | .dropdown-content-settings { 73 | display: none; 74 | position: absolute; 75 | right: -10px; 76 | background-color: #f9f9f9; 77 | min-width: 160px; 78 | box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); 79 | } 80 | 81 | .dropdown-settings:hover .dropdown-content-settings { 82 | display: block; 83 | } 84 | 85 | .modbg{ 86 | background: url("../images/githubpagesearchbg.jpg"); 87 | background-position: center center; 88 | background-size: cover; 89 | position: fixed; 90 | width: 100%; 91 | background-repeat: no-repeat; 92 | background-size: cover; 93 | } 94 | 95 | body{ 96 | display:inline-block; 97 | background-repeat: no-repeat; 98 | background-size: 100% 100%; 99 | background-attachment: fixed; 100 | padding-top: 40px; 101 | } 102 | 103 | html, body{ 104 | height: 100%; 105 | overflow: auto; 106 | } 107 | 108 | #circle{ 109 | margin: 20px auto; 110 | width: 45px; 111 | height: 45px; 112 | border: 5px white solid; 113 | border-left-color: #ff5500; 114 | border-right-color:#0c80fe; 115 | border-radius: 100%; 116 | animation: loading1 1s infinite linear; 117 | } 118 | @keyframes loading1{ 119 | from{transform: rotate(0deg)}to{transform: rotate(360deg)} 120 | } -------------------------------------------------------------------------------- /src/main/resources/static/css/homepage.css: -------------------------------------------------------------------------------- 1 | html, body{ 2 | height: 100%; 3 | overflow: auto; 4 | } 5 | /*设置背景样式*/ 6 | body{ 7 | display:inline-block; 8 | background-repeat: no-repeat; 9 | background-size: 100% 100%; 10 | background-attachment: fixed; 11 | padding-top: 40px; 12 | } 13 | 14 | /*头像样式*/ 15 | .icon{ 16 | width: 140px; 17 | height: 140px; 18 | border-radius: 50%; 19 | background-repeat: no-repeat; 20 | -webkit-border-radius: 100px; 21 | -moz-border-radius: 100px; 22 | overflow: hidden; 23 | /*position: fixed;*/ 24 | position: relative; 25 | left: 50%; 26 | top: 27%; 27 | padding-bottom: 100px; 28 | margin-left: -70px; 29 | animation: icon_animation 3s infinite alternate; 30 | animation-timing-function: ease-in-out; 31 | -webkit-transition: transform 3S; 32 | } 33 | 34 | /*网站图标样式*/ 35 | .title{ 36 | background-repeat: no-repeat; 37 | position: fixed; 38 | left: 50px; 39 | top: 20px; 40 | } 41 | 42 | /*头像动画*/ 43 | @keyframes icon_animation{ 44 | 0% {top: 27%;} 45 | 50% {top: 30%;} 46 | 100% {top: 27%;} 47 | } 48 | 49 | /*鼠标悬停头像旋转*/ 50 | .icon:hover{ 51 | transform: rotate(360deg); 52 | } 53 | 54 | /*背景和首页文字样式*/ 55 | .modbg{ 56 | background: url("../images/lynbg.jpg"); 57 | background-position: center center; 58 | background-size: cover; 59 | position: fixed; 60 | width: 100%; 61 | background-repeat: no-repeat; 62 | background-size: cover; 63 | } 64 | 65 | .header-words{ 66 | /*首选字体琥珀*/ 67 | font-family: "STHupo","Microsoft YaHei","宋体",sans-serif; 68 | } 69 | 70 | /*留言板样式*/ 71 | .talk{ 72 | font-family: "宋体"; 73 | margin-top: 40px; 74 | } 75 | 76 | /*留言列表样式*/ 77 | .message_list_title{ 78 | margin-top: 40px; 79 | } 80 | 81 | .message-content{ 82 | height: 200px; 83 | width: 100%; 84 | padding-top: 30px; 85 | padding-left: 15px; 86 | border-radius: 10px; 87 | font-size: large; 88 | } 89 | 90 | /*电子书下载和近期的文章list-group滚动条设置*/ 91 | #scroll-2{ 92 | width:200px; 93 | height:200px; 94 | overflow:auto; 95 | margin-bottom: 20px; 96 | } 97 | #scroll-2 div{ 98 | width:400px; 99 | height:400px; 100 | } 101 | #scroll-2::-webkit-scrollbar{ 102 | width:4px; 103 | height:4px; 104 | } 105 | #scroll-2::-webkit-scrollbar-track{ 106 | background: #f6f6f6; 107 | border-radius:2px; 108 | } 109 | #scroll-2::-webkit-scrollbar-thumb{ 110 | background: #aaa; 111 | border-radius:2px; 112 | } 113 | #scroll-2::-webkit-scrollbar-thumb:hover{ 114 | background: #747474; 115 | } 116 | #scroll-2::-webkit-scrollbar-corner{ 117 | background: #f6f6f6; 118 | } 119 | 120 | .booklist{ 121 | 122 | } 123 | .blog{ 124 | 125 | } 126 | .blog-more{ 127 | 128 | } 129 | 130 | .buttom { 131 | text-align: center; 132 | padding-left: 30px; 133 | position: fixed; 134 | bottom: 0px; 135 | left: 0; 136 | height: 40px; 137 | width: 100%; 138 | line-height: 40px; 139 | text-align: center; 140 | font-size: 10px; 141 | background-color: rgba(0,0,0,0.3); 142 | } 143 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/HELP-US-OUT.txt: -------------------------------------------------------------------------------- 1 | I hope you love Font Awesome. If you've found it useful, please do me a favor and check out my latest project, 2 | Fort Awesome (https://fortawesome.com). It makes it easy to put the perfect icons on your website. Choose from our awesome, 3 | comprehensive icon sets or copy and paste your own. 4 | 5 | Please. Check it out. 6 | 7 | -Dave Gandy 8 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/font-awesome-4.7.0/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/font-awesome-4.7.0/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/font-awesome-4.7.0/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/less/animated.less: -------------------------------------------------------------------------------- 1 | // Animated Icons 2 | // -------------------------- 3 | 4 | .@{fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .@{fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/less/bordered-pulled.less: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em @fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .@{fa-css-prefix}-pull-left { float: left; } 11 | .@{fa-css-prefix}-pull-right { float: right; } 12 | 13 | .@{fa-css-prefix} { 14 | &.@{fa-css-prefix}-pull-left { margin-right: .3em; } 15 | &.@{fa-css-prefix}-pull-right { margin-left: .3em; } 16 | } 17 | 18 | /* Deprecated as of 4.4.0 */ 19 | .pull-right { float: right; } 20 | .pull-left { float: left; } 21 | 22 | .@{fa-css-prefix} { 23 | &.pull-left { margin-right: .3em; } 24 | &.pull-right { margin-left: .3em; } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/less/core.less: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/less/fixed-width.less: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .@{fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/less/font-awesome.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables.less"; 7 | @import "mixins.less"; 8 | @import "path.less"; 9 | @import "core.less"; 10 | @import "larger.less"; 11 | @import "fixed-width.less"; 12 | @import "list.less"; 13 | @import "bordered-pulled.less"; 14 | @import "animated.less"; 15 | @import "rotated-flipped.less"; 16 | @import "stacked.less"; 17 | @import "icons.less"; 18 | @import "screen-reader.less"; 19 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/less/larger.less: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .@{fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .@{fa-css-prefix}-2x { font-size: 2em; } 11 | .@{fa-css-prefix}-3x { font-size: 3em; } 12 | .@{fa-css-prefix}-4x { font-size: 4em; } 13 | .@{fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/less/list.less: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: @fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .@{fa-css-prefix}-li { 11 | position: absolute; 12 | left: -@fa-li-width; 13 | width: @fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.@{fa-css-prefix}-lg { 17 | left: (-@fa-li-width + (4em / 14)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | .fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | .fa-icon-rotate(@degrees, @rotation) { 15 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation})"; 16 | -webkit-transform: rotate(@degrees); 17 | -ms-transform: rotate(@degrees); 18 | transform: rotate(@degrees); 19 | } 20 | 21 | .fa-icon-flip(@horiz, @vert, @rotation) { 22 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation}, mirror=1)"; 23 | -webkit-transform: scale(@horiz, @vert); 24 | -ms-transform: scale(@horiz, @vert); 25 | transform: scale(@horiz, @vert); 26 | } 27 | 28 | 29 | // Only display content to screen readers. A la Bootstrap 4. 30 | // 31 | // See: http://a11yproject.com/posts/how-to-hide-content/ 32 | 33 | .sr-only() { 34 | position: absolute; 35 | width: 1px; 36 | height: 1px; 37 | padding: 0; 38 | margin: -1px; 39 | overflow: hidden; 40 | clip: rect(0,0,0,0); 41 | border: 0; 42 | } 43 | 44 | // Use in conjunction with .sr-only to only display content when it's focused. 45 | // 46 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 47 | // 48 | // Credit: HTML5 Boilerplate 49 | 50 | .sr-only-focusable() { 51 | &:active, 52 | &:focus { 53 | position: static; 54 | width: auto; 55 | height: auto; 56 | margin: 0; 57 | overflow: visible; 58 | clip: auto; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/less/path.less: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}'); 7 | src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'), 8 | url('@{fa-font-path}/fontawesome-webfont.woff2?v=@{fa-version}') format('woff2'), 9 | url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'), 10 | url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'), 11 | url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/less/rotated-flipped.less: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); } 5 | .@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); } 6 | .@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); } 7 | 8 | .@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); } 9 | .@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .@{fa-css-prefix}-rotate-90, 15 | :root .@{fa-css-prefix}-rotate-180, 16 | :root .@{fa-css-prefix}-rotate-270, 17 | :root .@{fa-css-prefix}-flip-horizontal, 18 | :root .@{fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/less/screen-reader.less: -------------------------------------------------------------------------------- 1 | // Screen Readers 2 | // ------------------------- 3 | 4 | .sr-only { .sr-only(); } 5 | .sr-only-focusable { .sr-only-focusable(); } 6 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/less/stacked.less: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .@{fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .@{fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .@{fa-css-prefix}-inverse { color: @fa-inverse; } 21 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/scss/_animated.scss: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .#{$fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .#{$fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/scss/_bordered-pulled.scss: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em $fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .#{$fa-css-prefix}-pull-left { float: left; } 11 | .#{$fa-css-prefix}-pull-right { float: right; } 12 | 13 | .#{$fa-css-prefix} { 14 | &.#{$fa-css-prefix}-pull-left { margin-right: .3em; } 15 | &.#{$fa-css-prefix}-pull-right { margin-left: .3em; } 16 | } 17 | 18 | /* Deprecated as of 4.4.0 */ 19 | .pull-right { float: right; } 20 | .pull-left { float: left; } 21 | 22 | .#{$fa-css-prefix} { 23 | &.pull-left { margin-right: .3em; } 24 | &.pull-right { margin-left: .3em; } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/scss/_core.scss: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/scss/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .#{$fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/scss/_larger.scss: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .#{$fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .#{$fa-css-prefix}-2x { font-size: 2em; } 11 | .#{$fa-css-prefix}-3x { font-size: 3em; } 12 | .#{$fa-css-prefix}-4x { font-size: 4em; } 13 | .#{$fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/scss/_list.scss: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: $fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .#{$fa-css-prefix}-li { 11 | position: absolute; 12 | left: -$fa-li-width; 13 | width: $fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.#{$fa-css-prefix}-lg { 17 | left: -$fa-li-width + (4em / 14); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | @mixin fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | @mixin fa-icon-rotate($degrees, $rotation) { 15 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation})"; 16 | -webkit-transform: rotate($degrees); 17 | -ms-transform: rotate($degrees); 18 | transform: rotate($degrees); 19 | } 20 | 21 | @mixin fa-icon-flip($horiz, $vert, $rotation) { 22 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}, mirror=1)"; 23 | -webkit-transform: scale($horiz, $vert); 24 | -ms-transform: scale($horiz, $vert); 25 | transform: scale($horiz, $vert); 26 | } 27 | 28 | 29 | // Only display content to screen readers. A la Bootstrap 4. 30 | // 31 | // See: http://a11yproject.com/posts/how-to-hide-content/ 32 | 33 | @mixin sr-only { 34 | position: absolute; 35 | width: 1px; 36 | height: 1px; 37 | padding: 0; 38 | margin: -1px; 39 | overflow: hidden; 40 | clip: rect(0,0,0,0); 41 | border: 0; 42 | } 43 | 44 | // Use in conjunction with .sr-only to only display content when it's focused. 45 | // 46 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 47 | // 48 | // Credit: HTML5 Boilerplate 49 | 50 | @mixin sr-only-focusable { 51 | &:active, 52 | &:focus { 53 | position: static; 54 | width: auto; 55 | height: auto; 56 | margin: 0; 57 | overflow: visible; 58 | clip: auto; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/scss/_path.scss: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}'); 7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), 8 | url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'), 9 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), 10 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), 11 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/scss/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); } 5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); } 6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); } 7 | 8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); } 9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .#{$fa-css-prefix}-rotate-90, 15 | :root .#{$fa-css-prefix}-rotate-180, 16 | :root .#{$fa-css-prefix}-rotate-270, 17 | :root .#{$fa-css-prefix}-flip-horizontal, 18 | :root .#{$fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/scss/_screen-reader.scss: -------------------------------------------------------------------------------- 1 | // Screen Readers 2 | // ------------------------- 3 | 4 | .sr-only { @include sr-only(); } 5 | .sr-only-focusable { @include sr-only-focusable(); } 6 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/scss/_stacked.scss: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .#{$fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .#{$fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .#{$fa-css-prefix}-inverse { color: $fa-inverse; } 21 | -------------------------------------------------------------------------------- /src/main/resources/static/font-awesome-4.7.0/scss/font-awesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables"; 7 | @import "mixins"; 8 | @import "path"; 9 | @import "core"; 10 | @import "larger"; 11 | @import "fixed-width"; 12 | @import "list"; 13 | @import "bordered-pulled"; 14 | @import "animated"; 15 | @import "rotated-flipped"; 16 | @import "stacked"; 17 | @import "icons"; 18 | @import "screen-reader"; 19 | -------------------------------------------------------------------------------- /src/main/resources/static/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/images/bg.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/bg2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/images/bg2.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/bg3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/images/bg3.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/bg4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/images/bg4.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/bg5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/images/bg5.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/githubpagesearch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/images/githubpagesearch.png -------------------------------------------------------------------------------- /src/main/resources/static/images/githubpagesearch_25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/images/githubpagesearch_25.png -------------------------------------------------------------------------------- /src/main/resources/static/images/githubpagesearch_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/images/githubpagesearch_40.png -------------------------------------------------------------------------------- /src/main/resources/static/images/githubpagesearchbg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/images/githubpagesearchbg.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/images/icon.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/icon150.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/images/icon150.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/lynbg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/images/lynbg.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/lynbg1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/images/lynbg1.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/newtitle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/images/newtitle.png -------------------------------------------------------------------------------- /src/main/resources/static/images/pic1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/images/pic1.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/pic2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/images/pic2.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/pic5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/images/pic5.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/images/title.png -------------------------------------------------------------------------------- /src/main/resources/static/images/title1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/images/title1.png -------------------------------------------------------------------------------- /src/main/resources/static/images/title150_70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiepl1997/xpllyn/88b2f83b25e87cd1adc75600a1bf5e894bb028b9/src/main/resources/static/images/title150_70.png -------------------------------------------------------------------------------- /src/main/resources/static/js/functions.js: -------------------------------------------------------------------------------- 1 | // // 图标元素淡入 2 | // function load_animation(){ 3 | // $(document).ready(function() { 4 | // $("#titleimg").show(function() { 5 | // $("#titleimg").hide(); 6 | // $("#titleimg").fadeIn(2000); 7 | // }); 8 | // $("#icon_xpl").show(function() { 9 | // $("#icon_xpl").hide(); 10 | // $("#icon_xpl").fadeIn(3000); 11 | // }); 12 | // }); 13 | // } 14 | 15 | // //xpllyn图标动画 16 | // function title_animation(){ 17 | // $(document).ready(function() { 18 | // $("#titleimg").mouseenter(function(event) { 19 | // $("#titleimg").animate({ 20 | // height: '90px', 21 | // width: '180px' 22 | // },"fast"); 23 | // }); 24 | // $("#titleimg").mouseleave(function(event) { 25 | // $("#titleimg").animate({ 26 | // height: '70px', 27 | // width: '150px' 28 | // },"fast") 29 | // }); 30 | // }); 31 | // } 32 | 33 | $(document).ready(function() { 34 | //提交留言按钮 by xiepl1997 at 2019-8-24 35 | $("#submit_message").click(function(event) { 36 | if($("#message").val().trim() == ''){ 37 | alert("未填写留言哦~"); 38 | return; 39 | } 40 | else{ 41 | 42 | var str = $("#message").val().trim(); 43 | var param = { 44 | message : str, 45 | type : 'insertMessage' 46 | }; 47 | 48 | $.ajax({ 49 | url: './insertMessage', 50 | type: 'POST', 51 | contentType: 'application/x-www-form-urlencoded', 52 | // data: JSON.stringify(param), 53 | data: param, 54 | beforeSend : function(){ 55 | $("#loading").modal('show'); 56 | }, 57 | success : function(messageList){ 58 | if(messageList == null){ 59 | alert("提交失败"); 60 | return; 61 | } 62 | else{ 63 | // $("#message").val("").focus(); 64 | // var htmls = "\n" + 65 | // "\n" + 66 | // "

\n" + 67 | // "

\n" + 68 | // "

\n" + 69 | // "
\n" + 70 | // ""; 71 | // $("#message_group").innerHTML(htmls); 72 | var htmls = ""; 73 | var count = messageList.length; 74 | if(count > 8){ 75 | count = 8; 76 | } 77 | for(var i = 0; i < count; i++){ 78 | // var span = Date.parse(messageList[i].create_time); 79 | var dt = new Date(messageList[i].create_time); 80 | var timeStr = dt.getFullYear() + '-' + (dt.getMonth() + 1) + '-' + dt.getDate() + ' ' + dt.getHours() + ':' + dt.getMinutes() + ':' + dt.getSeconds(); 81 | 82 | htmls += ""; 83 | htmls += "

" + messageList[i].name + "

"; 84 | htmls += "

" + escapeHTML(messageList[i].content) + "

"; 85 | htmls += "

" + timeStr + "

"; 86 | htmls += "
"; 87 | } 88 | //htmls += "
……More……
"; 89 | $("#message_group").html(htmls); 90 | $("#message").val(""); 91 | alert("感谢您的留言!"); 92 | } 93 | }, 94 | error : function (data) { 95 | alert("提交失败!"); 96 | }, 97 | complete : function(){ 98 | $("#loading").modal('hide'); 99 | } 100 | }) 101 | 102 | 103 | 104 | } 105 | }); 106 | 107 | //显示全部留言 by xiepl1997 at 2019-8-25 108 | $("#show_all_message").click(function () { 109 | $.ajax({ 110 | url: './getAllMessages', 111 | type: 'GET', 112 | dataType: 'json', 113 | success: function (mList) { 114 | var htmls = ""; 115 | for(var i = 0; i < mList.length; i++){ 116 | // var span = new Date(messageList[i].create_time); 117 | var dt = new Date(mList[i].create_time); 118 | var timeStr = dt.getFullYear() + '-' + (dt.getMonth() + 1) + '-' + dt.getDate() + ' ' + dt.getHours() + ':' + dt.getMinutes() + ':' + dt.getSeconds(); 119 | 120 | if(i%2 == 0) 121 | htmls += ""; 122 | else 123 | htmls += ""; 124 | htmls += "

" + mList[i].name + "

"; 125 | htmls += "

" + escapeHTML(mList[i].content) + "

"; 126 | htmls += "

" + timeStr + "

"; 127 | htmls += "
"; 128 | } 129 | $("#all_message").html(htmls); 130 | }, 131 | error : function (data) { 132 | alert("获取失败!"); 133 | } 134 | }) 135 | }) 136 | 137 | //用户点击电子书,插入一条日志 138 | $(".booklist").click(function (e) { 139 | var str = $(this).attr("download"); //获取书名 140 | var param = { 141 | name : str, 142 | type : 'book_download' 143 | }; 144 | $.ajax({ 145 | url: './ebook_download', 146 | type: 'post', 147 | contentType: 'application/x-www-form-urlencoded', 148 | data: param, 149 | success: function (log) { 150 | 151 | } 152 | }) 153 | }) 154 | 155 | //用户点击文章时,插入一条日志 156 | $(".blog").click(function (e) { 157 | var str = $(this).children("h4").text(); 158 | var param = { 159 | name : str, 160 | type : 'readBlog' 161 | }; 162 | $.ajax({ 163 | url: './read_blog', 164 | type: 'post', 165 | contentType: 'application/x-www-form-urlencoded', 166 | data: param, 167 | success: function (log) { 168 | 169 | } 170 | }) 171 | }) 172 | 173 | //用户点击“更多文章”时,插入一条日志 174 | $(".blog-more").click(function (e) { 175 | var param = { 176 | name : '....more....', 177 | type : 'readMoreBlog' 178 | }; 179 | $.ajax({ 180 | url: './read_blog_more', 181 | type: 'post', 182 | contentType: 'application/x-www-form-urlencoded', 183 | data: param, 184 | success: function (log) { 185 | 186 | } 187 | }) 188 | }) 189 | }); 190 | 191 | /** 192 | * function:显示注册界面 193 | * date:2020/2/4 194 | * @constructor 195 | */ 196 | function ShowRegister() { 197 | $("#c1").removeAttr("hidden"); 198 | } 199 | 200 | /** 201 | *. 转义html(防XSS攻击) 202 | *. @param str 字符串 203 | */ 204 | function escapeHTML (str) { 205 | return str.replace( 206 | /[&<>'"]/g, 207 | tag => 208 | ({ 209 | '&': '&', 210 | '<': '<', 211 | '>': '>', 212 | "'": ''', 213 | '"': '"' 214 | }[tag] || tag) 215 | ); 216 | } 217 | 218 | 219 | 220 | -------------------------------------------------------------------------------- /src/main/resources/static/js/jq-paginator.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jq-paginator v2.0.2 3 | * http://jqPaginator.keenwon.com 4 | */ 5 | 6 | (function () { 7 | 'use strict'; 8 | 9 | /* eslint-env jquery */ 10 | 11 | var $ = jQuery; 12 | 13 | $.jqPaginator = function (el, options) { 14 | if (!(this instanceof $.jqPaginator)) { 15 | return new $.jqPaginator(el, options) 16 | } 17 | 18 | var self = this; 19 | 20 | self.$container = $(el); 21 | 22 | self.$container.data('jqPaginator', self); 23 | 24 | self.init = function () { 25 | if (options.first || options.prev || options.next || options.last || options.page) { 26 | options = $.extend( 27 | {}, 28 | { 29 | first: '', 30 | prev: '', 31 | next: '', 32 | last: '', 33 | page: '' 34 | }, 35 | options 36 | ); 37 | } 38 | 39 | self.options = $.extend({}, $.jqPaginator.defaultOptions, options); 40 | 41 | self.verify(); 42 | 43 | self.extendJquery(); 44 | 45 | self.render(); 46 | 47 | self.fireEvent(this.options.currentPage, 'init'); 48 | }; 49 | 50 | self.verify = function () { 51 | var opts = self.options; 52 | 53 | if (!self.isNumber(opts.totalPages)) { 54 | throw new Error('[jqPaginator] type error: totalPages') 55 | } 56 | 57 | if (!self.isNumber(opts.totalCounts)) { 58 | throw new Error('[jqPaginator] type error: totalCounts') 59 | } 60 | 61 | if (!self.isNumber(opts.pageSize)) { 62 | throw new Error('[jqPaginator] type error: pageSize') 63 | } 64 | 65 | if (!self.isNumber(opts.currentPage)) { 66 | throw new Error('[jqPaginator] type error: currentPage') 67 | } 68 | 69 | if (!self.isNumber(opts.visiblePages)) { 70 | throw new Error('[jqPaginator] type error: visiblePages') 71 | } 72 | 73 | if (!opts.totalPages && !opts.totalCounts) { 74 | throw new Error('[jqPaginator] totalCounts or totalPages is required') 75 | } 76 | 77 | if (!opts.totalPages && opts.totalCounts && !opts.pageSize) { 78 | throw new Error('[jqPaginator] pageSize is required') 79 | } 80 | 81 | if (opts.totalCounts && opts.pageSize) { 82 | opts.totalPages = Math.ceil(opts.totalCounts / opts.pageSize); 83 | } 84 | 85 | if (opts.currentPage < 1 || opts.currentPage > opts.totalPages) { 86 | throw new Error('[jqPaginator] currentPage is incorrect') 87 | } 88 | 89 | if (opts.totalPages < 1) { 90 | throw new Error('[jqPaginator] totalPages cannot be less currentPage') 91 | } 92 | }; 93 | 94 | self.extendJquery = function () { 95 | $.fn.jqPaginatorHTML = function (s) { 96 | return s 97 | ? this.before(s).remove() 98 | : $('

') 99 | .append(this.eq(0).clone()) 100 | .html() 101 | }; 102 | }; 103 | 104 | self.render = function () { 105 | self.renderHtml(); 106 | self.setStatus(); 107 | self.bindEvents(); 108 | }; 109 | 110 | self.renderHtml = function () { 111 | var html = []; 112 | 113 | var pages = self.getPages(); 114 | for (var i = 0, j = pages.length; i < j; i++) { 115 | html.push(self.buildItem('page', pages[i])); 116 | } 117 | 118 | self.isEnable('prev') && html.unshift(self.buildItem('prev', self.options.currentPage - 1)); 119 | self.isEnable('first') && html.unshift(self.buildItem('first', 1)); 120 | self.isEnable('statistics') && html.unshift(self.buildItem('statistics')); 121 | self.isEnable('next') && html.push(self.buildItem('next', self.options.currentPage + 1)); 122 | self.isEnable('last') && html.push(self.buildItem('last', self.options.totalPages)); 123 | 124 | if (self.options.wrapper) { 125 | self.$container.html( 126 | $(self.options.wrapper) 127 | .html(html.join('')) 128 | .jqPaginatorHTML() 129 | ); 130 | } else { 131 | self.$container.html(html.join('')); 132 | } 133 | }; 134 | 135 | self.buildItem = function (type, pageData) { 136 | var html = self.options[type] 137 | .replace(/{{page}}/g, pageData) 138 | .replace(/{{totalPages}}/g, self.options.totalPages) 139 | .replace(/{{totalCounts}}/g, self.options.totalCounts); 140 | 141 | return $(html) 142 | .attr({ 143 | 'jp-role': type, 144 | 'jp-data': pageData 145 | }) 146 | .jqPaginatorHTML() 147 | }; 148 | 149 | self.setStatus = function () { 150 | var options = self.options; 151 | 152 | if (!self.isEnable('first') || options.currentPage === 1) { 153 | $('[jp-role=first]', self.$container).addClass(options.disableClass); 154 | } 155 | if (!self.isEnable('prev') || options.currentPage === 1) { 156 | $('[jp-role=prev]', self.$container).addClass(options.disableClass); 157 | } 158 | if (!self.isEnable('next') || options.currentPage >= options.totalPages) { 159 | $('[jp-role=next]', self.$container).addClass(options.disableClass); 160 | } 161 | if (!self.isEnable('last') || options.currentPage >= options.totalPages) { 162 | $('[jp-role=last]', self.$container).addClass(options.disableClass); 163 | } 164 | 165 | $('[jp-role=page]', self.$container).removeClass(options.activeClass); 166 | $('[jp-role=page][jp-data=' + options.currentPage + ']', self.$container).addClass(options.activeClass); 167 | }; 168 | 169 | self.getPages = function () { 170 | var pages = []; 171 | 172 | var visiblePages = self.options.visiblePages; 173 | 174 | var currentPage = self.options.currentPage; 175 | 176 | var totalPages = self.options.totalPages; 177 | 178 | if (visiblePages > totalPages) { 179 | visiblePages = totalPages; 180 | } 181 | 182 | var half = Math.floor(visiblePages / 2); 183 | var start = currentPage - half + 1 - (visiblePages % 2); 184 | var end = currentPage + half; 185 | 186 | if (start < 1) { 187 | start = 1; 188 | end = visiblePages; 189 | } 190 | if (end > totalPages) { 191 | end = totalPages; 192 | start = 1 + totalPages - visiblePages; 193 | } 194 | 195 | var itPage = start; 196 | while (itPage <= end) { 197 | pages.push(itPage); 198 | itPage++; 199 | } 200 | 201 | return pages 202 | }; 203 | 204 | self.isNumber = function (value) { 205 | var type = typeof value; 206 | return type === 'number' || type === 'undefined' 207 | }; 208 | 209 | self.isEnable = function (type) { 210 | return self.options[type] && typeof self.options[type] === 'string' 211 | }; 212 | 213 | self.switchPage = function (pageIndex) { 214 | self.options.currentPage = pageIndex; 215 | self.render(); 216 | }; 217 | 218 | self.fireEvent = function (pageIndex, type) { 219 | return typeof self.options.onPageChange !== 'function' || self.options.onPageChange(pageIndex, type) !== false 220 | }; 221 | 222 | self.callMethod = function (method, options) { 223 | switch (method) { 224 | case 'option': 225 | self.options = $.extend({}, self.options, options); 226 | self.verify(); 227 | self.render(); 228 | break 229 | case 'destroy': 230 | self.$container.empty(); 231 | self.$container.removeData('jqPaginator'); 232 | break 233 | default: 234 | throw new Error('[jqPaginator] method "' + method + '" does not exist') 235 | } 236 | 237 | return self.$container 238 | }; 239 | 240 | self.bindEvents = function () { 241 | var opts = self.options; 242 | 243 | self.$container.off(); 244 | self.$container.on('click', '[jp-role]', function () { 245 | var $el = $(this); 246 | if ($el.hasClass(opts.disableClass) || $el.hasClass(opts.activeClass)) { 247 | return 248 | } 249 | 250 | var pageIndex = +$el.attr('jp-data'); 251 | if (self.fireEvent(pageIndex, 'change')) { 252 | self.switchPage(pageIndex); 253 | } 254 | }); 255 | }; 256 | 257 | self.init(); 258 | 259 | return self.$container 260 | }; 261 | 262 | $.jqPaginator.defaultOptions = { 263 | wrapper: '', 264 | first: '

  • First
  • ', 265 | prev: '', 266 | next: '', 267 | last: '
  • Last
  • ', 268 | page: '
  • {{page}}
  • ', 269 | totalPages: 0, 270 | totalCounts: 0, 271 | pageSize: 0, 272 | currentPage: 1, 273 | visiblePages: 7, 274 | disableClass: 'disabled', 275 | activeClass: 'active', 276 | onPageChange: null 277 | }; 278 | 279 | $.fn.jqPaginator = function () { 280 | var self = this; 281 | 282 | var args = Array.prototype.slice.call(arguments); 283 | 284 | if (typeof args[0] === 'string') { 285 | var $instance = $(self).data('jqPaginator'); 286 | if (!$instance) { 287 | throw new Error('[jqPaginator] the element is not instantiated') 288 | } else { 289 | return $instance.callMethod(args[0], args[1]) 290 | } 291 | } else { 292 | return new $.jqPaginator(this, args[0]) 293 | } 294 | }; 295 | 296 | }()); 297 | -------------------------------------------------------------------------------- /src/main/resources/templates/commons/footer.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/resources/templates/commons/navigation.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/main/resources/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | xpllyn登陆/注册 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 |
    17 |
    18 |
    19 |
    20 | 21 |

    22 |
    23 |

    24 | Welcome! 25 |

    26 |
    27 |
    28 | 29 | 30 | 记住我 31 |
    32 |    33 | 注册 34 |
    35 |
    36 |
    37 |
    38 |
    39 |
    40 | 41 | 65 | 80 |
    81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/main/resources/templates/userpage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 |

    hello

    9 | 10 | -------------------------------------------------------------------------------- /src/test/java/com/xpllyn/XpllynApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.xpllyn; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class XpllynApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | --------------------------------------------------------------------------------