Spring Security 6 with MVC on classpath resolves requestMatchers(HttpMethod, String) to MvcRequestMatcher, which fails to match the actual servlet paths for this service. Switching to explicit AntPathRequestMatcher instances bypasses MVC introspection and forces pure Ant pattern evaluation, fixing persistent 401 on public upload/serve endpoints. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|---|---|---|
| common | ||
| demo-service | ||
| docs | ||
| file-service | ||
| im-sdk | ||
| im-service | ||
| license-service | ||
| push-service | ||
| tenant-service | ||
| update-service | ||
| .dockerignore | ||
| .gitignore | ||
| .java-version | ||
| Dockerfile | ||
| Jenkinsfile | ||
| maven-settings.xml | ||
| pom.xml | ||
| README.md | ||
XuqmGroup-Server 后端文档
当前推荐阅读入口:
/docs/server/README.md
仓库内联调文档:docs/API_ACCESS.md
该文档保留为仓库内说明,线上地址、初始化账号与最新联调接口请以最新文档为准。
Spring Boot 3.4.4 · Java 21 · Maven 多模块
模块结构
XuqmGroup-Server/
├── pom.xml # 父 POM,统一依赖版本
├── common/ # 公共库(ApiResponse、JWT、异常处理)
├── tenant-service/ # 租户服务 :8081
├── im-service/ # IM 服务 :8082
├── push-service/ # 推送服务 :8083
└── update-service/ # 版本管理服务 :8084
技术栈
| 组件 | 版本 |
|---|---|
| Spring Boot | 3.4.4 |
| Java | 21 |
| Spring Security 6 | JWT 无状态认证 |
| Spring Data JPA | Hibernate 6,ddl-auto: update |
| MySQL | 8.x |
| Redis | 7.x(验证码、子账号会话) |
| JJWT | 0.12.6,HMAC-SHA256 |
| Spring Mail | 邮件验证码 |
| Spring WebSocket + STOMP | IM 实时通信 |
快速启动
# 前置:MySQL 8 + Redis 7 本地运行
cd XuqmGroup-Server
# 启动所有服务(各模块独立启动)
cd tenant-service && mvn spring-boot:run &
cd im-service && mvn spring-boot:run &
cd push-service && mvn spring-boot:run &
cd update-service && mvn spring-boot:run &
首次启动会自动建表(ddl-auto: update)。
运营平台默认管理员:admin / Admin@123456(可通过 ops.admin.* 配置覆盖)。
tenant-service(租户服务):8081
数据库表
| 表名 | 说明 |
|---|---|
t_tenant |
租户主账号 & 子账号 |
t_app |
应用 |
t_feature_service |
功能服务(IM/推送/版本管理) |
t_email_verification |
邮箱验证码 |
t_ops_admin |
运营平台管理员 |
接口列表
认证(无需 Token)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/auth/captcha |
获取图形验证码 |
| POST | /api/auth/send-email-code?email=&purpose= |
发送邮箱验证码(purpose: REGISTER / FORGOT_PASSWORD / SUB_ACCOUNT) |
| POST | /api/auth/register |
注册主账号 |
| POST | /api/auth/login |
登录,返回 JWT |
| POST | /api/auth/forgot-password?email= |
发送重置密码邮件 |
| POST | /api/auth/reset-password?email=&code=&newPassword= |
重置密码 |
注册请求体
{
"username": "demo",
"password": "Demo@123456",
"email": "demo@example.com",
"nickname": "Demo",
"emailCode": "AB3K"
}
登录请求体
{
"account": "demo",
"password": "Demo@123456",
"captchaKey": "uuid-from-captcha-api",
"captchaCode": "XK2P"
}
登录响应
{
"code": 200, "status": "0",
"data": { "token": "eyJ..." },
"message": "success"
}
应用管理(需 Token)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/apps |
查询当前租户的应用列表 |
| GET | /api/apps/{id} |
查询应用详情 |
| POST | /api/apps |
创建应用 |
| PUT | /api/apps/{id} |
更新应用信息 |
| DELETE | /api/apps/{id} |
删除应用 |
创建应用请求体
{
"name": "我的App",
"packageName": "com.example.myapp",
"description": "描述",
"iconUrl": "https://cdn.example.com/icon.png"
}
应用对象(响应)
{
"id": "uuid",
"tenantId": "uuid",
"name": "我的App",
"packageName": "com.example.myapp",
"appKey": "ak_xxxxxxxx",
"appSecret": "as_xxxxxxxx",
"createdAt": "2026-04-21T00:00:00"
}
说明:SDK 和 demo 侧统一传
appKey。当前默认值是ak_demo_chat,如果数据库里没有这条记录,tenant-service 会在启动时自动创建。IM 登录必须先存在注册用户,不再支持“登录即注册”。
功能服务(需 Token)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/apps/{appKey}/services |
获取应用下所有功能服务 |
| POST | /api/apps/{appKey}/services/toggle?platform=&serviceType=&enable= |
开启/关闭功能服务 |
| POST | /api/apps/{appKey}/services/{id}/regenerate-key |
重新生成 secretKey |
platform 枚举:ANDROID / IOS / HARMONY
serviceType 枚举:IM / PUSH / UPDATE
子账号(需 Token)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/sub-accounts |
查询子账号列表 |
| POST | /api/sub-accounts/send-verify-code?email= |
发送邮箱验证码(24h 内有效一次) |
| POST | /api/sub-accounts/verify-email?email=&code= |
验证邮箱(通过后 24h 内可创建) |
| POST | /api/sub-accounts |
创建子账号(需先验证邮箱) |
| DELETE | /api/sub-accounts/{id} |
禁用子账号 |
| GET | /api/sub-accounts/generate-password |
生成随机密码 |
创建子账号请求体
{
"username": "sub_user",
"password": "Sub@123456",
"email": "sub@example.com",
"nickname": "子账号"
}
运营平台(需 OPS Token)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/auth/ops/login |
运营平台登录 |
| GET | /api/ops/tenants?keyword=&page=&size= |
分页查询租户 |
| POST | /api/ops/tenants/{id}/toggle-status |
启用/禁用租户 |
| GET | /api/ops/statistics |
获取统计数据 |
运营登录请求体
{ "username": "admin", "password": "Admin@123456" }
统计响应
{
"data": {
"totalTenants": 128,
"todayNew": 5,
"activeApps": 42,
"onlineUsers": 0
}
}
im-service(IM 服务):8082
数据库表
| 表名 | 说明 |
|---|---|
im_account |
IM 用户账号(仅存 userId,不含昵称/头像) |
im_group |
群组(memberIds、adminIds 为逗号分隔字符串) |
im_message |
消息记录 |
im_keyword_filter |
关键词过滤规则 |
im_webhook_config |
Webhook 配置 |
IM 账号登录
POST /api/im/auth/login
?appKey=ak_xxx
&userId=user_001
&userSig=your_user_sig
服务端可本地签发 userSig,IM 管理页也支持生成与校验;管理员账号可用于服务端 SDK / REST API。
响应:{ "data": { "token": "eyJ..." } }
HTTP 消息接口
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/im/messages/send?appKey= |
发送消息 |
| POST | /api/im/messages/{id}/revoke?appKey= |
撤回消息 |
| GET | /api/im/messages/history/{toId}?appKey=&page=&size= |
查询历史消息 |
发送消息请求体
{
"toId": "user_002",
"chatType": "SINGLE",
"msgType": "TEXT",
"content": "Hello!",
"mentionedUserIds": ""
}
消息类型(MsgType)
| 值 | 说明 |
|---|---|
| TEXT | 文本(支持表情 Unicode / 自定义表情 ID) |
| IMAGE | 图片(content 为 URL) |
| VIDEO | 视频 |
| AUDIO | 语音 |
| FILE | 文件 |
| CUSTOM | 自定义(content 为 JSON 字符串) |
| LOCATION | 位置(content 为 {"lat":x,"lng":y,"address":"..."}) |
| NOTIFY | 系统通知 |
| RICH_TEXT | 图文混排 |
| CALL_AUDIO | 音频通话信令 |
| CALL_VIDEO | 视频通话信令 |
| FORWARD | 转发(content 为原消息 JSON) |
| REVOKED | 已撤回(系统占位,不可主动发送) |
WebSocket 实时通信
连接
ws://localhost:8082/ws/im?token=<im_jwt>
支持 STOMP 协议(可直接使用原生 WebSocket 发 JSON frame)。
发送消息(客户端 → 服务端)
{
"destination": "/app/chat.send",
"payload": {
"appKey": "ak_xxx",
"toId": "user_002",
"chatType": "SINGLE",
"msgType": "TEXT",
"content": "Hello!"
}
}
接收消息(服务端 → 客户端)
- 单聊推送至:
/user/{userId}/queue/messages - 群聊推送至:
/topic/group/{groupId}
Frame 格式:
{
"type": "MESSAGE",
"payload": {
"id": "uuid",
"fromId": "user_001",
"toId": "user_002",
"chatType": "SINGLE",
"msgType": "TEXT",
"content": "Hello!",
"revoked": false,
"createdAt": "2026-04-21T10:00:00"
}
}
撤回消息(客户端 → 服务端)
{
"destination": "/app/chat.revoke",
"payload": { "appKey": "ak_xxx", "messageId": "msg-uuid" }
}
撤回推送(服务端 → 客户端)
{
"type": "REVOKE",
"payload": { "msgId": "msg-uuid", "operatorId": "user_001" }
}
关键词过滤
在 im_keyword_filter 表中配置,支持 REPLACE(替换为 *)和 BLOCK(拒绝发送)两种动作,pattern 为正则表达式。
Webhook
在 im_webhook_config 表中为 App 配置 URL,消息发送后异步 HTTP POST 通知,超时 3000ms,格式:
{
"event": "message",
"appKey": "ak_xxx",
"message": { ...消息对象... }
}
push-service(推送服务):8083
数据库表
| 表名 | 说明 |
|---|---|
push_device_token |
设备推送 token(appKey + userId + vendor 唯一) |
支持厂商
HUAWEI / XIAOMI / OPPO / VIVO / HONOR / APNS(iOS)
接口
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/push/register |
注册设备 token |
| POST | /api/push/receive-push |
开启或关闭接收推送 |
| DELETE | /api/push/device/unregister |
解绑设备 token |
| POST | /api/push/send |
向指定用户推送通知 |
| POST | /api/push/internal/notify |
IM 服务内部调用,批量触发离线推送 |
其中 /api/push/register、/api/push/device/unregister、/api/push/receive-push 走 JWT 鉴权;/api/push/internal/notify 走内部 token。
注册 token
POST /api/push/register
?appKey=ak_xxx
&userId=user_001
&vendor=HUAWEI
&token=device_push_token
发送推送
POST /api/push/send
?appKey=ak_xxx
&userId=user_001
&title=新消息
&body=张三: Hello!
&payload={"type":"IM","msgId":"uuid"}
开关接收推送
POST /api/push/receive-push
?appKey=ak_xxx
&userId=user_001
&enabled=false
内部通知
{
"appKey": "ak_xxx",
"userIds": ["user_001", "user_002"],
"title": "群聊消息",
"body": "张三: Hello!",
"payload": "{\"type\":\"IM\",\"msgId\":\"uuid\"}"
}
环境变量配置
push:
huawei:
app-id: ${HUAWEI_APP_ID}
app-secret: ${HUAWEI_APP_SECRET}
xiaomi:
app-secret: ${XIAOMI_APP_SECRET}
apns:
key-id: ${APNS_KEY_ID}
team-id: ${APNS_TEAM_ID}
key-path: ${APNS_KEY_PATH} # .p8 文件路径
bundle-id: ${APNS_BUNDLE_ID}
production: false # true = 生产环境
update-service(版本管理服务):8084
数据库表
| 表名 | 说明 |
|---|---|
app_version |
原生 App 版本(Android APK / iOS 跳转链接) |
rn_bundle |
RN Bundle 版本 |
App 版本管理
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/updates/app/check |
检查更新 |
| POST | /api/v1/updates/app/upload |
上传版本(先传 file-service,再把 apkUrl 交给 update-service) |
| POST | /api/v1/updates/app/{id}/publish |
发布版本 |
| GET | /api/v1/updates/app/list |
版本列表 |
| POST | /api/v1/updates/store/app/{id}/execute-submit |
批量提交应用市场,过程会写入批次日志与逐市场状态 |
检查更新
GET /api/v1/updates/app/check
?appKey=ak_xxx
&platform=ANDROID
¤tVersionCode=10
响应(有更新):
{
"data": {
"needsUpdate": true,
"versionName": "1.1.0",
"versionCode": 11,
"downloadUrl": "http://update.xuqm.com/files/apk/xxx.apk",
"changeLog": "修复若干问题",
"forceUpdate": false,
"appStoreUrl": "",
"marketUrl": ""
}
}
platform 枚举:ANDROID / IOS / HARMONY
市场提审状态:PENDING / SUBMITTING / UNDER_REVIEW / APPROVED / REJECTED
上传 APK(两段式)
POST /api/file/upload
file=<binary>
拿到返回的 url 后,再调用:
POST /api/v1/updates/app/upload
appKey=ak_xxx
platform=ANDROID
versionName=1.1.0
versionCode=11
changeLog=更新内容
forceUpdate=false
apkUrl=https://file.dev.xuqinmin.com/api/file/<hash>
RN Bundle 管理
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/rn/update/check |
检查 RN Bundle 更新 |
| POST | /api/v1/rn/upload |
上传 Bundle 文件 |
| POST | /api/v1/rn/{id}/publish |
发布 Bundle |
检查 RN 更新
GET /api/v1/rn/update/check
?appKey=ak_xxx
&moduleId=main
&platform=android
¤tVersion=1.0.0
响应(有更新):
{
"data": {
"needsUpdate": true,
"latestVersion": "1.1.0",
"downloadUrl": "http://...",
"md5": "abc123...",
"minCommonVersion": "1.0.0",
"note": "热更新说明"
}
}
上传 Bundle(multipart/form-data)
POST /api/v1/rn/upload
appKey=ak_xxx
moduleId=main
platform=ANDROID
version=1.1.0
minCommonVersion=1.0.0
note=修复闪退
bundle=<binary .bundle 文件>
服务端自动计算 MD5,存储至 {upload-dir}/rn/{appKey}/{platform}/{moduleId}/。
环境变量配置
update:
upload-dir: ${UPDATE_UPLOAD_DIR:/tmp/xuqm-update}
base-url: ${UPDATE_BASE_URL:http://localhost:8084}