veadk-python 0.2.5__py3-none-any.whl → 0.2.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of veadk-python might be problematic. Click here for more details.

Files changed (52) hide show
  1. veadk/agent.py +19 -7
  2. veadk/cli/cli_deploy.py +2 -0
  3. veadk/cli/cli_init.py +25 -6
  4. veadk/consts.py +20 -1
  5. veadk/database/database_adapter.py +88 -0
  6. veadk/database/kv/redis_database.py +47 -0
  7. veadk/database/local_database.py +22 -4
  8. veadk/database/relational/mysql_database.py +58 -0
  9. veadk/database/vector/opensearch_vector_database.py +6 -3
  10. veadk/database/viking/viking_database.py +69 -0
  11. veadk/integrations/ve_cr/__init__.py +13 -0
  12. veadk/integrations/ve_cr/ve_cr.py +205 -0
  13. veadk/integrations/ve_faas/template/cookiecutter.json +2 -1
  14. veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/app.py +24 -1
  15. veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/requirements.txt +3 -1
  16. veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/run.sh +0 -7
  17. veadk/integrations/ve_faas/ve_faas.py +2 -0
  18. veadk/integrations/ve_faas/web_template/cookiecutter.json +17 -0
  19. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/__init__.py +13 -0
  20. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/clean.py +23 -0
  21. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/config.yaml.example +2 -0
  22. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/deploy.py +41 -0
  23. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/Dockerfile +23 -0
  24. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/app.py +123 -0
  25. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/init_db.py +46 -0
  26. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/models.py +36 -0
  27. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/requirements.txt +4 -0
  28. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/run.sh +21 -0
  29. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/static/css/style.css +368 -0
  30. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/static/js/admin.js +0 -0
  31. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/dashboard.html +21 -0
  32. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/edit_post.html +24 -0
  33. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/login.html +21 -0
  34. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/posts.html +53 -0
  35. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/base.html +45 -0
  36. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/index.html +29 -0
  37. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/post.html +14 -0
  38. veadk/integrations/ve_tos/ve_tos.py +92 -30
  39. veadk/knowledgebase/knowledgebase.py +8 -0
  40. veadk/runner.py +49 -16
  41. veadk/tracing/telemetry/attributes/extractors/common_attributes_extractors.py +5 -0
  42. veadk/tracing/telemetry/attributes/extractors/llm_attributes_extractors.py +253 -129
  43. veadk/tracing/telemetry/attributes/extractors/types.py +15 -4
  44. veadk/tracing/telemetry/opentelemetry_tracer.py +11 -5
  45. veadk/tracing/telemetry/telemetry.py +19 -4
  46. veadk/version.py +1 -1
  47. {veadk_python-0.2.5.dist-info → veadk_python-0.2.6.dist-info}/METADATA +1 -1
  48. {veadk_python-0.2.5.dist-info → veadk_python-0.2.6.dist-info}/RECORD +52 -30
  49. {veadk_python-0.2.5.dist-info → veadk_python-0.2.6.dist-info}/WHEEL +0 -0
  50. {veadk_python-0.2.5.dist-info → veadk_python-0.2.6.dist-info}/entry_points.txt +0 -0
  51. {veadk_python-0.2.5.dist-info → veadk_python-0.2.6.dist-info}/licenses/LICENSE +0 -0
  52. {veadk_python-0.2.5.dist-info → veadk_python-0.2.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,205 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from veadk.utils.volcengine_sign import ve_request
16
+ from veadk.utils.logger import get_logger
17
+ from veadk.consts import (
18
+ DEFAULT_CR_INSTANCE_NAME,
19
+ DEFAULT_CR_NAMESPACE_NAME,
20
+ DEFAULT_CR_REPO_NAME,
21
+ )
22
+ import time
23
+
24
+ logger = get_logger(__name__)
25
+
26
+
27
+ class VeCR:
28
+ def __init__(self, access_key: str, secret_key: str, region: str = "cn-beijing"):
29
+ self.ak = access_key
30
+ self.sk = secret_key
31
+ self.region = region
32
+ assert region in ["cn-beijing", "cn-guangzhou", "cn-shanghai"]
33
+ self.version = "2022-05-12"
34
+
35
+ def _create_instance(self, instance_name: str = DEFAULT_CR_INSTANCE_NAME) -> str:
36
+ """
37
+ create cr instance
38
+
39
+ Args:
40
+ instance_name: cr instance name
41
+
42
+ Returns:
43
+ cr instance name
44
+ """
45
+ status = self._check_instance(instance_name)
46
+ if status != "NONEXIST":
47
+ logger.debug(f"cr instance {instance_name} already running")
48
+ return instance_name
49
+ response = ve_request(
50
+ request_body={
51
+ "Name": instance_name,
52
+ "ResourceTags": [
53
+ {"Key": "provider", "Value": "veadk"},
54
+ ],
55
+ },
56
+ action="CreateRegistry",
57
+ ak=self.ak,
58
+ sk=self.sk,
59
+ service="cr",
60
+ version=self.version,
61
+ region=self.region,
62
+ host=f"cr.{self.region}.volcengineapi.com",
63
+ )
64
+ logger.debug(f"create cr instance {instance_name}: {response}")
65
+
66
+ while True:
67
+ status = self._check_instance(instance_name)
68
+ if status == "Running":
69
+ break
70
+ elif status == "Failed":
71
+ raise ValueError(f"cr instance {instance_name} create failed")
72
+ else:
73
+ logger.debug(f"cr instance status: {status}")
74
+ time.sleep(5)
75
+
76
+ return instance_name
77
+
78
+ def _check_instance(self, instance_name: str) -> str:
79
+ """
80
+ check cr instance status
81
+
82
+ Args:
83
+ instance_name: cr instance name
84
+
85
+ Returns:
86
+ cr instance status
87
+ """
88
+ response = ve_request(
89
+ request_body={
90
+ "Filter": {
91
+ "Names": [instance_name],
92
+ }
93
+ },
94
+ action="ListRegistries",
95
+ ak=self.ak,
96
+ sk=self.sk,
97
+ service="cr",
98
+ version=self.version,
99
+ region=self.region,
100
+ host=f"cr.{self.region}.volcengineapi.com",
101
+ )
102
+ logger.debug(f"check cr instance {instance_name}: {response}")
103
+
104
+ try:
105
+ if response["Result"]["TotalCount"] == 0:
106
+ return "NONEXIST"
107
+ return response["Result"]["Items"][0]["Status"]["Phase"]
108
+ except Exception as _:
109
+ raise ValueError(f"Error check cr instance {instance_name}: {response}")
110
+
111
+ def _create_namespace(
112
+ self,
113
+ instance_name: str = DEFAULT_CR_INSTANCE_NAME,
114
+ namespace_name: str = DEFAULT_CR_NAMESPACE_NAME,
115
+ ) -> str:
116
+ """
117
+ create cr namespace
118
+
119
+ Args:
120
+ instance_name: cr instance name
121
+ namespace_name: cr namespace name
122
+
123
+ Returns:
124
+ cr namespace name
125
+ """
126
+ response = ve_request(
127
+ request_body={
128
+ "Name": namespace_name,
129
+ "Registry": instance_name,
130
+ },
131
+ action="CreateNamespace",
132
+ ak=self.ak,
133
+ sk=self.sk,
134
+ service="cr",
135
+ version=self.version,
136
+ region=self.region,
137
+ host=f"cr.{self.region}.volcengineapi.com",
138
+ )
139
+ logger.debug(f"create cr namespace {namespace_name}: {response}")
140
+
141
+ if "Error" in response["ResponseMetadata"]:
142
+ error_code = response["ResponseMetadata"]["Error"]["Code"]
143
+ error_message = response["ResponseMetadata"]["Error"]["Message"]
144
+ if error_code == "AlreadyExists.Namespace":
145
+ logger.debug(f"cr namespace {namespace_name} already exists")
146
+ return namespace_name
147
+ else:
148
+ logger.error(
149
+ f"Error create cr namespace {namespace_name}: {error_code} {error_message}"
150
+ )
151
+ raise ValueError(
152
+ f"Error create cr namespace {namespace_name}: {error_code} {error_message}"
153
+ )
154
+
155
+ return namespace_name
156
+
157
+ def _create_repo(
158
+ self,
159
+ instance_name: str = DEFAULT_CR_INSTANCE_NAME,
160
+ namespace_name: str = DEFAULT_CR_NAMESPACE_NAME,
161
+ repo_name: str = DEFAULT_CR_REPO_NAME,
162
+ ) -> str:
163
+ """
164
+ create cr repo
165
+
166
+ Args:
167
+ instance_name: cr instance name
168
+ namespace_name: cr namespace name
169
+ repo_name: cr repo name
170
+
171
+ Returns:
172
+ cr repo name
173
+ """
174
+ response = ve_request(
175
+ request_body={
176
+ "Name": repo_name,
177
+ "Registry": instance_name,
178
+ "Namespace": namespace_name,
179
+ "Description": "veadk cr repo",
180
+ },
181
+ action="CreateRepository",
182
+ ak=self.ak,
183
+ sk=self.sk,
184
+ service="cr",
185
+ version=self.version,
186
+ region=self.region,
187
+ host=f"cr.{self.region}.volcengineapi.com",
188
+ )
189
+ logger.debug(f"create cr repo {repo_name}: {response}")
190
+
191
+ if "Error" in response["ResponseMetadata"]:
192
+ error_code = response["ResponseMetadata"]["Error"]["Code"]
193
+ error_message = response["ResponseMetadata"]["Error"]["Message"]
194
+ if error_code == "AlreadyExists.Repository":
195
+ logger.debug(f"cr repo {repo_name} already exists")
196
+ return repo_name
197
+ else:
198
+ logger.error(
199
+ f"Error create cr repo {repo_name}: {error_code} {error_message}"
200
+ )
201
+ raise ValueError(
202
+ f"Error create cr repo {repo_name}: {error_code} {error_message}"
203
+ )
204
+
205
+ return repo_name
@@ -7,5 +7,6 @@
7
7
  "veapig_instance_name": "",
8
8
  "veapig_service_name": "",
9
9
  "veapig_upstream_name": "",
10
- "use_adk_web": false
10
+ "use_adk_web": false,
11
+ "veadk_version": ""
11
12
  }
