大更新,太多了,具体进游戏查看详细更新内容
反正很多
This commit is contained in:
100
Server/JsonEdit/README.md
Normal file
100
Server/JsonEdit/README.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# JSON 批量编辑器
|
||||
|
||||
一个简洁强大的JSON编辑工具,专门用于批量添加键值对到JSON文件中的所有对象。
|
||||
|
||||
## 🚀 功能特点
|
||||
|
||||
- **批量添加属性**: 一键为JSON中所有对象添加新的键值对
|
||||
- **智能类型识别**: 自动识别并转换数据类型
|
||||
- **支持所有JSON数据类型**: 字符串、数字、布尔值、对象、数组、null
|
||||
- **文件上传**: 支持JSON文件拖拽上传
|
||||
- **即时下载**: 编辑完成后立即下载修改后的JSON文件
|
||||
- **无需服务器**: 纯前端实现,直接在浏览器中运行
|
||||
|
||||
## 📋 支持的数据类型
|
||||
|
||||
| 类型 | 输入示例 | 转换结果 |
|
||||
|------|----------|----------|
|
||||
| 字符串 | `hello world` | `"hello world"` |
|
||||
| 整数 | `123` | `123` |
|
||||
| 小数 | `3.14` | `3.14` |
|
||||
| 布尔值 | `true` 或 `false` | `true` 或 `false` |
|
||||
| 空值 | `null` | `null` |
|
||||
| 对象 | `{"key": "value"}` | `{"key": "value"}` |
|
||||
| 数组 | `[1, 2, 3]` | `[1, 2, 3]` |
|
||||
| 空字符串 | *(留空)* | `""` |
|
||||
|
||||
## 🎯 使用方法
|
||||
|
||||
### 1. 加载JSON数据
|
||||
- **方法一**: 点击"上传JSON文件"按钮选择文件
|
||||
- **方法二**: 直接在编辑器中粘贴JSON数据
|
||||
- **方法三**: 点击"加载示例数据"使用预设数据
|
||||
|
||||
### 2. 批量添加属性
|
||||
1. 在"键名"字段输入要添加的属性名,如: `能否购买`
|
||||
2. 在"键值"字段输入属性值,如: `true`
|
||||
3. 点击"批量添加到所有对象"按钮
|
||||
4. 系统会自动为JSON中的每个对象添加该属性
|
||||
|
||||
### 3. 下载结果
|
||||
点击"下载修改后的JSON"按钮保存编辑后的文件
|
||||
|
||||
## 💡 使用示例
|
||||
|
||||
### 原始JSON:
|
||||
```json
|
||||
{
|
||||
"小麦": {
|
||||
"花费": 120,
|
||||
"收益": 100,
|
||||
"品质": "普通"
|
||||
},
|
||||
"稻谷": {
|
||||
"花费": 100,
|
||||
"收益": 120,
|
||||
"品质": "普通"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 添加属性: 键名=`能否购买`, 键值=`true`
|
||||
|
||||
### 结果JSON:
|
||||
```json
|
||||
{
|
||||
"小麦": {
|
||||
"花费": 120,
|
||||
"收益": 100,
|
||||
"品质": "普通",
|
||||
"能否购买": true
|
||||
},
|
||||
"稻谷": {
|
||||
"花费": 100,
|
||||
"收益": 120,
|
||||
"品质": "普通",
|
||||
"能否购买": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 快速开始
|
||||
|
||||
1. 直接在浏览器中打开 `templates/json_editor.html` 文件
|
||||
2. 无需安装任何依赖或服务器
|
||||
3. 开始使用批量编辑功能
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
- 工具会递归处理嵌套对象,为所有找到的对象添加指定属性
|
||||
- 数组元素如果是对象,也会被添加属性
|
||||
- 确保JSON格式正确,否则无法处理
|
||||
- 修改前建议备份原始文件
|
||||
|
||||
## 🎨 界面说明
|
||||
|
||||
- **左侧边栏**: 文件操作、批量编辑功能、快速示例
|
||||
- **右侧编辑区**: JSON数据显示和编辑
|
||||
- **智能提示**: 实时显示操作结果和错误信息
|
||||
|
||||
这个工具特别适合游戏开发、配置文件管理等需要批量修改JSON数据的场景。
|
||||
65
Server/JsonEdit/example_formats.md
Normal file
65
Server/JsonEdit/example_formats.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# JSON格式化示例
|
||||
|
||||
以下展示三种不同的JSON格式化效果:
|
||||
|
||||
## 原始数据
|
||||
```json
|
||||
{"小麦":{"花费":120,"收益":100,"品质":"普通"},"稻谷":{"花费":100,"收益":120,"品质":"普通"}}
|
||||
```
|
||||
|
||||
## 1. 标准格式化(2空格缩进)
|
||||
```json
|
||||
{
|
||||
"小麦": {
|
||||
"花费": 120,
|
||||
"收益": 100,
|
||||
"品质": "普通"
|
||||
},
|
||||
"稻谷": {
|
||||
"花费": 100,
|
||||
"收益": 120,
|
||||
"品质": "普通"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 最小化(压缩)
|
||||
```json
|
||||
{"小麦":{"花费":120,"收益":100,"品质":"普通"},"稻谷":{"花费":100,"收益":120,"品质":"普通"}}
|
||||
```
|
||||
|
||||
## 3. 一行化(一个对象一行)
|
||||
```json
|
||||
{
|
||||
"小麦": {"花费":120,"收益":100,"品质":"普通"},
|
||||
"稻谷": {"花费":100,"收益":120,"品质":"普通"}
|
||||
}
|
||||
```
|
||||
|
||||
## 使用场景
|
||||
|
||||
- **标准格式化**: 适合阅读和编辑,开发时使用
|
||||
- **最小化**: 适合网络传输,节省带宽
|
||||
- **一行化**: 适合比较不同对象,每行一个对象便于查看差异
|
||||
|
||||
## 批量添加属性示例
|
||||
|
||||
添加键名: `能否购买`, 键值: `true`
|
||||
|
||||
### 结果:
|
||||
```json
|
||||
{
|
||||
"小麦": {
|
||||
"花费": 120,
|
||||
"收益": 100,
|
||||
"品质": "普通",
|
||||
"能否购买": true
|
||||
},
|
||||
"稻谷": {
|
||||
"花费": 100,
|
||||
"收益": 120,
|
||||
"品质": "普通",
|
||||
"能否购买": true
|
||||
}
|
||||
}
|
||||
```
|
||||
267
Server/JsonEdit/json_editor.py
Normal file
267
Server/JsonEdit/json_editor.py
Normal file
@@ -0,0 +1,267 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from flask import Flask, request, jsonify, render_template, send_file
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
class JSONFormatter:
|
||||
"""JSON格式化工具类"""
|
||||
|
||||
@staticmethod
|
||||
def format_standard(data, indent=2):
|
||||
"""标准格式化 - 带缩进的可读格式"""
|
||||
return json.dumps(data, ensure_ascii=False, indent=indent)
|
||||
|
||||
@staticmethod
|
||||
def minify(data):
|
||||
"""最小化 - 压缩去除空格"""
|
||||
return json.dumps(data, ensure_ascii=False, separators=(',', ':'))
|
||||
|
||||
@staticmethod
|
||||
def one_line_per_object(data):
|
||||
"""一行化 - 每个对象/元素占一行"""
|
||||
if isinstance(data, list):
|
||||
# 如果是数组,每个元素占一行
|
||||
lines = ['[']
|
||||
for i, item in enumerate(data):
|
||||
comma = ',' if i < len(data) - 1 else ''
|
||||
lines.append(f' {json.dumps(item, ensure_ascii=False)}{comma}')
|
||||
lines.append(']')
|
||||
return '\n'.join(lines)
|
||||
|
||||
elif isinstance(data, dict):
|
||||
# 如果是对象,每个键值对占一行
|
||||
lines = ['{']
|
||||
keys = list(data.keys())
|
||||
for i, key in enumerate(keys):
|
||||
comma = ',' if i < len(keys) - 1 else ''
|
||||
value_str = json.dumps(data[key], ensure_ascii=False)
|
||||
lines.append(f' {json.dumps(key, ensure_ascii=False)}: {value_str}{comma}')
|
||||
lines.append('}')
|
||||
return '\n'.join(lines)
|
||||
|
||||
else:
|
||||
# 基本类型直接返回
|
||||
return json.dumps(data, ensure_ascii=False)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""主页"""
|
||||
return render_template('json_editor.html')
|
||||
|
||||
@app.route('/api/format', methods=['POST'])
|
||||
def format_json():
|
||||
"""JSON格式化API"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
content = data.get('content', '')
|
||||
format_type = data.get('format_type', 'standard') # standard, minify, oneline
|
||||
|
||||
if not content.strip():
|
||||
return jsonify({'success': False, 'message': '请提供JSON内容'})
|
||||
|
||||
# 解析JSON
|
||||
try:
|
||||
json_data = json.loads(content)
|
||||
except json.JSONDecodeError as e:
|
||||
return jsonify({'success': False, 'message': f'JSON格式错误: {str(e)}'})
|
||||
|
||||
# 根据类型格式化
|
||||
formatter = JSONFormatter()
|
||||
|
||||
if format_type == 'standard':
|
||||
formatted = formatter.format_standard(json_data)
|
||||
message = 'JSON标准格式化完成'
|
||||
elif format_type == 'minify':
|
||||
formatted = formatter.minify(json_data)
|
||||
message = 'JSON最小化完成'
|
||||
elif format_type == 'oneline':
|
||||
formatted = formatter.one_line_per_object(json_data)
|
||||
message = 'JSON一行化格式完成'
|
||||
else:
|
||||
return jsonify({'success': False, 'message': '不支持的格式化类型'})
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': message,
|
||||
'formatted': formatted,
|
||||
'original_length': len(content),
|
||||
'formatted_length': len(formatted)
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'message': f'处理错误: {str(e)}'})
|
||||
|
||||
@app.route('/api/batch_add', methods=['POST'])
|
||||
def batch_add_property():
|
||||
"""批量添加属性API"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
content = data.get('content', '')
|
||||
key_name = data.get('key_name', '')
|
||||
key_value = data.get('key_value', '')
|
||||
|
||||
if not content.strip():
|
||||
return jsonify({'success': False, 'message': '请提供JSON内容'})
|
||||
|
||||
if not key_name.strip():
|
||||
return jsonify({'success': False, 'message': '请提供键名'})
|
||||
|
||||
# 解析JSON
|
||||
try:
|
||||
json_data = json.loads(content)
|
||||
except json.JSONDecodeError as e:
|
||||
return jsonify({'success': False, 'message': f'JSON格式错误: {str(e)}'})
|
||||
|
||||
# 智能解析键值
|
||||
parsed_value = parse_value(key_value)
|
||||
|
||||
# 批量添加属性
|
||||
count = add_property_to_all_objects(json_data, key_name, parsed_value)
|
||||
|
||||
# 格式化输出
|
||||
formatted = JSONFormatter.format_standard(json_data)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'成功为 {count} 个对象添加了属性 "{key_name}": {json.dumps(parsed_value, ensure_ascii=False)}',
|
||||
'formatted': formatted,
|
||||
'count': count
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'message': f'处理错误: {str(e)}'})
|
||||
|
||||
def parse_value(value_str):
|
||||
"""智能解析值的类型"""
|
||||
if value_str == '':
|
||||
return ''
|
||||
|
||||
# null
|
||||
if value_str.lower() == 'null':
|
||||
return None
|
||||
|
||||
# boolean
|
||||
if value_str.lower() == 'true':
|
||||
return True
|
||||
if value_str.lower() == 'false':
|
||||
return False
|
||||
|
||||
# number
|
||||
try:
|
||||
if '.' in value_str:
|
||||
return float(value_str)
|
||||
else:
|
||||
return int(value_str)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# JSON object or array
|
||||
if (value_str.startswith('{') and value_str.endswith('}')) or \
|
||||
(value_str.startswith('[') and value_str.endswith(']')):
|
||||
try:
|
||||
return json.loads(value_str)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
# string
|
||||
return value_str
|
||||
|
||||
def add_property_to_all_objects(obj, key, value):
|
||||
"""递归为所有对象添加属性"""
|
||||
count = 0
|
||||
|
||||
def traverse(current):
|
||||
nonlocal count
|
||||
if isinstance(current, dict):
|
||||
current[key] = value
|
||||
count += 1
|
||||
# 继续递归处理嵌套对象
|
||||
for val in current.values():
|
||||
if isinstance(val, (dict, list)) and val != current:
|
||||
traverse(val)
|
||||
elif isinstance(current, list):
|
||||
for item in current:
|
||||
traverse(item)
|
||||
|
||||
traverse(obj)
|
||||
return count
|
||||
|
||||
@app.route('/api/validate', methods=['POST'])
|
||||
def validate_json():
|
||||
"""JSON验证API"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
content = data.get('content', '')
|
||||
|
||||
if not content.strip():
|
||||
return jsonify({'success': False, 'message': '请提供JSON内容'})
|
||||
|
||||
try:
|
||||
json_data = json.loads(content)
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': 'JSON格式正确 ✓',
|
||||
'valid': True
|
||||
})
|
||||
except json.JSONDecodeError as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'JSON格式错误: {str(e)}',
|
||||
'valid': False,
|
||||
'error': str(e)
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'message': f'验证错误: {str(e)}'})
|
||||
|
||||
@app.route('/api/download', methods=['POST'])
|
||||
def download_json():
|
||||
"""下载JSON文件"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
content = data.get('content', '')
|
||||
format_type = data.get('format_type', 'standard')
|
||||
|
||||
if not content.strip():
|
||||
return jsonify({'success': False, 'message': '没有可下载的内容'})
|
||||
|
||||
# 验证JSON格式
|
||||
try:
|
||||
json_data = json.loads(content)
|
||||
except json.JSONDecodeError as e:
|
||||
return jsonify({'success': False, 'message': f'JSON格式错误: {str(e)}'})
|
||||
|
||||
# 格式化
|
||||
formatter = JSONFormatter()
|
||||
if format_type == 'minify':
|
||||
formatted_content = formatter.minify(json_data)
|
||||
elif format_type == 'oneline':
|
||||
formatted_content = formatter.one_line_per_object(json_data)
|
||||
else:
|
||||
formatted_content = formatter.format_standard(json_data)
|
||||
|
||||
# 生成文件名
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"edited_json_{format_type}_{timestamp}.json"
|
||||
|
||||
# 创建临时文件
|
||||
temp_file = f"temp_{filename}"
|
||||
with open(temp_file, 'w', encoding='utf-8') as f:
|
||||
f.write(formatted_content)
|
||||
|
||||
return send_file(temp_file, as_attachment=True, download_name=filename)
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'message': f'下载错误: {str(e)}'})
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 确保templates目录存在
|
||||
os.makedirs('templates', exist_ok=True)
|
||||
|
||||
# 运行应用
|
||||
app.run(debug=True, host='0.0.0.0', port=5000)
|
||||
2
Server/JsonEdit/requirements.txt
Normal file
2
Server/JsonEdit/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Flask==2.3.3
|
||||
Werkzeug==2.3.7
|
||||
627
Server/JsonEdit/templates/json_editor.html
Normal file
627
Server/JsonEdit/templates/json_editor.html
Normal file
@@ -0,0 +1,627 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>简易JSON编辑器 - 批量添加键值</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: #f5f5f5;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: #4a90e2;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
display: grid;
|
||||
grid-template-columns: 300px 1fr;
|
||||
gap: 0;
|
||||
min-height: 600px;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background: #f8f9fa;
|
||||
border-right: 1px solid #dee2e6;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.editor-area {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.section {
|
||||
background: white;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.section h3 {
|
||||
margin-bottom: 15px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: #4a90e2;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
margin: 5px 5px 5px 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: #357abd;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: #28a745;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background: #218838;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.input-group input {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.input-group input:focus {
|
||||
outline: none;
|
||||
border-color: #4a90e2;
|
||||
}
|
||||
|
||||
#jsonEditor {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
#jsonEditor:focus {
|
||||
outline: none;
|
||||
border-color: #4a90e2;
|
||||
}
|
||||
|
||||
.file-upload {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.file-upload input[type=file] {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
}
|
||||
|
||||
.alert {
|
||||
padding: 10px 15px;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 5px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background: #d1ecf1;
|
||||
color: #0c5460;
|
||||
border: 1px solid #bee5eb;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.main-content {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🔧 JSON批量编辑器</h1>
|
||||
<p>批量添加键值对到JSON文件</p>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<!-- 侧边栏 -->
|
||||
<div class="sidebar">
|
||||
<!-- 文件操作 -->
|
||||
<div class="section">
|
||||
<h3>📁 文件操作</h3>
|
||||
<div class="file-upload btn">
|
||||
<input type="file" id="fileInput" accept=".json" />
|
||||
上传JSON文件
|
||||
</div>
|
||||
<button class="btn btn-success" onclick="downloadJSON()">
|
||||
下载修改后的JSON
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 批量添加键值 -->
|
||||
<div class="section">
|
||||
<h3>⚡ 批量添加键值</h3>
|
||||
<div class="input-group">
|
||||
<label for="keyName">键名:</label>
|
||||
<input type="text" id="keyName" placeholder="例: 能否购买" />
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="keyValue">键值:</label>
|
||||
<input type="text" id="keyValue" placeholder="支持多种类型,见下方说明" />
|
||||
</div>
|
||||
<button class="btn btn-success" onclick="batchAddProperty()">
|
||||
批量添加到所有对象
|
||||
</button>
|
||||
|
||||
<div style="margin-top: 15px; padding: 10px; background: #f8f9fa; border-radius: 5px; font-size: 12px;">
|
||||
<strong>支持的数据类型:</strong><br>
|
||||
• 字符串: hello world<br>
|
||||
• 数字: 123 或 3.14<br>
|
||||
• 布尔值: true 或 false<br>
|
||||
• 空值: null<br>
|
||||
• 对象: {"key": "value"}<br>
|
||||
• 数组: [1, 2, 3]<br>
|
||||
<small style="color: #666;">系统会自动识别并转换数据类型</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 快速示例 -->
|
||||
<div class="section">
|
||||
<h3>📝 快速示例</h3>
|
||||
<button class="btn" onclick="loadSampleJSON()">
|
||||
加载示例数据
|
||||
</button>
|
||||
|
||||
<div style="margin-top: 15px;">
|
||||
<strong style="font-size: 12px;">快速填入示例键值:</strong><br>
|
||||
<button class="btn" style="font-size: 11px; padding: 5px 10px; margin: 2px;" onclick="fillExample('能否购买', 'true')">
|
||||
布尔值示例
|
||||
</button>
|
||||
<button class="btn" style="font-size: 11px; padding: 5px 10px; margin: 2px;" onclick="fillExample('价格', '150')">
|
||||
数字示例
|
||||
</button>
|
||||
<button class="btn" style="font-size: 11px; padding: 5px 10px; margin: 2px;" onclick="fillExample('备注', '新增属性')">
|
||||
字符串示例
|
||||
</button>
|
||||
<button class="btn" style="font-size: 11px; padding: 5px 10px; margin: 2px;" onclick="fillExample('tags', '["新", "热门"]')">
|
||||
数组示例
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- JSON格式化操作 -->
|
||||
<div class="section">
|
||||
<h3>🔧 格式化操作</h3>
|
||||
<button class="btn" onclick="formatJSONStandard()">
|
||||
标准格式化
|
||||
</button>
|
||||
<button class="btn btn-success" onclick="minifyJSON()">
|
||||
最小化(压缩)
|
||||
</button>
|
||||
<button class="btn" style="background: #17a2b8;" onclick="oneLinePerObject()">
|
||||
一行化(一个对象一行)
|
||||
</button>
|
||||
<button class="btn" style="background: #6f42c1; color: white;" onclick="validateJSON()">
|
||||
验证JSON格式
|
||||
</button>
|
||||
|
||||
<div style="margin-top: 15px; padding: 10px; background: #f8f9fa; border-radius: 5px; font-size: 12px;">
|
||||
<strong>格式化说明:</strong><br>
|
||||
• <strong>标准格式化</strong>: 2空格缩进,易于阅读<br>
|
||||
• <strong>最小化</strong>: 去除空格,节省空间<br>
|
||||
• <strong>一行化</strong>: 每个对象占一行,便于比较<br>
|
||||
• <strong>验证格式</strong>: 检查JSON语法是否正确<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 编辑区域 -->
|
||||
<div class="editor-area">
|
||||
<!-- 消息区域 -->
|
||||
<div id="messageArea"></div>
|
||||
|
||||
<!-- JSON编辑器 -->
|
||||
<textarea id="jsonEditor" placeholder="在此输入或上传JSON数据..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.getElementById('fileInput').addEventListener('change', handleFileUpload);
|
||||
});
|
||||
|
||||
// 显示消息
|
||||
function showMessage(message, type = 'success') {
|
||||
const messageArea = document.getElementById('messageArea');
|
||||
let alertClass = 'alert-success';
|
||||
|
||||
if (type === 'error') {
|
||||
alertClass = 'alert-error';
|
||||
} else if (type === 'info') {
|
||||
alertClass = 'alert-info';
|
||||
}
|
||||
|
||||
messageArea.innerHTML = `
|
||||
<div class="alert ${alertClass}">
|
||||
${message}
|
||||
</div>
|
||||
`;
|
||||
|
||||
setTimeout(() => {
|
||||
messageArea.innerHTML = '';
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 加载示例JSON
|
||||
function loadSampleJSON() {
|
||||
const sampleJSON = {
|
||||
"测试作物": {
|
||||
"花费": 1,
|
||||
"生长时间": 3,
|
||||
"收益": 9999,
|
||||
"品质": "普通"
|
||||
},
|
||||
"小麦": {
|
||||
"花费": 120,
|
||||
"生长时间": 120,
|
||||
"收益": 100,
|
||||
"品质": "普通"
|
||||
},
|
||||
"稻谷": {
|
||||
"花费": 100,
|
||||
"生长时间": 240,
|
||||
"收益": 120,
|
||||
"品质": "普通"
|
||||
}
|
||||
};
|
||||
|
||||
document.getElementById('jsonEditor').value = JSON.stringify(sampleJSON, null, 2);
|
||||
showMessage('示例数据已加载');
|
||||
}
|
||||
|
||||
// 快速填入示例键值对
|
||||
function fillExample(keyName, keyValue) {
|
||||
document.getElementById('keyName').value = keyName;
|
||||
document.getElementById('keyValue').value = keyValue;
|
||||
showMessage(`已填入示例: ${keyName} = ${keyValue}`, 'info');
|
||||
}
|
||||
|
||||
// 文件上传处理
|
||||
function handleFileUpload(event) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
if (!file.name.toLowerCase().endsWith('.json')) {
|
||||
showMessage('请选择JSON文件', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
try {
|
||||
const content = e.target.result;
|
||||
JSON.parse(content); // 验证JSON格式
|
||||
document.getElementById('jsonEditor').value = content;
|
||||
showMessage('文件上传成功');
|
||||
} catch (error) {
|
||||
showMessage('JSON格式错误: ' + error.message, 'error');
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
|
||||
// 清空文件输入
|
||||
event.target.value = '';
|
||||
}
|
||||
|
||||
// 批量添加属性
|
||||
function batchAddProperty() {
|
||||
const keyName = document.getElementById('keyName').value.trim();
|
||||
const keyValue = document.getElementById('keyValue').value.trim();
|
||||
const jsonContent = document.getElementById('jsonEditor').value.trim();
|
||||
|
||||
if (!keyName) {
|
||||
showMessage('请输入键名', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!jsonContent) {
|
||||
showMessage('请输入或上传JSON数据', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let jsonData = JSON.parse(jsonContent);
|
||||
|
||||
// 智能类型转换
|
||||
let processedValue = parseValue(keyValue);
|
||||
|
||||
// 批量添加属性
|
||||
const result = addPropertyToAllObjects(jsonData, keyName, processedValue);
|
||||
|
||||
if (result.count > 0) {
|
||||
document.getElementById('jsonEditor').value = JSON.stringify(jsonData, null, 2);
|
||||
showMessage(`成功为 ${result.count} 个对象添加了属性 "${keyName}": ${JSON.stringify(processedValue)}`);
|
||||
} else {
|
||||
showMessage('未找到可添加属性的对象', 'info');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
showMessage('JSON格式错误: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 智能解析值的类型
|
||||
function parseValue(value) {
|
||||
// 如果输入为空,返回空字符串
|
||||
if (value === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
// 尝试解析为null
|
||||
if (value.toLowerCase() === 'null') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 尝试解析为undefined(虽然JSON不支持,但转为null)
|
||||
if (value.toLowerCase() === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 尝试解析为布尔值
|
||||
if (value.toLowerCase() === 'true') {
|
||||
return true;
|
||||
}
|
||||
if (value.toLowerCase() === 'false') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 尝试解析为数字
|
||||
if (!isNaN(value) && !isNaN(parseFloat(value))) {
|
||||
// 检查是否为整数
|
||||
if (Number.isInteger(parseFloat(value))) {
|
||||
return parseInt(value, 10);
|
||||
} else {
|
||||
return parseFloat(value);
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试解析为JSON对象或数组
|
||||
if ((value.startsWith('{') && value.endsWith('}')) ||
|
||||
(value.startsWith('[') && value.endsWith(']'))) {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (e) {
|
||||
// 如果解析失败,当作字符串处理
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
// 默认当作字符串处理
|
||||
return value;
|
||||
}
|
||||
|
||||
// 递归为所有对象添加属性
|
||||
function addPropertyToAllObjects(obj, key, value) {
|
||||
let count = 0;
|
||||
|
||||
function traverse(current) {
|
||||
if (typeof current === 'object' && current !== null) {
|
||||
if (Array.isArray(current)) {
|
||||
// 处理数组
|
||||
current.forEach(item => traverse(item));
|
||||
} else {
|
||||
// 处理对象
|
||||
current[key] = value;
|
||||
count++;
|
||||
|
||||
// 继续递归处理嵌套对象
|
||||
Object.values(current).forEach(val => {
|
||||
if (typeof val === 'object' && val !== null && val !== current) {
|
||||
traverse(val);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
traverse(obj);
|
||||
return { count };
|
||||
}
|
||||
|
||||
// 下载JSON
|
||||
function downloadJSON() {
|
||||
const content = document.getElementById('jsonEditor').value.trim();
|
||||
|
||||
if (!content) {
|
||||
showMessage('没有可下载的内容', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 验证JSON格式
|
||||
JSON.parse(content);
|
||||
|
||||
const blob = new Blob([content], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `edited_json_${new Date().getTime()}.json`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
showMessage('JSON文件下载成功');
|
||||
} catch (error) {
|
||||
showMessage('JSON格式错误,无法下载: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 标准格式化JSON
|
||||
function formatJSONStandard() {
|
||||
const content = document.getElementById('jsonEditor').value.trim();
|
||||
|
||||
if (!content) {
|
||||
showMessage('请输入JSON数据', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const jsonData = JSON.parse(content);
|
||||
const formatted = JSON.stringify(jsonData, null, 2);
|
||||
document.getElementById('jsonEditor').value = formatted;
|
||||
showMessage('JSON标准格式化完成');
|
||||
} catch (error) {
|
||||
showMessage('JSON格式错误: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 最小化JSON(压缩)
|
||||
function minifyJSON() {
|
||||
const content = document.getElementById('jsonEditor').value.trim();
|
||||
|
||||
if (!content) {
|
||||
showMessage('请输入JSON数据', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const jsonData = JSON.parse(content);
|
||||
const minified = JSON.stringify(jsonData);
|
||||
document.getElementById('jsonEditor').value = minified;
|
||||
showMessage('JSON最小化完成');
|
||||
} catch (error) {
|
||||
showMessage('JSON格式错误: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 一行化格式(一个对象一行)
|
||||
function oneLinePerObject() {
|
||||
const content = document.getElementById('jsonEditor').value.trim();
|
||||
|
||||
if (!content) {
|
||||
showMessage('请输入JSON数据', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const jsonData = JSON.parse(content);
|
||||
let formatted = '';
|
||||
|
||||
if (Array.isArray(jsonData)) {
|
||||
// 如果是数组,每个元素占一行
|
||||
formatted = '[\n';
|
||||
jsonData.forEach((item, index) => {
|
||||
formatted += ' ' + JSON.stringify(item);
|
||||
if (index < jsonData.length - 1) {
|
||||
formatted += ',';
|
||||
}
|
||||
formatted += '\n';
|
||||
});
|
||||
formatted += ']';
|
||||
} else if (typeof jsonData === 'object' && jsonData !== null) {
|
||||
// 如果是对象,每个键值对占一行
|
||||
formatted = '{\n';
|
||||
const keys = Object.keys(jsonData);
|
||||
keys.forEach((key, index) => {
|
||||
formatted += ' ' + JSON.stringify(key) + ': ' + JSON.stringify(jsonData[key]);
|
||||
if (index < keys.length - 1) {
|
||||
formatted += ',';
|
||||
}
|
||||
formatted += '\n';
|
||||
});
|
||||
formatted += '}';
|
||||
} else {
|
||||
// 基本类型直接输出
|
||||
formatted = JSON.stringify(jsonData);
|
||||
}
|
||||
|
||||
document.getElementById('jsonEditor').value = formatted;
|
||||
showMessage('JSON一行化格式完成');
|
||||
} catch (error) {
|
||||
showMessage('JSON格式错误: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 验证JSON格式
|
||||
function validateJSON() {
|
||||
const content = document.getElementById('jsonEditor').value.trim();
|
||||
|
||||
if (!content) {
|
||||
showMessage('请输入JSON数据', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
JSON.parse(content);
|
||||
showMessage('JSON格式验证通过');
|
||||
} catch (error) {
|
||||
showMessage('JSON格式错误: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
337
Server/QQEmailSend.py
Normal file
337
Server/QQEmailSend.py
Normal file
@@ -0,0 +1,337 @@
|
||||
import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.application import MIMEApplication
|
||||
from email.header import Header
|
||||
import random
|
||||
import string
|
||||
import json
|
||||
import os
|
||||
|
||||
# 邮件发送配置
|
||||
SENDER_EMAIL = '3205788256@qq.com' # 发件人邮箱
|
||||
SENDER_AUTH_CODE = 'szcaxvbftusqddhi' # 授权码
|
||||
SMTP_SERVER = 'smtp.qq.com' # QQ邮箱SMTP服务器
|
||||
SMTP_PORT = 465 # QQ邮箱SSL端口
|
||||
|
||||
# 验证码缓存文件
|
||||
VERIFICATION_CACHE_FILE = os.path.join("config", "verification_codes.json")
|
||||
|
||||
class QQMailAPI:
|
||||
"""QQ邮箱发送邮件API类"""
|
||||
|
||||
def __init__(self, sender_email, authorization_code):
|
||||
"""
|
||||
初始化邮箱配置
|
||||
:param sender_email: 发送方QQ邮箱地址
|
||||
:param authorization_code: QQ邮箱授权码
|
||||
"""
|
||||
self.sender_email = sender_email
|
||||
self.authorization_code = authorization_code
|
||||
self.smtp_server = 'smtp.qq.com'
|
||||
self.smtp_port = 465 # SSL端口
|
||||
|
||||
def send_text_email(self, receiver_email, subject, content, cc_emails=None):
|
||||
"""
|
||||
发送纯文本邮件
|
||||
:param receiver_email: 接收方邮箱地址(单个)
|
||||
:param subject: 邮件主题
|
||||
:param content: 邮件正文内容
|
||||
:param cc_emails: 抄送邮箱列表
|
||||
:return: 发送成功返回True,失败返回False
|
||||
"""
|
||||
try:
|
||||
# 创建邮件对象
|
||||
message = MIMEText(content, 'plain', 'utf-8')
|
||||
message['From'] = Header(self.sender_email, 'utf-8')
|
||||
message['To'] = Header(receiver_email, 'utf-8')
|
||||
message['Subject'] = Header(subject, 'utf-8')
|
||||
|
||||
# 添加抄送
|
||||
if cc_emails:
|
||||
message['Cc'] = Header(",".join(cc_emails), 'utf-8')
|
||||
all_receivers = [receiver_email] + cc_emails
|
||||
else:
|
||||
all_receivers = [receiver_email]
|
||||
|
||||
# 连接SMTP服务器并发送邮件
|
||||
with smtplib.SMTP_SSL(self.smtp_server, self.smtp_port) as server:
|
||||
server.login(self.sender_email, self.authorization_code)
|
||||
server.sendmail(self.sender_email, all_receivers, message.as_string())
|
||||
|
||||
print(f"邮件发送成功:主题='{subject}', 收件人='{receiver_email}'")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"邮件发送失败:{str(e)}")
|
||||
return False
|
||||
|
||||
def send_html_email(self, receiver_email, subject, html_content, cc_emails=None, attachments=None):
|
||||
"""
|
||||
发送HTML格式邮件,可带附件
|
||||
:param receiver_email: 接收方邮箱地址(单个)
|
||||
:param subject: 邮件主题
|
||||
:param html_content: HTML格式的邮件正文
|
||||
:param cc_emails: 抄送邮箱列表
|
||||
:param attachments: 附件文件路径列表
|
||||
:return: 发送成功返回True,失败返回False
|
||||
"""
|
||||
try:
|
||||
# 创建带附件的邮件对象
|
||||
message = MIMEMultipart()
|
||||
message['From'] = Header(self.sender_email, 'utf-8')
|
||||
message['To'] = Header(receiver_email, 'utf-8')
|
||||
message['Subject'] = Header(subject, 'utf-8')
|
||||
|
||||
# 添加抄送
|
||||
if cc_emails:
|
||||
message['Cc'] = Header(",".join(cc_emails), 'utf-8')
|
||||
all_receivers = [receiver_email] + cc_emails
|
||||
else:
|
||||
all_receivers = [receiver_email]
|
||||
|
||||
# 添加HTML正文
|
||||
message.attach(MIMEText(html_content, 'html', 'utf-8'))
|
||||
|
||||
# 添加附件
|
||||
if attachments:
|
||||
for file_path in attachments:
|
||||
try:
|
||||
with open(file_path, 'rb') as file:
|
||||
attachment = MIMEApplication(file.read(), _subtype="octet-stream")
|
||||
attachment.add_header('Content-Disposition', 'attachment', filename=file_path.split("/")[-1])
|
||||
message.attach(attachment)
|
||||
except Exception as e:
|
||||
print(f"添加附件失败 {file_path}: {str(e)}")
|
||||
|
||||
# 连接SMTP服务器并发送邮件
|
||||
with smtplib.SMTP_SSL(self.smtp_server, self.smtp_port) as server:
|
||||
server.login(self.sender_email, self.authorization_code)
|
||||
server.sendmail(self.sender_email, all_receivers, message.as_string())
|
||||
|
||||
print(f"HTML邮件发送成功:主题='{subject}', 收件人='{receiver_email}'")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"HTML邮件发送失败:{str(e)}")
|
||||
return False
|
||||
|
||||
class EmailVerification:
|
||||
@staticmethod
|
||||
def generate_verification_code(length=6):
|
||||
"""
|
||||
生成指定长度的随机验证码
|
||||
|
||||
参数:
|
||||
length (int): 验证码长度,默认6位
|
||||
|
||||
返回:
|
||||
str: 生成的验证码
|
||||
"""
|
||||
# 生成包含大写字母和数字的验证码
|
||||
chars = string.ascii_uppercase + string.digits
|
||||
return ''.join(random.choice(chars) for _ in range(length))
|
||||
|
||||
@staticmethod
|
||||
def send_verification_email(qq_number, verification_code):
|
||||
"""
|
||||
发送验证码邮件到QQ邮箱
|
||||
|
||||
参数:
|
||||
qq_number (str): 接收者QQ号
|
||||
verification_code (str): 验证码
|
||||
|
||||
返回:
|
||||
bool: 发送成功返回True,否则返回False
|
||||
str: 成功或错误信息
|
||||
"""
|
||||
receiver_email = f"{qq_number}@qq.com"
|
||||
|
||||
# 创建邮件内容
|
||||
message = MIMEText(f'''
|
||||
<html>
|
||||
<body>
|
||||
<div style="font-family: Arial, sans-serif; color: #333;">
|
||||
<h2 style="color: #4CAF50;">萌芽农场 - 邮箱验证码</h2>
|
||||
<p>亲爱的玩家,您好!</p>
|
||||
<p>您正在注册萌芽农场游戏账号,您的验证码是:</p>
|
||||
<div style="background-color: #f2f2f2; padding: 10px; font-size: 24px; font-weight: bold; color: #4CAF50; text-align: center; margin: 20px 0;">
|
||||
{verification_code}
|
||||
</div>
|
||||
<p>该验证码有效期为5分钟,请勿泄露给他人。</p>
|
||||
<p>如果这不是您本人的操作,请忽略此邮件。</p>
|
||||
<p style="margin-top: 30px; font-size: 12px; color: #999;">
|
||||
本邮件由系统自动发送,请勿直接回复。
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
''', 'html', 'utf-8')
|
||||
|
||||
# 修正From头格式,符合QQ邮箱的要求
|
||||
message['From'] = SENDER_EMAIL
|
||||
message['To'] = receiver_email
|
||||
message['Subject'] = Header('【萌芽农场】注册验证码', 'utf-8')
|
||||
|
||||
try:
|
||||
# 使用SSL/TLS连接而不是STARTTLS
|
||||
smtp_obj = smtplib.SMTP_SSL(SMTP_SERVER, 465)
|
||||
smtp_obj.login(SENDER_EMAIL, SENDER_AUTH_CODE)
|
||||
smtp_obj.sendmail(SENDER_EMAIL, [receiver_email], message.as_string())
|
||||
smtp_obj.quit()
|
||||
return True, "验证码发送成功"
|
||||
except Exception as e:
|
||||
return False, f"发送验证码失败: {str(e)}"
|
||||
|
||||
@staticmethod
|
||||
def save_verification_code(qq_number, verification_code, expiry_time=300):
|
||||
"""
|
||||
保存验证码到缓存文件
|
||||
|
||||
参数:
|
||||
qq_number (str): QQ号
|
||||
verification_code (str): 验证码
|
||||
expiry_time (int): 过期时间(秒),默认5分钟
|
||||
|
||||
返回:
|
||||
bool: 保存成功返回True,否则返回False
|
||||
"""
|
||||
import time
|
||||
|
||||
# 创建目录(如果不存在)
|
||||
os.makedirs(os.path.dirname(VERIFICATION_CACHE_FILE), exist_ok=True)
|
||||
|
||||
# 读取现有的验证码数据
|
||||
verification_data = {}
|
||||
if os.path.exists(VERIFICATION_CACHE_FILE):
|
||||
try:
|
||||
with open(VERIFICATION_CACHE_FILE, 'r', encoding='utf-8') as file:
|
||||
verification_data = json.load(file)
|
||||
except:
|
||||
verification_data = {}
|
||||
|
||||
# 添加新的验证码
|
||||
expire_at = time.time() + expiry_time
|
||||
verification_data[qq_number] = {
|
||||
"code": verification_code,
|
||||
"expire_at": expire_at
|
||||
}
|
||||
|
||||
# 保存到文件
|
||||
try:
|
||||
with open(VERIFICATION_CACHE_FILE, 'w', encoding='utf-8') as file:
|
||||
json.dump(verification_data, file, indent=2, ensure_ascii=False)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"保存验证码失败: {str(e)}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def verify_code(qq_number, input_code):
|
||||
"""
|
||||
验证用户输入的验证码
|
||||
|
||||
参数:
|
||||
qq_number (str): QQ号
|
||||
input_code (str): 用户输入的验证码
|
||||
|
||||
返回:
|
||||
bool: 验证成功返回True,否则返回False
|
||||
str: 成功或错误信息
|
||||
"""
|
||||
import time
|
||||
|
||||
# 检查缓存文件是否存在
|
||||
if not os.path.exists(VERIFICATION_CACHE_FILE):
|
||||
return False, "验证码不存在或已过期"
|
||||
|
||||
# 读取验证码数据
|
||||
try:
|
||||
with open(VERIFICATION_CACHE_FILE, 'r', encoding='utf-8') as file:
|
||||
verification_data = json.load(file)
|
||||
except:
|
||||
return False, "验证码数据损坏"
|
||||
|
||||
# 检查该QQ号是否有验证码
|
||||
if qq_number not in verification_data:
|
||||
return False, "验证码不存在,请重新获取"
|
||||
|
||||
# 获取存储的验证码信息
|
||||
code_info = verification_data[qq_number]
|
||||
stored_code = code_info.get("code", "")
|
||||
expire_at = code_info.get("expire_at", 0)
|
||||
|
||||
# 检查验证码是否过期
|
||||
current_time = time.time()
|
||||
if current_time > expire_at:
|
||||
# 移除过期的验证码
|
||||
del verification_data[qq_number]
|
||||
with open(VERIFICATION_CACHE_FILE, 'w', encoding='utf-8') as file:
|
||||
json.dump(verification_data, file, indent=2, ensure_ascii=False)
|
||||
return False, "验证码已过期,请重新获取"
|
||||
|
||||
# 验证码比较(不区分大小写)
|
||||
if input_code.upper() == stored_code.upper():
|
||||
# 验证成功后移除该验证码
|
||||
del verification_data[qq_number]
|
||||
with open(VERIFICATION_CACHE_FILE, 'w', encoding='utf-8') as file:
|
||||
json.dump(verification_data, file, indent=2, ensure_ascii=False)
|
||||
return True, "验证码正确"
|
||||
else:
|
||||
return False, "验证码错误"
|
||||
|
||||
@staticmethod
|
||||
def clean_expired_codes():
|
||||
"""
|
||||
清理过期的验证码
|
||||
"""
|
||||
import time
|
||||
|
||||
if not os.path.exists(VERIFICATION_CACHE_FILE):
|
||||
return
|
||||
|
||||
try:
|
||||
with open(VERIFICATION_CACHE_FILE, 'r', encoding='utf-8') as file:
|
||||
verification_data = json.load(file)
|
||||
|
||||
current_time = time.time()
|
||||
removed_keys = []
|
||||
|
||||
# 找出过期的验证码
|
||||
for qq_number, code_info in verification_data.items():
|
||||
expire_at = code_info.get("expire_at", 0)
|
||||
if current_time > expire_at:
|
||||
removed_keys.append(qq_number)
|
||||
|
||||
# 移除过期的验证码
|
||||
for key in removed_keys:
|
||||
del verification_data[key]
|
||||
|
||||
# 保存更新后的数据
|
||||
with open(VERIFICATION_CACHE_FILE, 'w', encoding='utf-8') as file:
|
||||
json.dump(verification_data, file, indent=2, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
print(f"清理过期验证码失败: {str(e)}")
|
||||
|
||||
|
||||
# 测试邮件发送
|
||||
if __name__ == "__main__":
|
||||
# 清理过期验证码
|
||||
EmailVerification.clean_expired_codes()
|
||||
|
||||
# 生成验证码
|
||||
test_qq = input("请输入测试QQ号: ")
|
||||
verification_code = EmailVerification.generate_verification_code()
|
||||
print(f"生成的验证码: {verification_code}")
|
||||
|
||||
# 发送测试邮件
|
||||
success, message = EmailVerification.send_verification_email(test_qq, verification_code)
|
||||
print(f"发送结果: {success}, 消息: {message}")
|
||||
|
||||
if success:
|
||||
# 保存验证码
|
||||
EmailVerification.save_verification_code(test_qq, verification_code)
|
||||
|
||||
# 测试验证
|
||||
test_input = input("请输入收到的验证码: ")
|
||||
verify_success, verify_message = EmailVerification.verify_code(test_qq, test_input)
|
||||
print(f"验证结果: {verify_success}, 消息: {verify_message}")
|
||||
BIN
Server/QQEmailSend.zip
Normal file
BIN
Server/QQEmailSend.zip
Normal file
Binary file not shown.
1519
Server/TCPGameServer.py
Normal file
1519
Server/TCPGameServer.py
Normal file
File diff suppressed because it is too large
Load Diff
355
Server/TCPServer.py
Normal file
355
Server/TCPServer.py
Normal file
@@ -0,0 +1,355 @@
|
||||
import socket
|
||||
import threading
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
import logging
|
||||
import colorama
|
||||
from datetime import datetime
|
||||
|
||||
# 初始化colorama以支持跨平台彩色终端输出
|
||||
colorama.init()
|
||||
|
||||
# 自定义日志格式化器,带有颜色和分类
|
||||
class MinecraftStyleFormatter(logging.Formatter):
|
||||
"""Minecraft风格的日志格式化器,带有颜色和分类"""
|
||||
|
||||
# ANSI颜色代码
|
||||
COLORS = {
|
||||
'RESET': colorama.Fore.RESET,
|
||||
'BLACK': colorama.Fore.BLACK,
|
||||
'RED': colorama.Fore.RED,
|
||||
'GREEN': colorama.Fore.GREEN,
|
||||
'YELLOW': colorama.Fore.YELLOW,
|
||||
'BLUE': colorama.Fore.BLUE,
|
||||
'MAGENTA': colorama.Fore.MAGENTA,
|
||||
'CYAN': colorama.Fore.CYAN,
|
||||
'WHITE': colorama.Fore.WHITE,
|
||||
'BRIGHT_BLACK': colorama.Fore.LIGHTBLACK_EX,
|
||||
'BRIGHT_RED': colorama.Fore.LIGHTRED_EX,
|
||||
'BRIGHT_GREEN': colorama.Fore.LIGHTGREEN_EX,
|
||||
'BRIGHT_YELLOW': colorama.Fore.LIGHTYELLOW_EX,
|
||||
'BRIGHT_BLUE': colorama.Fore.LIGHTBLUE_EX,
|
||||
'BRIGHT_MAGENTA': colorama.Fore.LIGHTMAGENTA_EX,
|
||||
'BRIGHT_CYAN': colorama.Fore.LIGHTCYAN_EX,
|
||||
'BRIGHT_WHITE': colorama.Fore.LIGHTWHITE_EX,
|
||||
}
|
||||
|
||||
# 日志级别颜色(类似于Minecraft)
|
||||
LEVEL_COLORS = {
|
||||
'DEBUG': COLORS['BRIGHT_BLACK'],
|
||||
'INFO': COLORS['WHITE'],
|
||||
'WARNING': COLORS['YELLOW'],
|
||||
'ERROR': COLORS['RED'],
|
||||
'CRITICAL': COLORS['BRIGHT_RED'],
|
||||
}
|
||||
|
||||
# 类别及其颜色
|
||||
CATEGORIES = {
|
||||
'SERVER': COLORS['BRIGHT_CYAN'],
|
||||
'NETWORK': COLORS['BRIGHT_GREEN'],
|
||||
'CLIENT': COLORS['BRIGHT_YELLOW'],
|
||||
'SYSTEM': COLORS['BRIGHT_MAGENTA'],
|
||||
}
|
||||
|
||||
def format(self, record):
|
||||
# 获取日志级别颜色
|
||||
level_color = self.LEVEL_COLORS.get(record.levelname, self.COLORS['WHITE'])
|
||||
|
||||
# 从记录名称中确定类别,默认为SERVER
|
||||
category_name = getattr(record, 'category', 'SERVER')
|
||||
category_color = self.CATEGORIES.get(category_name, self.COLORS['WHITE'])
|
||||
|
||||
# 格式化时间戳,类似于Minecraft:[HH:MM:SS]
|
||||
timestamp = datetime.now().strftime('%H:%M:%S')
|
||||
|
||||
# 格式化消息
|
||||
formatted_message = f"{self.COLORS['BRIGHT_BLACK']}[{timestamp}] {category_color}[{category_name}] {level_color}{record.levelname}: {record.getMessage()}{self.COLORS['RESET']}"
|
||||
|
||||
return formatted_message
|
||||
|
||||
|
||||
class TCPServer:
|
||||
def __init__(self, host='127.0.0.1', port=9000, buffer_size=4096):
|
||||
"""初始化TCP服务器"""
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.buffer_size = buffer_size
|
||||
self.socket = None
|
||||
self.clients = {} # 存储客户端连接 {client_id: (socket, address)}
|
||||
self.running = False
|
||||
self.client_buffers = {} # 每个客户端的消息缓冲区
|
||||
|
||||
# 配置日志
|
||||
self._setup_logging()
|
||||
|
||||
def _setup_logging(self):
|
||||
"""设置Minecraft风格的日志系统"""
|
||||
# 创建日志器
|
||||
self.logger = logging.getLogger('TCPServer')
|
||||
self.logger.setLevel(logging.INFO)
|
||||
|
||||
# 清除任何现有的处理器
|
||||
if self.logger.handlers:
|
||||
self.logger.handlers.clear()
|
||||
|
||||
# 创建控制台处理器
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(logging.INFO)
|
||||
|
||||
# 设置格式化器
|
||||
formatter = MinecraftStyleFormatter()
|
||||
console_handler.setFormatter(formatter)
|
||||
|
||||
# 添加处理器到日志器
|
||||
self.logger.addHandler(console_handler)
|
||||
|
||||
def log(self, level, message, category='SERVER'):
|
||||
"""使用指定的分类和级别记录日志"""
|
||||
record = logging.LogRecord(
|
||||
name=self.logger.name,
|
||||
level=getattr(logging, level),
|
||||
pathname='',
|
||||
lineno=0,
|
||||
msg=message,
|
||||
args=(),
|
||||
exc_info=None
|
||||
)
|
||||
record.category = category
|
||||
self.logger.handle(record)
|
||||
|
||||
def start(self):
|
||||
"""启动服务器"""
|
||||
try:
|
||||
# 创建TCP套接字
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) # 禁用Nagle算法
|
||||
|
||||
# 绑定地址和监听
|
||||
self.socket.bind((self.host, self.port))
|
||||
self.socket.listen(5)
|
||||
|
||||
self.running = True
|
||||
self.log('INFO', f"服务器启动,监听 {self.host}:{self.port}", 'SERVER')
|
||||
|
||||
# 接受客户端连接的主循环
|
||||
self._accept_clients()
|
||||
|
||||
except Exception as e:
|
||||
self.log('ERROR', f"服务器启动错误: {e}", 'SYSTEM')
|
||||
self.stop()
|
||||
|
||||
def _accept_clients(self):
|
||||
"""接受客户端连接的循环"""
|
||||
while self.running:
|
||||
try:
|
||||
# 接受新的客户端连接
|
||||
client_socket, address = self.socket.accept()
|
||||
client_id = f"{address[0]}:{address[1]}"
|
||||
|
||||
self.log('INFO', f"新客户端连接: {client_id}", 'NETWORK')
|
||||
|
||||
# 存储客户端信息
|
||||
self.clients[client_id] = (client_socket, address)
|
||||
self.client_buffers[client_id] = ""
|
||||
|
||||
# 创建处理线程
|
||||
client_thread = threading.Thread(
|
||||
target=self._handle_client,
|
||||
args=(client_id,)
|
||||
)
|
||||
client_thread.daemon = True
|
||||
client_thread.start()
|
||||
|
||||
# 通知客户端连接成功
|
||||
self.send_data(client_id, {"type": "connection_status", "status": "connected"})
|
||||
|
||||
except KeyboardInterrupt:
|
||||
self.log('INFO', "收到中断信号,服务器停止中...", 'SYSTEM')
|
||||
break
|
||||
except Exception as e:
|
||||
self.log('ERROR', f"接受连接时出错: {e}", 'NETWORK')
|
||||
time.sleep(1) # 避免CPU过度使用
|
||||
|
||||
def _handle_client(self, client_id):
|
||||
"""处理客户端消息的线程"""
|
||||
client_socket, _ = self.clients.get(client_id, (None, None))
|
||||
if not client_socket:
|
||||
return
|
||||
|
||||
# 设置超时,用于定期检查连接状态
|
||||
client_socket.settimeout(30)
|
||||
|
||||
while self.running and client_id in self.clients:
|
||||
try:
|
||||
# 接收数据
|
||||
data = client_socket.recv(self.buffer_size)
|
||||
|
||||
if not data:
|
||||
# 客户端断开连接
|
||||
self.log('INFO', f"客户端 {client_id} 断开连接", 'CLIENT')
|
||||
self._remove_client(client_id)
|
||||
break
|
||||
|
||||
# 处理接收的数据
|
||||
self._process_data(client_id, data)
|
||||
|
||||
except socket.timeout:
|
||||
# 发送保活消息
|
||||
try:
|
||||
self.send_data(client_id, {"type": "ping"})
|
||||
except:
|
||||
self.log('INFO', f"客户端 {client_id} 连接超时", 'CLIENT')
|
||||
self._remove_client(client_id)
|
||||
break
|
||||
except Exception as e:
|
||||
self.log('ERROR', f"处理客户端 {client_id} 数据时出错: {e}", 'CLIENT')
|
||||
self._remove_client(client_id)
|
||||
break
|
||||
|
||||
def _process_data(self, client_id, data):
|
||||
"""处理从客户端接收的数据"""
|
||||
# 将接收的字节添加到缓冲区
|
||||
try:
|
||||
decoded_data = data.decode('utf-8')
|
||||
self.client_buffers[client_id] += decoded_data
|
||||
|
||||
# 处理可能包含多条JSON消息的缓冲区
|
||||
self._process_buffer(client_id)
|
||||
|
||||
except UnicodeDecodeError as e:
|
||||
self.log('ERROR', f"解码客户端 {client_id} 数据出错: {e}", 'CLIENT')
|
||||
|
||||
def _process_buffer(self, client_id):
|
||||
"""处理客户端消息缓冲区"""
|
||||
buffer = self.client_buffers.get(client_id, "")
|
||||
|
||||
# 按换行符分割消息
|
||||
while '\n' in buffer:
|
||||
message_end = buffer.find('\n')
|
||||
message_text = buffer[:message_end].strip()
|
||||
buffer = buffer[message_end + 1:]
|
||||
|
||||
# 处理非空消息
|
||||
if message_text:
|
||||
try:
|
||||
# 解析JSON消息
|
||||
message = json.loads(message_text)
|
||||
self.log('INFO', f"从客户端 {client_id} 接收JSON: {message}", 'CLIENT')
|
||||
|
||||
# 处理消息 - 实现自定义逻辑
|
||||
self._handle_message(client_id, message)
|
||||
|
||||
except json.JSONDecodeError:
|
||||
# 非JSON格式,作为原始文本处理
|
||||
self.log('INFO', f"从客户端 {client_id} 接收文本: {message_text}", 'CLIENT')
|
||||
self._handle_raw_message(client_id, message_text)
|
||||
|
||||
# 更新缓冲区
|
||||
self.client_buffers[client_id] = buffer
|
||||
|
||||
def _handle_message(self, client_id, message):
|
||||
"""处理JSON消息 - 可被子类覆盖以实现自定义逻辑"""
|
||||
# 默认实现:简单回显
|
||||
response = {
|
||||
"type": "response",
|
||||
"original": message,
|
||||
"timestamp": time.time()
|
||||
}
|
||||
self.send_data(client_id, response)
|
||||
|
||||
def _handle_raw_message(self, client_id, message):
|
||||
"""处理原始文本消息 - 可被子类覆盖以实现自定义逻辑"""
|
||||
# 默认实现:简单回显
|
||||
response = {
|
||||
"type": "text_response",
|
||||
"content": f"收到: {message}",
|
||||
"timestamp": time.time()
|
||||
}
|
||||
self.send_data(client_id, response)
|
||||
|
||||
def send_data(self, client_id, data):
|
||||
"""向指定客户端发送JSON数据"""
|
||||
if client_id not in self.clients:
|
||||
self.log('WARNING', f"客户端 {client_id} 不存在,无法发送数据", 'NETWORK')
|
||||
return False
|
||||
|
||||
client_socket, _ = self.clients[client_id]
|
||||
|
||||
try:
|
||||
# 转换为JSON字符串,添加换行符
|
||||
if isinstance(data, (dict, list)):
|
||||
message = json.dumps(data) + '\n'
|
||||
else:
|
||||
message = str(data) + '\n'
|
||||
|
||||
# 发送数据
|
||||
client_socket.sendall(message.encode('utf-8'))
|
||||
return True
|
||||
except Exception as e:
|
||||
self.log('ERROR', f"向客户端 {client_id} 发送数据时出错: {e}", 'NETWORK')
|
||||
self._remove_client(client_id)
|
||||
return False
|
||||
|
||||
def broadcast(self, data, exclude=None):
|
||||
"""向所有客户端广播消息,可选排除特定客户端"""
|
||||
exclude = exclude or []
|
||||
for client_id in list(self.clients.keys()):
|
||||
if client_id not in exclude:
|
||||
self.send_data(client_id, data)
|
||||
|
||||
def _remove_client(self, client_id):
|
||||
"""断开并移除客户端连接"""
|
||||
if client_id in self.clients:
|
||||
client_socket, _ = self.clients[client_id]
|
||||
try:
|
||||
client_socket.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
del self.clients[client_id]
|
||||
if client_id in self.client_buffers:
|
||||
del self.client_buffers[client_id]
|
||||
|
||||
self.log('INFO', f"客户端 {client_id} 已移除", 'CLIENT')
|
||||
|
||||
def stop(self):
|
||||
"""停止服务器"""
|
||||
self.running = False
|
||||
|
||||
# 关闭所有客户端连接
|
||||
for client_id in list(self.clients.keys()):
|
||||
self._remove_client(client_id)
|
||||
|
||||
# 关闭服务器套接字
|
||||
if self.socket:
|
||||
try:
|
||||
self.socket.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
self.log('INFO', "服务器已停止", 'SERVER')
|
||||
|
||||
|
||||
# 使用示例
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
# 创建并启动服务器
|
||||
server = TCPServer()
|
||||
|
||||
# 以阻塞方式启动服务器
|
||||
server_thread = threading.Thread(target=server.start)
|
||||
server_thread.daemon = True
|
||||
server_thread.start()
|
||||
|
||||
# 运行直到按Ctrl+C
|
||||
print("服务器运行中,按Ctrl+C停止...")
|
||||
while True:
|
||||
time.sleep(1)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n程序被用户中断")
|
||||
if 'server' in locals():
|
||||
server.stop()
|
||||
sys.exit(0)
|
||||
BIN
Server/__pycache__/QQEmailSend.cpython-313.pyc
Normal file
BIN
Server/__pycache__/QQEmailSend.cpython-313.pyc
Normal file
Binary file not shown.
BIN
Server/__pycache__/TCPServer.cpython-313.pyc
Normal file
BIN
Server/__pycache__/TCPServer.cpython-313.pyc
Normal file
Binary file not shown.
BIN
Server/__pycache__/web_admin.cpython-313.pyc
Normal file
BIN
Server/__pycache__/web_admin.cpython-313.pyc
Normal file
Binary file not shown.
28
Server/config/crop_data.json
Normal file
28
Server/config/crop_data.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"测试作物": {"花费": 1, "生长时间": 3, "收益": 9999, "品质": "普通", "描述": "测试作物", "耐候性": 10, "等级": 1, "经验": 999},
|
||||
"小麦": {"花费": 120, "生长时间": 120, "收益": 100, "品质": "普通", "描述": "基础作物,品质较低,适合新手种植", "耐候性": 10, "等级": 1, "经验": 10},
|
||||
"稻谷": {"花费": 100, "生长时间": 240, "收益": 120, "品质": "普通", "描述": "适合大规模种植的基础作物", "耐候性": 10, "等级": 1, "经验": 10},
|
||||
"玉米": {"花费": 70, "生长时间": 600, "收益": 90, "品质": "普通", "描述": "营养丰富的优良作物,适合稍有经验的玩家", "耐候性": 15, "等级": 2, "经验": 15},
|
||||
"土豆": {"花费": 75, "生长时间": 360, "收益": 90, "品质": "普通", "描述": "容易种植的耐寒作物", "耐候性": 12, "等级": 1, "经验": 10},
|
||||
"胡萝卜": {"花费": 60, "生长时间": 480, "收益": 80, "品质": "普通", "描述": "适合新手的健康作物", "耐候性": 12, "等级": 1, "经验": 10},
|
||||
"草莓": {"花费": 120, "生长时间": 960, "收益": 150, "品质": "优良", "描述": "营养丰富的果实,收益不错", "耐候性": 14, "等级": 2, "经验": 20},
|
||||
"番茄": {"花费": 100, "生长时间": 720, "收益": 130, "品质": "优良", "描述": "常见作物,适合小规模种植", "耐候性": 12, "等级": 2, "经验": 15},
|
||||
"大豆": {"花费": 90, "生长时间": 840, "收益": 110, "品质": "优良", "描述": "富含蛋白质的基础作物", "耐候性": 11, "等级": 2, "经验": 12},
|
||||
"蓝莓": {"花费": 150, "生长时间": 1200, "收益": 200, "品质": "稀有", "描述": "较为稀有的作物,市场价值较高", "耐候性": 18, "等级": 3, "经验": 25},
|
||||
"洋葱": {"花费": 85, "生长时间": 600, "收益": 105, "品质": "稀有", "描述": "烹饪常用的作物,适合中级种植", "耐候性": 10, "等级": 2, "经验": 10},
|
||||
"南瓜": {"花费": 180, "生长时间": 1440, "收益": 250, "品质": "稀有", "描述": "秋季收获的高收益作物", "耐候性": 20, "等级": 4, "经验": 30},
|
||||
"葡萄": {"花费": 200, "生长时间": 1200, "收益": 300, "品质": "稀有", "描述": "需要特殊管理的高收益作物", "耐候性": 15, "等级": 4, "经验": 35},
|
||||
"柿子": {"花费": 160, "生长时间": 1080, "收益": 240, "品质": "稀有", "描述": "富含营养的秋季作物", "耐候性": 18, "等级": 3, "经验": 28},
|
||||
"花椰菜": {"花费": 130, "生长时间": 960, "收益": 170, "品质": "稀有", "描述": "耐寒的高品质作物,适合经验丰富的玩家", "耐候性": 17, "等级": 3, "经验": 22},
|
||||
"芦笋": {"花费": 200, "生长时间": 1560, "收益": 280, "品质": "稀有", "描述": "市场需求量高的稀有作物", "耐候性": 15, "等级": 4, "经验": 30},
|
||||
"香草": {"花费": 250, "生长时间": 1800, "收益": 400, "品质": "史诗", "描述": "非常稀有且收益极高的作物", "耐候性": 22, "等级": 5, "经验": 40},
|
||||
"西瓜": {"花费": 240, "生长时间": 2400, "收益": 420, "品质": "史诗", "描述": "夏季丰产的高价值作物", "耐候性": 21, "等级": 5, "经验": 45},
|
||||
"甜菜": {"花费": 220, "生长时间": 2160, "收益": 350, "品质": "史诗", "描述": "营养丰富的根茎作物,收益较高", "耐候性": 20, "等级": 5, "经验": 38},
|
||||
"甘蔗": {"花费": 260, "生长时间": 3000, "收益": 450, "品质": "史诗", "描述": "需要充足水源的高价值作物", "耐候性": 18, "等级": 5, "经验": 50},
|
||||
"龙果": {"花费": 400, "生长时间": 4800, "收益": 600, "品质": "传奇", "描述": "极为稀有的热带作物,产量和价值都极高", "耐候性": 25, "等级": 6, "经验": 60},
|
||||
"松露": {"花费": 500, "生长时间": 7200, "收益": 700, "品质": "传奇", "描述": "极其珍贵的地下作物,市场价格极高", "耐候性": 23, "等级": 7, "经验": 80},
|
||||
"人参": {"花费": 450, "生长时间": 6600, "收益": 650, "品质": "传奇", "描述": "需要耐心等待的珍贵药材", "耐候性": 22, "等级": 6, "经验": 75},
|
||||
"富贵竹": {"花费": 450, "生长时间": 6600, "收益": 650, "品质": "传奇", "描述": "需要耐心等待的珍贵药材", "耐候性": 22, "等级": 6, "经验": 75},
|
||||
"芦荟": {"花费": 450, "生长时间": 6600, "收益": 650, "品质": "传奇", "描述": "需要耐心等待的珍贵药材", "耐候性": 22, "等级": 6, "经验": 75},
|
||||
"金橘": {"花费": 420, "生长时间": 4800, "收益": 620, "品质": "传奇", "描述": "少见的耐寒果树,市场需求量极大", "耐候性": 26, "等级": 7, "经验": 70}
|
||||
}
|
||||
32
Server/config/crop_data.json.backup
Normal file
32
Server/config/crop_data.json.backup
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
|
||||
"测试作物": {"花费": 1, "生长时间": 3, "收益": 9999, "品质": "普通", "描述": "测试作物", "耐候性": 10, "等级": 1, "经验": 999},
|
||||
|
||||
"小麦": {"花费": 120, "生长时间": 120, "收益": 100, "品质": "普通", "描述": "基础作物,品质较低,适合新手种植", "耐候性": 10, "等级": 1, "经验": 10},
|
||||
"稻谷": {"花费": 100, "生长时间": 240, "收益": 120, "品质": "普通", "描述": "适合大规模种植的基础作物", "耐候性": 10, "等级": 1, "经验": 10},
|
||||
"玉米": {"花费": 70, "生长时间": 600, "收益": 90, "品质": "普通", "描述": "营养丰富的优良作物,适合稍有经验的玩家", "耐候性": 15, "等级": 2, "经验": 15},
|
||||
"土豆": {"花费": 75, "生长时间": 360, "收益": 90, "品质": "普通", "描述": "容易种植的耐寒作物", "耐候性": 12, "等级": 1, "经验": 10},
|
||||
"胡萝卜": {"花费": 60, "生长时间": 480, "收益": 80, "品质": "普通", "描述": "适合新手的健康作物", "耐候性": 12, "等级": 1, "经验": 10},
|
||||
|
||||
"草莓": {"花费": 120, "生长时间": 960, "收益": 150, "品质": "优良", "描述": "营养丰富的果实,收益不错", "耐候性": 14, "等级": 2, "经验": 20},
|
||||
"番茄": {"花费": 100, "生长时间": 720, "收益": 130, "品质": "优良", "描述": "常见作物,适合小规模种植", "耐候性": 12, "等级": 2, "经验": 15},
|
||||
"大豆": {"花费": 90, "生长时间": 840, "收益": 110, "品质": "优良", "描述": "富含蛋白质的基础作物", "耐候性": 11, "等级": 2, "经验": 12},
|
||||
|
||||
"蓝莓": {"花费": 150, "生长时间": 1200, "收益": 200, "品质": "稀有", "描述": "较为稀有的作物,市场价值较高", "耐候性": 18, "等级": 3, "经验": 25},
|
||||
"洋葱": {"花费": 85, "生长时间": 600, "收益": 105, "品质": "稀有", "描述": "烹饪常用的作物,适合中级种植", "耐候性": 10, "等级": 2, "经验": 10},
|
||||
"南瓜": {"花费": 180, "生长时间": 1440, "收益": 250, "品质": "稀有", "描述": "秋季收获的高收益作物", "耐候性": 20, "等级": 4, "经验": 30},
|
||||
"葡萄": {"花费": 200, "生长时间": 1200, "收益": 300, "品质": "稀有", "描述": "需要特殊管理的高收益作物", "耐候性": 15, "等级": 4, "经验": 35},
|
||||
"柿子": {"花费": 160, "生长时间": 1080, "收益": 240, "品质": "稀有", "描述": "富含营养的秋季作物", "耐候性": 18, "等级": 3, "经验": 28},
|
||||
"花椰菜": {"花费": 130, "生长时间": 960, "收益": 170, "品质": "稀有", "描述": "耐寒的高品质作物,适合经验丰富的玩家", "耐候性": 17, "等级": 3, "经验": 22},
|
||||
"芦笋": {"花费": 200, "生长时间": 1560, "收益": 280, "品质": "稀有", "描述": "市场需求量高的稀有作物", "耐候性": 15, "等级": 4, "经验": 30},
|
||||
|
||||
"香草": {"花费": 250, "生长时间": 1800, "收益": 400, "品质": "史诗", "描述": "非常稀有且收益极高的作物", "耐候性": 22, "等级": 5, "经验": 40},
|
||||
"西瓜": {"花费": 240, "生长时间": 2400, "收益": 420, "品质": "史诗", "描述": "夏季丰产的高价值作物", "耐候性": 21, "等级": 5, "经验": 45},
|
||||
"甜菜": {"花费": 220, "生长时间": 2160, "收益": 350, "品质": "史诗", "描述": "营养丰富的根茎作物,收益较高", "耐候性": 20, "等级": 5, "经验": 38},
|
||||
"甘蔗": {"花费": 260, "生长时间": 3000, "收益": 450, "品质": "史诗", "描述": "需要充足水源的高价值作物", "耐候性": 18, "等级": 5, "经验": 50},
|
||||
|
||||
"龙果": {"花费": 400, "生长时间": 4800, "收益": 600, "品质": "传奇", "描述": "极为稀有的热带作物,产量和价值都极高", "耐候性": 25, "等级": 6, "经验": 60},
|
||||
"松露": {"花费": 500, "生长时间": 7200, "收益": 700, "品质": "传奇", "描述": "极其珍贵的地下作物,市场价格极高", "耐候性": 23, "等级": 7, "经验": 80},
|
||||
"人参": {"花费": 450, "生长时间": 6600, "收益": 650, "品质": "传奇", "描述": "需要耐心等待的珍贵药材", "耐候性": 22, "等级": 6, "经验": 75},
|
||||
"金橘": {"花费": 420, "生长时间": 4800, "收益": 620, "品质": "传奇", "描述": "少见的耐寒果树,市场需求量极大", "耐候性": 26, "等级": 7, "经验": 70}
|
||||
}
|
||||
64
Server/config/initial_player_data_template.json
Normal file
64
Server/config/initial_player_data_template.json
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"experience": 0,
|
||||
"level": 1,
|
||||
"money": 1000,
|
||||
"farm_name": "农场",
|
||||
"user_name": "shumengya",
|
||||
"player_name": "玩家昵称",
|
||||
"user_password": "0123456789",
|
||||
"last_login_time": "2025年12时09分35秒",
|
||||
"total_login_time": "0时0分0秒",
|
||||
"farm_lots": [
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": true, "is_planted": false, "max_grow_time": 3},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": true, "is_planted": false, "max_grow_time": 3},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": true, "is_planted": false, "max_grow_time": 3},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": true, "is_planted": false, "max_grow_time": 3},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": true, "is_planted": false, "max_grow_time": 3},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
|
||||
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5}
|
||||
],
|
||||
"player_bag": []
|
||||
}
|
||||
6
Server/config/verification_codes.json
Normal file
6
Server/config/verification_codes.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"1232132": {
|
||||
"code": "5UFH2Z",
|
||||
"expire_at": 1748005553.6155243
|
||||
}
|
||||
}
|
||||
124
Server/deployment_guide.md
Normal file
124
Server/deployment_guide.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# 萌芽农场游戏服务器部署指南
|
||||
|
||||
## 系统要求
|
||||
- Python 3.7 或更高版本
|
||||
- 稳定的互联网连接
|
||||
- 建议:2GB+ 内存,足够的磁盘空间存储玩家数据
|
||||
|
||||
## 安装步骤
|
||||
|
||||
### 1. 准备环境
|
||||
```bash
|
||||
# 在服务器上创建项目文件夹
|
||||
mkdir MengYaFarm
|
||||
cd MengYaFarm
|
||||
|
||||
# 克隆或上传服务器代码到此文件夹
|
||||
# (手动上传文件或使用Git)
|
||||
```
|
||||
|
||||
### 2. 安装依赖
|
||||
```bash
|
||||
# 创建虚拟环境(推荐)
|
||||
python -m venv venv
|
||||
# Linux/Mac激活虚拟环境
|
||||
source venv/bin/activate
|
||||
# Windows激活虚拟环境
|
||||
# venv\Scripts\activate
|
||||
|
||||
# 安装依赖
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 3. 配置服务器
|
||||
1. 确保已创建所需文件夹:
|
||||
```bash
|
||||
mkdir -p game_saves config
|
||||
```
|
||||
|
||||
2. 创建初始玩家数据模板 (如果尚未存在):
|
||||
```bash
|
||||
# 在config目录中创建initial_player_data_template.json
|
||||
```
|
||||
|
||||
3. 检查 TCPGameServer.py 中的服务器地址和端口配置:
|
||||
```python
|
||||
server_host: str = "0.0.0.0" # 使用0.0.0.0允许所有网络接口访问
|
||||
server_port: int = 9000 # 确保此端口在防火墙中开放
|
||||
```
|
||||
|
||||
4. 如需使用QQ邮箱验证功能,请在QQEmailSend.py中更新发件邮箱配置:
|
||||
```python
|
||||
SENDER_EMAIL = 'your_qq_number@qq.com' # 发件人邮箱
|
||||
SENDER_AUTH_CODE = 'your_auth_code' # 授权码
|
||||
```
|
||||
|
||||
### 4. 启动服务器
|
||||
```bash
|
||||
# 直接启动
|
||||
python Server/TCPGameServer.py
|
||||
|
||||
# 或使用nohup在后台运行
|
||||
nohup python Server/TCPGameServer.py > server.log 2>&1 &
|
||||
```
|
||||
|
||||
### 5. 监控与维护
|
||||
- 服务器日志会输出到控制台或server.log
|
||||
- 玩家数据存储在game_saves文件夹中
|
||||
- 定期备份game_saves文件夹以防数据丢失
|
||||
|
||||
### 6. 防火墙配置
|
||||
确保服务器防火墙允许TCP 9000端口的入站连接:
|
||||
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
sudo ufw allow 9000/tcp
|
||||
|
||||
# CentOS/RHEL
|
||||
sudo firewall-cmd --permanent --add-port=9000/tcp
|
||||
sudo firewall-cmd --reload
|
||||
```
|
||||
|
||||
### 7. 系统服务配置 (可选)
|
||||
可以创建systemd服务使服务器自动启动:
|
||||
|
||||
```bash
|
||||
# 创建服务文件
|
||||
sudo nano /etc/systemd/system/mengyafarm.service
|
||||
|
||||
# 添加以下内容
|
||||
[Unit]
|
||||
Description=MengYa Farm Game Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=your_username
|
||||
WorkingDirectory=/path/to/MengYaFarm
|
||||
ExecStart=/path/to/MengYaFarm/venv/bin/python /path/to/MengYaFarm/Server/TCPGameServer.py
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
# 启用并启动服务
|
||||
sudo systemctl enable mengyafarm.service
|
||||
sudo systemctl start mengyafarm.service
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 服务器无法启动
|
||||
- 检查Python版本
|
||||
- 确认所有依赖已正确安装
|
||||
- 检查端口是否被占用
|
||||
|
||||
### 客户端无法连接
|
||||
- 确认服务器IP和端口配置正确
|
||||
- 检查防火墙设置
|
||||
- 验证网络连接
|
||||
|
||||
### 发送验证码失败
|
||||
- 检查QQ邮箱和授权码设置
|
||||
- 确认SMTP服务器可访问
|
||||
414
Server/game_saves/2143323382.json
Normal file
414
Server/game_saves/2143323382.json
Normal file
@@ -0,0 +1,414 @@
|
||||
{
|
||||
"experience": 0,
|
||||
"level": 1,
|
||||
"money": 1000,
|
||||
"farm_name": "柚大青の小农场",
|
||||
"player_name": "柚大青",
|
||||
"user_name": "2143323382",
|
||||
"user_password": "tyh@19900420",
|
||||
"last_login_time": "2025年05月25日16时43分38秒",
|
||||
"total_login_time": "0时0分29秒",
|
||||
"farm_lots": [
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 3
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 3
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 3
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 3
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 3
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
}
|
||||
],
|
||||
"player_bag": []
|
||||
}
|
||||
370
Server/game_saves/3205788256.json.backup
Normal file
370
Server/game_saves/3205788256.json.backup
Normal file
@@ -0,0 +1,370 @@
|
||||
{
|
||||
"experience": 508,
|
||||
"farm_lots": [
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 3
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 3
|
||||
},
|
||||
{
|
||||
"crop_type": "花椰菜",
|
||||
"grow_time": 960,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": true,
|
||||
"max_grow_time": 960
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 3
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 3
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 3
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 3
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 3
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 4800
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 1200
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 4800
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 2400
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 3
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 3
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 120
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 720
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 2400
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 1200
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 1800
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 600
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 480
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 960
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 960
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": false,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 5
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 2400
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 3
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 3
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 960
|
||||
},
|
||||
{
|
||||
"crop_type": "",
|
||||
"grow_time": 0,
|
||||
"is_dead": false,
|
||||
"is_diged": true,
|
||||
"is_planted": false,
|
||||
"max_grow_time": 960
|
||||
}
|
||||
],
|
||||
"player_bag": [
|
||||
{
|
||||
"count": 18,
|
||||
"name": "测试作物",
|
||||
"quality": "普通"
|
||||
},
|
||||
{
|
||||
"count": 253,
|
||||
"name": "金橘",
|
||||
"quality": "传奇"
|
||||
},
|
||||
{
|
||||
"name": "胡萝卜",
|
||||
"quality": "普通",
|
||||
"count": 17
|
||||
},
|
||||
{
|
||||
"name": "花椰菜",
|
||||
"quality": "稀有",
|
||||
"count": 11
|
||||
},
|
||||
{
|
||||
"name": "芦笋",
|
||||
"quality": "稀有",
|
||||
"count": 8
|
||||
},
|
||||
{
|
||||
"name": "草莓",
|
||||
"quality": "优良",
|
||||
"count": 4
|
||||
},
|
||||
{
|
||||
"name": "香草",
|
||||
"quality": "史诗",
|
||||
"count": 3
|
||||
}
|
||||
],
|
||||
"farm_name": "树萌芽の大农场",
|
||||
"player_name": "树萌芽",
|
||||
"level": 36,
|
||||
"money": 40013,
|
||||
"last_login_time": "2025年05月24日11时51分35秒",
|
||||
"total_login_time": "100时33分38秒",
|
||||
"user_name": "3205788256",
|
||||
"user_password": "tyh@19900420"
|
||||
}
|
||||
4
Server/requirements.txt
Normal file
4
Server/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
# Game Server Dependencies
|
||||
colorama>=0.4.6 # For colored terminal output
|
||||
# Email Requirements
|
||||
# Standard library dependencies are not listed (socket, threading, json, etc.)
|
||||
Reference in New Issue
Block a user