From cc946c5e93b9389516618f182ec2d4988960babd Mon Sep 17 00:00:00 2001 From: Hokori Date: Fri, 18 Apr 2025 18:18:56 +0800 Subject: [PATCH] Initial project setup with README.md and base structure --- .gitattributes | 2 + .gitignore | 33 ++ .mvn/wrapper/maven-wrapper.properties | 19 ++ README.md | 90 ++++++ mvnw | 259 +++++++++++++++ mvnw.cmd | 149 +++++++++ pom.xml | 107 +++++++ .../com/hokori/webwx/Api/PersonalAPI.java | 39 +++ .../com/hokori/webwx/Api/base/ContactApi.java | 148 +++++++++ .../hokori/webwx/Api/base/DownloadApi.java | 67 ++++ .../com/hokori/webwx/Api/base/FavorApi.java | 41 +++ .../com/hokori/webwx/Api/base/GroupApi.java | 294 ++++++++++++++++++ .../com/hokori/webwx/Api/base/LabelApi.java | 53 ++++ .../com/hokori/webwx/Api/base/LoginApi.java | 127 ++++++++ .../com/hokori/webwx/Api/base/MessageApi.java | 205 ++++++++++++ .../hokori/webwx/Api/base/PersonalApi.java | 79 +++++ src/main/java/com/hokori/webwx/Api/ck.java | 113 +++++++ .../webwx/Controller/CallbackController.java | 17 + .../webwx/Controller/LoginController.java | 226 ++++++++++++++ .../com/hokori/webwx/Service/ChatService.java | 5 + .../hokori/webwx/Service/LoginService.java | 10 + .../webwx/Service/impl/ChatServiceImpl.java | 27 ++ .../webwx/Service/impl/LoginServiceImpl.java | 118 +++++++ .../com/hokori/webwx/WebwxApplication.java | 13 + .../hokori/webwx/model/GewechatMessage.java | 117 +++++++ .../com/hokori/webwx/util/OkhttpUtil.java | 123 ++++++++ src/main/resources/application.yml | 7 + src/main/resources/static/css/chat.css | 237 ++++++++++++++ src/main/resources/static/css/login.css | 141 +++++++++ src/main/resources/static/index.html | 11 + src/main/resources/templates/chat.html | 130 ++++++++ src/main/resources/templates/login.html | 79 +++++ src/main/resources/templates/qrlogin.html | 92 ++++++ .../hokori/webwx/WebwxApplicationTests.java | 13 + 34 files changed, 3191 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100644 README.md create mode 100755 mvnw create mode 100644 mvnw.cmd create mode 100644 pom.xml create mode 100644 src/main/java/com/hokori/webwx/Api/PersonalAPI.java create mode 100644 src/main/java/com/hokori/webwx/Api/base/ContactApi.java create mode 100644 src/main/java/com/hokori/webwx/Api/base/DownloadApi.java create mode 100644 src/main/java/com/hokori/webwx/Api/base/FavorApi.java create mode 100644 src/main/java/com/hokori/webwx/Api/base/GroupApi.java create mode 100644 src/main/java/com/hokori/webwx/Api/base/LabelApi.java create mode 100644 src/main/java/com/hokori/webwx/Api/base/LoginApi.java create mode 100644 src/main/java/com/hokori/webwx/Api/base/MessageApi.java create mode 100644 src/main/java/com/hokori/webwx/Api/base/PersonalApi.java create mode 100644 src/main/java/com/hokori/webwx/Api/ck.java create mode 100644 src/main/java/com/hokori/webwx/Controller/CallbackController.java create mode 100644 src/main/java/com/hokori/webwx/Controller/LoginController.java create mode 100644 src/main/java/com/hokori/webwx/Service/ChatService.java create mode 100644 src/main/java/com/hokori/webwx/Service/LoginService.java create mode 100644 src/main/java/com/hokori/webwx/Service/impl/ChatServiceImpl.java create mode 100644 src/main/java/com/hokori/webwx/Service/impl/LoginServiceImpl.java create mode 100644 src/main/java/com/hokori/webwx/WebwxApplication.java create mode 100644 src/main/java/com/hokori/webwx/model/GewechatMessage.java create mode 100644 src/main/java/com/hokori/webwx/util/OkhttpUtil.java create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/static/css/chat.css create mode 100644 src/main/resources/static/css/login.css create mode 100644 src/main/resources/static/index.html create mode 100644 src/main/resources/templates/chat.html create mode 100644 src/main/resources/templates/login.html create mode 100644 src/main/resources/templates/qrlogin.html create mode 100644 src/test/java/com/hokori/webwx/WebwxApplicationTests.java diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..3b41682 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..d58dfb7 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/README.md b/README.md new file mode 100644 index 0000000..98fce36 --- /dev/null +++ b/README.md @@ -0,0 +1,90 @@ +# 网页微信(WebWeChat) + +## 项目简介 +本项目基于Spring Boot框架,通过Gewechat技术实现网页版微信功能。支持用户登录、消息收发、联系人管理、群组操作等功能,提供Web端微信使用体验。 + +## 功能模块 +- **用户登录**:通过微信扫码登录,支持回调验证 +- **消息系统**:实现消息的发送、接收与展示 +- **联系人管理**:支持好友、群组信息的查询与维护 +- **文件传输**:集成文件下载功能(通过DownloadApi) +- **个性化设置**:标签管理和收藏功能(FavorApi、LabelApi) + +## 技术栈 +- **后端**:Java 8+, Spring Boot 2.x +- **HTTP客户端**:OkHttp +- **前端**:HTML5 + CSS3(使用Thymeleaf模板) +- **API模块化**:分层设计(Controller-Service-Api) +- **配置管理**:Spring Boot application.yml + +## 目录结构 +``` +src/ +├── main/ +│ ├── java/ +│ │ └── com.hokori.webwx/ +│ │ ├── Api # 微信API接口实现 +│ │ ├── Controller # Web控制器层 +│ │ ├── Service # 业务逻辑层 +│ │ └── util # 工具类 +│ └── resources/ +│ ├── static/ # 前端静态资源(CSS/JS) +│ └── templates/ # Thymeleaf模板页面 +└── test/ # 单元测试 +``` + +## 快速开始 +### 环境要求 +- JDK 8+ +- Maven 3.6+ +- MySQL(如需持久化,需配置application.yml) + +### 运行步骤 +1. 克隆项目 +```bash +git clone https://github.com/your-repo/webwx.git +``` + +2. 构建项目 +```bash +mvn clean package +``` + +3. 启动应用 +```bash +mvn spring-boot:run +``` + +4. 访问地址 +```bash +http://localhost:8080/login +``` + +## 使用说明 +1. 访问登录页面扫码登录微信 +2. 登录成功后跳转至聊天界面 +3. 支持: + - 好友消息实时推送 + - 文件下载(通过文件链接) + - 群组消息管理 + - 联系人信息查看 + +## API文档 +主要API接口位于`com.hokori.webwx.Api`包: +- **LoginApi**:微信登录验证 +- **MessageApi**:消息收发核心接口 +- **GroupApi**:群组管理功能 +- **DownloadApi**:文件下载服务 + +## 贡献指南 +1. Fork项目 +2. 创建功能分支:`git checkout -b feature/X` +3. 提交修改:`git commit -m 'Add some feature'` +4. Push到分支:`git push origin feature/X` +5. 提交Pull Request + +## 问题反馈 +遇到问题请提交Issue,说明: +- 操作步骤 +- 错误截图 +- 环境配置 \ No newline at end of file diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..19529dd --- /dev/null +++ b/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..249bdf3 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..fabc2d7 --- /dev/null +++ b/pom.xml @@ -0,0 +1,107 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.4.4 + + + com.hokori + webwx + 0.0.1-SNAPSHOT + webwx + Demo project for Spring Boot + + + + + + + + + + + + + + + 17 + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-web + + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.mashape.unirest + unirest-java + 1.4.9 + + + + + org.json + json + 20231013 + + + com.alibaba.fastjson2 + fastjson2 + 2.0.25 + + + + com.squareup.okhttp3 + okhttp + 4.12.0 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/src/main/java/com/hokori/webwx/Api/PersonalAPI.java b/src/main/java/com/hokori/webwx/Api/PersonalAPI.java new file mode 100644 index 0000000..27a9efe --- /dev/null +++ b/src/main/java/com/hokori/webwx/Api/PersonalAPI.java @@ -0,0 +1,39 @@ +package com.hokori.webwx.Api; + +import com.mashape.unirest.http.HttpResponse; +import com.mashape.unirest.http.Unirest; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class PersonalAPI { + @Value("${wx.host}") + private String host; + + @Value("${wx.tokenid}") + private String tokenid; + + private String getUrl() { + return host; + } + + public String getProfile(String appid) { + log.info("获取个人信息"); + Unirest.setTimeouts(0, 0); + try { + HttpResponse response = Unirest.post(getUrl() + "/personal/getProfile") + .header("X-GEWE-TOKEN", tokenid) + .header("Content-Type", "application/json") + .body("{\n \"appId\": \"" + appid + "\"\n}") + .asString(); + log.info("获取成功"); + return response.getBody(); + } catch (Exception e) { + log.error("设备列表请求失败", e); + return "请求失败"; + } + } +} diff --git a/src/main/java/com/hokori/webwx/Api/base/ContactApi.java b/src/main/java/com/hokori/webwx/Api/base/ContactApi.java new file mode 100644 index 0000000..364e9d7 --- /dev/null +++ b/src/main/java/com/hokori/webwx/Api/base/ContactApi.java @@ -0,0 +1,148 @@ +package com.hokori.webwx.Api.base; + +import com.alibaba.fastjson2.JSONObject; +import com.hokori.webwx.util.OkhttpUtil; + +import java.util.Arrays; +import java.util.List; + +public class ContactApi { + + /** + * + * @param appId + * @return + */ + public static JSONObject a(String appId){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + return OkhttpUtil.postJSON("/login/checkOnline",param); + } + + /** + * 获取通讯录列表 + * @param appId + * @return + */ + public static JSONObject fetchContactsList(String appId){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + return OkhttpUtil.postJSON("/contacts/fetchContactsList",param); + } + + /** + * 获取群/好友简要信息 + * @param appId + * @return + */ + public static JSONObject getBriefInfo(String appId, List wxids){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("wxids",wxids); + return OkhttpUtil.postJSON("/contacts/getBriefInfo",param); + } + + /** + * 获取群/好友详细信息 + * @param appId + * @return + */ + public static JSONObject getDetailInfo(String appId, List wxids){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("wxids",wxids); + return OkhttpUtil.postJSON("/contacts/getDetailInfo",param); + } + + /** + * 搜索好友 + * @param appId + * @return + */ + public static JSONObject search(String appId,String contactsInfo){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("contactsInfo",contactsInfo); + return OkhttpUtil.postJSON("/contacts/search",param); + } + + /** + * 添加联系人/同意添加好友 + * @param appId + * @return + */ + public static JSONObject search(String appId,Integer scene,Integer option,String v3, String v4,String content){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("scene",scene); + param.put("option",option); + param.put("v3",v3); + param.put("v4",v4); + param.put("content",content); + return OkhttpUtil.postJSON("/contacts/addContacts",param); + } + + /** + * 删除好友 + * @param appId + * @return + */ + public static JSONObject deleteFriend(String appId,String wxid){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("wxid",wxid); + return OkhttpUtil.postJSON("/contacts/deleteFriend",param); + } + + /** + * 设置好友仅聊天 + * @param appId + * @return + */ + public static JSONObject setFriendPermissions(String appId,String wxid,Boolean onlyChat){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("wxid",wxid); + param.put("onlyChat",onlyChat); + return OkhttpUtil.postJSON("/contacts/setFriendPermissions",param); + } + + /** + * 设置好友备注 + * @param appId + * @return + */ + public static JSONObject setFriendRemark(String appId,String wxid,String remark){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("wxid",wxid); + param.put("onlyChat",remark); + return OkhttpUtil.postJSON("/contacts/setFriendRemark",param); + } + + /** + * 获取手机通讯录 + * @param appId + * @return + */ + public static JSONObject getPhoneAddressList(String appId,List phones){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("wxid",phones); + return OkhttpUtil.postJSON("/contacts/getPhoneAddressList",param); + } + + /** + * 上传手机通讯录 + * @param appId + * @return + */ + public static JSONObject uploadPhoneAddressList(String appId,List phones,Integer opType){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("wxid",phones); + param.put("opType",opType); + return OkhttpUtil.postJSON("/contacts/uploadPhoneAddressList",param); + } + +} \ No newline at end of file diff --git a/src/main/java/com/hokori/webwx/Api/base/DownloadApi.java b/src/main/java/com/hokori/webwx/Api/base/DownloadApi.java new file mode 100644 index 0000000..f4822f0 --- /dev/null +++ b/src/main/java/com/hokori/webwx/Api/base/DownloadApi.java @@ -0,0 +1,67 @@ +package com.hokori.webwx.Api.base; + +import com.alibaba.fastjson2.JSONObject; +import com.hokori.webwx.util.OkhttpUtil; + +/** + * 下载模块 + */ +public class DownloadApi { + + /** + * 下载图片 + */ + public static JSONObject downloadImage(String appId, String xml, Integer type){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("xml",xml); + param.put("type",type); + return OkhttpUtil.postJSON("/message/downloadImage",param); + } + + /** + * 下载语音 + */ + public static JSONObject downloadVoice(String appId, String xml, Long msgId){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("xml",xml); + param.put("msgId",msgId); + return OkhttpUtil.postJSON("/message/downloadVoice",param); + } + + /** + * 下载视频 + */ + public static JSONObject downloadVideo(String appId, String xml){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("xml",xml); + return OkhttpUtil.postJSON("/message/downloadVideo",param); + } + + /** + * 下载emoji + */ + public static JSONObject downloadEmojiMd5(String appId, String emojiMd5){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("emojiMd5",emojiMd5); + return OkhttpUtil.postJSON("/message/downloadEmojiMd5",param); + } + /** + * cdn下载 + */ + public static JSONObject downloadImage(String appId, String aesKey, String fileId, String type, String totalSize, String suffix){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("aesKey",aesKey); + param.put("fileId",fileId); + param.put("totalSize",totalSize); + param.put("type",type); + param.put("suffix",suffix); + return OkhttpUtil.postJSON("/message/downloadCdn",param); + } + + +} \ No newline at end of file diff --git a/src/main/java/com/hokori/webwx/Api/base/FavorApi.java b/src/main/java/com/hokori/webwx/Api/base/FavorApi.java new file mode 100644 index 0000000..4e6d19d --- /dev/null +++ b/src/main/java/com/hokori/webwx/Api/base/FavorApi.java @@ -0,0 +1,41 @@ +package com.hokori.webwx.Api.base; + +import com.alibaba.fastjson2.JSONObject; +import com.hokori.webwx.util.OkhttpUtil; + +/** + * 收藏夹模块 + */ +public class FavorApi { + + /** + * 同步收藏夹 + */ + public static JSONObject sync(String appId, String syncKey) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + param.put("syncKey", syncKey); + return OkhttpUtil.postJSON("/favor/sync", param); + } + + /** + * 获取收藏夹内容 + */ + public static JSONObject getContent(String appId, Integer favId) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + param.put("favId", favId); + return OkhttpUtil.postJSON("/favor/getContent", param); + } + + /** + * 删除收藏夹 + */ + public static JSONObject delete(String appId, Integer favId) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + param.put("favId", favId); + return OkhttpUtil.postJSON("/favor/delete", param); + } + +} \ No newline at end of file diff --git a/src/main/java/com/hokori/webwx/Api/base/GroupApi.java b/src/main/java/com/hokori/webwx/Api/base/GroupApi.java new file mode 100644 index 0000000..ef4fb30 --- /dev/null +++ b/src/main/java/com/hokori/webwx/Api/base/GroupApi.java @@ -0,0 +1,294 @@ +package com.hokori.webwx.Api.base; + +import com.alibaba.fastjson2.JSONObject; +import com.hokori.webwx.util.OkhttpUtil; + +import java.util.List; + +/** + * 群模块 + */ +public class GroupApi { + + /** + * 创建微信群 + * @param appId + * @return + */ + public static JSONObject createChatroom(String appId, List wxids){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("wxid",wxids); + return OkhttpUtil.postJSON("/group/createChatroom",param); + } + + /** + * 修改群名称 + * @param appId + * @return + */ + public static JSONObject modifyChatroomName(String appId, String chatroomName,String chatroomId){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("chatroomName",chatroomName); + param.put("chatroomId",chatroomId); + return OkhttpUtil.postJSON("/group/modifyChatroomName",param); + } + + /** + * 修改群备注 + * @param appId + * @return + */ + public static JSONObject modifyChatroomRemark(String appId, String chatroomRemark,String chatroomId){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("chatroomRemark",chatroomRemark); + param.put("chatroomId",chatroomId); + return OkhttpUtil.postJSON("/group/modifyChatroomRemark",param); + } + + /** + * 修改我在群内的昵称 + * @param appId + * @return + */ + public static JSONObject modifyChatroomNickNameForSelf(String appId, String nickName,String chatroomId){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("nickName",nickName); + param.put("chatroomId",chatroomId); + return OkhttpUtil.postJSON("/group/modifyChatroomNickNameForSelf",param); + } + + /** + * 邀请/添加 进群 + * @param appId + * @return + */ + public static JSONObject inviteMember(String appId, List wxids,String chatroomId, String reason){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("wxids",wxids); + param.put("reason",reason); + param.put("chatroomId",chatroomId); + return OkhttpUtil.postJSON("/group/inviteMember",param); + } + + /** + * 删除群成员 + * @param appId + * @return + */ + public static JSONObject removeMember(String appId, List wxids,String chatroomId){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("wxids",wxids); + param.put("chatroomId",chatroomId); + return OkhttpUtil.postJSON("/group/removeMember",param); + } + + /** + * 退出群聊 + * @param appId + * @return + */ + public static JSONObject modifyChatroomName(String appId,String chatroomId){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("chatroomId",chatroomId); + return OkhttpUtil.postJSON("/group/quitChatroom",param); + } + + /** + * 解散群聊 + * @param appId + * @return + */ + public static JSONObject disbandChatroom(String appId,String chatroomId){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("chatroomId",chatroomId); + return OkhttpUtil.postJSON("/group/disbandChatroom",param); + } + + /** + * 获取群信息 + * @param appId + * @return + */ + public static JSONObject getChatroomInfo(String appId,String chatroomId){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("chatroomId",chatroomId); + return OkhttpUtil.postJSON("/group/getChatroomInfo",param); + } + + /** + * 获取群成员列表 + * @param appId + * @return + */ + public static JSONObject getChatroomMemberList(String appId,String chatroomId){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("chatroomId",chatroomId); + return OkhttpUtil.postJSON("/group/getChatroomMemberList",param); + } + + /** + * 获取群成员详情 + * @param appId + * @return + */ + public static JSONObject getChatroomMemberDetail(String appId,String chatroomId,List memberWxids){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("memberWxids",memberWxids); + param.put("chatroomId",chatroomId); + return OkhttpUtil.postJSON("/group/getChatroomMemberDetail",param); + } + + /** + * 获取群公告 + * @param appId + * @return + */ + public static JSONObject getChatroomAnnouncement(String appId,String chatroomId){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("chatroomId",chatroomId); + return OkhttpUtil.postJSON("/group/getChatroomAnnouncement",param); + } + + /** + * 设置群公告 + * @param appId + * @return + */ + public static JSONObject setChatroomAnnouncement(String appId,String chatroomId, String content){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("chatroomId",chatroomId); + param.put("content",content); + return OkhttpUtil.postJSON("/group/setChatroomAnnouncement",param); + } + + /** + * 同意进群 + * @param appId + * @return + */ + public static JSONObject agreeJoinRoom(String appId, String url){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("chatroomName",url); + return OkhttpUtil.postJSON("/group/agreeJoinRoom",param); + } + + /** + * 添加群成员为好友 + * @param appId + * @return + */ + public static JSONObject addGroupMemberAsFriend(String appId, String memberWxid,String chatroomId,String content){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("memberWxid",memberWxid); + param.put("content",content); + param.put("chatroomId",chatroomId); + return OkhttpUtil.postJSON("/group/addGroupMemberAsFriend",param); + } + + /** + * 获取群二维码 + * @param appId + * @return + */ + public static JSONObject getChatroomQrCode(String appId,String chatroomId){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("chatroomId",chatroomId); + return OkhttpUtil.postJSON("/group/getChatroomQrCode",param); + } + + /** + * 群保存到通讯录 + * @param appId + * @return + */ + public static JSONObject saveContractList(String appId, Integer operType,String chatroomId){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("chatroomName",operType); + param.put("chatroomId",chatroomId); + return OkhttpUtil.postJSON("/group/saveContractList",param); + } + + /** + * 管理员操作 + * @param appId + * @return + */ + public static JSONObject adminOperate(String appId,String chatroomId,List wxids,Integer operType){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("wxids",wxids); + param.put("operType",operType); + param.put("chatroomId",chatroomId); + return OkhttpUtil.postJSON("/group/adminOperate",param); + } + + /** + * 聊天置顶 + * @param appId + * @return + */ + public static JSONObject pinChat(String appId, boolean top,String chatroomId){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("top",top); + param.put("chatroomId",chatroomId); + return OkhttpUtil.postJSON("/group/pinChat",param); + } + + /** + * 设置消息免打扰 + * @param appId + * @return + */ + public static JSONObject setMsgSilence(String appId, boolean silence,String chatroomId){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("silence",silence); + param.put("chatroomId",chatroomId); + return OkhttpUtil.postJSON("/group/setMsgSilence",param); + } + + /** + * 扫码进群 + * @param appId + * @return + */ + public static JSONObject joinRoomUsingQRCode(String appId, String qrUrl){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("qrUrl",qrUrl); + return OkhttpUtil.postJSON("/group/joinRoomUsingQRCode",param); + } + + /** + * 确认进群申请 + * @param appId + * @return + */ + public static JSONObject roomAccessApplyCheckApprove(String appId, String newMsgId,String chatroomId, String msgContent){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("newMsgId",newMsgId); + param.put("msgContent",msgContent); + param.put("chatroomId",chatroomId); + return OkhttpUtil.postJSON("/group/roomAccessApplyCheckApprove",param); + } + +} \ No newline at end of file diff --git a/src/main/java/com/hokori/webwx/Api/base/LabelApi.java b/src/main/java/com/hokori/webwx/Api/base/LabelApi.java new file mode 100644 index 0000000..aa8647c --- /dev/null +++ b/src/main/java/com/hokori/webwx/Api/base/LabelApi.java @@ -0,0 +1,53 @@ +package com.hokori.webwx.Api.base; + +import com.alibaba.fastjson2.JSONObject; +import com.hokori.webwx.util.OkhttpUtil; + +import java.util.List; + +/** + * 标签模块 + */ +public class LabelApi { + + /** + * 添加标签 + */ + public static JSONObject add(String appId, String labelName) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + param.put("labelName", labelName); + return OkhttpUtil.postJSON("/label/add", param); + } + + /** + * 删除标签 + */ + public static JSONObject delete(String appId, String labelIds) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + param.put("labelIds", labelIds); + return OkhttpUtil.postJSON("/label/delete", param); + } + + /** + * 添加标签 + */ + public static JSONObject list(String appId) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + return OkhttpUtil.postJSON("/label/list", param); + } + + /** + * 添加标签 + */ + public static JSONObject modifyMemberList(String appId, String labelIds, List wxIds) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + param.put("labelIds", labelIds); + param.put("wxIds", wxIds); + return OkhttpUtil.postJSON("/label/modifyMemberList", param); + } + +} \ No newline at end of file diff --git a/src/main/java/com/hokori/webwx/Api/base/LoginApi.java b/src/main/java/com/hokori/webwx/Api/base/LoginApi.java new file mode 100644 index 0000000..370e4f7 --- /dev/null +++ b/src/main/java/com/hokori/webwx/Api/base/LoginApi.java @@ -0,0 +1,127 @@ +package com.hokori.webwx.Api.base; + +import java.util.List; + +import com.alibaba.fastjson2.JSONObject; +import com.hokori.webwx.util.OkhttpUtil; + +/** + * 登录模块 + */ +public class LoginApi { + + + /** + * 获取tokenId 将tokenId 配置到OkhttpUtil 类中的token 属性 + * + * @return + */ + public static JSONObject getToken() { + return OkhttpUtil.postJSON("/tools/getTokenId", new JSONObject()); + } + + /** + * 设置微信消息的回调地址 + * + * @return + */ + public static JSONObject setCallback(String token,String callbackUrl) { + JSONObject param = new JSONObject(); + param.put("token",token); + param.put("callbackUrl",callbackUrl); + return OkhttpUtil.postJSON("/tools/setCallback", param); + } + + /** + * 获取登录二维码 + * + * @param appId 设备id 首次登录传空,后续登录传返回的appid + * @return + */ + public static JSONObject getQr(String appId) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + return OkhttpUtil.postJSON("/login/getLoginQrCode", param); + } + + /** + * 确认登陆 + * + * @param appId + * @param uuid 取码返回的uuid + * @param captchCode 登录验证码(跨省登录会出现此提示,使用同省代理ip能避免此问题,也能使账号更加稳定) + * @return + */ + public static JSONObject checkQr(String appId, String uuid, String captchCode) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + param.put("uuid", uuid); + param.put("captchCode", captchCode); + return OkhttpUtil.postJSON("/login/checkLogin", param); + } + + /** + * 退出微信 + * + * @param appId + * @return + */ + public static JSONObject logOut(String appId) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + return OkhttpUtil.postJSON("/login/logout", param); + } + + /** + * 弹框登录 + * + * @param appId + * @return + */ + public static JSONObject dialogLogin(String appId) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + return OkhttpUtil.postJSON("/login/dialogLogin", param); + } + + /** + * 检查是否在线 + * + * @param appId + * @return + */ + public static JSONObject checkOnline(String appId) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + return OkhttpUtil.postJSON("/login/checkOnline", param); + } + + /** + * 退出 + * + * @param appId + * @return + */ + public static JSONObject logout(String appId) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + return OkhttpUtil.postJSON("/login/logout", param); + } + + /** + * 设备列表 + * + * @param token + * @return + */ + public static JSONObject devicelist() { + JSONObject param = new JSONObject(); + return OkhttpUtil.postJSON("/login/deviceList", param); + } + + public static void main(String[] args) { + JSONObject tkdata = getToken(); + String token = tkdata.getString("data"); + System.out.println(token); + } +} \ No newline at end of file diff --git a/src/main/java/com/hokori/webwx/Api/base/MessageApi.java b/src/main/java/com/hokori/webwx/Api/base/MessageApi.java new file mode 100644 index 0000000..e8718ee --- /dev/null +++ b/src/main/java/com/hokori/webwx/Api/base/MessageApi.java @@ -0,0 +1,205 @@ +package com.hokori.webwx.Api.base; + +import com.alibaba.fastjson2.JSONObject; +import com.hokori.webwx.util.OkhttpUtil; + +/** + * 消息模块 + */ +public class MessageApi { + + /** + * 发送文字消息 + */ + public static JSONObject postText(String appId, String toWxid, String content, String ats) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + param.put("toWxid", toWxid); + param.put("content", content); + param.put("ats", ats); + return OkhttpUtil.postJSON("/message/postText", param); + } + + /** + * 发送文件消息 + */ + public static JSONObject postFile(String appId, String toWxid, String fileUrl, String fileName) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + param.put("toWxid", toWxid); + param.put("fileUrl", fileUrl); + param.put("fileName", fileName); + return OkhttpUtil.postJSON("/message/postFile", param); + } + + /** + * 发送图片消息 + */ + public static JSONObject postImage(String appId, String toWxid, String imgUrl) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + param.put("toWxid", toWxid); + param.put("imgUrl", imgUrl); + return OkhttpUtil.postJSON("/message/postImage", param); + } + + /** + * 发送语音消息 + */ + public static JSONObject postVoice(String appId, String toWxid, String voiceUrl, Integer voiceDuration) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + param.put("toWxid", toWxid); + param.put("voiceUrl", voiceUrl); + param.put("voiceDuration", voiceDuration); + return OkhttpUtil.postJSON("/message/postVoice", param); + } + + /** + * 发送视频消息 + */ + public static JSONObject postVideo(String appId, String toWxid, String videoUrl, String thumbUrl,Integer videoDuration) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + param.put("toWxid", toWxid); + param.put("videoUrl", videoUrl); + param.put("thumbUrl", thumbUrl); + param.put("videoDuration", videoDuration); + return OkhttpUtil.postJSON("/message/postVideo", param); + } + + /** + * 发送链接消息 + */ + public static JSONObject postLink(String appId, String toWxid, String title, String desc, String linkUrl, String thumbUrl) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + param.put("toWxid", toWxid); + param.put("title", title); + param.put("desc", desc); + param.put("linkUrl", linkUrl); + param.put("thumbUrl", thumbUrl); + return OkhttpUtil.postJSON("/message/postLink", param); + } + + /** + * 发送名片消息 + */ + public static JSONObject postNameCard(String appId, String toWxid, String nickName, String nameCardWxid) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + param.put("toWxid", toWxid); + param.put("nickName", nickName); + param.put("nameCardWxid", nameCardWxid); + return OkhttpUtil.postJSON("/message/postNameCard", param); + } + + /** + * 发送emoji消息 + */ + public static JSONObject postEmoji(String appId, String toWxid, String emojiMd5, String emojiSize) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + param.put("toWxid", toWxid); + param.put("emojiMd5", emojiMd5); + param.put("emojiSize", emojiSize); + return OkhttpUtil.postJSON("/message/postEmoji", param); + } + + /** + * 发送appmsg消息 + */ + public static JSONObject postAppMsg(String appId, String toWxid, String appmsg) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + param.put("toWxid", toWxid); + param.put("appmsg", appmsg); + return OkhttpUtil.postJSON("/message/postAppMsg", param); + } + + /** + * 发送小程序消息 + */ + public static JSONObject postMiniApp(String appId, String toWxid, String miniAppId, String displayName, String pagePath, String coverImgUrl, String title, String userName) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + param.put("toWxid", toWxid); + param.put("miniAppId", miniAppId); + param.put("displayName", displayName); + param.put("pagePath", pagePath); + param.put("coverImgUrl", coverImgUrl); + param.put("title", title); + param.put("userName", userName); + return OkhttpUtil.postJSON("/message/postMiniApp", param); + } + + /** + * 转发文件 + */ + public static JSONObject forwardFile(String appId, String toWxid, String xml) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + param.put("toWxid", toWxid); + param.put("xml", xml); + return OkhttpUtil.postJSON("/message/forwardFile", param); + } + + /** + * 转发图片 + */ + public static JSONObject forwardImage(String appId, String toWxid, String xml) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + param.put("toWxid", toWxid); + param.put("xml", xml); + return OkhttpUtil.postJSON("/message/forwardImage", param); + } + + /** + * 转发视频 + */ + public static JSONObject forwardVideo(String appId, String toWxid, String xml) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + param.put("toWxid", toWxid); + param.put("xml", xml); + return OkhttpUtil.postJSON("/message/forwardVideo", param); + } + + /** + * 转发链接 + */ + public static JSONObject forwardUrl(String appId, String toWxid, String xml) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + param.put("toWxid", toWxid); + param.put("xml", xml); + return OkhttpUtil.postJSON("/message/forwardUrl", param); + } + + /** + * 转发小程序 + */ + public static JSONObject forwardMiniApp(String appId, String toWxid, String xml, String coverImgUrl) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + param.put("toWxid", toWxid); + param.put("xml", xml); + param.put("coverImgUrl", coverImgUrl); + return OkhttpUtil.postJSON("/message/forwardMiniApp", param); + } + + /** + * 撤回消息 + */ + public static JSONObject revokeMsg(String appId, String toWxid, String msgId, String newMsgId,String createTime) { + JSONObject param = new JSONObject(); + param.put("appId", appId); + param.put("toWxid", toWxid); + param.put("msgId", msgId); + param.put("newMsgId", newMsgId); + param.put("createTime", createTime); + return OkhttpUtil.postJSON("/message/revokeMsg", param); + } + +} \ No newline at end of file diff --git a/src/main/java/com/hokori/webwx/Api/base/PersonalApi.java b/src/main/java/com/hokori/webwx/Api/base/PersonalApi.java new file mode 100644 index 0000000..8a45800 --- /dev/null +++ b/src/main/java/com/hokori/webwx/Api/base/PersonalApi.java @@ -0,0 +1,79 @@ +package com.hokori.webwx.Api.base; + +import com.alibaba.fastjson2.JSONObject; +import com.hokori.webwx.util.OkhttpUtil; + +/** + * 个人模块 + */ +public class PersonalApi { + + /** + * 获取个人资料 + */ + public static JSONObject getProfile(String appId){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + return OkhttpUtil.postJSON("/personal/getProfile",param); + } + + + /** + * 获取自己的二维码 + */ + public static JSONObject getQrCode(String appId){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + return OkhttpUtil.postJSON("/personal/getQrCode",param); + } + + /** + * 获取设备记录 + */ + public static JSONObject getSafetyInfo(String appId){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + return OkhttpUtil.postJSON("/personal/getSafetyInfo",param); + } + + /** + * 隐私设置 + */ + public static JSONObject privacySettings(String appId,Integer option,boolean open){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("option",option); + param.put("open",open); + return OkhttpUtil.postJSON("/personal/privacySettings",param); + } + + /** + * 修改个人信息 + */ + public static JSONObject updateProfile(String appId,String city,String country,String nickName,String province,String sex,String signature){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("city",city); + param.put("country",country); + param.put("nickName",nickName); + param.put("province",province); + param.put("sex",sex); + param.put("signature",signature); + return OkhttpUtil.postJSON("/personal/updateProfile",param); + } + + /** + * 修改头像 + */ + public static JSONObject updateHeadImg(String appId,String headImgUrl){ + JSONObject param = new JSONObject(); + param.put("appId",appId); + param.put("headImgUrl",headImgUrl); + return OkhttpUtil.postJSON("/personal/updateHeadImg",param); + } + + public static void main(String[] args) { + JSONObject profile = PersonalApi.getProfile("wx_EXRnjSJgOzuOf81cDE0ke"); + } + +} \ No newline at end of file diff --git a/src/main/java/com/hokori/webwx/Api/ck.java b/src/main/java/com/hokori/webwx/Api/ck.java new file mode 100644 index 0000000..c469650 --- /dev/null +++ b/src/main/java/com/hokori/webwx/Api/ck.java @@ -0,0 +1,113 @@ +package com.hokori.webwx.Api; + +import com.mashape.unirest.http.HttpResponse; +import com.mashape.unirest.http.Unirest; +import org.json.JSONObject; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class ck { + + + @Value("${wx.host}") + private String host; + + @Value("${wx.tokenid}") + private String tokenid; + + private String getUrl() { + return host; + } + + public String getLoginQrCode(String appid) { + Unirest.setTimeouts(0, 0); + try { + HttpResponse response = Unirest.post(getUrl() + "/login/getLoginQrCode") + .header("X-GEWE-TOKEN", tokenid) + .header("Content-Type", "application/json") + .body("{\n \"appId\": \"" + appid + "\"\n}") + .asString(); + return response.getBody(); + } catch (Exception e) { + log.error("设备列表请求失败", e); + return "请求失败"; + } + } + + public String checkLogin(String appid, String uuid, String captchCode) { + Unirest.setTimeouts(0, 0); + try { + HttpResponse response = Unirest.post(getUrl() + "/login/checkLogin") + .header("X-GEWE-TOKEN", tokenid) + .header("Content-Type", "application/json") + .body("{\n \"appId\": \"" + appid + "\",\n \"uuid\": \"" + uuid + "\",\n \"uuid\": \"" + captchCode + "\"\n}") + .asString(); + return response.getBody(); + } catch (Exception e) { + log.error("设备列表请求失败", e); + return "请求失败"; + } + } + public String checkLogin(String appid, String uuid) { + Unirest.setTimeouts(0, 0); + try { + HttpResponse response = Unirest.post(getUrl() + "/login/checkLogin") + .header("X-GEWE-TOKEN", tokenid) + .header("Content-Type", "application/json") + .body("{\n \"appId\": \"" + appid + "\",\n \"uuid\": \"" + uuid + "\"\n}") + .asString(); + return response.getBody(); + } catch (Exception e) { + log.error("设备列表请求失败", e); + return "请求失败"; + } + } + + + public String deviceList() { + Unirest.setTimeouts(0, 0); + try { + HttpResponse response = Unirest.post(getUrl() + "/login/deviceList") + .header("X-GEWE-TOKEN", tokenid) + .header("Content-Type", "application/json") + .asString(); + return response.getBody(); + } catch (Exception e) { + log.error("设备列表请求失败", e); + return "列表获取失败"; + } + } + + public Boolean checkOnline(String appid) { + Unirest.setTimeouts(0, 0); + try { + HttpResponse response = Unirest.post(getUrl() + "/login/checkOnline") + .header("X-GEWE-TOKEN", tokenid) + .header("Content-Type", "application/json") + .body("{\n \"appId\": \"" + appid + "\"\n}") + .asString(); + + String body = response.getBody(); + + // 解析JSON响应 + org.json.JSONObject jsonObject = new org.json.JSONObject(body); + + // 检查ret值是否为200 + if (jsonObject.getInt("ret") == 200) { + // 提取并返回data字段的布尔值 + return jsonObject.getBoolean("data"); + } else { + log.error("请求返回错误: " + jsonObject.getString("msg")); + return false; + } + } catch (Exception e) { + log.error("检查设备在线状态失败", e); + return false; + } + } + +} diff --git a/src/main/java/com/hokori/webwx/Controller/CallbackController.java b/src/main/java/com/hokori/webwx/Controller/CallbackController.java new file mode 100644 index 0000000..8e6bf23 --- /dev/null +++ b/src/main/java/com/hokori/webwx/Controller/CallbackController.java @@ -0,0 +1,17 @@ +package com.hokori.webwx.Controller; + +import org.springframework.web.bind.annotation.*; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.hokori.webwx.model.GewechatMessage; + + +@RestController +public class CallbackController { + + @RequestMapping("/callback") + public void callback(@RequestBody JSONObject msg){ + System.out.println(msg); + } +} diff --git a/src/main/java/com/hokori/webwx/Controller/LoginController.java b/src/main/java/com/hokori/webwx/Controller/LoginController.java new file mode 100644 index 0000000..191e41e --- /dev/null +++ b/src/main/java/com/hokori/webwx/Controller/LoginController.java @@ -0,0 +1,226 @@ +package com.hokori.webwx.Controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.jackson.JsonObjectSerializer; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.json.JSONArray; +import org.json.JSONObject; + +import com.hokori.webwx.Api.ck; +import com.hokori.webwx.Api.base.LoginApi; +import com.hokori.webwx.Service.ChatService; +import com.hokori.webwx.Service.LoginService; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Controller +public class LoginController { + + @Autowired + private LoginService loginService; + + @Autowired + private ck loginck; + + @Autowired + private ChatService chatService; + + @GetMapping("/") + public String index(HttpServletRequest request) { + // 检查Cookie中是否存在deviceId + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if ("deviceId".equals(cookie.getName())) { + String deviceId = cookie.getValue(); + // 验证设备是否已登录 + String deviceListJson = loginService.usaerList(); + try { + // 解析JSON字符串 + JSONObject jsonObject = new JSONObject(deviceListJson); + JSONArray devicesArray = jsonObject.getJSONArray("devices"); + + // 检查设备是否已登录 + for (int i = 0; i < devicesArray.length(); i++) { + JSONObject device = devicesArray.getJSONObject(i); + if (device.getString("device").equals(deviceId) && device.getBoolean("isLogin")) { + // 设备已登录,重定向到聊天页面 + return "redirect:/chat"; + } + } + } catch (Exception e) { + // 解析失败,重定向到登录页面 + return "redirect:/login"; + } + break; + } + } + } + + // 如果没有Cookie或设备未登录,重定向到登录页面 + return "redirect:/login"; + } + + @GetMapping("/chat") + public String chat(Model model,HttpServletRequest request) { + Cookie[] cookies = request.getCookies(); + String tx = ""; + if (cookies!= null) { + for (Cookie cookie : cookies) { + if ("deviceId".equals(cookie.getName())) { + String deviceId = cookie.getValue(); + // 验证设备是否已登录 + Boolean isLogin = loginck.checkOnline(deviceId); + if (isLogin) { + // 设备已登录,获取用户头像 + tx = chatService.getUser(deviceId); + // 将tx添加到Model中以便模板使用 + model.addAttribute("tx", tx); + return "chat"; + } else { + // 设备未登录,重定向到登录页面 + return "redirect:/login"; + } + } + } + }else{ + // 设备未登录,重定向到登录页面 + return "redirect:/login"; + } + + model.addAttribute("tx", tx); + return "chat"; + } + + @GetMapping("/login") + public String login(Model model, HttpServletRequest request) { + // 检查Cookie中是否存在deviceId + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if ("deviceId".equals(cookie.getName())) { + String deviceId = cookie.getValue(); + // 验证设备是否已登录 + String deviceListJson = loginService.usaerList(); + try { + // 解析JSON字符串 + JSONObject jsonObject = new JSONObject(deviceListJson); + JSONArray devicesArray = jsonObject.getJSONArray("devices"); + + // 检查设备是否已登录 + for (int i = 0; i < devicesArray.length(); i++) { + JSONObject device = devicesArray.getJSONObject(i); + if (device.getString("device").equals(deviceId) && device.getBoolean("isLogin")) { + // 设备已登录,重定向到根路径 + return "redirect:/"; + } + } + } catch (Exception e) { + // 解析失败,继续显示登录页面 + } + break; + } + } + } + + // 如果没有Cookie或设备未登录,显示登录页面 + // 调用LoginService的usaerList方法获取设备列表数据 + String deviceListJson = loginService.usaerList(); + + try { + // 解析JSON字符串 + JSONObject jsonObject = new JSONObject(deviceListJson); + JSONArray devicesArray = jsonObject.getJSONArray("devices"); + + // 将JSON数组转换为Model属性 + model.addAttribute("devices", devicesArray.toList()); + } catch (Exception e) { + // 添加一个空列表,避免模板渲染错误 + model.addAttribute("devices", new JSONArray().toList()); + } + + return "login"; + } + + @GetMapping("/qrlogin") + public String qrlogin(Model model, HttpServletRequest request) { + JSONObject req = new JSONObject(loginService.newlogin()); + + // 从返回的JSON中获取data字段,它是一个JSONObject而不是JSONArray + JSONObject data = req.getJSONObject("data"); + + // 直接从data对象中提取所需的三个值 + try { + String uuid = data.getString("uuid"); + String appId = data.getString("appId"); + String qrImgBase64 = data.getString("qrImgBase64"); + + // 将值添加到Model中供前端使用 + model.addAttribute("uuid", uuid); + model.addAttribute("appId", appId); + model.addAttribute("qrCodeBase64", qrImgBase64); + } catch (Exception e) { + // 如果获取失败,可以记录错误日志 + // log.error("获取登录二维码数据失败", e); + } + + return "qrlogin"; + } + + @PostMapping("/api/checklogin") + public ResponseEntity checkLogin(@RequestParam String appid, @RequestParam String uuid, HttpServletResponse response) { + JSONObject loginJson = new JSONObject(loginService.ckqr(appid, uuid)); + + return ResponseEntity.ok(loginJson.toString()); + } + /** + * 处理设备登录并设置Cookie + * @param deviceId 设备ID + * @param response HTTP响应对象 + * @return 登录结果 + */ + @PostMapping("/api/login") + @ResponseBody + public ResponseEntity processLogin(@RequestParam String deviceId, HttpServletResponse response) { + // 验证设备是否存在且已登录 + String deviceListJson = loginService.usaerList(); + boolean isValidDevice = false; + + try { + // 解析JSON字符串 + JSONObject jsonObject = new JSONObject(deviceListJson); + JSONArray devicesArray = jsonObject.getJSONArray("devices"); + + // 使用流处理JSON数组 + for (int i = 0; i < devicesArray.length(); i++) { + JSONObject device = devicesArray.getJSONObject(i); + if (device.getString("device").equals(deviceId) && device.getBoolean("isLogin")) { + isValidDevice = true; + break; + } + } + } catch (Exception e) { + return ResponseEntity.badRequest().body("解析设备数据失败"); + } + + if (isValidDevice) { + // 创建Cookie并设置 + Cookie deviceCookie = new Cookie("deviceId", deviceId); + deviceCookie.setPath("/"); + deviceCookie.setMaxAge(7 * 24 * 60 * 60); // 设置Cookie有效期为7天 + response.addCookie(deviceCookie); + + return ResponseEntity.ok("登录成功"); + } else { + return ResponseEntity.badRequest().body("设备未登录或不存在"); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/hokori/webwx/Service/ChatService.java b/src/main/java/com/hokori/webwx/Service/ChatService.java new file mode 100644 index 0000000..3bf9749 --- /dev/null +++ b/src/main/java/com/hokori/webwx/Service/ChatService.java @@ -0,0 +1,5 @@ +package com.hokori.webwx.Service; + +public interface ChatService { + public String getUser(String appid); +} diff --git a/src/main/java/com/hokori/webwx/Service/LoginService.java b/src/main/java/com/hokori/webwx/Service/LoginService.java new file mode 100644 index 0000000..df52efc --- /dev/null +++ b/src/main/java/com/hokori/webwx/Service/LoginService.java @@ -0,0 +1,10 @@ +package com.hokori.webwx.Service; + +import java.util.List; + +public interface LoginService { + public String usaerList(); + public String relogin(String appid); + public String newlogin(); + public String ckqr(String appid, String uuid); +} diff --git a/src/main/java/com/hokori/webwx/Service/impl/ChatServiceImpl.java b/src/main/java/com/hokori/webwx/Service/impl/ChatServiceImpl.java new file mode 100644 index 0000000..d3f9048 --- /dev/null +++ b/src/main/java/com/hokori/webwx/Service/impl/ChatServiceImpl.java @@ -0,0 +1,27 @@ +package com.hokori.webwx.Service.impl; + +import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.hokori.webwx.Api.PersonalAPI; +import com.hokori.webwx.Service.ChatService; + +import lombok.extern.slf4j.Slf4j; + +@Service +@Slf4j +public class ChatServiceImpl implements ChatService { + @Autowired + private PersonalAPI personalAPI; + + public String getUser(String appid) { + String smallHeadImgUrl = ""; + JSONObject name = new JSONObject(personalAPI.getProfile(appid)); + if (name.getInt("ret") == 200) { + log.info("获取昵称"); + smallHeadImgUrl = name.getJSONObject("data").getString("smallHeadImgUrl"); + } + return smallHeadImgUrl; + } +} diff --git a/src/main/java/com/hokori/webwx/Service/impl/LoginServiceImpl.java b/src/main/java/com/hokori/webwx/Service/impl/LoginServiceImpl.java new file mode 100644 index 0000000..85b42d8 --- /dev/null +++ b/src/main/java/com/hokori/webwx/Service/impl/LoginServiceImpl.java @@ -0,0 +1,118 @@ +package com.hokori.webwx.Service.impl; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.hokori.webwx.Api.base.LoginApi; +import com.hokori.webwx.Api.base.PersonalApi; +import com.hokori.webwx.Service.LoginService; + +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONArray; + +@Service +@Slf4j +public class LoginServiceImpl implements LoginService { + + + @Override + public String usaerList() { + log.info("调用设备列表接口"); + JSONObject dlistJson = LoginApi.devicelist(); + + List deviceList = new ArrayList<>(); + JSONArray resultArray = new JSONArray(); + + try { + if (dlistJson != null && dlistJson.getIntValue("ret") == 200) { + JSONArray dataArray = dlistJson.getJSONArray("data"); + if (dataArray != null) { + for (int i = 0; i < dataArray.size(); i++) { + deviceList.add(dataArray.getString(i)); + } + } + } else { + log.error("获取设备列表失败: {}", dlistJson != null ? dlistJson.toJSONString() : "null"); + } + + for (String device : deviceList) { + log.info("获取设备 {} 的信息", device); + JSONObject profileJson = PersonalApi.getProfile(device); + String nikname = ""; + String smallHeadImgUrl = ""; + if (profileJson != null && profileJson.getIntValue("ret") == 200) { + JSONObject data = profileJson.getJSONObject("data"); + if (data != null) { + log.info("获取昵称和头像"); + nikname = data.getString("nickName"); + smallHeadImgUrl = data.getString("smallHeadImgUrl"); + } + } else { + log.error("获取设备 {} 的 Profile 失败: {}", device, profileJson != null ? profileJson.toJSONString() : "null"); + } + + JSONObject onlineJson = LoginApi.checkOnline(device); + boolean isLogin = false; + if (onlineJson != null && onlineJson.getIntValue("ret") == 200) { + isLogin = onlineJson.getBooleanValue("data"); + } else { + log.error("检查设备 {} 在线状态失败: {}", device, onlineJson != null ? onlineJson.toJSONString() : "null"); + } + + // 创建设备信息的JSON对象 + JSONObject deviceInfo = new JSONObject(); + deviceInfo.put("device", device); + deviceInfo.put("smallHeadImgUrl", smallHeadImgUrl); + deviceInfo.put("nikname", nikname); + deviceInfo.put("isLogin", isLogin); + + // 添加到结果数组 + resultArray.add(deviceInfo); + } + } catch (Exception e) { + log.error("处理设备列表数据失败", e); + } + + // 将结果数组包装到一个JSON对象中并返回字符串 + JSONObject result = new JSONObject(); + result.put("devices", resultArray); + return result.toJSONString(); + } + + public String newlogin() { + JSONObject loginJson = LoginApi.getQr(null); + if (loginJson != null && loginJson.getIntValue("ret") == 200) { + return loginJson.toJSONString(); + } else { + log.error("获取登录二维码失败: {}", loginJson!= null? loginJson.toJSONString() : "null"); + return "获取登录二维码失败"; + } + } + + public String relogin(String appid) { + JSONObject loginJson = LoginApi.getQr(appid); + if (loginJson != null && loginJson.getIntValue("ret") == 200) { + return loginJson.toJSONString(); + } else { + log.error("获取登录二维码失败: {}", loginJson!= null? loginJson.toJSONString() : "null"); + return "获取登录二维码失败"; + } + } + + public String ckqr(String appid, String uuid) { + + JSONObject loginJson = LoginApi.checkQr(appid, uuid, null); + if (loginJson!= null && loginJson.getIntValue("ret") == 200) { + return loginJson.toJSONString(); + } else { + log.error("登录失败: {}", loginJson!= null? loginJson.toJSONString() : "null"); + return loginJson.toJSONString(); + } + + } +} + diff --git a/src/main/java/com/hokori/webwx/WebwxApplication.java b/src/main/java/com/hokori/webwx/WebwxApplication.java new file mode 100644 index 0000000..e1c133b --- /dev/null +++ b/src/main/java/com/hokori/webwx/WebwxApplication.java @@ -0,0 +1,13 @@ +package com.hokori.webwx; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class WebwxApplication { + + public static void main(String[] args) { + SpringApplication.run(WebwxApplication.class, args); + } + +} diff --git a/src/main/java/com/hokori/webwx/model/GewechatMessage.java b/src/main/java/com/hokori/webwx/model/GewechatMessage.java new file mode 100644 index 0000000..c1d14a8 --- /dev/null +++ b/src/main/java/com/hokori/webwx/model/GewechatMessage.java @@ -0,0 +1,117 @@ +package com.hokori.webwx.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +// 使用 @JsonIgnoreProperties(ignoreUnknown = true) 避免因为 Gewechat 发送了未定义的字段而导致反序列化失败 +@JsonIgnoreProperties(ignoreUnknown = true) +public class GewechatMessage { + + // 假设 Gewechat 发送的消息 ID 字段名为 messageId 或 msgId + @JsonProperty("messageId") // 如果 Gewechat 的字段名不同,请修改这里,例如 @JsonProperty("msg_id") + private String messageId; + + // 假设发送者 ID 字段名为 senderId 或 from_wxid + @JsonProperty("senderId") + private String senderId; + + // 假设接收者 ID 字段名为 receiverId 或 to_wxid (通常是机器人自己的 WxID) + @JsonProperty("receiverId") + private String receiverId; + + // 假设群聊 ID 字段名为 roomId 或 room_wxid (如果是群消息) + @JsonProperty("roomId") + private String roomId; + + // 假设消息内容字段名为 content 或 text + @JsonProperty("content") + private String content; + + // 假设消息类型字段名为 messageType 或 type (例如:text, image, voice, video, file, system...) + @JsonProperty("messageType") + private String messageType; + + // 假设时间戳字段名为 timestamp (可能是 long 或 String) + @JsonProperty("timestamp") + private Long timestamp; + + // 可以根据需要添加更多字段,例如: + // @JsonProperty("senderNickname") + // private String senderNickname; + // + // @JsonProperty("isGroupMessage") + // private boolean isGroupMessage; + + // --- Getters and Setters --- + + public String getMessageId() { + return messageId; + } + + public void setMessageId(String messageId) { + this.messageId = messageId; + } + + public String getSenderId() { + return senderId; + } + + public void setSenderId(String senderId) { + this.senderId = senderId; + } + + public String getReceiverId() { + return receiverId; + } + + public void setReceiverId(String receiverId) { + this.receiverId = receiverId; + } + + public String getRoomId() { + return roomId; + } + + public void setRoomId(String roomId) { + this.roomId = roomId; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getMessageType() { + return messageType; + } + + public void setMessageType(String messageType) { + this.messageType = messageType; + } + + public Long getTimestamp() { + return timestamp; + } + + public void setTimestamp(Long timestamp) { + this.timestamp = timestamp; + } + + // --- toString() for logging --- + + @Override + public String toString() { + return "GewechatMessage{" + + "messageId='" + messageId + '\'' + + ", senderId='" + senderId + '\'' + + ", receiverId='" + receiverId + '\'' + + ", roomId='" + roomId + '\'' + + ", content='" + content + '\'' + + ", messageType='" + messageType + '\'' + + ", timestamp=" + timestamp + + '}'; + } +} diff --git a/src/main/java/com/hokori/webwx/util/OkhttpUtil.java b/src/main/java/com/hokori/webwx/util/OkhttpUtil.java new file mode 100644 index 0000000..a079c22 --- /dev/null +++ b/src/main/java/com/hokori/webwx/util/OkhttpUtil.java @@ -0,0 +1,123 @@ +package com.hokori.webwx.util; + +import com.alibaba.fastjson2.JSONObject; +import okhttp3.*; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import org.springframework.beans.factory.annotation.Value; + +import java.io.IOException; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +public class OkhttpUtil { + + // @Value("${wx.host}") + // private static String baseUrl; + private final static String baseUrl = "http://127.0.0.1:2531/v2/api"; + + private final static String token = "b974b1013013491490915972160434e9"; + + public static OkHttpClient okHttpClient() { + TrustManager[] trustManagers = buildTrustManagers(); + return new OkHttpClient.Builder() + .connectTimeout(60, TimeUnit.SECONDS) + .readTimeout(60, TimeUnit.SECONDS) + .writeTimeout(60, TimeUnit.SECONDS) + .sslSocketFactory(createSSLSocketFactory(trustManagers), (X509TrustManager) trustManagers[0]) + .hostnameVerifier((hostName, sessino) -> true) + .retryOnConnectionFailure(false)//是否开启缓存 + .build(); + } + + private static TrustManager[] buildTrustManagers() { + return new TrustManager[]{ + new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[]{}; + } + } + }; + } + + private static SSLSocketFactory createSSLSocketFactory(TrustManager[] trustAllCerts) { + SSLSocketFactory ssfFactory = null; + try { + SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(null, trustAllCerts, new SecureRandom()); + ssfFactory = sc.getSocketFactory(); + } catch (Exception e) { + e.printStackTrace(); + } + return ssfFactory; + } + + public static JSONObject postJSON(String route,JSONObject param){ + Map header = new HashMap<>(); + if(token != null){ + header.put("X-GEWE-TOKEN",token); + } + try { + if(baseUrl == null || "".equals(baseUrl)){ + throw new RuntimeException("baseUrl 未配置"); + } + String res = json(baseUrl + route, header, param.toJSONString(), okHttpClient()); + System.out.println(res); + JSONObject jsonObject = JSONObject.parse(res); + if(jsonObject.getInteger("ret") == 200){ + return jsonObject; + }else{ + throw new RuntimeException(res); + } + } catch (Exception e) { + System.out.println("url="+baseUrl + route); + throw new RuntimeException("请求失败" + e.getMessage()); + } + } + + private static String json(String url, Map header, String json, OkHttpClient client) throws IOException { + // 创建一个请求 Builder + Request.Builder builder = new Request.Builder(); + // 创建一个 request + Request request = builder.url(url).build(); + + // 创建一个 Headers.Builder + Headers.Builder headerBuilder = request.headers().newBuilder(); + + // 装载请求头参数 + Iterator> headerIterator = header.entrySet().iterator(); + headerIterator.forEachRemaining(e -> { + headerBuilder.add(e.getKey(), (String) e.getValue()); + }); + headerBuilder.add("Content-Type", "application/json"); + + // application/octet-stream + RequestBody requestBody = FormBody.create(MediaType.parse("application/json"), json); + + // 设置自定义的 builder + builder.headers(headerBuilder.build()).post(requestBody); + + try (Response execute = client.newCall(builder.build()).execute()) { + return execute.body().string(); + } + } + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..8a3bcd8 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,7 @@ +spring: + application: + name: webwx + +wx: + host: http://127.0.0.1:2531/v2/api + tokenid: b974b1013013491490915972160434e9 \ No newline at end of file diff --git a/src/main/resources/static/css/chat.css b/src/main/resources/static/css/chat.css new file mode 100644 index 0000000..5fc9285 --- /dev/null +++ b/src/main/resources/static/css/chat.css @@ -0,0 +1,237 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} +body { + font-family: Arial, sans-serif; + background-color: #f5f5f5; + height: 100vh; + display: flex; + overflow: hidden; +} +/* 左侧聊天列表 */ +.sidebar { + width: 280px; + background-color: #2e2e2e; + color: #fff; + display: flex; + flex-direction: column; + border-right: 1px solid #444; +} +.search-bar { + padding: 10px; + background-color: #2e2e2e; + border-bottom: 1px solid #444; + display: flex; + align-items: center; +} +.search-bar input { + background-color: #444; + border: none; + border-radius: 4px; + padding: 8px 10px; + color: #fff; + width: 100%; + margin-right: 10px; +} +.search-bar .add-btn { + color: #aaa; + font-size: 20px; + cursor: pointer; +} +.chat-list { + flex: 1; + overflow-y: auto; +} +.chat-item { + padding: 10px; + display: flex; + border-bottom: 1px solid #444; + cursor: pointer; + position: relative; +} +.chat-item:hover { + background-color: #3a3a3a; +} +.chat-item.active { + background-color: #3a3a3a; +} +.avatar { + width: 40px; + height: 40px; + border-radius: 3px; + margin-right: 10px; + background-color: #666; + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; +} +.avatar img { + width: 100%; + height: 100%; + object-fit: cover; +} +.chat-info { + flex: 1; +} +.chat-header { + display: flex; + justify-content: space-between; + margin-bottom: 5px; +} +.chat-name { + font-weight: bold; + color: #eee; +} +.chat-time { + font-size: 12px; + color: #999; +} +.chat-preview { + font-size: 13px; + color: #aaa; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +/* 右侧聊天内容 */ +.chat-container { + flex: 1; + display: flex; + flex-direction: column; + background-color: #f5f5f5; +} +.chat-header-bar { + padding: 10px 20px; + background-color: #f5f5f5; + border-bottom: 1px solid #ddd; + display: flex; + justify-content: space-between; + align-items: center; +} +.chat-title { + font-weight: bold; + font-size: 16px; +} +.header-actions { + color: #666; + font-size: 20px; + cursor: pointer; +} +.messages-container { + flex: 1; + padding: 20px; + overflow-y: auto; + background-color: #f5f5f5; +} +.message { + margin-bottom: 15px; + display: flex; + align-items: flex-start; +} +.message.sent { + flex-direction: row-reverse; +} +.message-avatar { + width: 40px; + height: 40px; + border-radius: 3px; + margin: 0 10px; + background-color: #ccc; + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; +} + +.message-avatar img { + width: 100%; + height: 100%; + object-fit: cover; +} +.message-content { + max-width: 60%; + padding: 10px 15px; + border-radius: 4px; + position: relative; +} +.message.received .message-content { + background-color: white; +} +.message.sent .message-content { + background-color: #95ec69; +} +.message-time { + display: block; + text-align: center; + color: #999; + font-size: 12px; + margin: 15px 0; +} +/* 输入区域 */ +.input-area { + padding: 10px; + background-color: #f5f5f5; + border-top: 1px solid #ddd; + display: flex; + align-items: center; +} +.emoji-btn, .file-btn { + font-size: 24px; + color: #666; + margin: 0 10px; + cursor: pointer; +} +.message-input { + flex: 1; + padding: 10px; + border: none; + border-radius: 4px; + background-color: white; + resize: none; + height: 40px; + margin: 0 10px; +} +.send-btn { + background-color: #07C160; + color: white; + border: none; + border-radius: 4px; + padding: 8px 15px; + cursor: pointer; +} +.send-btn:hover { + background-color: #06b057; +} +/* 侧边导航 */ +.nav-sidebar { + width: 60px; + background-color: #2e2e2e; + display: flex; + flex-direction: column; + align-items: center; + padding: 20px 0; +} +.nav-item { + width: 40px; + height: 40px; + margin-bottom: 20px; + display: flex; + justify-content: center; + align-items: center; + color: #aaa; + cursor: pointer; + border-radius: 50%; +} +.nav-item.active { + background-color: #07C160; + color: white; +} +.nav-avatar { + width: 30px; + height: 30px; + border-radius: 50%; + object-fit: cover; +} \ No newline at end of file diff --git a/src/main/resources/static/css/login.css b/src/main/resources/static/css/login.css new file mode 100644 index 0000000..58382a3 --- /dev/null +++ b/src/main/resources/static/css/login.css @@ -0,0 +1,141 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} +body { + font-family: Arial, sans-serif; + background-color: #07C160; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; +} +.login-container { + background-color: rgba(224, 240, 233, 0.9); + width: 90%; + max-width: 500px; + border-radius: 15px; + padding: 30px 20px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + position: relative; +} +.user-card { + background-color: #07C160; + border-radius: 10px; + padding: 15px; + margin-bottom: 15px; + display: flex; + align-items: center; +} +.avatar { + width: 60px; + height: 60px; + background-color: white; + border-radius: 5px; + margin-right: 15px; + display: flex; + justify-content: center; + align-items: center; + position: relative; +} +.avatar-text { + color: #333; + font-size: 12px; + text-align: center; + line-height: 1.2; +} +.user-info { + flex: 1; +} +.username { + color: white; + font-size: 20px; + font-weight: bold; + margin-bottom: 5px; +} +.user-id { + color: rgba(255, 255, 255, 0.8); + font-size: 12px; + background-color: rgba(0, 0, 0, 0.2); + padding: 3px 8px; + border-radius: 10px; + display: inline-block; +} +.buttons { + display: flex; + justify-content: space-between; + margin-top: 20px; +} +.btn { + background-color: #07C160; + color: white; + border: none; + border-radius: 5px; + padding: 12px 0; + font-size: 16px; + cursor: pointer; + width: 48%; + text-align: center; +} + +.status-indicator { + width: 12px; + height: 12px; + border-radius: 50%; + margin-left: 10px; + border: 1px solid rgba(255, 255, 255, 0.5); +} + +.status-indicator.online { + background-color: #00FF00; + box-shadow: 0 0 5px #00FF00; +} + +.status-indicator.offline { + background-color: #FF0000; + box-shadow: 0 0 5px #FF0000; +} + +/* 用户卡片选中效果 */ +.user-card { + transition: all 0.3s ease; + cursor: pointer; + position: relative; + overflow: hidden; +} + +.user-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); +} + +.user-card.selected { + background-color: #059e4f; + transform: scale(1.02); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3); +} + +.user-card.selected::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border: 2px solid #fff; + border-radius: 8px; + animation: pulse 1.5s infinite; +} + +@keyframes pulse { + 0% { + box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.7); + } + 70% { + box-shadow: 0 0 0 10px rgba(255, 255, 255, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); + } +} \ No newline at end of file diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html new file mode 100644 index 0000000..69c4249 --- /dev/null +++ b/src/main/resources/static/index.html @@ -0,0 +1,11 @@ + + + + + + Document + + + 首页测试 + + \ No newline at end of file diff --git a/src/main/resources/templates/chat.html b/src/main/resources/templates/chat.html new file mode 100644 index 0000000..38ea0a2 --- /dev/null +++ b/src/main/resources/templates/chat.html @@ -0,0 +1,130 @@ + + + + + + 微信聊天 + + + + + + + + + + +
+
+
雪白厌氧和他的朋友们 (4)
+
+
+
+
星期六 13:38
+ +
+
👤
+
+
+ +
+
👤
+
真恶心啊
+
+ +
星期六 13:43
+ +
+
👤
+
抓起来看下
+
+ +
+
👤
+
?
+
+ +
+
👤
+
?
+
+ +
+
👤
+
太远了看不清
+
+
+
+
😊
+
📎
+ + +
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html new file mode 100644 index 0000000..daa2407 --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,79 @@ + + + + + + 微信登录 + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/qrlogin.html b/src/main/resources/templates/qrlogin.html new file mode 100644 index 0000000..26f0365 --- /dev/null +++ b/src/main/resources/templates/qrlogin.html @@ -0,0 +1,92 @@ + + + + + + 扫码登录 + + + +
+

请使用微信扫描二维码登录

+ + 登录二维码 + +

AppID:

+

请在手机上确认登录

+
+ + + + \ No newline at end of file diff --git a/src/test/java/com/hokori/webwx/WebwxApplicationTests.java b/src/test/java/com/hokori/webwx/WebwxApplicationTests.java new file mode 100644 index 0000000..ec626e5 --- /dev/null +++ b/src/test/java/com/hokori/webwx/WebwxApplicationTests.java @@ -0,0 +1,13 @@ +package com.hokori.webwx; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class WebwxApplicationTests { + + @Test + void contextLoads() { + } + +}