@@ -36,6 +36,8 @@ from veadk.tracing.telemetry.exporters.tls_exporter import TLSExporter
36
36
  from veadk.tracing.telemetry.opentelemetry_tracer import OpentelemetryTracer
37
37
  from veadk.types import AgentRunConfig
38
38
  from veadk.utils.logger import get_logger
39
+ from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
40
+ from opentelemetry import context
39
41
 
40
42
  logger = get_logger(__name__)
41
43
 
@@ -117,6 +119,8 @@ async def agent_card() -> dict:
117
119
  agent_card = await agent_card_builder.build()
118
120
  return agent_card.model_dump()
119
121
 
122
+ async def get_cozeloop_space_id() -> dict:
123
+ return {"space_id": os.getenv("OBSERVABILITY_OPENTELEMETRY_COZELOOP_SERVICE_NAME", default="")}
120
124
 
121
125
  load_tracer()
122
126
 
@@ -132,7 +136,7 @@ a2a_app = init_app(
132
136
 
133
137
  a2a_app.post("/run_agent", operation_id="run_agent", tags=["mcp"])(run_agent_func)
134
138
  a2a_app.get("/agent_card", operation_id="agent_card", tags=["mcp"])(agent_card)
135
-
139
+ a2a_app.get("/get_cozeloop_space_id", operation_id="get_cozeloop_space_id", tags=["mcp"])(get_cozeloop_space_id)
136
140
 
137
141
  # === Build mcp server ===
138
142
 
@@ -159,6 +163,25 @@ app = FastAPI(
159
163
  redoc_url=None
160
164
  )
161
165
 
166
+ @app.middleware("http")
167
+ async def otel_context_middleware(request, call_next):
168
+ carrier = {
169
+ "traceparent": request.headers.get("Traceparent"),
170
+ "tracestate": request.headers.get("Tracestate"),
171
+ }
172
+ logger.debug(f"carrier: {carrier}")
173
+ if carrier["traceparent"] is None:
174
+ return await call_next(request)
175
+ else:
176
+ ctx = TraceContextTextMapPropagator().extract(carrier=carrier)
177
+ logger.debug(f"ctx: {ctx}")
178
+ token = context.attach(ctx)
179
+ try:
180
+ response = await call_next(request)
181
+ finally:
182
+ context.detach(token)
183
+ return response
184
+
162
185
  # Mount A2A routes to main app
163
186
  for route in a2a_app.routes:
164
187
  app.routes.append(route)
@@ -1 +1,3 @@
1
- veadk-python
1
+ veadk-python=={{ cookiecutter.veadk_version }}
2
+ fastapi
3
+ uvicorn[standard]
@@ -33,13 +33,6 @@ while [[ $# -gt 0 ]]; do
33
33
  esac
34
34
  done
35
35
 
36
- # in case of deployment deps not installed in user's requirements.txt
37
- if pip list | grep -q "^fastapi \|^uvicorn "; then
38
- echo "fastapi and uvicorn already installed"
39
- else
40
- python3 -m pip install uvicorn[standard] fastapi
41
- fi
42
-
43
36
  # Check if MODEL_AGENT_API_KEY is set
44
37
  if [ -z "$MODEL_AGENT_API_KEY" ]; then
45
38
  echo "MODEL_AGENT_API_KEY is not set. Please set it in your environment variables."
@@ -152,6 +152,8 @@ class VeFaaS:
152
152
  "GatewayName": gateway_name,
153
153
  "ServiceName": service_name,
154
154
  "UpstreamName": upstream_name,
155
+ "EnableKeyAuth": True,
156
+ "EnableMcpSession": True,
155
157
  },
156
158
  "TemplateId": self.template_id,
157
159
  },
@@ -0,0 +1,17 @@
1
+ {
2
+ "local_dir_name": "veadk_vefaas_web_proj",
3
+ "app_name": "simple-blog",
4
+ "vefaas_application_name": "simple-blog",
5
+ "veapig_instance_name": "",
6
+ "veapig_service_name": "",
7
+ "veapig_upstream_name": "",
8
+ "use_adk_web": false,
9
+ "veadk_version": "",
10
+ "_copy_without_render": [
11
+ "*.html",
12
+ "*.css",
13
+ "*.js",
14
+ "static/**/*",
15
+ "templates/**/*"
16
+ ]
17
+ }
@@ -0,0 +1,13 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
@@ -0,0 +1,23 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from veadk.cloud.cloud_app import CloudApp
16
+
17
+ def main() -> None:
18
+ cloud_app = CloudApp(vefaas_application_name="{{cookiecutter.vefaas_application_name}}")
19
+ cloud_app.delete_self()
20
+
21
+
22
+ if __name__ == "__main__":
23
+ main()
@@ -0,0 +1,2 @@
1
+ VOLCENGINE_ACCESS_KEY:
2
+ VOLCENGINE_SECRET_KEY:
@@ -0,0 +1,41 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import asyncio
16
+ from pathlib import Path
17
+
18
+ from veadk.cloud.cloud_agent_engine import CloudAgentEngine
19
+
20
+ async def main():
21
+ engine = CloudAgentEngine()
22
+
23
+ cloud_app = engine.deploy(
24
+ path=str(Path(__file__).parent / "src"),
25
+ application_name="{{cookiecutter.vefaas_application_name}}",
26
+ gateway_name="{{cookiecutter.veapig_instance_name}}",
27
+ gateway_service_name="{{cookiecutter.veapig_service_name}}",
28
+ gateway_upstream_name="{{cookiecutter.veapig_upstream_name}}",
29
+ use_adk_web={{cookiecutter.use_adk_web}},
30
+ local_test=False, # Set to True for local testing before deploy to VeFaaS
31
+ )
32
+ print(f"VeFaaS application ID: {cloud_app.vefaas_application_id}")
33
+
34
+ if {{cookiecutter.use_adk_web}}:
35
+ print(f"Web is running at: {cloud_app.vefaas_endpoint}")
36
+ else:
37
+ print(f"Web template does not support use_adk_web=False")
38
+
39
+
40
+ if __name__ == "__main__":
41
+ asyncio.run(main())
@@ -0,0 +1,23 @@
1
+ # 使用官方Python镜像作为基础镜像
2
+ FROM python:3.9-slim
3
+
4
+ # 设置工作目录
5
+ WORKDIR /app
6
+
7
+ # 复制依赖文件并安装依赖
8
+ COPY requirements.txt .
9
+ RUN pip install --no-cache-dir -r requirements.txt
10
+
11
+ # 复制项目文件
12
+ COPY . .
13
+
14
+ # 设置环境变量
15
+ ENV FLASK_APP=app.py
16
+ ENV FLASK_ENV=production
17
+ ENV PYTHONUNBUFFERED=1
18
+
19
+ # 暴露端口
20
+ EXPOSE 8000
21
+
22
+ # 启动命令
23
+ CMD ["bash", "run.sh"]
@@ -0,0 +1,123 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from flask import Flask, render_template, request, redirect, url_for, flash, session
16
+ from models import db, Post, User
17
+ from werkzeug.security import generate_password_hash, check_password_hash
18
+ import os
19
+
20
+ app = Flask(__name__)
21
+ app.config['SECRET_KEY'] = 'your-secret-key-here'
22
+ app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
23
+ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
24
+ app.instance_path = os.path.join("/tmp", "flask_instance")
25
+ os.makedirs(app.instance_path, exist_ok=True)
26
+
27
+ db.init_app(app)
28
+
29
+ # 前台首页
30
+ @app.route('/')
31
+ def index():
32
+ page = request.args.get('page', 1, type=int)
33
+ posts = Post.query.order_by(Post.created_at.desc()).paginate(
34
+ page=page, per_page=5, error_out=False)
35
+ return render_template('index.html', posts=posts)
36
+
37
+ # 文章详情页
38
+ @app.route('/post/<int:post_id>')
39
+ def post_detail(post_id):
40
+ post = Post.query.get_or_404(post_id)
41
+ return render_template('post.html', post=post)
42
+
43
+ # 后台登录页
44
+ @app.route('/admin/login', methods=['GET', 'POST'])
45
+ def admin_login():
46
+ if request.method == 'POST':
47
+ username = request.form['username']
48
+ password = request.form['password']
49
+
50
+ user = User.query.filter_by(username=username).first()
51
+
52
+ if user and check_password_hash(user.password, password):
53
+ session['admin_logged_in'] = True
54
+ return redirect(url_for('admin_dashboard'))
55
+ else:
56
+ flash('用户名或密码错误')
57
+
58
+ return render_template('admin/login.html')
59
+
60
+ # 后台登出
61
+ @app.route('/admin/logout')
62
+ def admin_logout():
63
+ session.pop('admin_logged_in', None)
64
+ return redirect(url_for('admin_login'))
65
+
66
+ # 后台管理面板
67
+ @app.route('/admin/dashboard')
68
+ def admin_dashboard():
69
+ if not session.get('admin_logged_in'):
70
+ return redirect(url_for('admin_login'))
71
+
72
+ post_count = Post.query.count()
73
+ return render_template('admin/dashboard.html', post_count=post_count)
74
+
75
+ # 文章管理
76
+ @app.route('/admin/posts')
77
+ def admin_posts():
78
+ if not session.get('admin_logged_in'):
79
+ return redirect(url_for('admin_login'))
80
+
81
+ page = request.args.get('page', 1, type=int)
82
+ posts = Post.query.order_by(Post.created_at.desc()).paginate(
83
+ page=page, per_page=10, error_out=False)
84
+ return render_template('admin/posts.html', posts=posts)
85
+
86
+ # 创建/编辑文章
87
+ @app.route('/admin/post', methods=['GET', 'POST'])
88
+ @app.route('/admin/post/<int:post_id>', methods=['GET', 'POST'])
89
+ def admin_edit_post(post_id=None):
90
+ if not session.get('admin_logged_in'):
91
+ return redirect(url_for('admin_login'))
92
+
93
+ if post_id:
94
+ post = Post.query.get_or_404(post_id)
95
+ else:
96
+ post = Post()
97
+
98
+ if request.method == 'POST':
99
+ post.title = request.form['title']
100
+ post.content = request.form['content']
101
+
102
+ if post_id is None:
103
+ db.session.add(post)
104
+ db.session.commit()
105
+ flash('文章保存成功')
106
+ return redirect(url_for('admin_posts'))
107
+
108
+ return render_template('admin/edit_post.html', post=post)
109
+
110
+ # 删除文章
111
+ @app.route('/admin/post/delete/<int:post_id>', methods=['POST'])
112
+ def admin_delete_post(post_id):
113
+ if not session.get('admin_logged_in'):
114
+ return redirect(url_for('admin_login'))
115
+
116
+ post = Post.query.get_or_404(post_id)
117
+ db.session.delete(post)
118
+ db.session.commit()
119
+ flash('文章删除成功')
120
+ return redirect(url_for('admin_posts'))
121
+
122
+ if __name__ == '__main__':
123
+ app.run(debug=True)
@@ -0,0 +1,46 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from app import app, db
16
+ from models import User
17
+ from werkzeug.security import generate_password_hash
18
+ from sqlalchemy.exc import OperationalError
19
+
20
+ def init_database():
21
+ with app.app_context():
22
+ try:
23
+ # 创建所有数据库表
24
+ db.metadata.create_all(bind=db.engine, checkfirst=True)
25
+ print("数据库表创建成功")
26
+ except OperationalError as e:
27
+ if "table already exists" in str(e).lower():
28
+ print("数据库表已存在,跳过创建")
29
+ else:
30
+ print(f"创建数据库表时出错: {e}")
31
+ raise
32
+
33
+ # 创建默认管理员账户(如不存在)
34
+ if not User.query.filter_by(username='admin').first():
35
+ admin = User(
36
+ username='admin',
37
+ password=generate_password_hash('admin123')
38
+ )
39
+ db.session.add(admin)
40
+ db.session.commit()
41
+ print("默认管理员账户创建成功")
42
+ else:
43
+ print("默认管理员账户已存在,跳过创建")
44
+
45
+ if __name__ == '__main__':
46
+ init_database()
@@ -0,0 +1,36 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from flask_sqlalchemy import SQLAlchemy
16
+ from datetime import datetime
17
+
18
+ db = SQLAlchemy()
19
+
20
+ class Post(db.Model):
21
+ id = db.Column(db.Integer, primary_key=True)
22
+ title = db.Column(db.String(200), nullable=False)
23
+ content = db.Column(db.Text, nullable=False)
24
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
25
+ updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
26
+
27
+ def __repr__(self):
28
+ return f'<Post {self.title}>'
29
+
30
+ class User(db.Model):
31
+ id = db.Column(db.Integer, primary_key=True)
32
+ username = db.Column(db.String(80), unique=True, nullable=False)
33
+ password = db.Column(db.String(200), nullable=False)
34
+
35
+ def __repr__(self):
36
+ return f'<User {self.username}>'
@@ -0,0 +1,4 @@
1
+ Flask==2.3.2
2
+ Flask-SQLAlchemy==3.0.5
3
+ Werkzeug==2.3.6
4
+ gunicorn==20.1.0
@@ -0,0 +1,21 @@
1
+ #!/bin/bash
2
+
3
+ # 兼容源码部署到faas & 镜像部署到faas
4
+ pip install -r requirements.txt
5
+
6
+ HOST="0.0.0.0"
7
+ PORT="${_FAAS_RUNTIME_PORT:-8000}"
8
+
9
+ export SERVER_HOST=$HOST
10
+ export SERVER_PORT=$PORT
11
+
12
+ # 设置环境变量
13
+ export FLASK_APP=app.py
14
+ export FLASK_ENV=production
15
+
16
+ # 初始化数据库
17
+ python init_db.py
18
+
19
+ echo "Starting Web application..."
20
+ # 启动应用,使用生产服务器配置
21
+ exec python -m gunicorn -w 4 -b $SERVER_HOST:$SERVER_PORT app:app