This commit is contained in:
2025-12-30 15:23:46 +08:00
530 changed files with 9383 additions and 161088 deletions

View File

@@ -0,0 +1,26 @@
---
description: Implement an approved OpenSpec change and keep tasks in sync.
---
The user wants to apply the following change. Use the openspec instructions to implement the approved change.
<ChangeId>
$ARGUMENTS
</ChangeId>
<!-- OPENSPEC:START -->
**Guardrails**
- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required.
- Keep changes tightly scoped to the requested outcome.
- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications.
**Steps**
Track these steps as TODOs and complete them one by one.
1. Read `changes/<id>/proposal.md`, `design.md` (if present), and `tasks.md` to confirm scope and acceptance criteria.
2. Work through tasks sequentially, keeping edits minimal and focused on the requested change.
3. Confirm completion before updating statuses—make sure every item in `tasks.md` is finished.
4. Update the checklist after all work is done so each task is marked `- [x]` and reflects reality.
5. Reference `openspec list` or `openspec show <item>` when additional context is required.
**Reference**
- Use `openspec show <id> --json --deltas-only` if you need additional context from the proposal while implementing.
<!-- OPENSPEC:END -->

View File

@@ -0,0 +1,30 @@
---
description: Archive a deployed OpenSpec change and update specs.
---
The user wants to archive the following deployed change. Use the openspec instructions to archive the change and update specs.
<ChangeId>
$ARGUMENTS
</ChangeId>
<!-- OPENSPEC:START -->
**Guardrails**
- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required.
- Keep changes tightly scoped to the requested outcome.
- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications.
**Steps**
1. Determine the change ID to archive:
- If this prompt already includes a specific change ID (for example inside a `<ChangeId>` block populated by slash-command arguments), use that value after trimming whitespace.
- If the conversation references a change loosely (for example by title or summary), run `openspec list` to surface likely IDs, share the relevant candidates, and confirm which one the user intends.
- Otherwise, review the conversation, run `openspec list`, and ask the user which change to archive; wait for a confirmed change ID before proceeding.
- If you still cannot identify a single change ID, stop and tell the user you cannot archive anything yet.
2. Validate the change ID by running `openspec list` (or `openspec show <id>`) and stop if the change is missing, already archived, or otherwise not ready to archive.
3. Run `openspec archive <id> --yes` so the CLI moves the change and applies spec updates without prompts (use `--skip-specs` only for tooling-only work).
4. Review the command output to confirm the target specs were updated and the change landed in `changes/archive/`.
5. Validate with `openspec validate --strict` and inspect with `openspec show <id>` if anything looks off.
**Reference**
- Use `openspec list` to confirm change IDs before archiving.
- Inspect refreshed specs with `openspec list --specs` and address any validation issues before handing off.
<!-- OPENSPEC:END -->

View File

@@ -0,0 +1,31 @@
---
description: Scaffold a new OpenSpec change and validate strictly.
---
The user has requested the following change proposal. Use the openspec instructions to create their change proposal.
<UserRequest>
$ARGUMENTS
</UserRequest>
<!-- OPENSPEC:START -->
**Guardrails**
- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required.
- Keep changes tightly scoped to the requested outcome.
- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications.
- Identify any vague or ambiguous details and ask the necessary follow-up questions before editing files.
- Do not write any code during the proposal stage. Only create design documents (proposal.md, tasks.md, design.md, and spec deltas). Implementation happens in the apply stage after approval.
**Steps**
1. Review `openspec/project.md`, run `openspec list` and `openspec list --specs`, and inspect related code or docs (e.g., via `rg`/`ls`) to ground the proposal in current behaviour; note any gaps that require clarification.
2. Choose a unique verb-led `change-id` and scaffold `proposal.md`, `tasks.md`, and `design.md` (when needed) under `openspec/changes/<id>/`.
3. Map the change into concrete capabilities or requirements, breaking multi-scope efforts into distinct spec deltas with clear relationships and sequencing.
4. Capture architectural reasoning in `design.md` when the solution spans multiple systems, introduces new patterns, or demands trade-off discussion before committing to specs.
5. Draft spec deltas in `changes/<id>/specs/<capability>/spec.md` (one folder per capability) using `## ADDED|MODIFIED|REMOVED Requirements` with at least one `#### Scenario:` per requirement and cross-reference related capabilities when relevant.
6. Draft `tasks.md` as an ordered list of small, verifiable work items that deliver user-visible progress, include validation (tests, tooling), and highlight dependencies or parallelizable work.
7. Validate with `openspec validate <id> --strict` and resolve every issue before sharing the proposal.
**Reference**
- Use `openspec show <id> --json --deltas-only` or `openspec show <spec> --type spec` to inspect details when validation fails.
- Search existing requirements with `rg -n "Requirement:|Scenario:" openspec/specs` before writing new ones.
- Explore the codebase with `rg <keyword>`, `ls`, or direct file reads so proposals align with current implementation realities.
<!-- OPENSPEC:END -->

4
.gitignore vendored
View File

@@ -3,3 +3,7 @@
/WxCheckApi/.vs
/WxCheckApi/*.user
/.vscode
WxCheckMvc/obj
WxCheckMvc/.vs
WxCheckMvc/bin
node_modules

View File

@@ -0,0 +1,93 @@
# 会话管理和用户管理页面优化
## 1. 优化conversations模块的发送方式显示
### 实现方案
- 修改 `ConversationList.vue` 中的发送方式列将其重构为tag标签形式
-`sendMethod` 为文字类型时显示success样式的tag标签内容为"text"
-`sendMethod` 为语音类型时显示默认样式的tag标签内容为"voice"
### 代码修改点
- `src/views/ConversationList.vue:98-104`修改发送方式列的实现添加tag标签模板
## 2. 实现手机号脱敏与时间格式化功能
### 实现方案
- 创建工具函数处理手机号脱敏和时间格式化
- 实现手机号自动脱敏默认隐藏中间4-8位数字
- 添加点击交互,支持显示/隐藏完整手机号
- 实现时间格式转换,将时间数据中的"T"字符替换为空格
### 代码修改点
- 创建 `src/utils/formatters.js`:添加手机号脱敏和时间格式化工具函数
- `src/views/ConversationList.vue`:应用手机号脱敏和时间格式化
- `src/views/UserList.vue`:应用手机号脱敏和时间格式化
## 3. 重构表格组件与实现无限滚动分页
### 实现方案
- 将现有el-table组件包装在el-scrollbar中实现滚动加载
- 修改 `fetchConversations` 方法,添加分页参数
- 实现滚动到底部自动加载下一页数据
- 添加加载状态提示,避免重复请求
- 设置固定分页大小为20条/页
### 代码修改点
- `src/views/ConversationList.vue`
- 添加el-scrollbar组件包装el-table
- 修改fetchConversations方法添加分页逻辑
- 实现滚动加载功能
- 移除传统分页控件
## 4. 优化用户管理页面
### 实现方案
- 移除UserList页面的分页控件及相关逻辑
- 调整表格配置,不分页加载所有用户数据
- 应用时间格式转换,将时间数据中的"T"字符替换为空格
### 代码修改点
- `src/views/UserList.vue`
- 移除分页控件和相关数据
- 修改fetchUsers方法移除分页逻辑
- 应用时间格式化
## 5. 文档记录
### 实现方案
- 在openspec目录下创建详细的修改记录文档
- 记录修改内容、原因及影响范围
### 代码修改点
- 创建 `openspec/changes/optimize-conversation-user-management/` 目录
- 创建 `proposal.md`:描述问题和解决方案
- 创建 `tasks.md`:列出具体实现任务
- 创建 `implementation.md`:详细记录技术实现和最佳实践
## 实现顺序
1. 创建工具函数文件 `src/utils/formatters.js`
2. 优化ConversationList.vue的发送方式显示
3. 实现手机号脱敏与时间格式化功能
4. 重构表格组件与实现无限滚动分页
5. 优化用户管理页面
6. 创建修改记录文档
## 预期效果
- 会话记录页面:
- 发送方式以tag标签形式显示
- 手机号自动脱敏,支持点击显示/隐藏完整号码
- 时间格式统一为"YYYY-MM-DD HH:mm:ss"
- 表格实现无限滚动加载,提升用户体验
- 用户管理页面:
- 移除分页控件,加载所有用户数据
- 时间格式统一为"YYYY-MM-DD HH:mm:ss"
- 手机号自动脱敏,支持点击显示/隐藏完整号码
- 代码质量:
- 工具函数复用性高
- 代码结构清晰,易于维护
- 符合openspec开发规范
- 详细的修改记录文档

View File

@@ -0,0 +1,106 @@
# 后台管理网站开发计划
## 1. 项目初始化
* 初始化Vue 3.x + Element Plus + Vite项目
* 配置项目目录结构
* 安装必要依赖vue-router、Pinia、axios
## 2. 项目基础配置
* 创建路由配置文件:`src/router/index.js`
* 创建Pinia状态管理`src/store/index.js``src/store/auth.js``src/store/theme.js`
* 配置axios拦截器`src/utils/request.js`
* 创建全局样式文件:`src/styles/main.scss``src/styles/variables.scss``src/styles/responsive.scss`
## 3. 登录功能实现
* 创建登录页面:`src/views/Login.vue`
* 实现登录验证逻辑固定账号密码Admin/Admin
* 实现登录状态管理
* 配置路由守卫,保护受保护页面
## 4. 主题切换功能
* 创建主题切换组件:`src/components/ThemeSwitcher.vue`
* 实现深色/浅色模式切换逻辑
* 实现主题状态持久化localStorage
## 5. 响应式布局和菜单
* 创建布局组件:`src/components/Layout/Layout.vue``src/components/Layout/Sidebar.vue``src/components/Layout/Header.vue`
* 实现侧边栏菜单(桌面/平板端)
* 实现抽屉式菜单(手机端)
* 实现响应式布局适配
## 6. 会话记录管理页面
* 创建会话记录管理页面:`src/views/ConversationList.vue`
* 实现会话记录查询功能
* 实现多条件筛选功能(时间范围、用户、消息类型、部门)
* 实现会话记录表格展示
## 7. 用户管理页面
* 创建用户管理页面:`src/views/UserList.vue`
* 实现用户列表查询功能
* 实现用户表格展示
## 8. 首页实现
* 创建首页:`src/views/Home.vue`
* 实现系统概览和统计信息展示
## 9. 测试和优化
* 测试所有功能模块
* 优化响应式布局
* 优化页面性能
* 完善错误处理
## 10. 文档更新
* 更新项目文档
* 记录开发过程中的变更
## 技术规范遵循
* 使用Vue 3.x Composition API和`<script setup>`语法
* 遵循Element Plus组件库规范
* 实现响应式设计,优先适配手机宽度
* 采用模块化设计思路
* 严格遵循openspec文档中规定的API接口和数据模型
## 变更记录要求
* 所有代码修改、功能调整或配置变更均需在changes文档中进行详细记录
* 记录内容包括:变更时间、变更人、变更模块、变更类型(新增/修改/删除、变更具体内容、关联需求ID及变更原因

18
AGENTS.md Normal file
View File

@@ -0,0 +1,18 @@
<!-- OPENSPEC:START -->
# OpenSpec Instructions
These instructions are for AI assistants working in this project.
Always open `@/openspec/AGENTS.md` when the request:
- Mentions planning or proposals (words like proposal, spec, change, plan)
- Introduces new capabilities, breaking changes, architecture shifts, or big performance/security work
- Sounds ambiguous and you need the authoritative spec before coding
Use `@/openspec/AGENTS.md` to learn:
- How to create and apply change proposals
- Spec format and conventions
- Project structure and guidelines
Keep this managed block so 'openspec update' can refresh the instructions.
<!-- OPENSPEC:END -->

View File

@@ -1,13 +0,0 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "9.0.10",
"commands": [
"dotnet-ef"
],
"rollForward": false
}
}
}

View File

@@ -1,35 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
// 使用 IntelliSense 找出 C# 调试存在哪些属性
// 将悬停用于现有属性的说明
// 有关详细信息,请访问 https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md。
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// 如果已更改目标框架,请确保更新程序路径。
"program": "${workspaceFolder}/bin/Debug/net8.0/WxCheckApi.dll",
"args": [],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
// 启用在启动 ASP.NET Core 时启动 Web 浏览器。有关详细信息: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
}
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}

View File

@@ -1,41 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/WxCheckApi.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/WxCheckApi.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/WxCheckApi.csproj"
],
"problemMatcher": "$msCompile"
}
]
}

View File

@@ -1,766 +0,0 @@
# 微信小程序:语音信息打卡 API 接口文档
本文档详细描述了WxCheck项目中Login控制器和Check控制器的所有API接口包括接口功能、请求参数、响应格式以及调用示例。
## 基础信息
- **API基础路径**`https://wx-xcx-check.blv-oa.com:4433/api/[controller]/[action]`
- **请求方式**POST
## 统一响应格式
所有接口返回以下统一格式的JSON响应
```json
{
"success": true/false,
"message": "操作结果描述",
"data": {...}, // 可选,返回的具体数据
"error": "错误信息" // 可选,错误时返回
}
```
## 1. Login控制器接口
### 1.1 用户注册接口
#### 接口描述
用户信息更新功能根据传入的UserKey更新用户的UserName、WeChatName和PhoneNumber信息。
#### 接口路径
`/api/Login/Register`
#### 请求参数
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| UserName | string | 否 | 用户名(可以为空) |
| UserKey | string | 是 | 用户唯一标识键 |
| WeChatName | string | 否 | 微信名称 |
| PhoneNumber | string | 否 | 电话号码 |
| AvatarUrl | string | 否 | 头像地址 |
#### 请求示例
```json
{
"UserName": "张三",
"UserKey": "openid_from_wechat",
"WeChatName": "张三的微信",
"PhoneNumber": "13800138000",
"AvatarUrl": "https://example.com/avatar.jpg"
}
```
#### 响应示例
**成功响应:**
```json
{
"success": true,
"data": {
"Id": 1,
"UserName": "张三",
"UserKey": "openid_from_wechat",
"WeChatName": "张三的微信",
"PhoneNumber": "13800138000",
"AvatarUrl": "https://example.com/avatar.jpg",
"FirstLoginTime": "2023-10-31T10:00:00",
"IsDisabled": false,
"CreateTime": "2023-10-31T10:00:00",
"UpdateTime": "2023-10-31T15:30:00",
"Token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
```
**失败响应:**
```json
{
"success": false,
"message": "用户不存在"
}
```
```json
{
"success": false,
"message": "更新用户信息失败",
"error": "数据库连接错误"
}
```
### 1.2 用户登录接口
#### 接口描述
用户登录功能将微信小程序code转换为OpenID并验证用户身份。如果用户不存在则自动创建新用户记录。无论用户是否存在都返回完整的用户信息和Token。
#### 接口路径
`/api/Login/Login`
#### 请求参数
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| Code | string | 是 | 微信小程序登录凭证code用于获取OpenID |
#### 请求示例
```json
{
"Code": "wx_login_code_here"
}
```
#### 响应示例
**成功响应(已存在用户):**
```json
{
"success": true,
"data": {
"Id": 1,
"UserName": "张三",
"UserKey": "openid_from_wechat",
"WeChatName": "张三的微信",
"PhoneNumber": "13800138000",
"AvatarUrl": "https://example.com/avatar.jpg",
"FirstLoginTime": "2023-10-31T10:00:00",
"IsDisabled": false,
"CreateTime": "2023-10-31T10:00:00",
"UpdateTime": "2023-10-31T10:00:00",
"Token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
```
**成功响应(新用户自动注册):**
```json
{
"success": true,
"data": {
"Id": 2,
"UserName": "",
"UserKey": "new_openid_from_wechat",
"WeChatName": "",
"PhoneNumber": "",
"AvatarUrl": "",
"FirstLoginTime": "2023-10-31T16:00:00",
"IsDisabled": false,
"CreateTime": "2023-10-31T16:00:00",
"UpdateTime": "2023-10-31T16:00:00",
"Token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
```
**失败响应:**
```json
{
"success": false,
"message": "用户已被禁用"
}
```
```json
{
"success": false,
"message": "获取微信OpenID失败",
"error": "微信API调用失败"
}
```
## 2. Check控制器接口
### 2.1 检查地址接口
#### 接口描述
根据会话记录的GUID查询经纬度信息并转换为详细地址更新到数据库中。
#### 接口路径
`/api/Check/CheckAddress`
#### 请求参数
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| Guid | string | 是 | 会话唯一标识 |
#### 请求示例
```json
{
"Guid": "会话唯一标识"
}
```
#### 响应示例
**成功响应:**
```json
{
"success": true,
"message": "地址更新成功",
"address": "北京市海淀区中关村南大街5号"
}
```
**失败响应:**
```json
{
"success": false,
"message": "记录不存在或已被删除"
}
```
```json
{
"success": false,
"message": "更新失败",
"error": "数据库操作错误"
}
```
### 2.2 添加会话记录接口
#### 接口描述
添加新的会话记录到系统中。
#### 接口路径
`/api/Check/AddConversation`
#### 请求参数
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| UserKey | string | 是 | 用户唯一标识键 |
| ConversationContent | string | 是 | 会话内容 |
| SendMethod | string | 是 | 发送方式 |
| UserLocation | string | 否 | 用户定位信息(经纬度格式:"纬度,经度" |
| MessageType | int | 否 | 1:公有消息2:私有消息 |
| Guid | string | 否 | 会话唯一标识(不提供则系统自动生成) |
#### 请求示例
```json
{
"UserKey": "user_123456",
"ConversationContent": "这是一条测试消息",
"SendMethod": "文本",
"UserLocation": "39.9087,116.3975",
"MessageType": 1
}
```
#### 响应示例
**成功响应:**
```json
{
"success": true,
"message": "收到!",
"conversationGuid": "会话唯一标识",
"receivedTime": "2023-10-31 10:05:00"
}
```
**失败响应:**
```json
{
"success": false,
"message": "发送失败",
"error": "数据库操作错误"
}
```
### 2.3 查询会话记录接口
#### 接口描述
根据用户唯一标识键查询该用户的所有会话记录。
#### 接口路径
`https://wx-xcx-check.blv-oa.com/api/Check/GetConversations`
#### 请求参数
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| UserKey | string | 是 | 用户唯一标识键 |
| MessageType | int | 否 | 0:不判断消息类型默认1:只返回公有消息2:只返回私有消息 |
#### 请求示例
```json
{
"UserKey": "user_123456",
"MessageType": 0
}
```
#### 响应示例
**成功响应:**
```json
{
"success": true,
"data": [
{
"Id": 1,
"Guid": "会话唯一标识",
"UserKey": "user_123456",
"ConversationContent": "这是一条测试消息",
"SendMethod": "文本",
"UserLocation": "北京市海淀区",
"Latitude": "39.9087",
"Longitude": "116.3975",
"RecordTime": "2023-10-31T10:05:00",
"RecordTimeUTCStamp": 1698732300000,
"IsDeleted": false,
"CreateTime": "2023-10-31T10:05:00",
"MessageType": 1
},
{
"Id": 2,
"Guid": "会话唯一标识",
"UserKey": "user_123456",
"ConversationContent": "这是第二条测试消息",
"SendMethod": "图片",
"UserLocation": "北京市朝阳区",
"Latitude": "39.9180",
"Longitude": "116.4272",
"RecordTime": "2023-10-31T10:06:00",
"RecordTimeUTCStamp": 1698732360000,
"IsDeleted": false,
"CreateTime": "2023-10-31T10:06:00",
"MessageType": 2
}
]
}
```
**失败响应:**
```json
{
"success": false,
"message": "查询失败",
"error": "数据库连接错误"
}
```
### 2.4 分页查询会话记录接口
#### 接口描述
分页查询用户的会话记录每页默认10条按时间戳从**最新到最旧**排序,支持根据消息类型进行过滤。
#### 接口路径
`https://wx-xcx-check.blv-oa.com:4433/api/Check/GetConversationsByPage`
#### 请求参数
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| UserKey | string | 是 | 用户唯一标识键 |
| Page | int | 否 | 页码默认为1小于1时自动设为1 |
| PageSize | int | 否 | 每页数量默认为10最大100小于1时自动设为10 |
| MessageType | int | 否 | 0:不判断消息类型默认1:只返回公有消息2:只返回私有消息 |
#### 请求示例
```json
{
"UserKey": "user_123456",
"Page": 1,
"PageSize": 10,
"MessageType": 0
}
```
#### 响应示例
**成功响应:**
```json
{
"success": true,
"data": {
"conversations": [
{
"Id": 1,
"Guid": "会话唯一标识",
"UserKey": "user_123456",
"ConversationContent": "这是一条测试消息",
"SendMethod": "文本",
"UserLocation": "北京市海淀区",
"Latitude": "39.9087",
"Longitude": "116.3975",
"RecordTime": "2023-10-31T10:00:00",
"RecordTimeUTCStamp": 1698727200000,
"IsDeleted": false,
"CreateTime": "2023-10-31T10:00:00",
"MessageType": 1
},
{
"Id": 2,
"Guid": "会话唯一标识",
"UserKey": "user_123456",
"ConversationContent": "这是一条私有消息",
"SendMethod": "文本",
"UserLocation": "北京市朝阳区",
"Latitude": "39.9180",
"Longitude": "116.4272",
"RecordTime": "2023-10-31T09:55:00",
"RecordTimeUTCStamp": 1698724500000,
"IsDeleted": false,
"CreateTime": "2023-10-31T09:55:00",
"MessageType": 2
}
],
"totalCount": 25,
"page": 1,
"pageSize": 10,
"totalPages": 3
}
}
```
**失败响应:**
```json
{
"success": false,
"message": "查询失败",
"error": "数据库操作错误"
}
```
### 2.5 根据GUID查询会话记录接口
#### 接口描述
根据会话唯一标识GUID查询会话记录详情不考虑IsDeleted状态。
#### 接口路径
`/api/Check/GetConversationByGuid`
#### 请求参数
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| Guid | string | 是 | 会话唯一标识 |
#### 请求示例
```json
{
"Guid": "会话唯一标识"
}
```
#### 响应示例
**成功响应:**
```json
{
"success": true,
"message": "查询成功",
"data": {
"Id": 1,
"Guid": "会话唯一标识",
"UserKey": "user_123456",
"ConversationContent": "这是一条测试消息",
"SendMethod": "文本",
"UserLocation": "北京市海淀区",
"Latitude": "39.9087",
"Longitude": "116.3975",
"RecordTime": "2023-10-31T10:05:00",
"RecordTimeUTCStamp": 1698732300000,
"IsDeleted": false,
"CreateTime": "2023-10-31T10:05:00",
"MessageType": 1
}
}
```
**失败响应:**
```json
{
"success": false,
"message": "未找到该记录"
}
```
```json
{
"success": false,
"message": "查询失败",
"error": "数据库操作错误"
}
```
### 2.6 更新会话记录接口
#### 接口描述
更新指定GUID的会话记录需要验证UserKey的权限。每次更新时RecordTime会自动更新为当前时间。
#### 接口路径
`/api/Check/UpdateConversation`
#### 请求参数
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| Guid | string | 是 | 会话唯一标识 |
| UserKey | string | 是 | 用户唯一标识键(用于权限验证) |
| ConversationContent | string | 是 | 新的会话内容 |
| SendMethod | string | 是 | 新的发送方式 |
| UserLocation | string | 否 | 新的用户定位信息 |
| MessageType | int | 否 | 1:公有消息2:私有消息 |
#### 请求示例
```json
{
"Guid": "会话唯一标识",
"UserKey": "user_123456",
"ConversationContent": "更新后的会话内容",
"SendMethod": "文本",
"UserLocation": "北京市西城区",
"MessageType": 1
}
```
#### 响应示例
**成功响应:**
```json
{
"success": true,
"message": "更新成功",
"receivedTime": "2023-10-31 10:10:00"
}
```
**失败响应:**
```json
{
"success": false,
"message": "记录不存在或无权限修改"
}
```
```json
{
"success": false,
"message": "更新失败",
"error": "数据库操作错误"
}
```
### 2.7 删除会话记录接口
#### 接口描述
软删除会话记录将IsDeleted标记为1需要验证UserKey的权限。
#### 接口路径
`/api/Check/DeleteConversation`
#### 请求参数
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| Guid | string | 是 | 会话唯一标识 |
| UserKey | string | 是 | 用户唯一标识键(用于权限验证) |
#### 请求示例
```json
{
"Guid": "会话唯一标识",
"UserKey": "user_123456"
}
```
#### 响应示例
**成功响应:**
```json
{
"success": true,
"message": "删除成功"
}
```
**失败响应:**
```json
{
"success": false,
"message": "记录不存在或已被删除"
}
```
```json
{
"success": false,
"message": "删除失败",
"error": "数据库操作错误"
}
```
### 2.8 从Redis Stream读取消息接口
#### 接口描述
从Redis Stream读取新的会话消息。
#### 接口路径
`/api/Check/ReadMessageFromRedis`
#### 请求参数
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| GroupName | string | 否 | 消费组名称默认xcx_group |
| ConsumerName | string | 否 | 消费者名称默认consumer_时间戳 |
| Count | int | 否 | 读取消息数量默认1 |
#### 请求示例
```json
{
"GroupName": "xcx_group",
"ConsumerName": "consumer_1",
"Count": 5
}
```
#### 响应示例
**成功响应:**
```json
{
"success": true,
"message": "成功读取消息",
"data": [
{
"MessageId": "1635739200000-0",
"Id": "1",
"Guid": "会话唯一标识",
"UserKey": "user_123456",
"ConversationContent": "测试消息",
"SendMethod": "文本",
"UserLocation": "北京市海淀区",
"Latitude": "39.9087",
"Longitude": "116.3975",
"RecordTime": "2023-10-31T10:00:00",
"RecordTimeUTCStamp": "1698727200000",
"IsDeleted": "false",
"CreateTime": "2023-10-31T10:00:00",
"MessageType": "1",
"UserName": "张三",
"WeChatName": "张三的微信",
"PhoneNumber": "13800138000",
"AvatarUrl": "https://example.com/avatar.jpg"
}
]
}
```
**失败响应:**
```json
{
"success": false,
"message": "读取消息失败",
"error": "Redis操作错误"
}
```
## 3. 接口调用说明
### 3.1 HTTP客户端调用示例JavaScript
```javascript
// 封装API请求函数
async function callApi(endpoint, data) {
try {
const response = await fetch(`http://your-api-domain/api${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
const result = await response.json();
return result;
} catch (error) {
console.error('API调用失败:', error);
throw error;
}
}
// 用户信息更新示例
async function updateUser() {
const result = await callApi('/Login/Register', {
UserName: '张三',
UserKey: 'openid_from_wechat',
WeChatName: '张三的微信',
PhoneNumber: '13800138000'
});
if (result.success) {
console.log('用户信息更新成功');
} else {
console.log('用户信息更新失败:', result.message);
}
}
// 用户登录示例
async function loginUser() {
const result = await callApi('/Login/Login', {
Code: 'wx_login_code_here'
});
if (result.success) {
console.log('登录成功,用户信息:', result.data);
// 保存用户信息和Token到本地存储
localStorage.setItem('userInfo', JSON.stringify(result.data));
localStorage.setItem('token', result.data.Token);
} else {
console.log('登录失败:', result.message);
}
}
// 添加会话示例
async function addConversation() {
const result = await callApi('/Check/AddConversation', {
UserKey: 'user_123456',
ConversationContent: '测试会话内容',
SendMethod: '文本',
UserLocation: '北京',
MessageType: 0
});
if (result.success) {
console.log('会话添加成功');
} else {
console.log('会话添加失败:', result.message);
}
}
```
### 3.2 常见错误处理
1. **数据库连接错误**:检查数据库服务是否正常运行,连接字符串是否正确
2. **权限验证失败**确保提供的UserKey与操作资源匹配
3. **记录不存在**在更新或删除前确认记录ID和UserKey的正确性
4. **网络错误**检查API服务是否正常运行网络连接是否稳定
## 4. 安全注意事项
1. **参数验证**:所有接口都应在前端进行基本的数据格式验证
2. **UserKey保护**UserKey作为用户身份标识应妥善保护避免泄露
3. **错误信息处理**:生产环境中应避免返回详细的错误信息,防止信息泄露
4. **请求频率限制**建议在生产环境中对API接口实施请求频率限制防止滥用
## 5. 接口维护信息
- **最后更新时间**2023-11-01
- **维护人员**:系统管理员
- **版本号**v1.2.0

View File

@@ -1,199 +0,0 @@
using CSRedis;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using static CSRedis.CSRedisClient;
namespace Common
{
/// <summary>
/// Redis缓存辅助类
/// </summary>
public class CSRedisCacheHelper
{
public static CSRedisClient? redis;
public static CSRedisClient? redis14;
public static CSRedisClient? redis15;
private const string ip = "127.0.0.1";
//private const string port = "6379";
private const string port = "6800";
static CSRedisCacheHelper()
{
var redisHostStr = string.Format("{0}:{1}", ip, port);
if (!string.IsNullOrEmpty(redisHostStr))
{
redis = new CSRedisClient(redisHostStr + ",password=,defaultDatabase=0");
redis15 = new CSRedisClient(redisHostStr + ",password=,defaultDatabase=15");
var DingYueMsg = ("CellCorelDRAWUser", new Action<SubscribeMessageEventArgs>(async (args) =>
{
string body = args.Body;
}));
CSRedisCacheHelper.redis.Subscribe(DingYueMsg);
}
}
/// <summary>
/// 添加缓存
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="value"></param>
public static void Set<T>(string key, T value, int ExpireTime)
{
redis?.Set(key, value, ExpireTime * 60);
}
public static T Get<T>(string key)
{
return redis.Get<T>(key);
}
public static void Forever<T>(string key, T value)
{
redis.Set(key, value, -1);
}
public static void Del(string key)
{
redis.Del(key);
}
public static void ListPush<T>(string key, T value)
{
redis.LPush(key, value);
}
/// <summary>
/// 判断是否存在
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static bool Contains(string key)
{
bool result = redis.Exists(key);
return result;
}
public static string? XAdd(string key, params (string, string)[] fieldValues)
{
try
{
var result = redis.XAdd(key, fieldValues);
return result;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"XAdd error: {ex.Message}");
return null;
}
}
public static string? XReadGroup(string key, string group, string consumer, int count = 1, string id = null)
{
try
{
id = id ?? ">";
var result = redis.XReadGroup(group, consumer, count, 0, (key, id));
if (result != null && result.Length > 0)
{
// 处理消息
var messages = new List<Dictionary<string, object>>();
foreach (var streamResult in result)
{
foreach (var entry in streamResult.data)
{
var message = new Dictionary<string, object>
{
["Id"] = entry.id,
["Values"] = entry.items
};
messages.Add(message);
// 确认消息已处理
redis.XAck(key, group, entry.id);
}
}
return System.Text.Json.JsonSerializer.Serialize(messages);
}
return null;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"XReadGroup error: {ex.Message}");
return null;
}
}
public static (string key, (string id, string items)[] data)[] XReadGroup(string group, string consumer, long count, long block, params (string key, string id)[] streams)
{
try
{
var result = redis.XReadGroup(group, consumer, count, block, streams);
if (result != null && result.Length > 0)
{
// 处理消息并确认已处理
var processedResults = new List<(string key, (string id, string items)[] data)>();
foreach (var streamResult in result)
{
var messages = new List<(string id, string items)>();
foreach (var entry in streamResult.data)
{
// 确认消息已处理
redis.XAck(streamResult.key, group, entry.id);
// entry是一个元组 (string id, string[] items)
// 我们需要将string[] items转换为string items
var itemsArray = entry.items;
var itemsString = string.Join(",", itemsArray);
messages.Add((entry.id, itemsString));
}
processedResults.Add((streamResult.key, messages.ToArray()));
}
return processedResults.ToArray();
}
return Array.Empty<(string key, (string id, string items)[] data)>();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"XReadGroup error: {ex.Message}");
return Array.Empty<(string key, (string id, string items)[] data)>();
}
}
public static bool XGroupCreate(string key, string group, string id = "0")
{
try
{
redis.XGroupCreate(key, group, id, true);
return true;
}
catch
{
return false;
}
}
/// <summary>
/// 发布消息
/// </summary>
/// <param name="Topic"></param>
/// <param name="Payload"></param>
public static void Publish(string Topic, string Payload)
{
CSRedisCacheHelper.redis.PublishNoneMessageId(Topic, Payload);
}
}
}

View File

@@ -1,869 +0,0 @@
using Common;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using MySql.Data.MySqlClient;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using System.Web;
namespace WxCheckApi.Controllers
{
[Route("api/[controller]/[action]")]
[ApiController]
public class CheckController : ControllerBase
{
private readonly MySqlConnection _connection;
private readonly HttpClient _httpClient;
private readonly IConfiguration _configuration;
public CheckController(MySqlConnection connection, IHttpClientFactory httpClientFactory, IConfiguration configuration)
{
_connection = connection;
_httpClient = httpClientFactory.CreateClient();
_configuration = configuration;
}
// 将经纬度转换为地址信息
public async Task<string> ConvertCoordinatesToAddress(string longitude,string latitude)
{
try
{
// 使用高德地图API进行逆地理编码
string apiKey = _configuration["AmapApi:ApiKey"] ?? "4d5cb7818664ada68ae5f68783b8bd4c";
string url = $"https://restapi.amap.com/v3/geocode/regeo?output=json&location={longitude},{latitude}&key={apiKey}&radius=1000&extensions=all";
var response = await _httpClient.GetStringAsync(url);
var jsonDoc = JsonDocument.Parse(response);
var root = jsonDoc.RootElement;
if (root.GetProperty("status").GetString() == "1" && root.TryGetProperty("regeocode", out var regeocodeElement) && regeocodeElement.ValueKind != JsonValueKind.Null)
{
if (regeocodeElement.TryGetProperty("formatted_address", out var formatted_address))
{
return formatted_address.ToString();
}
if (regeocodeElement.TryGetProperty("addressComponent", out var addressComponent))
{
string province = addressComponent.TryGetProperty("province", out var provinceElement) && provinceElement.ValueKind == JsonValueKind.String ? provinceElement.GetString() : "";
string city = addressComponent.TryGetProperty("city", out var cityElement) && cityElement.ValueKind == JsonValueKind.String ? cityElement.GetString() : "";
string district = addressComponent.TryGetProperty("district", out var districtElement) && districtElement.ValueKind == JsonValueKind.String ? districtElement.GetString() : "";
string township = addressComponent.TryGetProperty("township", out var townshipElement) && townshipElement.ValueKind == JsonValueKind.String ? townshipElement.GetString() : "";
// 获取街道和门牌号信息
string street = "";
string streetNumber = "";
double distance = 0;
// 方法1从addressComponent获取街道信息
if (addressComponent.TryGetProperty("streetNumber", out var streetNumberElement) && streetNumberElement.ValueKind == JsonValueKind.Object)
{
street = streetNumberElement.TryGetProperty("street", out var streetElement) && streetElement.ValueKind == JsonValueKind.String ? streetElement.GetString() : "";
streetNumber = streetNumberElement.TryGetProperty("number", out var numberElement) && numberElement.ValueKind == JsonValueKind.String ? numberElement.GetString() : "";
// 获取距离信息
if (streetNumberElement.TryGetProperty("distance", out var distanceElement) && distanceElement.ValueKind == JsonValueKind.String)
{
double.TryParse(distanceElement.GetString(), out distance);
}
}
// 方法2如果方法1没有获取到街道信息尝试从aoi信息中获取
if (string.IsNullOrEmpty(street) && regeocodeElement.TryGetProperty("aois", out var aoisElement) && aoisElement.ValueKind == JsonValueKind.Array)
{
var aoisArray = aoisElement.EnumerateArray();
foreach (var aoi in aoisArray)
{
if (aoi.TryGetProperty("name", out var aoiNameElement) && aoiNameElement.ValueKind == JsonValueKind.String)
{
street = aoiNameElement.GetString();
break; // 取第一个AOI作为街道信息
}
}
}
// 方法3如果前两种方法都没有获取到街道信息尝试从pois信息中获取
if (string.IsNullOrEmpty(street) && regeocodeElement.TryGetProperty("pois", out var poisElement) && poisElement.ValueKind == JsonValueKind.Array)
{
var poisArray = poisElement.EnumerateArray();
foreach (var poi in poisArray)
{
if (poi.TryGetProperty("name", out var poiNameElement) && poiNameElement.ValueKind == JsonValueKind.String)
{
street = poiNameElement.GetString();
break; // 取第一个POI作为街道信息
}
}
}
// 方法4如果以上方法都没有获取到街道信息尝试从formatted_address中解析
if (string.IsNullOrEmpty(street) && regeocodeElement.TryGetProperty("formatted_address", out var formattedAddressElement) && formattedAddressElement.ValueKind == JsonValueKind.String)
{
string formattedAddress = formattedAddressElement.GetString();
// 尝试从格式化地址中提取街道信息
// 格式化地址通常格式为:省 市 区 街道 具体地址
// 使用字符串数组作为分隔符
string[] separators = { " ", "省", "市", "区", "县", "镇", "街道", "路", "巷", "号" };
var addressParts = formattedAddress.Split(separators, StringSplitOptions.RemoveEmptyEntries);
// 查找可能包含街道信息的部分
for (int i = 0; i < addressParts.Length; i++)
{
var part = addressParts[i];
// 如果部分包含"路"、"街"、"巷"等关键词,可能是街道信息
if (part.Contains("路") || part.Contains("街") || part.Contains("巷") || part.Contains("道"))
{
street = part;
// 如果下一个部分存在且不是区县名称,可能是门牌号
if (i + 1 < addressParts.Length &&
!addressParts[i + 1].Contains("区") &&
!addressParts[i + 1].Contains("县"))
{
streetNumber = addressParts[i + 1];
}
break;
}
}
}
// 方法5如果仍然没有获取到街道信息尝试从nearestRoad信息中获取
if (string.IsNullOrEmpty(street) && regeocodeElement.TryGetProperty("streetNumber", out var nearestStreetElement) &&
nearestStreetElement.ValueKind == JsonValueKind.Object)
{
if (nearestStreetElement.TryGetProperty("street", out var nearestStreetNameElement) && nearestStreetNameElement.ValueKind == JsonValueKind.String)
{
street = nearestStreetNameElement.GetString();
}
}
// 构建详细地址字符串
string address = "";
if (!string.IsNullOrEmpty(province))
{
address += province;
}
if (!string.IsNullOrEmpty(city) && city != province)
{
address += " " + city;
}
if (!string.IsNullOrEmpty(district))
{
address += " " + district;
}
if (!string.IsNullOrEmpty(township))
{
address += " " + township;
}
if (!string.IsNullOrEmpty(street))
{
address += " " + street;
}
if (!string.IsNullOrEmpty(streetNumber))
{
address += " " + streetNumber;
}
// 如果有距离信息,添加到地址后面
if (distance > 0)
{
address += $" {distance:F1}米";
}
if (string.IsNullOrEmpty(address))
{
return "未获取到位置信息(高德返回值为空) " + latitude + "," + longitude;
}
return address.Trim();
}
}
return latitude + "," + longitude; ; // 如果API调用失败返回原始值
}
catch (Exception)
{
return latitude + "," + longitude; ; // 如果发生异常,返回原始值
}
}
[HttpPost]
public async Task<IActionResult> CheckAddress([FromBody] CheckAddressRequest request)
{
try
{
if (_connection.State != ConnectionState.Open)
{
await _connection.OpenAsync();
}
// 从数据库查询经纬度信息
string latitude = "";
string longitude = "";
using (MySqlCommand cmd = new MySqlCommand("SELECT Latitude, Longitude FROM xcx_conversation WHERE Guid = @Guid AND IsDeleted = 0", _connection))
{
cmd.Parameters.AddWithValue("@Guid", request.Guid);
using (var reader = await cmd.ExecuteReaderAsync())
{
if (await reader.ReadAsync())
{
latitude = reader.IsDBNull(0) ? "" : reader.GetString(0);
longitude = reader.IsDBNull(1) ? "" : reader.GetString(1);
}
else
{
return NotFound(new { success = false, message = "记录不存在或已被删除" });
}
}
}
// 转换经纬度为地址
var address = await ConvertCoordinatesToAddress(longitude, latitude);
// 更新数据库中的UserLocation字段
using (MySqlCommand cmd = new MySqlCommand("UPDATE xcx_conversation SET UserLocation = @UserLocation WHERE Guid = @Guid AND IsDeleted = 0", _connection))
{
cmd.Parameters.AddWithValue("@Guid", request.Guid);
cmd.Parameters.AddWithValue("@UserLocation", address);
int rowsAffected = await cmd.ExecuteNonQueryAsync();
if (rowsAffected == 0)
{
return NotFound(new { success = false, message = "记录不存在或已被删除" });
}
}
return Ok(new { success = true, message = "地址更新成功", address = address });
}
catch (Exception ex)
{
return StatusCode(500, new { success = false, message = "更新失败", error = ex.Message });
}
finally
{
if (_connection.State == ConnectionState.Open)
{
await _connection.CloseAsync();
}
}
}
// 添加会话记录
[HttpPost]
public async Task<IActionResult> AddConversation([FromBody] ConversationRequest request)
{
DateTime nowtime = DateTime.Now;
try
{
if (_connection.State != ConnectionState.Open)
{
await _connection.OpenAsync();
}
// 解析经纬度并转换为地址
string address = "";
string latitude = "";
string longitude = "";
// 否则尝试从UserLocation字段解析
if (!string.IsNullOrEmpty(request.UserLocation))
{
string[] parts = request.UserLocation.Split(',');
if (parts.Length == 2)
{
if (double.TryParse(parts[0], out double lat) && double.TryParse(parts[1], out double lng))
{
longitude = lng.ToString();
latitude = lat.ToString();
address = "";// await ConvertCoordinatesToAddress(latitude, longitude);
}
}
}
// 生成GUID
string conversationGuid = string.IsNullOrEmpty(request.Guid) ? Guid.NewGuid().ToString("N") : request.Guid;
long conversationId = 0;
using (MySqlCommand cmd = new MySqlCommand("INSERT INTO xcx_conversation (UserKey, ConversationContent, SendMethod, UserLocation, Latitude, Longitude, RecordTime, RecordTimeUTCStamp, IsDeleted, CreateTime, MessageType, Guid, SpeakingTime) VALUES (@UserKey, @ConversationContent, @SendMethod, @UserLocation, @Latitude, @Longitude, @RecordTime, @RecordTimeUTCStamp, @IsDeleted, @CreateTime, @MessageType, @Guid, @SpeakingTime); SELECT LAST_INSERT_ID();", _connection))
{
cmd.Parameters.AddWithValue("@UserKey", request.UserKey);
cmd.Parameters.AddWithValue("@MessageType", request.MessageType);
cmd.Parameters.AddWithValue("@ConversationContent", request.ConversationContent);
cmd.Parameters.AddWithValue("@SendMethod", request.SendMethod);
cmd.Parameters.AddWithValue("@UserLocation", address);
cmd.Parameters.AddWithValue("@Latitude", latitude);
cmd.Parameters.AddWithValue("@Longitude", longitude);
cmd.Parameters.AddWithValue("@RecordTime", nowtime);
cmd.Parameters.AddWithValue("@CreateTime", nowtime);
cmd.Parameters.AddWithValue("@RecordTimeUTCStamp", DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
cmd.Parameters.AddWithValue("@IsDeleted", 0);
cmd.Parameters.AddWithValue("@Guid", conversationGuid);
cmd.Parameters.AddWithValue("@SpeakingTime", request.SpeakingTime);
object result = await cmd.ExecuteScalarAsync();
conversationId = Convert.ToInt64(result);
}
// 查询刚插入的记录,并左连接用户表
if (conversationId > 0)
{
string query = @"SELECT convs.Id, convs.Guid, convs.UserKey, convs.ConversationContent, convs.SendMethod,
convs.UserLocation, convs.Latitude, convs.Longitude, convs.RecordTime,
convs.RecordTimeUTCStamp, convs.IsDeleted, convs.CreateTime, convs.MessageType, convs.SpeakingTime,
users.UserName, users.WeChatName, users.PhoneNumber, users.AvatarUrl
FROM xcx_conversation AS convs
LEFT JOIN xcx_users AS users ON convs.UserKey = users.UserKey
WHERE convs.Guid = @Guid";
using (MySqlCommand cmd = new MySqlCommand(query, _connection))
{
cmd.Parameters.AddWithValue("@Guid", conversationGuid);
using (var reader = await cmd.ExecuteReaderAsync())
{
if (await reader.ReadAsync())
{
// 构建要发送到Redis的数据
var messageData = new Dictionary<string, string>
{
["Id"] = reader.GetInt64(0).ToString(),
["Guid"] = reader.IsDBNull(1) ? "" : reader.GetString(1),
["UserKey"] = reader.GetString(2),
["ConversationContent"] = reader.GetString(3),
["SendMethod"] = reader.GetString(4),
["UserLocation"] = reader.IsDBNull(5) ? "" : reader.GetString(5),
["Latitude"] = reader.IsDBNull(6) ? "" : reader.GetString(6),
["Longitude"] = reader.IsDBNull(7) ? "" : reader.GetString(7),
["RecordTime"] = reader.GetDateTime(8).ToString("yyyy-MM-dd HH:mm:ss"),
["RecordTimeUTCStamp"] = reader.GetInt64(9).ToString(),
["IsDeleted"] = reader.GetBoolean(10).ToString(),
["CreateTime"] = reader.GetDateTime(11).ToString("yyyy-MM-dd HH:mm:ss"),
["MessageType"] = reader.GetInt32(12).ToString(),
["SpeakingTime"] = reader.IsDBNull(13) ? "" : reader.GetInt32(13).ToString(),
["UserName"] = reader.IsDBNull(14) ? "" : reader.GetString(14),
["WeChatName"] = reader.IsDBNull(15) ? "" : reader.GetString(15),
["PhoneNumber"] = reader.IsDBNull(16) ? "" : reader.GetString(16),
["AvatarUrl"] = reader.IsDBNull(17) ? "" : reader.GetString(17)
};
// 发送到Redis Stream
try
{
// 确保Stream和Group存在
CSRedisCacheHelper.XGroupCreate("xcx_msg", "xcx_group", "0");
// 将Dictionary转换为params (string, string)[]格式
var fieldValues = messageData.SelectMany(kvp => new (string, string)[] { (kvp.Key, kvp.Value) }).ToArray();
// 添加消息到Stream
string messageId = CSRedisCacheHelper.XAdd("xcx_msg", fieldValues);
// 记录日志(可选)
System.Diagnostics.Debug.WriteLine($"消息已发送到Redis Stream: {messageId}");
}
catch (Exception ex)
{
// 记录错误但不影响主流程
System.Diagnostics.Debug.WriteLine($"发送到Redis Stream失败: {ex.Message}");
}
}
}
}
}
return Ok(new { success = true, message = "收到!", conversationGuid, receivedTime = nowtime.ToString("yyyy-MM-dd HH:mm:ss") });
}
catch (Exception ex)
{
return StatusCode(500, new { success = false, message = "发送失败", error = ex.Message });
}
finally
{
if (_connection.State == ConnectionState.Open)
{
await _connection.CloseAsync();
}
}
}
// 根据UserKey查询会话记录
[HttpPost]
public async Task<IActionResult> GetConversations([FromBody] UserKeyRequest request)
{
try
{
if (_connection.State != ConnectionState.Open)
{
await _connection.OpenAsync();
}
List<ConversationResponse> conversations = new List<ConversationResponse>();
// 构建查询SQL根据MessageType参数决定是否添加过滤条件
string query = "SELECT Id, Guid, UserKey, ConversationContent, SendMethod, UserLocation, Latitude, Longitude, RecordTime, RecordTimeUTCStamp, IsDeleted, CreateTime, MessageType, SpeakingTime FROM xcx_conversation WHERE UserKey = @UserKey AND IsDeleted = 0";
if (request.MessageType == 1)
{
query += " AND MessageType = @MessageType";
}
query += " ORDER BY RecordTimeUTCStamp DESC";
using (MySqlCommand cmd = new MySqlCommand(query, _connection))
{
cmd.Parameters.AddWithValue("@UserKey", request.UserKey);
using (var reader = await cmd.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
conversations.Add(new ConversationResponse
{
Id = reader.GetInt64(0),
Guid = reader.IsDBNull(1) ? "" : reader.GetString(1),
UserKey = reader.GetString(2),
ConversationContent = reader.GetString(3),
SendMethod = reader.GetString(4),
UserLocation = reader.IsDBNull(5) ? "" : reader.GetString(5),
Latitude = reader.IsDBNull(6) ? "" : reader.GetString(6),
Longitude = reader.IsDBNull(7) ? "" : reader.GetString(7),
RecordTime = reader.GetDateTime(8),
RecordTimeUTCStamp = reader.GetInt64(9),
IsDeleted = reader.GetBoolean(10),
CreateTime = reader.GetDateTime(11),
MessageType = reader.GetInt32(12),
SpeakingTime = reader.IsDBNull(13) ? null : reader.GetInt32(13)
});
}
}
}
return Ok(new { success = true, data = conversations.OrderBy(z => z.RecordTimeUTCStamp) });
}
catch (Exception ex)
{
return StatusCode(500, new { success = false, message = "查询失败", error = ex.Message });
}
finally
{
if (_connection.State == ConnectionState.Open)
{
await _connection.CloseAsync();
}
}
}
// 更新会话记录
[HttpPost]
public async Task<IActionResult> UpdateConversation([FromBody] UpdateConversationRequest request)
{
try
{
if (_connection.State != ConnectionState.Open)
{
await _connection.OpenAsync();
}
DateTime nowtime = DateTime.Now;
using (MySqlCommand cmd = new MySqlCommand("UPDATE xcx_conversation SET ConversationContent = @ConversationContent, SendMethod = @SendMethod, UserLocation = @UserLocation, MessageType = @MessageType, RecordTime = @RecordTime WHERE Guid = @Guid AND UserKey = @UserKey", _connection))
{
cmd.Parameters.AddWithValue("@Guid", request.Guid);
cmd.Parameters.AddWithValue("@UserKey", request.UserKey);
cmd.Parameters.AddWithValue("@ConversationContent", request.ConversationContent);
cmd.Parameters.AddWithValue("@SendMethod", request.SendMethod);
cmd.Parameters.AddWithValue("@UserLocation", request.UserLocation ?? "");
cmd.Parameters.AddWithValue("@MessageType", request.MessageType);
cmd.Parameters.AddWithValue("@RecordTime", nowtime);
int rowsAffected = await cmd.ExecuteNonQueryAsync();
if (rowsAffected == 0)
{
return NotFound(new { success = false, message = "记录不存在或无权限修改" });
}
}
return Ok(new { success = true, message = "更新成功" , receivedTime = nowtime.ToString("yyyy-MM-dd HH:mm:ss") });
}
catch (Exception ex)
{
return StatusCode(500, new { success = false, message = "更新失败", error = ex.Message });
}
finally
{
if (_connection.State == ConnectionState.Open)
{
await _connection.CloseAsync();
}
}
}
// 软删除会话记录
[HttpPost]
public async Task<IActionResult> DeleteConversation([FromBody] DeleteConversationRequest request)
{
try
{
if (_connection.State != ConnectionState.Open)
{
await _connection.OpenAsync();
}
using (MySqlCommand cmd = new MySqlCommand("UPDATE xcx_conversation SET IsDeleted = 1 WHERE Guid = @Guid AND UserKey = @UserKey AND IsDeleted = 0", _connection))
{
cmd.Parameters.AddWithValue("@Guid", request.Guid);
cmd.Parameters.AddWithValue("@UserKey", request.UserKey);
int rowsAffected = await cmd.ExecuteNonQueryAsync();
if (rowsAffected == 0)
{
return NotFound(new { success = false, message = "记录不存在或已被删除" });
}
}
return Ok(new { success = true, message = "删除成功" });
}
catch (Exception ex)
{
return StatusCode(500, new { success = false, message = "删除失败", error = ex.Message });
}
finally
{
if (_connection.State == ConnectionState.Open)
{
await _connection.CloseAsync();
}
}
}
// 根据GUID查询会话记录不考虑IsDeleted状态
[HttpPost]
public async Task<IActionResult> GetConversationByGuid([FromBody] GetConversationByGuidRequest request)
{
try
{
if (_connection.State != ConnectionState.Open)
{
await _connection.OpenAsync();
}
// 查询记录不考虑IsDeleted状态
string query = @"SELECT Id, Guid, UserKey, ConversationContent, SendMethod, UserLocation,
Latitude, Longitude, RecordTime, RecordTimeUTCStamp, IsDeleted, CreateTime, MessageType, SpeakingTime
FROM xcx_conversation
WHERE Guid = @Guid";
using (MySqlCommand cmd = new MySqlCommand(query, _connection))
{
cmd.Parameters.AddWithValue("@Guid", request.Guid);
using (var reader = await cmd.ExecuteReaderAsync())
{
if (await reader.ReadAsync())
{
var conversation = new ConversationResponse
{
Id = reader.GetInt64(0),
Guid = reader.IsDBNull(1) ? "" : reader.GetString(1),
UserKey = reader.GetString(2),
ConversationContent = reader.GetString(3),
SendMethod = reader.GetString(4),
UserLocation = reader.IsDBNull(5) ? "" : reader.GetString(5),
Latitude = reader.IsDBNull(6) ? "" : reader.GetString(6),
Longitude = reader.IsDBNull(7) ? "" : reader.GetString(7),
RecordTime = reader.GetDateTime(8),
RecordTimeUTCStamp = reader.GetInt64(9),
IsDeleted = reader.GetBoolean(10),
CreateTime = reader.GetDateTime(11),
MessageType = reader.GetInt32(12),
SpeakingTime = reader.IsDBNull(13) ? null : reader.GetInt32(13)
};
return Ok(new { success = true, message = "查询成功", data = conversation });
}
else
{
return NotFound(new { success = false, message = "未找到该记录" });
}
}
}
}
catch (Exception ex)
{
return StatusCode(500, new { success = false, message = "查询失败", error = ex.Message });
}
finally
{
if (_connection.State == ConnectionState.Open)
{
await _connection.CloseAsync();
}
}
}
// 分页查询会话记录
[HttpPost]
public async Task<IActionResult> GetConversationsByPage([FromBody] PaginationRequest request)
{
try
{
if (_connection.State != ConnectionState.Open)
{
await _connection.OpenAsync();
}
// 验证并设置默认值
if (request.Page < 1) request.Page = 1;
if (request.PageSize < 1 || request.PageSize > 100) request.PageSize = 10;
int offset = (request.Page - 1) * request.PageSize;
List<ConversationResponse> conversations = new List<ConversationResponse>();
// 构建分页查询SQL根据MessageType参数决定是否添加过滤条件
string query = @"SELECT Id, Guid, UserKey, ConversationContent, SendMethod, UserLocation, Latitude, Longitude, RecordTime, RecordTimeUTCStamp, IsDeleted, CreateTime, MessageType, SpeakingTime
FROM xcx_conversation
WHERE UserKey = @UserKey AND IsDeleted = 0";
if (request.MessageType == 1)
{
query += " AND MessageType = @MessageType";
}
query += " ORDER BY RecordTimeUTCStamp DESC LIMIT @Offset, @Limit";
using (MySqlCommand cmd = new MySqlCommand(query, _connection))
{
cmd.Parameters.AddWithValue("@UserKey", request.UserKey);
if (request.MessageType == 1)
{
cmd.Parameters.AddWithValue("@MessageType", request.MessageType);
}
cmd.Parameters.AddWithValue("@Offset", offset);
cmd.Parameters.AddWithValue("@Limit", request.PageSize);
using (var reader = await cmd.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
conversations.Add(new ConversationResponse
{
Id = reader.GetInt64(0),
Guid = reader.IsDBNull(1) ? "" : reader.GetString(1),
UserKey = reader.GetString(2),
ConversationContent = reader.GetString(3),
SendMethod = reader.GetString(4),
UserLocation = reader.IsDBNull(5) ? "" : reader.GetString(5),
Latitude = reader.IsDBNull(6) ? "" : reader.GetString(6),
Longitude = reader.IsDBNull(7) ? "" : reader.GetString(7),
RecordTime = reader.GetDateTime(8),
RecordTimeUTCStamp = reader.GetInt64(9),
IsDeleted = reader.GetBoolean(10),
CreateTime = reader.GetDateTime(11),
MessageType = reader.GetInt32(12),
SpeakingTime = reader.IsDBNull(13) ? null : reader.GetInt32(13)
});
}
}
}
// 查询总数根据MessageType参数决定是否添加过滤条件
int totalCount = 0;
string countQuery = "SELECT COUNT(*) FROM xcx_conversation WHERE UserKey = @UserKey AND IsDeleted = 0";
if (request.MessageType == 1)
{
countQuery += " AND MessageType = @MessageType";
}
using (MySqlCommand countCmd = new MySqlCommand(countQuery, _connection))
{
countCmd.Parameters.AddWithValue("@UserKey", request.UserKey);
if (request.MessageType == 1)
{
countCmd.Parameters.AddWithValue("@MessageType", request.MessageType);
}
totalCount = Convert.ToInt32(await countCmd.ExecuteScalarAsync());
}
int totalPages = (int)Math.Ceiling((double)totalCount / request.PageSize);
return Ok(new {
success = true,
data = new {
conversations = conversations.OrderBy(z => z.RecordTimeUTCStamp),
totalCount = totalCount,
page = request.Page,
pageSize = request.PageSize,
totalPages = totalPages
}
});
}
catch (Exception ex)
{
return StatusCode(500, new { success = false, message = "查询失败", error = ex.Message });
}
finally
{
if (_connection.State == ConnectionState.Open)
{
await _connection.CloseAsync();
}
}
}
// 从Redis Stream读取消息
[HttpPost]
public async Task<IActionResult> ReadMessageFromRedis([FromBody] RedisMessageRequest request)
{
try
{
// 确保Stream和Group存在
CSRedisCacheHelper.XGroupCreate("xcx_msg", "xcx_group", "0");
// 从Redis Stream读取消息
string groupName = request.GroupName ?? "xcx_group";
string consumerName = request.ConsumerName ?? "consumer_" + DateTime.Now.Ticks;
string messages = CSRedisCacheHelper.XReadGroup("xcx_msg", groupName, consumerName, request.Count ?? 1);
if (string.IsNullOrEmpty(messages))
{
return Ok(new { success = true, message = "没有新消息", data = new List<string>() });
}
// 解析消息
var messageList = new List<object>();
try
{
var messageEntries = System.Text.Json.JsonSerializer.Deserialize<List<Dictionary<string, object>>>(messages);
if (messageEntries != null)
{
foreach (var entry in messageEntries)
{
if (entry.TryGetValue("Id", out var id) &&
entry.TryGetValue("Values", out var values))
{
var messageData = new Dictionary<string, string>();
// 如果Values是JsonElement需要进一步解析
if (values is JsonElement valuesElement && valuesElement.ValueKind == JsonValueKind.Object)
{
foreach (var property in valuesElement.EnumerateObject())
{
messageData[property.Name] = property.Value.GetString() ?? "";
}
}
else if (values is Dictionary<string, string> valuesDict)
{
messageData = valuesDict;
}
messageData["MessageId"] = id.ToString();
messageList.Add(messageData);
}
}
}
}
catch (Exception parseEx)
{
// 如果解析失败,尝试直接返回原始消息
System.Diagnostics.Debug.WriteLine($"解析消息失败: {parseEx.Message}");
messageList.Add(new { RawMessage = messages });
}
return Ok(new { success = true, message = "成功读取消息", data = messageList });
}
catch (Exception ex)
{
return StatusCode(500, new { success = false, message = "读取消息失败", error = ex.Message });
}
}
}
// 请求和响应模型
public class ConversationRequest
{
public string UserKey { get; set; }
public string ConversationContent { get; set; }
public string SendMethod { get; set; }
public string UserLocation { get; set; }
public double Latitude { get; set; }
public double Longitude { get; set; }
public int MessageType { get; set; } = 1; // 1:公有2:私有
public string? Guid { get; set; } // 会话唯一标识
public int? SpeakingTime { get; set; } // 对话时长
}
public class UserKeyRequest
{
public string UserKey { get; set; }
public int MessageType { get; set; } = 0; // 0:不判断消息类型1:公有2:私有
}
public class PaginationRequest
{
public string UserKey { get; set; }
public int Page { get; set; } = 1; // 默认第一页
public int PageSize { get; set; } = 10; // 默认每页10条
public int MessageType { get; set; } = 0; // 0:不判断消息类型1:公有2:私有
}
public class UpdateConversationRequest
{
public long? Id { get; set; }
public string Guid { get; set; } // 会话唯一标识
public string UserKey { get; set; }
public string ConversationContent { get; set; }
public string SendMethod { get; set; }
public string UserLocation { get; set; }
public int MessageType { get; set; } = 0;
public int? SpeakingTime { get; set; }
}
public class DeleteConversationRequest
{
public long? Id { get; set; }
public string Guid { get; set; } // 会话唯一标识
public string UserKey { get; set; }
}
public class CheckAddressRequest
{
public long? Id { get; set; }
public string Guid { get; set; } // 会话唯一标识
}
public class GetConversationByGuidRequest
{
public long? Id { get; set; }
public string Guid { get; set; } // 会话唯一标识
}
public class ConversationResponse
{
public long Id { get; set; }
public string Guid { get; set; } // 会话唯一标识
public string UserKey { get; set; }
public string ConversationContent { get; set; }
public string SendMethod { get; set; }
public string UserLocation { get; set; }
public string Latitude { get; set; }
public string Longitude { get; set; }
public DateTime RecordTime { get; set; }
public long RecordTimeUTCStamp { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreateTime { get; set; }
public int MessageType { get; set; } // 1:公有消息2:私有消息
public int? SpeakingTime { get; set; }
}
public class RedisMessageRequest
{
public string GroupName { get; set; } = "xcx_group";
public string ConsumerName { get; set; }
public int? Count { get; set; } = 1;
}
}

View File

@@ -1,324 +0,0 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using MySql.Data.MySqlClient;
using System;
using System.Data;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using System.Net.Http;
using System.Text.Json;
namespace WxCheckMvc.Controllers
{
[Route("api/[controller]/[action]")]
[ApiController]
public class LoginController : ControllerBase
{
private readonly MySqlConnection _connection;
private readonly IHttpClientFactory _httpClientFactory;
public IConfiguration? configuration { get; set; }
public LoginController(MySqlConnection connection, IHttpClientFactory httpClientFactory, IConfiguration? configuration)
{
_connection = connection;
_httpClientFactory = httpClientFactory;
this.configuration = configuration;
}
// 获取微信小程序OpenID
private async Task<string> GetWxOpenIdAsync(string code)
{
try
{
var appId = configuration["WeChat:AppId"];
var appSecret = configuration["WeChat:AppSecret"];
if (string.IsNullOrEmpty(appId) || string.IsNullOrEmpty(appSecret))
{
throw new Exception("微信小程序配置缺失");
}
var httpClient = _httpClientFactory.CreateClient();
var url = $"https://api.weixin.qq.com/sns/jscode2session?appid={appId}&secret={appSecret}&js_code={code}&grant_type=authorization_code";
var response = await httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync();
var jsonDocument = JsonDocument.Parse(responseContent);
if (jsonDocument.RootElement.TryGetProperty("openid", out var openidElement))
{
return openidElement.GetString();
}
else
{
// 如果有错误信息,抛出异常
if (jsonDocument.RootElement.TryGetProperty("errcode", out var errcodeElement) &&
jsonDocument.RootElement.TryGetProperty("errmsg", out var errmsgElement))
{
throw new Exception($"获取OpenID失败: {errcodeElement.GetInt32()} - {errmsgElement.GetString()}");
}
throw new Exception("获取OpenID失败: 响应中未包含openid");
}
}
catch (Exception ex)
{
throw new Exception($"获取微信OpenID时发生错误: {ex.Message}");
}
}
private string GetToken(string entity)
{
string TokenString;
var claims = new Claim[]
{
new Claim(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.Name, entity)
};
var secretByte = Encoding.UTF8.GetBytes(configuration["JwT:SecretKey"]);
var signingKey = new SymmetricSecurityKey(secretByte);
var a = SecurityAlgorithms.HmacSha256;
var signingCredentials = new SigningCredentials(signingKey, a);
var token = new JwtSecurityToken(
issuer: configuration["JwT:Issuer"],
audience: configuration["JwT:Audience"],//接收
claims: claims,//存放的用户信息
notBefore: DateTime.UtcNow,//发布时间
expires: DateTime.UtcNow.AddMonths(12),
signingCredentials: signingCredentials
//有效期设置为1天signingCredentials //数字名
);
TokenString = new JwtSecurityTokenHandler().WriteToken(token);
return TokenString;
}
// 用户注册接口
[HttpPost]
public async Task<IActionResult> Register([FromBody] RegisterRequest request)
{
try
{
if (_connection.State != ConnectionState.Open)
{
await _connection.OpenAsync();
}
// 检查用户是否存在
UserResponse user = null;
using (MySqlCommand checkCmd = new MySqlCommand("SELECT Id, UserName, UserKey, WeChatName, PhoneNumber, AvatarUrl, FirstLoginTime, IsDisabled, CreateTime, UpdateTime FROM xcx_users WHERE UserKey = @UserKey", _connection))
{
checkCmd.Parameters.AddWithValue("@UserKey", request.UserKey);
using (var reader = await checkCmd.ExecuteReaderAsync())
{
if (await reader.ReadAsync())
{
user = new UserResponse
{
Id = reader.GetInt64(0),
UserName = reader.IsDBNull(1) ? "" : reader.GetString(1),
UserKey = reader.GetString(2),
WeChatName = reader.IsDBNull(3) ? "" : reader.GetString(3),
PhoneNumber = reader.IsDBNull(4) ? "" : reader.GetString(4),
AvatarUrl = reader.IsDBNull(5) ? "" : reader.GetString(5),
FirstLoginTime = reader.GetDateTime(6),
IsDisabled = reader.GetBoolean(7),
CreateTime = reader.GetDateTime(8),
UpdateTime = reader.GetDateTime(9)
};
}
}
}
if (user == null)
{
return NotFound(new { success = false, message = "用户不存在" });
}
// 更新用户信息
using (MySqlCommand cmd = new MySqlCommand("UPDATE xcx_users SET UserName = @UserName, WeChatName = @WeChatName, PhoneNumber = @PhoneNumber, AvatarUrl = @AvatarUrl, UpdateTime = NOW() WHERE UserKey = @UserKey", _connection))
{
cmd.Parameters.AddWithValue("@UserName", request.UserName ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@WeChatName", request.WeChatName ?? "");
cmd.Parameters.AddWithValue("@PhoneNumber", request.PhoneNumber ?? "");
cmd.Parameters.AddWithValue("@AvatarUrl", request.AvatarUrl ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@UserKey", request.UserKey);
await cmd.ExecuteNonQueryAsync();
}
// 获取更新后的用户信息
UserResponse updatedUser = null;
using (MySqlCommand cmd = new MySqlCommand("SELECT Id, UserName, UserKey, WeChatName, PhoneNumber, AvatarUrl, FirstLoginTime, IsDisabled, CreateTime, UpdateTime FROM xcx_users WHERE UserKey = @UserKey", _connection))
{
cmd.Parameters.AddWithValue("@UserKey", request.UserKey);
using (var reader = await cmd.ExecuteReaderAsync())
{
if (await reader.ReadAsync())
{
updatedUser = new UserResponse
{
Id = reader.GetInt64(0),
UserName = reader.IsDBNull(1) ? "" : reader.GetString(1),
UserKey = reader.GetString(2),
WeChatName = reader.IsDBNull(3) ? "" : reader.GetString(3),
PhoneNumber = reader.IsDBNull(4) ? "" : reader.GetString(4),
AvatarUrl = reader.IsDBNull(5) ? "" : reader.GetString(5),
FirstLoginTime = reader.GetDateTime(6),
IsDisabled = reader.GetBoolean(7),
CreateTime = reader.GetDateTime(8),
UpdateTime = reader.GetDateTime(9),
Token = GetToken(request.UserKey)
};
}
}
}
return Ok(new { success = true, data = updatedUser });
}
catch (Exception ex)
{
return StatusCode(500, new { success = false, message = "更新用户信息失败", error = ex.Message });
}
finally
{
if (_connection.State == ConnectionState.Open)
{
await _connection.CloseAsync();
}
}
}
// 用户登录接口
[HttpPost]
public async Task<IActionResult> Login([FromBody] LoginRequest request)
{
try
{
string openId;
try
{
openId = await GetWxOpenIdAsync(request.Code);
}
catch (Exception ex)
{
return BadRequest(new { success = false, message = "获取微信OpenID失败", error = ex.Message });
}
if (_connection.State != ConnectionState.Open)
{
await _connection.OpenAsync();
}
UserResponse user = null;
// 检查用户是否存在
using (MySqlCommand checkCmd = new MySqlCommand("SELECT COUNT(1) FROM xcx_users WHERE UserKey = @UserKey", _connection))
{
checkCmd.Parameters.AddWithValue("@UserKey", openId);
int count = Convert.ToInt32(await checkCmd.ExecuteScalarAsync());
// 如果用户不存在,则注册新用户
if (count == 0)
{
using (MySqlCommand insertCmd = new MySqlCommand("INSERT INTO xcx_users (UserKey, FirstLoginTime, IsDisabled, CreateTime, UpdateTime) VALUES (@UserKey, @FirstLoginTime, @IsDisabled, NOW(), NOW())", _connection))
{
insertCmd.Parameters.AddWithValue("@UserKey", openId);
insertCmd.Parameters.AddWithValue("@FirstLoginTime", DateTime.Now);
insertCmd.Parameters.AddWithValue("@IsDisabled", 0); // 默认启用
await insertCmd.ExecuteNonQueryAsync();
}
}
}
// 获取用户信息
using (MySqlCommand cmd = new MySqlCommand("SELECT Id, UserName, UserKey, WeChatName, PhoneNumber, AvatarUrl, FirstLoginTime, IsDisabled, CreateTime, UpdateTime FROM xcx_users WHERE UserKey = @UserKey", _connection))
{
cmd.Parameters.AddWithValue("@UserKey", openId);
using (var reader = await cmd.ExecuteReaderAsync())
{
if (await reader.ReadAsync())
{
user = new UserResponse
{
Id = reader.GetInt64(0),
UserName = reader.IsDBNull(1) ? "" : reader.GetString(1),
UserKey = reader.GetString(2),
WeChatName = reader.IsDBNull(3) ? "" : reader.GetString(3),
PhoneNumber = reader.IsDBNull(4) ? "" : reader.GetString(4),
AvatarUrl = reader.IsDBNull(5) ? "" : reader.GetString(5),
FirstLoginTime = reader.GetDateTime(6),
IsDisabled = reader.GetBoolean(7),
CreateTime = reader.GetDateTime(8),
UpdateTime = reader.GetDateTime(9),
Token = GetToken(openId)
};
}
}
}
if (user == null)
{
return NotFound(new { success = false, message = "用户不存在" });
}
if (user.IsDisabled)
{
return Ok(new { success = false, message = "用户已被禁用" });
}
return Ok(new { success = true, data = user });
}
catch (Exception ex)
{
return StatusCode(500, new { success = false, message = "登录失败", error = ex.Message });
}
finally
{
if (_connection.State == ConnectionState.Open)
{
await _connection.CloseAsync();
}
}
}
}
// 请求和响应模型
public class RegisterRequest
{
public string UserName { get; set; }
public string UserKey { get; set; } // 改为直接传入UserKey
public string WeChatName { get; set; }
public string PhoneNumber { get; set; }
public string AvatarUrl { get; set; }
}
public class LoginRequest
{
public string Code { get; set; }
}
public class UserResponse
{
public long Id { get; set; }
public string UserName { get; set; }
public string UserKey { get; set; }
public string WeChatName { get; set; }
public string PhoneNumber { get; set; }
public string AvatarUrl { get; set; }
public DateTime FirstLoginTime { get; set; }
public bool IsDisabled { get; set; }
public DateTime CreateTime { get; set; }
public DateTime UpdateTime { get; set; }
public string Token { get; set; }
}
}

View File

@@ -1,33 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace WxCheckApi.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
}

View File

@@ -1,70 +0,0 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using MySql.Data.MySqlClient;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// 添加HttpClientFactory
builder.Services.AddHttpClient();
// 添加数据库连接
builder.Services.AddScoped<MySqlConnection>(sp => {
var connectionString = builder.Configuration.GetConnectionString("MySQLConnection");
return new MySqlConnection(connectionString);
});
builder.Services.AddAuthorization();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(option =>
{
string DefaultKey = "B,EZipeApY3cNj3~4RP0UMR=H>9x8.1!E85wmZ]]py2d$Y?5";
var sec = Encoding.UTF8.GetBytes(builder.Configuration["JWT:SecretKey"] ?? DefaultKey);
option.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["JwT:Issuer"],
ValidAudience = builder.Configuration["JwT:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(sec)
};
option.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var token = context.Request.Headers["token"].FirstOrDefault();
if (string.IsNullOrEmpty(token))
{
// 如果没有找到 token 头部,则继续检查 Authorization 头部
token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
}
// 如果找到了 token则将其设置到 HttpContext 中
if (!string.IsNullOrEmpty(token))
{
context.Token = token;
}
return Task.CompletedTask;
}
};
});
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseAuthentication(); // 添加认证中间件
app.UseAuthorization(); // 使用授权中间件
app.UseAuthorization();
app.MapControllers();
app.Run();

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<DeleteExistingFiles>true</DeleteExistingFiles>
<ExcludeApp_Data>false</ExcludeApp_Data>
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<PublishProvider>FileSystem</PublishProvider>
<PublishUrl>bin\Release\net8.0\publish\</PublishUrl>
<WebPublishMethod>FileSystem</WebPublishMethod>
<_TargetId>Folder</_TargetId>
<SiteUrlToLaunchAfterPublish />
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<ProjectGuid>e545c738-a21b-71f3-9fb9-a68d8018822d</ProjectGuid>
<SelfContained>false</SelfContained>
</PropertyGroup>
</Project>

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<_PublishTargetUrl>E:\Project\WxCheck\WxCheckApi\bin\Release\net8.0\publish\</_PublishTargetUrl>
<History>True|2025-11-08T01:48:12.8385703Z||;True|2025-11-07T15:09:26.3902793+08:00||;True|2025-11-07T11:51:31.5643958+08:00||;True|2025-11-07T11:50:44.2146653+08:00||;True|2025-11-04T14:29:55.7628000+08:00||;False|2025-11-04T14:29:50.0660192+08:00||;True|2025-11-04T14:20:52.6380538+08:00||;True|2025-11-04T10:14:31.0406197+08:00||;True|2025-11-04T10:01:51.9971474+08:00||;True|2025-11-04T09:53:16.2195499+08:00||;True|2025-11-04T09:45:30.2430508+08:00||;True|2025-11-04T08:52:40.6424728+08:00||;True|2025-11-03T21:04:36.1464303+08:00||;True|2025-11-03T20:46:33.3635634+08:00||;True|2025-11-03T20:45:29.5368625+08:00||;True|2025-11-03T20:42:14.5975957+08:00||;True|2025-11-03T20:40:43.0062117+08:00||;True|2025-11-03T20:16:38.4711929+08:00||;True|2025-11-03T20:14:42.8936169+08:00||;True|2025-11-03T19:35:15.7051947+08:00||;True|2025-11-03T19:01:58.6546141+08:00||;True|2025-11-03T17:59:30.0861681+08:00||;True|2025-11-03T16:15:01.5245126+08:00||;True|2025-11-03T16:06:34.1178642+08:00||;True|2025-10-31T17:30:06.3039818+08:00||;True|2025-10-31T16:20:12.7490590+08:00||;True|2025-10-31T15:10:14.4751645+08:00||;</History>
<LastFailureDetails />
</PropertyGroup>
</Project>

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<DeleteExistingFiles>true</DeleteExistingFiles>
<ExcludeApp_Data>false</ExcludeApp_Data>
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<PublishProvider>FileSystem</PublishProvider>
<PublishUrl>bin\Release\net8.0\publish\</PublishUrl>
<WebPublishMethod>FileSystem</WebPublishMethod>
<_TargetId>Folder</_TargetId>
<SiteUrlToLaunchAfterPublish />
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<ProjectGuid>e545c738-a21b-71f3-9fb9-a68d8018822d</ProjectGuid>
<SelfContained>false</SelfContained>
</PropertyGroup>
</Project>

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<_PublishTargetUrl>E:\Project_Class\WX_XCX\WxCheck_Wx_Prod\WxCheckApi\bin\Release\net8.0\publish\</_PublishTargetUrl>
<History>True|2025-12-05T03:46:42.6952752Z||;True|2025-12-03T17:28:08.6000818+08:00||;True|2025-12-03T15:36:17.3153352+08:00||;True|2025-12-03T15:34:35.0408800+08:00||;True|2025-12-03T15:32:13.7754473+08:00||;True|2025-12-03T15:23:43.3405041+08:00||;True|2025-12-03T11:08:15.0823391+08:00||;True|2025-12-03T11:04:29.8829020+08:00||;True|2025-12-03T11:00:07.4056298+08:00||;True|2025-12-03T10:56:38.5220608+08:00||;True|2025-12-03T10:51:59.6142114+08:00||;</History>
<LastFailureDetails />
</PropertyGroup>
</Project>

View File

@@ -1,31 +0,0 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:6041",
"sslPort": 0
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "weatherforecast",
"applicationUrl": "http://localhost:5065",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "weatherforecast",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -1,13 +0,0 @@
namespace WxCheckApi
{
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
}

View File

@@ -1,16 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CSRedisCore" Version="3.8.806" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.21" />
<PackageReference Include="MySql.Data" Version="8.4.0" />
<PackageReference Include="System.Text.Json" Version="8.0.0" />
</ItemGroup>
</Project>

View File

@@ -1,6 +0,0 @@
@WxCheckApi_HostAddress = http://localhost:5065
GET {{WxCheckApi_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@@ -1,4 +0,0 @@
<Solution>
<Project Path="../WxCheckMvc/WxCheckMvc.csproj" Id="5f5aee53-ea7f-4a13-a039-d664f136a7f8" />
<Project Path="WxCheckApi.csproj" />
</Solution>

View File

@@ -1,8 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -1,25 +0,0 @@
{
"JwT": {
"SecretKey": "1%猜U36eraIYI?3s9dI}46an不Nn>P]3)$9:dCnS5=ajAu%8B5]15hF到20T20QBD]Mt9}2z76jO#Glg&0yDy7k-2zVdt&Z5ur>=l)QF2^1&Dq04m76U2P9wvlWf",
"Issuer": "微信小程序token",
"Audience": "W*u93xxp*08DnW@%6}5Tjh6bE?;hW"
},
"WeChat": {
"AppId": "wx42e9add0f91af98b",
"AppSecret": "5620f00b40297efaf3d197d61ae184d6"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"MySQLConnection": "Server=47.119.147.104;Database=wx_xcx_check;user id=root;password=hbfjW6A_eob;port=3307;"
},
"AmapApi": {
"ApiKey": "4d5cb7818664ada68ae5f68783b8bd4c"
}
}

View File

@@ -0,0 +1,325 @@
using Microsoft.AspNetCore.Mvc;
using MySql.Data.MySqlClient;
using System;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
namespace WxCheckMvc.Controllers
{
[Route("api/[controller]/[action]")]
[ApiController]
public class AdminController : ControllerBase
{
private readonly MySqlConnection _connection;
public AdminController(MySqlConnection connection)
{
_connection = connection;
}
[HttpPost]
public async Task<IActionResult> QueryConversations([FromBody] ConversationQueryRequest request)
{
try
{
if (_connection.State != ConnectionState.Open)
{
await _connection.OpenAsync();
}
List<ConversationQueryResponse> conversations = [];
string query = @"SELECT c.Id, c.Guid, c.UserKey, c.ConversationContent, c.SendMethod,
c.UserLocation, c.Latitude, c.Longitude, c.RecordTime,
c.RecordTimeUTCStamp, c.IsDeleted, c.CreateTime, c.MessageType, c.SpeakingTime,
u.UserName, u.WeChatName, u.PhoneNumber, u.AvatarUrl, u.Department
FROM xcx_conversation c
LEFT JOIN xcx_users u ON c.UserKey = u.UserKey
WHERE c.IsDeleted = 0";
var parameters = new List<MySqlParameter>();
if (!string.IsNullOrEmpty(request.UserKey))
{
query += " AND c.UserKey = @UserKey";
parameters.Add(new MySqlParameter("@UserKey", request.UserKey));
}
if (request.MessageType.HasValue)
{
query += " AND c.MessageType = @MessageType";
parameters.Add(new MySqlParameter("@MessageType", request.MessageType.Value));
}
if (request.StartTime.HasValue)
{
long startUtcStamp = new DateTimeOffset(request.StartTime.Value).ToUnixTimeMilliseconds();
query += " AND c.RecordTimeUTCStamp >= @StartTime";
parameters.Add(new MySqlParameter("@StartTime", startUtcStamp));
}
if (request.EndTime.HasValue)
{
long endUtcStamp = new DateTimeOffset(request.EndTime.Value).ToUnixTimeMilliseconds();
query += " AND c.RecordTimeUTCStamp <= @EndTime";
parameters.Add(new MySqlParameter("@EndTime", endUtcStamp));
}
if (!string.IsNullOrEmpty(request.Department))
{
query += " AND u.Department = @Department";
parameters.Add(new MySqlParameter("@Department", request.Department));
}
query += " ORDER BY c.RecordTimeUTCStamp DESC";
int offset = (request.Page - 1) * request.PageSize;
query += " LIMIT @Limit OFFSET @Offset";
parameters.Add(new MySqlParameter("@Limit", request.PageSize));
parameters.Add(new MySqlParameter("@Offset", offset));
using (MySqlCommand cmd = new(query, _connection))
{
cmd.Parameters.AddRange(parameters.ToArray());
using (var reader = await cmd.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
conversations.Add(new ConversationQueryResponse
{
Id = reader.GetInt64(0),
Guid = reader.IsDBNull(1) ? "" : reader.GetString(1),
UserKey = reader.GetString(2),
ConversationContent = reader.GetString(3),
SendMethod = reader.GetString(4),
UserLocation = reader.IsDBNull(5) ? "" : reader.GetString(5),
Latitude = reader.IsDBNull(6) ? "" : reader.GetString(6),
Longitude = reader.IsDBNull(7) ? "" : reader.GetString(7),
RecordTime = reader.GetDateTime(8),
RecordTimeUTCStamp = reader.GetInt64(9),
IsDeleted = reader.GetBoolean(10),
CreateTime = reader.GetDateTime(11),
MessageType = reader.GetInt32(12),
SpeakingTime = reader.IsDBNull(13) ? null : reader.GetInt32(13),
UserName = reader.IsDBNull(14) ? "" : reader.GetString(14),
WeChatName = reader.IsDBNull(15) ? "" : reader.GetString(15),
PhoneNumber = reader.IsDBNull(16) ? "" : reader.GetString(16),
AvatarUrl = reader.IsDBNull(17) ? "" : reader.GetString(17),
Department = reader.IsDBNull(18) ? "" : reader.GetString(18)
});
}
}
}
return Ok(new { success = true, data = conversations });
}
catch (Exception ex)
{
return StatusCode(500, new { success = false, message = "查询失败", error = ex.Message });
}
finally
{
if (_connection.State == ConnectionState.Open)
{
await _connection.CloseAsync();
}
}
}
[HttpGet]
public async Task<IActionResult> QueryUsers()
{
try
{
if (_connection.State != ConnectionState.Open)
{
await _connection.OpenAsync();
}
List<UserQueryResponse> users = [];
string query = @"SELECT Id, UserName, UserKey, WeChatName, PhoneNumber,
FirstLoginTime, IsDisabled, CreateTime, UpdateTime, AvatarUrl, Department
FROM xcx_users
WHERE PhoneNumber IS NOT NULL
AND PhoneNumber != ''
AND UserName IS NOT NULL
AND UserName != ''
AND UserKey IS NOT NULL
AND UserKey != ''
AND IsDisabled = 0
ORDER BY FirstLoginTime DESC";
using (MySqlCommand cmd = new(query, _connection))
{
using (var reader = await cmd.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
users.Add(new UserQueryResponse
{
Id = reader.GetInt64(0),
UserName = reader.IsDBNull(1) ? "" : reader.GetString(1),
UserKey = reader.GetString(2),
WeChatName = reader.IsDBNull(3) ? "" : reader.GetString(3),
PhoneNumber = reader.IsDBNull(4) ? "" : reader.GetString(4),
FirstLoginTime = reader.GetDateTime(5),
IsDisabled = reader.GetBoolean(6),
CreateTime = reader.GetDateTime(7),
UpdateTime = reader.GetDateTime(8),
AvatarUrl = reader.IsDBNull(9) ? "" : reader.GetString(9),
Department = reader.IsDBNull(10) ? "" : reader.GetString(10)
});
}
}
}
return Ok(new { success = true, data = users });
}
catch (Exception ex)
{
return StatusCode(500, new { success = false, message = "查询失败", error = ex.Message });
}
finally
{
if (_connection.State == ConnectionState.Open)
{
await _connection.CloseAsync();
}
}
}
[HttpGet]
public async Task<IActionResult> QueryStats()
{
try
{
if (_connection.State != ConnectionState.Open)
{
await _connection.OpenAsync();
}
// 1) 活跃用户:最近 7 天登录UpdateTime且 UserKey/PhoneNumber 不为空
long activeUsers;
using (MySqlCommand cmd = new(@"
SELECT COUNT(1)
FROM xcx_users
WHERE UpdateTime >= DATE_SUB(NOW(), INTERVAL 7 DAY)
AND UserKey IS NOT NULL AND UserKey <> ''
AND PhoneNumber IS NOT NULL AND PhoneNumber <> ''", _connection))
{
activeUsers = Convert.ToInt64(await cmd.ExecuteScalarAsync());
}
// 2) 总会话记录数
long totalConversations;
using (MySqlCommand cmd = new("SELECT COUNT(1) FROM xcx_conversation", _connection))
{
totalConversations = Convert.ToInt64(await cmd.ExecuteScalarAsync());
}
// 3) 今日新增会话记录CreateTime 在今天内)
long todayNewConversations;
using (MySqlCommand cmd = new(@"
SELECT COUNT(1)
FROM xcx_conversation
WHERE CreateTime >= CURDATE()
AND CreateTime < DATE_ADD(CURDATE(), INTERVAL 1 DAY)", _connection))
{
todayNewConversations = Convert.ToInt64(await cmd.ExecuteScalarAsync());
}
// 4) 总用户数UserKey/PhoneNumber 不为空
long totalUsers;
using (MySqlCommand cmd = new(@"
SELECT COUNT(1)
FROM xcx_users
WHERE UserKey IS NOT NULL AND UserKey <> ''
AND PhoneNumber IS NOT NULL AND PhoneNumber <> ''", _connection))
{
totalUsers = Convert.ToInt64(await cmd.ExecuteScalarAsync());
}
var data = new AdminStatsResponse
{
ActiveUsers = activeUsers,
TotalConversations = totalConversations,
TodayNewConversations = todayNewConversations,
TotalUsers = totalUsers
};
return Ok(new { success = true, data });
}
catch (Exception ex)
{
return StatusCode(500, new { success = false, message = "查询失败", error = ex.Message });
}
finally
{
if (_connection.State == ConnectionState.Open)
{
await _connection.CloseAsync();
}
}
}
}
public class ConversationQueryRequest
{
public DateTime? StartTime { get; set; }
public DateTime? EndTime { get; set; }
public string UserKey { get; set; }
public int? MessageType { get; set; }
public string Department { get; set; }
public int Page { get; set; } = 1;
public int PageSize { get; set; } = 20;
}
public class ConversationQueryResponse
{
public long Id { get; set; }
public string Guid { get; set; }
public string UserKey { get; set; }
public string ConversationContent { get; set; }
public string SendMethod { get; set; }
public string UserLocation { get; set; }
public string Latitude { get; set; }
public string Longitude { get; set; }
public DateTime RecordTime { get; set; }
public long RecordTimeUTCStamp { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreateTime { get; set; }
public int MessageType { get; set; }
public int? SpeakingTime { get; set; }
public string UserName { get; set; }
public string WeChatName { get; set; }
public string PhoneNumber { get; set; }
public string AvatarUrl { get; set; }
public string Department { get; set; }
}
public class UserQueryResponse
{
public long Id { get; set; }
public string UserName { get; set; }
public string UserKey { get; set; }
public string WeChatName { get; set; }
public string PhoneNumber { get; set; }
public DateTime FirstLoginTime { get; set; }
public bool IsDisabled { get; set; }
public DateTime CreateTime { get; set; }
public DateTime UpdateTime { get; set; }
public string AvatarUrl { get; set; }
public string Department { get; set; }
}
public class AdminStatsResponse
{
public long ActiveUsers { get; set; }
public long TotalConversations { get; set; }
public long TodayNewConversations { get; set; }
public long TotalUsers { get; set; }
}
}

View File

@@ -91,7 +91,7 @@ namespace WxCheckMvc.Controllers
}
string updateSql = "UPDATE xcx_users SET AvatarUrl = @AvatarUrl, UpdateTime = NOW() WHERE UserKey = @UserKey";
using (MySqlCommand cmd = new MySqlCommand(updateSql, _connection))
using (MySqlCommand cmd = new(updateSql, _connection))
{
cmd.Parameters.AddWithValue("@AvatarUrl", fullUrl);
cmd.Parameters.AddWithValue("@UserKey", userKey);
@@ -212,13 +212,13 @@ namespace WxCheckMvc.Controllers
{
var part = addressParts[i];
// 如果部分包含"路"、"街"、"巷"等关键词,可能是街道信息
if (part.Contains("路") || part.Contains("街") || part.Contains("巷") || part.Contains("道"))
if (part.Contains('路') || part.Contains('街') || part.Contains('巷') || part.Contains('道'))
{
street = part;
// 如果下一个部分存在且不是区县名称,可能是门牌号
if (i + 1 < addressParts.Length &&
!addressParts[i + 1].Contains("区") &&
!addressParts[i + 1].Contains("县"))
!addressParts[i + 1].Contains('区') &&
!addressParts[i + 1].Contains('县'))
{
streetNumber = addressParts[i + 1];
}
@@ -299,7 +299,7 @@ namespace WxCheckMvc.Controllers
string latitude = "";
string longitude = "";
using (MySqlCommand cmd = new MySqlCommand("SELECT Latitude, Longitude FROM xcx_conversation WHERE Guid = @Guid AND IsDeleted = 0", _connection))
using (MySqlCommand cmd = new("SELECT Latitude, Longitude FROM xcx_conversation WHERE Guid = @Guid AND IsDeleted = 0", _connection))
{
cmd.Parameters.AddWithValue("@Guid", request.Guid);
@@ -321,7 +321,7 @@ namespace WxCheckMvc.Controllers
var address = await ConvertCoordinatesToAddress(longitude, latitude);
// 更新数据库中的UserLocation字段
using (MySqlCommand cmd = new MySqlCommand("UPDATE xcx_conversation SET UserLocation = @UserLocation WHERE Guid = @Guid AND IsDeleted = 0", _connection))
using (MySqlCommand cmd = new("UPDATE xcx_conversation SET UserLocation = @UserLocation WHERE Guid = @Guid AND IsDeleted = 0", _connection))
{
cmd.Parameters.AddWithValue("@Guid", request.Guid);
cmd.Parameters.AddWithValue("@UserLocation", address);
@@ -334,7 +334,7 @@ namespace WxCheckMvc.Controllers
}
}
return Ok(new { success = true, message = "地址更新成功", address = address });
return Ok(new { success = true, message = "地址更新成功", address });
}
catch (Exception ex)
{
@@ -383,7 +383,7 @@ namespace WxCheckMvc.Controllers
// 生成GUID
string conversationGuid = string.IsNullOrEmpty(request.Guid) ? Guid.NewGuid().ToString("N") : request.Guid;
long conversationId = 0;
using (MySqlCommand cmd = new MySqlCommand("INSERT INTO xcx_conversation (UserKey, ConversationContent, SendMethod, UserLocation, Latitude, Longitude, RecordTime, RecordTimeUTCStamp, IsDeleted, CreateTime, MessageType, Guid, SpeakingTime) VALUES (@UserKey, @ConversationContent, @SendMethod, @UserLocation, @Latitude, @Longitude, @RecordTime, @RecordTimeUTCStamp, @IsDeleted, @CreateTime, @MessageType, @Guid, @SpeakingTime); SELECT LAST_INSERT_ID();", _connection))
using (MySqlCommand cmd = new("INSERT INTO xcx_conversation (UserKey, ConversationContent, SendMethod, UserLocation, Latitude, Longitude, RecordTime, RecordTimeUTCStamp, IsDeleted, CreateTime, MessageType, Guid, SpeakingTime) VALUES (@UserKey, @ConversationContent, @SendMethod, @UserLocation, @Latitude, @Longitude, @RecordTime, @RecordTimeUTCStamp, @IsDeleted, @CreateTime, @MessageType, @Guid, @SpeakingTime); SELECT LAST_INSERT_ID();", _connection))
{
cmd.Parameters.AddWithValue("@UserKey", request.UserKey);
cmd.Parameters.AddWithValue("@MessageType", request.MessageType);
@@ -414,7 +414,7 @@ namespace WxCheckMvc.Controllers
LEFT JOIN xcx_users AS users ON convs.UserKey = users.UserKey
WHERE convs.Guid = @Guid";
using (MySqlCommand cmd = new MySqlCommand(query, _connection))
using (MySqlCommand cmd = new(query, _connection))
{
cmd.Parameters.AddWithValue("@Guid", conversationGuid);
using (var reader = await cmd.ExecuteReaderAsync())
@@ -495,7 +495,7 @@ namespace WxCheckMvc.Controllers
await _connection.OpenAsync();
}
List<ConversationResponse> conversations = new List<ConversationResponse>();
List<ConversationResponse> conversations = [];
// 构建查询SQL根据MessageType参数决定是否添加过滤条件
string query = "SELECT Id, Guid, UserKey, ConversationContent, SendMethod, UserLocation, Latitude, Longitude, RecordTime, RecordTimeUTCStamp, IsDeleted, CreateTime, MessageType, SpeakingTime FROM xcx_conversation WHERE UserKey = @UserKey AND IsDeleted = 0";
@@ -505,7 +505,7 @@ namespace WxCheckMvc.Controllers
}
query += " ORDER BY RecordTimeUTCStamp DESC";
using (MySqlCommand cmd = new MySqlCommand(query, _connection))
using (MySqlCommand cmd = new(query, _connection))
{
cmd.Parameters.AddWithValue("@UserKey", request.UserKey);
@@ -561,13 +561,13 @@ namespace WxCheckMvc.Controllers
}
DateTime nowtime = DateTime.Now;
using (MySqlCommand cmd = new MySqlCommand("UPDATE xcx_conversation SET ConversationContent = @ConversationContent, SendMethod = @SendMethod, UserLocation = @UserLocation, MessageType = @MessageType, RecordTime = @RecordTime WHERE Guid = @Guid AND UserKey = @UserKey", _connection))
using (MySqlCommand cmd = new("UPDATE xcx_conversation SET ConversationContent = @ConversationContent, SendMethod = @SendMethod, UserLocation = @UserLocation, MessageType = @MessageType, RecordTime = @RecordTime WHERE Guid = @Guid AND UserKey = @UserKey", _connection))
{
cmd.Parameters.AddWithValue("@Guid", request.Guid);
cmd.Parameters.AddWithValue("@UserKey", request.UserKey);
cmd.Parameters.AddWithValue("@ConversationContent", request.ConversationContent);
cmd.Parameters.AddWithValue("@SendMethod", request.SendMethod);
cmd.Parameters.AddWithValue("@UserLocation", request.UserLocation ?? "");
cmd.Parameters.AddWithValue("@UserLocation", "");//request.UserLocation ?? "");
cmd.Parameters.AddWithValue("@MessageType", request.MessageType);
cmd.Parameters.AddWithValue("@RecordTime", nowtime);
@@ -605,7 +605,7 @@ namespace WxCheckMvc.Controllers
await _connection.OpenAsync();
}
using (MySqlCommand cmd = new MySqlCommand("UPDATE xcx_conversation SET IsDeleted = 1 WHERE Guid = @Guid AND UserKey = @UserKey AND IsDeleted = 0", _connection))
using (MySqlCommand cmd = new("UPDATE xcx_conversation SET IsDeleted = 1 WHERE Guid = @Guid AND UserKey = @UserKey AND IsDeleted = 0", _connection))
{
cmd.Parameters.AddWithValue("@Guid", request.Guid);
cmd.Parameters.AddWithValue("@UserKey", request.UserKey);
@@ -649,7 +649,7 @@ namespace WxCheckMvc.Controllers
FROM xcx_conversation
WHERE Guid = @Guid";
using (MySqlCommand cmd = new MySqlCommand(query, _connection))
using (MySqlCommand cmd = new(query, _connection))
{
cmd.Parameters.AddWithValue("@Guid", request.Guid);
@@ -714,7 +714,7 @@ namespace WxCheckMvc.Controllers
int offset = (request.Page - 1) * request.PageSize;
List<ConversationResponse> conversations = new List<ConversationResponse>();
List<ConversationResponse> conversations = [];
// 构建分页查询SQL根据MessageType参数决定是否添加过滤条件
string query = @"SELECT Id, Guid, UserKey, ConversationContent, SendMethod, UserLocation, Latitude, Longitude, RecordTime, RecordTimeUTCStamp, IsDeleted, CreateTime, MessageType, SpeakingTime
@@ -726,7 +726,7 @@ namespace WxCheckMvc.Controllers
}
query += " ORDER BY RecordTimeUTCStamp DESC LIMIT @Offset, @Limit";
using (MySqlCommand cmd = new MySqlCommand(query, _connection))
using (MySqlCommand cmd = new(query, _connection))
{
cmd.Parameters.AddWithValue("@UserKey", request.UserKey);
if (request.MessageType == 1)
@@ -769,7 +769,7 @@ namespace WxCheckMvc.Controllers
countQuery += " AND MessageType = @MessageType";
}
using (MySqlCommand countCmd = new MySqlCommand(countQuery, _connection))
using (MySqlCommand countCmd = new(countQuery, _connection))
{
countCmd.Parameters.AddWithValue("@UserKey", request.UserKey);
if (request.MessageType == 1)

View File

@@ -8,6 +8,7 @@ using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.Net.Http;
using System.Text.Json;
@@ -110,7 +111,10 @@ namespace WxCheckMvc.Controllers
{
await _connection.OpenAsync();
}
if (string.IsNullOrEmpty(request.UserKey))
{
return BadRequest(new { success = false, message = "UserKey不能为空" });
}
// 检查用户是否存在
UserResponse user = null;
using (MySqlCommand checkCmd = new MySqlCommand("SELECT Id, UserName, UserKey, WeChatName, PhoneNumber, AvatarUrl, FirstLoginTime, IsDisabled, CreateTime, UpdateTime FROM xcx_users WHERE UserKey = @UserKey", _connection))
@@ -142,6 +146,31 @@ namespace WxCheckMvc.Controllers
return NotFound(new { success = false, message = "用户不存在" });
}
// 在验证之前,先对 UserName 和 PhoneNumber 去除空格和标点符号
string cleanedUserName = request.UserName ?? string.Empty;
string cleanedPhoneNumber = request.PhoneNumber ?? string.Empty;
// PhoneNumber 只保留数字
cleanedPhoneNumber = Regex.Replace(cleanedPhoneNumber, "\\D", "");
// UserName 去除标点、符号和空白(保留所有字母/汉字/罕见字形以及数字)
cleanedUserName = Regex.Replace(cleanedUserName, @"[\p{P}\p{S}\s]+", "").Trim();
// 验证 UserName 不为空
if (string.IsNullOrEmpty(cleanedUserName))
{
return BadRequest(new { success = false, message = "用户名不能为空或仅包含非法字符" });
}
// 验证 PhoneNumber 是否为合法手机号(以 1 开头,共 11 位数字)
if (!Regex.IsMatch(cleanedPhoneNumber, "^1\\d{10}$"))
{
return BadRequest(new { success = false, message = "手机号格式错误" });
}
// 将清理后的值写回 request确保更新数据库时使用清理后的值
request.UserName = cleanedUserName;
request.PhoneNumber = cleanedPhoneNumber;
// 更新用户信息
using (MySqlCommand cmd = new MySqlCommand("UPDATE xcx_users SET UserName = @UserName, WeChatName = @WeChatName, PhoneNumber = @PhoneNumber, AvatarUrl = @AvatarUrl, UpdateTime = NOW() WHERE UserKey = @UserKey", _connection))
{
@@ -237,6 +266,15 @@ namespace WxCheckMvc.Controllers
await insertCmd.ExecuteNonQueryAsync();
}
}
else
{
// 用户已存在:更新最后一次接口调用时间
using (MySqlCommand updateCmd = new MySqlCommand("UPDATE xcx_users SET UpdateTime = NOW() WHERE UserKey = @UserKey", _connection))
{
updateCmd.Parameters.AddWithValue("@UserKey", openId);
await updateCmd.ExecuteNonQueryAsync();
}
}
}
// 获取用户信息
@@ -296,7 +334,7 @@ namespace WxCheckMvc.Controllers
public class RegisterRequest
{
public string UserName { get; set; }
public string UserKey { get; set; } // 改为直接传入UserKey
public string UserKey { get; set; }
public string WeChatName { get; set; }
public string PhoneNumber { get; set; }
public string AvatarUrl { get; set; }

View File

@@ -10,7 +10,17 @@ builder.Services.AddControllersWithViews();
// <20><><EFBFBD><EFBFBD>HttpClientFactory
builder.Services.AddHttpClient();
builder.Services.AddCors(options =>
{
options.AddPolicy(name: "KuaYu",
policy =>
{
policy
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݿ<EFBFBD><DDBF><EFBFBD><EFBFBD><EFBFBD>
builder.Services.AddScoped<MySqlConnection>(sp => {
var connectionString = builder.Configuration.GetConnectionString("MySQLConnection");
@@ -68,7 +78,7 @@ if (!app.Environment.IsDevelopment())
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCors("KuaYu");
app.UseRouting();
app.UseAuthorization();

View File

@@ -2,8 +2,8 @@
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<_PublishTargetUrl>E:\Project_Class\WX_XCX\WxCheck_Wx_Prod\WxCheckMvc\bin\Release\net8.0\publish\</_PublishTargetUrl>
<History>True|2025-12-05T10:56:51.7439135Z||;True|2025-12-05T17:44:11.4130698+08:00||;</History>
<_PublishTargetUrl>E:\Project_Class\WX_XCX\Wx_WxCheck_Prod\WxCheckMvc\bin\Release\net8.0\publish\</_PublishTargetUrl>
<History>True|2025-12-25T06:00:56.3451051Z||;True|2025-12-24T20:05:02.2999541+08:00||;True|2025-12-24T16:33:44.2108439+08:00||;True|2025-12-24T15:32:13.8037439+08:00||;True|2025-12-12T11:09:28.8147447+08:00||;True|2025-12-11T17:04:53.2856075+08:00||;True|2025-12-11T17:04:22.0809574+08:00||;True|2025-12-05T18:56:51.7439135+08:00||;True|2025-12-05T17:44:11.4130698+08:00||;</History>
<LastFailureDetails />
</PropertyGroup>
</Project>

View File

@@ -2,6 +2,6 @@
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ActiveDebugProfile>https</ActiveDebugProfile>
<NameOfLastUsedPublishProfile>E:\Project_Class\WX_XCX\WxCheck_Wx_Prod\WxCheckMvc\Properties\PublishProfiles\FolderProfile.pubxml</NameOfLastUsedPublishProfile>
<NameOfLastUsedPublishProfile>E:\Project_Class\WX_XCX\Wx_WxCheck_Prod\WxCheckMvc\Properties\PublishProfiles\FolderProfile.pubxml</NameOfLastUsedPublishProfile>
</PropertyGroup>
</Project>

View File

@@ -1,489 +0,0 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v8.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v8.0": {
"WxCheckMvc/1.0.0": {
"dependencies": {
"CSRedisCore": "3.8.807",
"Microsoft.AspNetCore.Authentication.JwtBearer": "8.0.22",
"MySql.Data": "8.4.0"
},
"runtime": {
"WxCheckMvc.dll": {}
}
},
"BouncyCastle.Cryptography/2.2.1": {
"runtime": {
"lib/net6.0/BouncyCastle.Cryptography.dll": {
"assemblyVersion": "2.0.0.0",
"fileVersion": "2.2.1.47552"
}
}
},
"CSRedisCore/3.8.807": {
"dependencies": {
"Newtonsoft.Json": "13.0.1"
},
"runtime": {
"lib/netstandard2.0/CSRedisCore.dll": {
"assemblyVersion": "3.8.807.0",
"fileVersion": "3.8.807.0"
}
}
},
"Google.Protobuf/3.25.1": {
"runtime": {
"lib/net5.0/Google.Protobuf.dll": {
"assemblyVersion": "3.25.1.0",
"fileVersion": "3.25.1.0"
}
}
},
"K4os.Compression.LZ4/1.3.5": {
"runtime": {
"lib/net6.0/K4os.Compression.LZ4.dll": {
"assemblyVersion": "1.3.5.0",
"fileVersion": "1.3.5.0"
}
}
},
"K4os.Compression.LZ4.Streams/1.3.5": {
"dependencies": {
"K4os.Compression.LZ4": "1.3.5",
"K4os.Hash.xxHash": "1.0.8"
},
"runtime": {
"lib/net6.0/K4os.Compression.LZ4.Streams.dll": {
"assemblyVersion": "1.3.5.0",
"fileVersion": "1.3.5.0"
}
}
},
"K4os.Hash.xxHash/1.0.8": {
"runtime": {
"lib/net6.0/K4os.Hash.xxHash.dll": {
"assemblyVersion": "1.0.8.0",
"fileVersion": "1.0.8.0"
}
}
},
"Microsoft.AspNetCore.Authentication.JwtBearer/8.0.22": {
"dependencies": {
"Microsoft.IdentityModel.Protocols.OpenIdConnect": "7.1.2"
},
"runtime": {
"lib/net8.0/Microsoft.AspNetCore.Authentication.JwtBearer.dll": {
"assemblyVersion": "8.0.22.0",
"fileVersion": "8.0.2225.52808"
}
}
},
"Microsoft.IdentityModel.Abstractions/7.1.2": {
"runtime": {
"lib/net8.0/Microsoft.IdentityModel.Abstractions.dll": {
"assemblyVersion": "7.1.2.0",
"fileVersion": "7.1.2.41121"
}
}
},
"Microsoft.IdentityModel.JsonWebTokens/7.1.2": {
"dependencies": {
"Microsoft.IdentityModel.Tokens": "7.1.2"
},
"runtime": {
"lib/net8.0/Microsoft.IdentityModel.JsonWebTokens.dll": {
"assemblyVersion": "7.1.2.0",
"fileVersion": "7.1.2.41121"
}
}
},
"Microsoft.IdentityModel.Logging/7.1.2": {
"dependencies": {
"Microsoft.IdentityModel.Abstractions": "7.1.2"
},
"runtime": {
"lib/net8.0/Microsoft.IdentityModel.Logging.dll": {
"assemblyVersion": "7.1.2.0",
"fileVersion": "7.1.2.41121"
}
}
},
"Microsoft.IdentityModel.Protocols/7.1.2": {
"dependencies": {
"Microsoft.IdentityModel.Logging": "7.1.2",
"Microsoft.IdentityModel.Tokens": "7.1.2"
},
"runtime": {
"lib/net8.0/Microsoft.IdentityModel.Protocols.dll": {
"assemblyVersion": "7.1.2.0",
"fileVersion": "7.1.2.41121"
}
}
},
"Microsoft.IdentityModel.Protocols.OpenIdConnect/7.1.2": {
"dependencies": {
"Microsoft.IdentityModel.Protocols": "7.1.2",
"System.IdentityModel.Tokens.Jwt": "7.1.2"
},
"runtime": {
"lib/net8.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll": {
"assemblyVersion": "7.1.2.0",
"fileVersion": "7.1.2.41121"
}
}
},
"Microsoft.IdentityModel.Tokens/7.1.2": {
"dependencies": {
"Microsoft.IdentityModel.Logging": "7.1.2"
},
"runtime": {
"lib/net8.0/Microsoft.IdentityModel.Tokens.dll": {
"assemblyVersion": "7.1.2.0",
"fileVersion": "7.1.2.41121"
}
}
},
"Microsoft.Win32.SystemEvents/4.7.0": {
"runtime": {
"lib/netstandard2.0/Microsoft.Win32.SystemEvents.dll": {
"assemblyVersion": "4.0.2.0",
"fileVersion": "4.700.19.56404"
}
},
"runtimeTargets": {
"runtimes/win/lib/netcoreapp3.0/Microsoft.Win32.SystemEvents.dll": {
"rid": "win",
"assetType": "runtime",
"assemblyVersion": "4.0.2.0",
"fileVersion": "4.700.19.56404"
}
}
},
"MySql.Data/8.4.0": {
"dependencies": {
"BouncyCastle.Cryptography": "2.2.1",
"Google.Protobuf": "3.25.1",
"K4os.Compression.LZ4.Streams": "1.3.5",
"System.Configuration.ConfigurationManager": "4.4.1",
"System.Security.Permissions": "4.7.0",
"ZstdSharp.Port": "0.7.1"
},
"runtime": {
"lib/net8.0/MySql.Data.dll": {
"assemblyVersion": "8.4.0.0",
"fileVersion": "8.4.0.0"
}
},
"runtimeTargets": {
"runtimes/win-x64/native/comerr64.dll": {
"rid": "win-x64",
"assetType": "native",
"fileVersion": "4.1.0.0"
},
"runtimes/win-x64/native/gssapi64.dll": {
"rid": "win-x64",
"assetType": "native",
"fileVersion": "4.1.0.0"
},
"runtimes/win-x64/native/k5sprt64.dll": {
"rid": "win-x64",
"assetType": "native",
"fileVersion": "4.1.0.0"
},
"runtimes/win-x64/native/krb5_64.dll": {
"rid": "win-x64",
"assetType": "native",
"fileVersion": "4.1.0.0"
},
"runtimes/win-x64/native/krbcc64.dll": {
"rid": "win-x64",
"assetType": "native",
"fileVersion": "4.1.0.0"
}
}
},
"Newtonsoft.Json/13.0.1": {
"runtime": {
"lib/netstandard2.0/Newtonsoft.Json.dll": {
"assemblyVersion": "13.0.0.0",
"fileVersion": "13.0.1.25517"
}
}
},
"System.Configuration.ConfigurationManager/4.4.1": {
"dependencies": {
"System.Security.Cryptography.ProtectedData": "4.4.0"
},
"runtime": {
"lib/netstandard2.0/System.Configuration.ConfigurationManager.dll": {
"assemblyVersion": "4.0.0.0",
"fileVersion": "4.6.25921.2"
}
}
},
"System.Drawing.Common/4.7.0": {
"dependencies": {
"Microsoft.Win32.SystemEvents": "4.7.0"
},
"runtime": {
"lib/netstandard2.0/System.Drawing.Common.dll": {
"assemblyVersion": "4.0.0.1",
"fileVersion": "4.6.26919.2"
}
},
"runtimeTargets": {
"runtimes/unix/lib/netcoreapp3.0/System.Drawing.Common.dll": {
"rid": "unix",
"assetType": "runtime",
"assemblyVersion": "4.0.2.0",
"fileVersion": "4.700.19.56404"
},
"runtimes/win/lib/netcoreapp3.0/System.Drawing.Common.dll": {
"rid": "win",
"assetType": "runtime",
"assemblyVersion": "4.0.2.0",
"fileVersion": "4.700.19.56404"
}
}
},
"System.IdentityModel.Tokens.Jwt/7.1.2": {
"dependencies": {
"Microsoft.IdentityModel.JsonWebTokens": "7.1.2",
"Microsoft.IdentityModel.Tokens": "7.1.2"
},
"runtime": {
"lib/net8.0/System.IdentityModel.Tokens.Jwt.dll": {
"assemblyVersion": "7.1.2.0",
"fileVersion": "7.1.2.41121"
}
}
},
"System.Security.Cryptography.ProtectedData/4.4.0": {
"runtime": {
"lib/netstandard2.0/System.Security.Cryptography.ProtectedData.dll": {
"assemblyVersion": "4.0.2.0",
"fileVersion": "4.6.25519.3"
}
},
"runtimeTargets": {
"runtimes/win/lib/netstandard2.0/System.Security.Cryptography.ProtectedData.dll": {
"rid": "win",
"assetType": "runtime",
"assemblyVersion": "4.0.2.0",
"fileVersion": "4.6.25519.3"
}
}
},
"System.Security.Permissions/4.7.0": {
"dependencies": {
"System.Windows.Extensions": "4.7.0"
},
"runtime": {
"lib/netcoreapp3.0/System.Security.Permissions.dll": {
"assemblyVersion": "4.0.3.0",
"fileVersion": "4.700.19.56404"
}
}
},
"System.Windows.Extensions/4.7.0": {
"dependencies": {
"System.Drawing.Common": "4.7.0"
},
"runtime": {
"lib/netcoreapp3.0/System.Windows.Extensions.dll": {
"assemblyVersion": "4.0.1.0",
"fileVersion": "4.700.19.56404"
}
},
"runtimeTargets": {
"runtimes/win/lib/netcoreapp3.0/System.Windows.Extensions.dll": {
"rid": "win",
"assetType": "runtime",
"assemblyVersion": "4.0.1.0",
"fileVersion": "4.700.19.56404"
}
}
},
"ZstdSharp.Port/0.7.1": {
"runtime": {
"lib/net7.0/ZstdSharp.dll": {
"assemblyVersion": "0.7.1.0",
"fileVersion": "0.7.1.0"
}
}
}
}
},
"libraries": {
"WxCheckMvc/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"BouncyCastle.Cryptography/2.2.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-A6Zr52zVqJKt18ZBsTnX0qhG0kwIQftVAjLmszmkiR/trSp8H+xj1gUOzk7XHwaKgyREMSV1v9XaKrBUeIOdvQ==",
"path": "bouncycastle.cryptography/2.2.1",
"hashPath": "bouncycastle.cryptography.2.2.1.nupkg.sha512"
},
"CSRedisCore/3.8.807": {
"type": "package",
"serviceable": true,
"sha512": "sha512-fu0ZGIRdq1q0dZR+ecJxajfdLiRNWBR+UKx9Ob43rSwPQT/9duIJ8DLThkDjlx/0CHtX8oktv3rYAHS/mIy8bw==",
"path": "csrediscore/3.8.807",
"hashPath": "csrediscore.3.8.807.nupkg.sha512"
},
"Google.Protobuf/3.25.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Sw9bq4hOD+AaS3RrnmP5IT25cyZ/T1qpM0e8+G+23Nojhv7+ScJFPEAQo1m4EFQWhXoI4FRZDrK+wjHCPw9yxg==",
"path": "google.protobuf/3.25.1",
"hashPath": "google.protobuf.3.25.1.nupkg.sha512"
},
"K4os.Compression.LZ4/1.3.5": {
"type": "package",
"serviceable": true,
"sha512": "sha512-TS4mqlT0X1OlnvOGNfl02QdVUhuqgWuCnn7UxupIa7C9Pb6qlQ5yZA2sPhRh0OSmVULaQU64KV4wJuu//UyVQQ==",
"path": "k4os.compression.lz4/1.3.5",
"hashPath": "k4os.compression.lz4.1.3.5.nupkg.sha512"
},
"K4os.Compression.LZ4.Streams/1.3.5": {
"type": "package",
"serviceable": true,
"sha512": "sha512-M0NufZI8ym3mm6F6HMSPz1jw7TJGdY74fjAtbIXATmnAva/8xLz50eQZJI9tf9mMeHUaFDg76N1BmEh8GR5zeA==",
"path": "k4os.compression.lz4.streams/1.3.5",
"hashPath": "k4os.compression.lz4.streams.1.3.5.nupkg.sha512"
},
"K4os.Hash.xxHash/1.0.8": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Wp2F7BamQ2Q/7Hk834nV9vRQapgcr8kgv9Jvfm8J3D0IhDqZMMl+a2yxUq5ltJitvXvQfB8W6K4F4fCbw/P6YQ==",
"path": "k4os.hash.xxhash/1.0.8",
"hashPath": "k4os.hash.xxhash.1.0.8.nupkg.sha512"
},
"Microsoft.AspNetCore.Authentication.JwtBearer/8.0.22": {
"type": "package",
"serviceable": true,
"sha512": "sha512-3lqhBK+t4u8Ajl2je5UC9jCoDI+8zLz/YBVjwxQKfFF9NyzACf4QQmlmKnpH/LdkVSxCjLwvJ1ko4k0EAgy8cg==",
"path": "microsoft.aspnetcore.authentication.jwtbearer/8.0.22",
"hashPath": "microsoft.aspnetcore.authentication.jwtbearer.8.0.22.nupkg.sha512"
},
"Microsoft.IdentityModel.Abstractions/7.1.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-33eTIA2uO/L9utJjZWbKsMSVsQf7F8vtd6q5mQX7ZJzNvCpci5fleD6AeANGlbbb7WX7XKxq9+Dkb5e3GNDrmQ==",
"path": "microsoft.identitymodel.abstractions/7.1.2",
"hashPath": "microsoft.identitymodel.abstractions.7.1.2.nupkg.sha512"
},
"Microsoft.IdentityModel.JsonWebTokens/7.1.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-cloLGeZolXbCJhJBc5OC05uhrdhdPL6MWHuVUnkkUvPDeK7HkwThBaLZ1XjBQVk9YhxXE2OvHXnKi0PLleXxDg==",
"path": "microsoft.identitymodel.jsonwebtokens/7.1.2",
"hashPath": "microsoft.identitymodel.jsonwebtokens.7.1.2.nupkg.sha512"
},
"Microsoft.IdentityModel.Logging/7.1.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-YCxBt2EeJP8fcXk9desChkWI+0vFqFLvBwrz5hBMsoh0KJE6BC66DnzkdzkJNqMltLromc52dkdT206jJ38cTw==",
"path": "microsoft.identitymodel.logging/7.1.2",
"hashPath": "microsoft.identitymodel.logging.7.1.2.nupkg.sha512"
},
"Microsoft.IdentityModel.Protocols/7.1.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-SydLwMRFx6EHPWJ+N6+MVaoArN1Htt92b935O3RUWPY1yUF63zEjvd3lBu79eWdZUwedP8TN2I5V9T3nackvIQ==",
"path": "microsoft.identitymodel.protocols/7.1.2",
"hashPath": "microsoft.identitymodel.protocols.7.1.2.nupkg.sha512"
},
"Microsoft.IdentityModel.Protocols.OpenIdConnect/7.1.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-6lHQoLXhnMQ42mGrfDkzbIOR3rzKM1W1tgTeMPLgLCqwwGw0d96xFi/UiX/fYsu7d6cD5MJiL3+4HuI8VU+sVQ==",
"path": "microsoft.identitymodel.protocols.openidconnect/7.1.2",
"hashPath": "microsoft.identitymodel.protocols.openidconnect.7.1.2.nupkg.sha512"
},
"Microsoft.IdentityModel.Tokens/7.1.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-oICJMqr3aNEDZOwnH5SK49bR6Z4aX0zEAnOLuhloumOSuqnNq+GWBdQyrgILnlcT5xj09xKCP/7Y7gJYB+ls/g==",
"path": "microsoft.identitymodel.tokens/7.1.2",
"hashPath": "microsoft.identitymodel.tokens.7.1.2.nupkg.sha512"
},
"Microsoft.Win32.SystemEvents/4.7.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-mtVirZr++rq+XCDITMUdnETD59XoeMxSpLRIII7JRI6Yj0LEDiO1pPn0ktlnIj12Ix8bfvQqQDMMIF9wC98oCA==",
"path": "microsoft.win32.systemevents/4.7.0",
"hashPath": "microsoft.win32.systemevents.4.7.0.nupkg.sha512"
},
"MySql.Data/8.4.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-NA273x7ybutfGwGbF2cd8rLVM5t7AkZCzRHr/+tGms1FeMlfl+LgfjHXcb5qN1QxFpeNQQKZ+vqZw8v/S8gUiA==",
"path": "mysql.data/8.4.0",
"hashPath": "mysql.data.8.4.0.nupkg.sha512"
},
"Newtonsoft.Json/13.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==",
"path": "newtonsoft.json/13.0.1",
"hashPath": "newtonsoft.json.13.0.1.nupkg.sha512"
},
"System.Configuration.ConfigurationManager/4.4.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-jz3TWKMAeuDEyrPCK5Jyt4bzQcmzUIMcY9Ud6PkElFxTfnsihV+9N/UCqvxe1z5gc7jMYAnj7V1COMS9QKIuHQ==",
"path": "system.configuration.configurationmanager/4.4.1",
"hashPath": "system.configuration.configurationmanager.4.4.1.nupkg.sha512"
},
"System.Drawing.Common/4.7.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-v+XbyYHaZjDfn0ENmJEV1VYLgGgCTx1gnfOBcppowbpOAriglYgGCvFCPr2EEZyBvXlpxbEsTwkOlInl107ahA==",
"path": "system.drawing.common/4.7.0",
"hashPath": "system.drawing.common.4.7.0.nupkg.sha512"
},
"System.IdentityModel.Tokens.Jwt/7.1.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Thhbe1peAmtSBFaV/ohtykXiZSOkx59Da44hvtWfIMFofDA3M3LaVyjstACf2rKGn4dEDR2cUpRAZ0Xs/zB+7Q==",
"path": "system.identitymodel.tokens.jwt/7.1.2",
"hashPath": "system.identitymodel.tokens.jwt.7.1.2.nupkg.sha512"
},
"System.Security.Cryptography.ProtectedData/4.4.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-cJV7ScGW7EhatRsjehfvvYVBvtiSMKgN8bOVI0bQhnF5bU7vnHVIsH49Kva7i7GWaWYvmEzkYVk1TC+gZYBEog==",
"path": "system.security.cryptography.protecteddata/4.4.0",
"hashPath": "system.security.cryptography.protecteddata.4.4.0.nupkg.sha512"
},
"System.Security.Permissions/4.7.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-dkOV6YYVBnYRa15/yv004eCGRBVADXw8qRbbNiCn/XpdJSUXkkUeIvdvFHkvnko4CdKMqG8yRHC4ox83LSlMsQ==",
"path": "system.security.permissions/4.7.0",
"hashPath": "system.security.permissions.4.7.0.nupkg.sha512"
},
"System.Windows.Extensions/4.7.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-CeWTdRNfRaSh0pm2gDTJFwVaXfTq6Xwv/sA887iwPTneW7oMtMlpvDIO+U60+3GWTB7Aom6oQwv5VZVUhQRdPQ==",
"path": "system.windows.extensions/4.7.0",
"hashPath": "system.windows.extensions.4.7.0.nupkg.sha512"
},
"ZstdSharp.Port/0.7.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Idgg+mJEyAujqDPzA3APy9dNoyw0YQcNA65GgYjktDRtJ+nvx/hv+J+m6Eax3JJMGEYGy04oc5YNP6ZvQ3Y1vQ==",
"path": "zstdsharp.port/0.7.1",
"hashPath": "zstdsharp.port.0.7.1.nupkg.sha512"
}
}
}

View File

@@ -1,19 +0,0 @@
{
"runtimeOptions": {
"tfm": "net8.0",
"frameworks": [
{
"name": "Microsoft.NETCore.App",
"version": "8.0.0"
},
{
"name": "Microsoft.AspNetCore.App",
"version": "8.0.0"
}
],
"configProperties": {
"System.GC.Server": true,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,8 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -1,9 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@@ -1,489 +0,0 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v8.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v8.0": {
"WxCheckMvc/1.0.0": {
"dependencies": {
"CSRedisCore": "3.8.807",
"Microsoft.AspNetCore.Authentication.JwtBearer": "8.0.22",
"MySql.Data": "8.4.0"
},
"runtime": {
"WxCheckMvc.dll": {}
}
},
"BouncyCastle.Cryptography/2.2.1": {
"runtime": {
"lib/net6.0/BouncyCastle.Cryptography.dll": {
"assemblyVersion": "2.0.0.0",
"fileVersion": "2.2.1.47552"
}
}
},
"CSRedisCore/3.8.807": {
"dependencies": {
"Newtonsoft.Json": "13.0.1"
},
"runtime": {
"lib/netstandard2.0/CSRedisCore.dll": {
"assemblyVersion": "3.8.807.0",
"fileVersion": "3.8.807.0"
}
}
},
"Google.Protobuf/3.25.1": {
"runtime": {
"lib/net5.0/Google.Protobuf.dll": {
"assemblyVersion": "3.25.1.0",
"fileVersion": "3.25.1.0"
}
}
},
"K4os.Compression.LZ4/1.3.5": {
"runtime": {
"lib/net6.0/K4os.Compression.LZ4.dll": {
"assemblyVersion": "1.3.5.0",
"fileVersion": "1.3.5.0"
}
}
},
"K4os.Compression.LZ4.Streams/1.3.5": {
"dependencies": {
"K4os.Compression.LZ4": "1.3.5",
"K4os.Hash.xxHash": "1.0.8"
},
"runtime": {
"lib/net6.0/K4os.Compression.LZ4.Streams.dll": {
"assemblyVersion": "1.3.5.0",
"fileVersion": "1.3.5.0"
}
}
},
"K4os.Hash.xxHash/1.0.8": {
"runtime": {
"lib/net6.0/K4os.Hash.xxHash.dll": {
"assemblyVersion": "1.0.8.0",
"fileVersion": "1.0.8.0"
}
}
},
"Microsoft.AspNetCore.Authentication.JwtBearer/8.0.22": {
"dependencies": {
"Microsoft.IdentityModel.Protocols.OpenIdConnect": "7.1.2"
},
"runtime": {
"lib/net8.0/Microsoft.AspNetCore.Authentication.JwtBearer.dll": {
"assemblyVersion": "8.0.22.0",
"fileVersion": "8.0.2225.52808"
}
}
},
"Microsoft.IdentityModel.Abstractions/7.1.2": {
"runtime": {
"lib/net8.0/Microsoft.IdentityModel.Abstractions.dll": {
"assemblyVersion": "7.1.2.0",
"fileVersion": "7.1.2.41121"
}
}
},
"Microsoft.IdentityModel.JsonWebTokens/7.1.2": {
"dependencies": {
"Microsoft.IdentityModel.Tokens": "7.1.2"
},
"runtime": {
"lib/net8.0/Microsoft.IdentityModel.JsonWebTokens.dll": {
"assemblyVersion": "7.1.2.0",
"fileVersion": "7.1.2.41121"
}
}
},
"Microsoft.IdentityModel.Logging/7.1.2": {
"dependencies": {
"Microsoft.IdentityModel.Abstractions": "7.1.2"
},
"runtime": {
"lib/net8.0/Microsoft.IdentityModel.Logging.dll": {
"assemblyVersion": "7.1.2.0",
"fileVersion": "7.1.2.41121"
}
}
},
"Microsoft.IdentityModel.Protocols/7.1.2": {
"dependencies": {
"Microsoft.IdentityModel.Logging": "7.1.2",
"Microsoft.IdentityModel.Tokens": "7.1.2"
},
"runtime": {
"lib/net8.0/Microsoft.IdentityModel.Protocols.dll": {
"assemblyVersion": "7.1.2.0",
"fileVersion": "7.1.2.41121"
}
}
},
"Microsoft.IdentityModel.Protocols.OpenIdConnect/7.1.2": {
"dependencies": {
"Microsoft.IdentityModel.Protocols": "7.1.2",
"System.IdentityModel.Tokens.Jwt": "7.1.2"
},
"runtime": {
"lib/net8.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll": {
"assemblyVersion": "7.1.2.0",
"fileVersion": "7.1.2.41121"
}
}
},
"Microsoft.IdentityModel.Tokens/7.1.2": {
"dependencies": {
"Microsoft.IdentityModel.Logging": "7.1.2"
},
"runtime": {
"lib/net8.0/Microsoft.IdentityModel.Tokens.dll": {
"assemblyVersion": "7.1.2.0",
"fileVersion": "7.1.2.41121"
}
}
},
"Microsoft.Win32.SystemEvents/4.7.0": {
"runtime": {
"lib/netstandard2.0/Microsoft.Win32.SystemEvents.dll": {
"assemblyVersion": "4.0.2.0",
"fileVersion": "4.700.19.56404"
}
},
"runtimeTargets": {
"runtimes/win/lib/netcoreapp3.0/Microsoft.Win32.SystemEvents.dll": {
"rid": "win",
"assetType": "runtime",
"assemblyVersion": "4.0.2.0",
"fileVersion": "4.700.19.56404"
}
}
},
"MySql.Data/8.4.0": {
"dependencies": {
"BouncyCastle.Cryptography": "2.2.1",
"Google.Protobuf": "3.25.1",
"K4os.Compression.LZ4.Streams": "1.3.5",
"System.Configuration.ConfigurationManager": "4.4.1",
"System.Security.Permissions": "4.7.0",
"ZstdSharp.Port": "0.7.1"
},
"runtime": {
"lib/net8.0/MySql.Data.dll": {
"assemblyVersion": "8.4.0.0",
"fileVersion": "8.4.0.0"
}
},
"runtimeTargets": {
"runtimes/win-x64/native/comerr64.dll": {
"rid": "win-x64",
"assetType": "native",
"fileVersion": "4.1.0.0"
},
"runtimes/win-x64/native/gssapi64.dll": {
"rid": "win-x64",
"assetType": "native",
"fileVersion": "4.1.0.0"
},
"runtimes/win-x64/native/k5sprt64.dll": {
"rid": "win-x64",
"assetType": "native",
"fileVersion": "4.1.0.0"
},
"runtimes/win-x64/native/krb5_64.dll": {
"rid": "win-x64",
"assetType": "native",
"fileVersion": "4.1.0.0"
},
"runtimes/win-x64/native/krbcc64.dll": {
"rid": "win-x64",
"assetType": "native",
"fileVersion": "4.1.0.0"
}
}
},
"Newtonsoft.Json/13.0.1": {
"runtime": {
"lib/netstandard2.0/Newtonsoft.Json.dll": {
"assemblyVersion": "13.0.0.0",
"fileVersion": "13.0.1.25517"
}
}
},
"System.Configuration.ConfigurationManager/4.4.1": {
"dependencies": {
"System.Security.Cryptography.ProtectedData": "4.4.0"
},
"runtime": {
"lib/netstandard2.0/System.Configuration.ConfigurationManager.dll": {
"assemblyVersion": "4.0.0.0",
"fileVersion": "4.6.25921.2"
}
}
},
"System.Drawing.Common/4.7.0": {
"dependencies": {
"Microsoft.Win32.SystemEvents": "4.7.0"
},
"runtime": {
"lib/netstandard2.0/System.Drawing.Common.dll": {
"assemblyVersion": "4.0.0.1",
"fileVersion": "4.6.26919.2"
}
},
"runtimeTargets": {
"runtimes/unix/lib/netcoreapp3.0/System.Drawing.Common.dll": {
"rid": "unix",
"assetType": "runtime",
"assemblyVersion": "4.0.2.0",
"fileVersion": "4.700.19.56404"
},
"runtimes/win/lib/netcoreapp3.0/System.Drawing.Common.dll": {
"rid": "win",
"assetType": "runtime",
"assemblyVersion": "4.0.2.0",
"fileVersion": "4.700.19.56404"
}
}
},
"System.IdentityModel.Tokens.Jwt/7.1.2": {
"dependencies": {
"Microsoft.IdentityModel.JsonWebTokens": "7.1.2",
"Microsoft.IdentityModel.Tokens": "7.1.2"
},
"runtime": {
"lib/net8.0/System.IdentityModel.Tokens.Jwt.dll": {
"assemblyVersion": "7.1.2.0",
"fileVersion": "7.1.2.41121"
}
}
},
"System.Security.Cryptography.ProtectedData/4.4.0": {
"runtime": {
"lib/netstandard2.0/System.Security.Cryptography.ProtectedData.dll": {
"assemblyVersion": "4.0.2.0",
"fileVersion": "4.6.25519.3"
}
},
"runtimeTargets": {
"runtimes/win/lib/netstandard2.0/System.Security.Cryptography.ProtectedData.dll": {
"rid": "win",
"assetType": "runtime",
"assemblyVersion": "4.0.2.0",
"fileVersion": "4.6.25519.3"
}
}
},
"System.Security.Permissions/4.7.0": {
"dependencies": {
"System.Windows.Extensions": "4.7.0"
},
"runtime": {
"lib/netcoreapp3.0/System.Security.Permissions.dll": {
"assemblyVersion": "4.0.3.0",
"fileVersion": "4.700.19.56404"
}
}
},
"System.Windows.Extensions/4.7.0": {
"dependencies": {
"System.Drawing.Common": "4.7.0"
},
"runtime": {
"lib/netcoreapp3.0/System.Windows.Extensions.dll": {
"assemblyVersion": "4.0.1.0",
"fileVersion": "4.700.19.56404"
}
},
"runtimeTargets": {
"runtimes/win/lib/netcoreapp3.0/System.Windows.Extensions.dll": {
"rid": "win",
"assetType": "runtime",
"assemblyVersion": "4.0.1.0",
"fileVersion": "4.700.19.56404"
}
}
},
"ZstdSharp.Port/0.7.1": {
"runtime": {
"lib/net7.0/ZstdSharp.dll": {
"assemblyVersion": "0.7.1.0",
"fileVersion": "0.7.1.0"
}
}
}
}
},
"libraries": {
"WxCheckMvc/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"BouncyCastle.Cryptography/2.2.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-A6Zr52zVqJKt18ZBsTnX0qhG0kwIQftVAjLmszmkiR/trSp8H+xj1gUOzk7XHwaKgyREMSV1v9XaKrBUeIOdvQ==",
"path": "bouncycastle.cryptography/2.2.1",
"hashPath": "bouncycastle.cryptography.2.2.1.nupkg.sha512"
},
"CSRedisCore/3.8.807": {
"type": "package",
"serviceable": true,
"sha512": "sha512-fu0ZGIRdq1q0dZR+ecJxajfdLiRNWBR+UKx9Ob43rSwPQT/9duIJ8DLThkDjlx/0CHtX8oktv3rYAHS/mIy8bw==",
"path": "csrediscore/3.8.807",
"hashPath": "csrediscore.3.8.807.nupkg.sha512"
},
"Google.Protobuf/3.25.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Sw9bq4hOD+AaS3RrnmP5IT25cyZ/T1qpM0e8+G+23Nojhv7+ScJFPEAQo1m4EFQWhXoI4FRZDrK+wjHCPw9yxg==",
"path": "google.protobuf/3.25.1",
"hashPath": "google.protobuf.3.25.1.nupkg.sha512"
},
"K4os.Compression.LZ4/1.3.5": {
"type": "package",
"serviceable": true,
"sha512": "sha512-TS4mqlT0X1OlnvOGNfl02QdVUhuqgWuCnn7UxupIa7C9Pb6qlQ5yZA2sPhRh0OSmVULaQU64KV4wJuu//UyVQQ==",
"path": "k4os.compression.lz4/1.3.5",
"hashPath": "k4os.compression.lz4.1.3.5.nupkg.sha512"
},
"K4os.Compression.LZ4.Streams/1.3.5": {
"type": "package",
"serviceable": true,
"sha512": "sha512-M0NufZI8ym3mm6F6HMSPz1jw7TJGdY74fjAtbIXATmnAva/8xLz50eQZJI9tf9mMeHUaFDg76N1BmEh8GR5zeA==",
"path": "k4os.compression.lz4.streams/1.3.5",
"hashPath": "k4os.compression.lz4.streams.1.3.5.nupkg.sha512"
},
"K4os.Hash.xxHash/1.0.8": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Wp2F7BamQ2Q/7Hk834nV9vRQapgcr8kgv9Jvfm8J3D0IhDqZMMl+a2yxUq5ltJitvXvQfB8W6K4F4fCbw/P6YQ==",
"path": "k4os.hash.xxhash/1.0.8",
"hashPath": "k4os.hash.xxhash.1.0.8.nupkg.sha512"
},
"Microsoft.AspNetCore.Authentication.JwtBearer/8.0.22": {
"type": "package",
"serviceable": true,
"sha512": "sha512-3lqhBK+t4u8Ajl2je5UC9jCoDI+8zLz/YBVjwxQKfFF9NyzACf4QQmlmKnpH/LdkVSxCjLwvJ1ko4k0EAgy8cg==",
"path": "microsoft.aspnetcore.authentication.jwtbearer/8.0.22",
"hashPath": "microsoft.aspnetcore.authentication.jwtbearer.8.0.22.nupkg.sha512"
},
"Microsoft.IdentityModel.Abstractions/7.1.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-33eTIA2uO/L9utJjZWbKsMSVsQf7F8vtd6q5mQX7ZJzNvCpci5fleD6AeANGlbbb7WX7XKxq9+Dkb5e3GNDrmQ==",
"path": "microsoft.identitymodel.abstractions/7.1.2",
"hashPath": "microsoft.identitymodel.abstractions.7.1.2.nupkg.sha512"
},
"Microsoft.IdentityModel.JsonWebTokens/7.1.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-cloLGeZolXbCJhJBc5OC05uhrdhdPL6MWHuVUnkkUvPDeK7HkwThBaLZ1XjBQVk9YhxXE2OvHXnKi0PLleXxDg==",
"path": "microsoft.identitymodel.jsonwebtokens/7.1.2",
"hashPath": "microsoft.identitymodel.jsonwebtokens.7.1.2.nupkg.sha512"
},
"Microsoft.IdentityModel.Logging/7.1.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-YCxBt2EeJP8fcXk9desChkWI+0vFqFLvBwrz5hBMsoh0KJE6BC66DnzkdzkJNqMltLromc52dkdT206jJ38cTw==",
"path": "microsoft.identitymodel.logging/7.1.2",
"hashPath": "microsoft.identitymodel.logging.7.1.2.nupkg.sha512"
},
"Microsoft.IdentityModel.Protocols/7.1.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-SydLwMRFx6EHPWJ+N6+MVaoArN1Htt92b935O3RUWPY1yUF63zEjvd3lBu79eWdZUwedP8TN2I5V9T3nackvIQ==",
"path": "microsoft.identitymodel.protocols/7.1.2",
"hashPath": "microsoft.identitymodel.protocols.7.1.2.nupkg.sha512"
},
"Microsoft.IdentityModel.Protocols.OpenIdConnect/7.1.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-6lHQoLXhnMQ42mGrfDkzbIOR3rzKM1W1tgTeMPLgLCqwwGw0d96xFi/UiX/fYsu7d6cD5MJiL3+4HuI8VU+sVQ==",
"path": "microsoft.identitymodel.protocols.openidconnect/7.1.2",
"hashPath": "microsoft.identitymodel.protocols.openidconnect.7.1.2.nupkg.sha512"
},
"Microsoft.IdentityModel.Tokens/7.1.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-oICJMqr3aNEDZOwnH5SK49bR6Z4aX0zEAnOLuhloumOSuqnNq+GWBdQyrgILnlcT5xj09xKCP/7Y7gJYB+ls/g==",
"path": "microsoft.identitymodel.tokens/7.1.2",
"hashPath": "microsoft.identitymodel.tokens.7.1.2.nupkg.sha512"
},
"Microsoft.Win32.SystemEvents/4.7.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-mtVirZr++rq+XCDITMUdnETD59XoeMxSpLRIII7JRI6Yj0LEDiO1pPn0ktlnIj12Ix8bfvQqQDMMIF9wC98oCA==",
"path": "microsoft.win32.systemevents/4.7.0",
"hashPath": "microsoft.win32.systemevents.4.7.0.nupkg.sha512"
},
"MySql.Data/8.4.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-NA273x7ybutfGwGbF2cd8rLVM5t7AkZCzRHr/+tGms1FeMlfl+LgfjHXcb5qN1QxFpeNQQKZ+vqZw8v/S8gUiA==",
"path": "mysql.data/8.4.0",
"hashPath": "mysql.data.8.4.0.nupkg.sha512"
},
"Newtonsoft.Json/13.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==",
"path": "newtonsoft.json/13.0.1",
"hashPath": "newtonsoft.json.13.0.1.nupkg.sha512"
},
"System.Configuration.ConfigurationManager/4.4.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-jz3TWKMAeuDEyrPCK5Jyt4bzQcmzUIMcY9Ud6PkElFxTfnsihV+9N/UCqvxe1z5gc7jMYAnj7V1COMS9QKIuHQ==",
"path": "system.configuration.configurationmanager/4.4.1",
"hashPath": "system.configuration.configurationmanager.4.4.1.nupkg.sha512"
},
"System.Drawing.Common/4.7.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-v+XbyYHaZjDfn0ENmJEV1VYLgGgCTx1gnfOBcppowbpOAriglYgGCvFCPr2EEZyBvXlpxbEsTwkOlInl107ahA==",
"path": "system.drawing.common/4.7.0",
"hashPath": "system.drawing.common.4.7.0.nupkg.sha512"
},
"System.IdentityModel.Tokens.Jwt/7.1.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Thhbe1peAmtSBFaV/ohtykXiZSOkx59Da44hvtWfIMFofDA3M3LaVyjstACf2rKGn4dEDR2cUpRAZ0Xs/zB+7Q==",
"path": "system.identitymodel.tokens.jwt/7.1.2",
"hashPath": "system.identitymodel.tokens.jwt.7.1.2.nupkg.sha512"
},
"System.Security.Cryptography.ProtectedData/4.4.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-cJV7ScGW7EhatRsjehfvvYVBvtiSMKgN8bOVI0bQhnF5bU7vnHVIsH49Kva7i7GWaWYvmEzkYVk1TC+gZYBEog==",
"path": "system.security.cryptography.protecteddata/4.4.0",
"hashPath": "system.security.cryptography.protecteddata.4.4.0.nupkg.sha512"
},
"System.Security.Permissions/4.7.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-dkOV6YYVBnYRa15/yv004eCGRBVADXw8qRbbNiCn/XpdJSUXkkUeIvdvFHkvnko4CdKMqG8yRHC4ox83LSlMsQ==",
"path": "system.security.permissions/4.7.0",
"hashPath": "system.security.permissions.4.7.0.nupkg.sha512"
},
"System.Windows.Extensions/4.7.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-CeWTdRNfRaSh0pm2gDTJFwVaXfTq6Xwv/sA887iwPTneW7oMtMlpvDIO+U60+3GWTB7Aom6oQwv5VZVUhQRdPQ==",
"path": "system.windows.extensions/4.7.0",
"hashPath": "system.windows.extensions.4.7.0.nupkg.sha512"
},
"ZstdSharp.Port/0.7.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Idgg+mJEyAujqDPzA3APy9dNoyw0YQcNA65GgYjktDRtJ+nvx/hv+J+m6Eax3JJMGEYGy04oc5YNP6ZvQ3Y1vQ==",
"path": "zstdsharp.port/0.7.1",
"hashPath": "zstdsharp.port.0.7.1.nupkg.sha512"
}
}
}

Some files were not shown because too many files have changed in this diff Show More