Render AND Redirect_to

本文详细介绍了Rails应用中render和redirect_to的区别及其使用场景,包括如何渲染模板、跳转到动作、链接到资源等,并提供了丰富的实例。
==render==
render可以翻译成"渲染",也就是说,render仅仅渲染了一个新的模板,而没有执行相应的action。它是简单的页面渲染,可以指定渲染页面和布局文件,不会发送请求,不会执行action函数,不会重新加载服务器数据。
如果要链接到同一个控制器中的动作,不用指定 :controller 选项,因为默认情况下使用的就是当前控制器。
在开发模式下(默认),每次请求 Rails 都会重新加载程序,因此修改之后无需重启服务器。
#flash、cookie 及 session 都能够完成视图间的数据传递。但 flash 一般用于简单的提示消息,而 cookie 及 session 则常见于用户信息管理。
#form_for和model绑定,而form_tag不是;form_tag想传什么参数都行,没有约束。
#路由使用 HTTP 方法和 URL 匹配请求,把四个 URL 映射到七个不同的动作上。
每产生一个Model程式时,Rails自动产生对应的Migration档案,类似于 db/migrate/20110519123430_create_events.rb,Rails是按照时间戳来命名档案的,每次产生的档案名不同,以避免多人开发时的冲突。
Migration档案不需要和Model一一对应。
在 Rails 中,方法分为 public、private 和 protected 三种,只有 public 方法才能作为控制器的动作。
:handlers 表示用来处理模板的程序,HTML 模板一般使用 :erb,XML 模板使用 :builder,:coffee 用来把 CoffeeScript 转换成 JavaScript。


render(:text => string)	#直接渲染出文本	
render(:inline => string, [:type => "rhtml"|"rxml"])	#把传入的string渲染成模板(rhtml或者rxml)
render(:action => action_name)		#直接调用某个action的模板,相当于forward到一个view
render(:file => path, [:use_full_path => true|false])	#使用某个模板文件render, 当use_full_path参数为true时可以传入相对路径
render(:template => name)	#使用模板名render,e.x.: render(:template => “blog/short_list”)
render(:partial => name)		#以局部模板渲染
render(:nothing=>true)	#什么也不输出,包括layout
render()				#默认的的render, 相当于render(:action => self)

render :plain will set the content type to text/plain
render :html will set the content type to text/html
render :body will not set the content type header.
reder :text 	将来版本中不再使用。
render   'article/show'	转向其它方法
 
用render渲染一个模板能够显示错误信息,但用redirect_to重新请求就没有.
redirect_to :action => :index需要跳转到index这个action,然后渲染index.html.erb,额外增加了一次http请求。直接render "index", :alert => 'Your book was not found!'就把额外的这次请求给免了.


method: :patch 选项告诉 Rails,提交这个表单时使用 PATCH 方法发送请求。根据 REST 架构,更新资源时要使用 HTTP PATCH 方法。

按照约定,局部视图的文件名以下划线开头。

渲染局部视图中的集合:把代码抽出来,写入局部视图文件(app/views/comments/_comment.html.erb )中,“<%= render @article.comments %>”,render会使用局部视图渲染@article.comments 集合,render 方法会遍历 @article.comments 集合,把每个评论赋值给一个和局部视图同名的本地变量,在这个例子中本地变量是 comment,这个本地变量可以在局部视图中使用。
渲染局部视图中的表单:把代码也移到局部视图文件(app/views/comments/_form.html.erb)中, “<%= render "comments/form" %>”,render 方法的参数就是要渲染的局部视图,Rails 很智能,能解析其中的斜线,知道要渲染 文件夹( app/views/comments)中的 _form.html.erb 模板。

1。无论是render 还是 redirect_to 都是方法体内的内容全部执行完再跳转,就算跳转了,方法体内的还是会全部执行的
2。render 是跳转到对应的view下html

=== redirect_to ===
redirect_to实现的是action方法的跳转,向浏览器发起一个新的请求,执行对应的action,重新加载服务器数据,不保留页面原有数据;如:
redirect_to :controller => 'thing',:action => 'edit', :id => 7	#跳转到相应的方法,同时传递参数 id ,貌似redirect_to不能以post的方式提交数据,默认的方式是get,详见 http://stackoverflow.com/questions/2144391/ruby-on-rails-post-parameters-on-redirect-to。
redirect_to edit_multiple_items_path(:page =>2), :notice => 'items updated'   使用path的方式。w

redirect_to :back		#回到上一次访问的页面
redirect_to "http://localhost/"	#跳转到本地首页
redirect_to "/images/1.jpg"		#跳转到图片资源

####Link_to
#ref: http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to
#	 http://mixandgo.com/blog/how-to-use-link_to-in-rails
#	http://stackoverflow.com/questions/3762430/rails-preserving-get-query-string-parameters-in-link-to/4174493#4174493

<%= link_to "Home", root_path %>
# => <a href="/">Home</a>

#	Link-ing to a resource
<%= link_to "Profile", user_path(@user) %>
# => <a href="/users/1">Profile</a>

link_to "Comment wall", profile_path(@profile, :anchor => "wall")
#=> <a href="/profiles/1#wall">Comment wall</a>

link_to "Ruby on Rails search", :controller => "searches", :query => "ruby on rails"
#=> <a href="/searches?query=ruby+on+rails">Ruby on Rails search</a>

link_to "Nonsense search", searches_path(:foo => "bar", :baz => "quux")
#=> <a href="/searches?foo=bar&amp;baz=quux">Nonsense search</a>

给link_to加上class样式 比如我们的class样式为: classror:
<%= link_to "Link name", { :controller => 'controller_name',:action => 'action_name' },:class => 'classror' -%>

给link_to加上ID样式 这里说的不是javascript调用的 这里说的是样式ID(#):
<%= link_to "Link name", { :controller => 'controller_name',:action => 'action_name' },:id=> 'idror' -%>


转载于:https://my.oschina.net/u/2407499/blog/531979

module KanbansHelper def kanban_contextual_menu(&block) menu = KanbanContextualMenu.new( kanban: @kanban, user: @user, view_context: self # 传递当前 Helper 实例作为视图上下文 ) concat(menu.display(capture(menu, &block))) end def name_to_css(name) name.gsub(' ','-').downcase end def jquery_dialog_div(title=:field_issue) "<div id='dialog-window' title='#{ l(title) }'></div>" end def render_pane_to_js(pane, user=nil) if Kanban.valid_panes.include?(pane) return render_to_string(:partial => pane, :locals => {:user => user }) else '' end end # Returns the CSS class for jQuery to hook into. Current users are # allowed to Drag and Drop items into their own list, but not other # people's lists def allowed_to_assign_staffed_issue_to(user) if allowed_to_manage? || User.current == user 'allowed' else '' end end def over_pane_limit?(limit, counter) if !counter.nil? && !limit.nil? && counter.to_i >= limit.to_i # 0 based counter return 'over-limit' else return '' end end # Was the last journal with a note created by someone other than the # assigned to user? def updated_note_on_issue?(issue) if issue && issue.journals.present? last_journal_with_note = issue.journals.select {|journal| journal.notes.present?}.last if last_journal_with_note && issue.assigned_to_id != last_journal_with_note.user_id last_journal_with_note else return false end end end def issue_updated_note_icon(issue) if last_journal = updated_note_on_issue?(issue) image_tag('comment.png', :class => "updated-note issue-show-popup issue-id-#{h(issue.id)}", :id => "issue-#{h(issue.id)}", :alt => l(:kanban_text_updated_issue), :title => h(last_journal.notes)) end end def kanban_issue_css_classes(issue) css = 'kanban-issue ' + issue.css_classes if User.current.logged? && !issue.assigned_to_id.nil? && issue.assigned_to_id != User.current.id css << ' assigned-to-other' end css << ' issue-behind-schedule' if issue.behind_schedule? css << ' issue-overdue' if issue.overdue? css << ' parent-issue' if issue.root? && issue.children.count > 0 css end def issue_icon_link(issue) if Setting.gravatar_enabled? && issue.assigned_to img = avatar(issue.assigned_to, { :class => 'gravatar icon-gravatar', :size => 10, :title => l(:field_assigned_to) + ": " + issue.assigned_to.name }) link_to(img, :controller => 'issues', :action => 'show', :id => issue) else link_to(image_tag('ticket.png', :style => 'float:left;'), :controller => 'issues', :action => 'show', :id => issue) end end def column_configured?(column) case column when :incoming KanbanPane::IncomingPane.configured? when :backlog KanbanPane::BacklogPane.configured? when :selected KanbanPane::QuickPane.configured? || KanbanPane::SelectedPane.configured? when :staffed true # always end end # Calculates the width of the column. Max of 96 since they need # some extra for the borders. def column_width(column) # weights of the columns column_ratios = { :incoming => 1, :backlog => 1, :selected => 1, :staffed => 4 } return 0.0 if column == :incoming && !column_configured?(:incoming) return 0.0 if column == :backlog && !column_configured?(:backlog) return 0.0 if column == :selected && !column_configured?(:selected) visible = 0 visible += column_ratios[:incoming] if column_configured?(:incoming) visible += column_ratios[:backlog] if column_configured?(:backlog) visible += column_ratios[:selected] if column_configured?(:selected) visible += column_ratios[:staffed] if column_configured?(:staffed) return ((column_ratios[column].to_f / visible) * 96).round(2) end def my_kanban_column_width(column) column_ratios = { :project => 1, :testing => 1, :active => 1, :selected => 1, :backlog => 1 } # Vertical column if column == :incoming return (KanbanPane::IncomingPane.configured? ? 100.0 : 0.0) end # Inside of Project, max width if column == :finished || column == :canceled return 100.0 end return 0.0 if column == :active && !KanbanPane::ActivePane.configured? return 0.0 if column == :testing && !KanbanPane::TestingPane.configured? return 0.0 if column == :selected && !KanbanPane::SelectedPane.configured? return 0.0 if column == :backlog && !KanbanPane::BacklogPane.configured? visible = 0 visible += column_ratios[:project] visible += column_ratios[:active] if KanbanPane::ActivePane.configured? visible += column_ratios[:testing] if KanbanPane::TestingPane.configured? visible += column_ratios[:selected] if KanbanPane::SelectedPane.configured? visible += column_ratios[:backlog] if KanbanPane::BacklogPane.configured? return ((column_ratios[column].to_f / visible) * 96).round(2) end # Calculates the width of the column. Max of 96 since they need # some extra for the borders. def staffed_column_width(column) # weights of the columns column_ratios = { :user => 1, :active => 2, :testing => 2, :finished => 2, :canceled => 2 } return 0.0 if column == :active && !KanbanPane::ActivePane.configured? return 0.0 if column == :testing && !KanbanPane::TestingPane.configured? return 0.0 if column == :finished && !KanbanPane::FinishedPane.configured? return 0.0 if column == :canceled && !KanbanPane::CanceledPane.configured? visible = 0 visible += column_ratios[:user] visible += column_ratios[:active] if KanbanPane::ActivePane.configured? visible += column_ratios[:testing] if KanbanPane::TestingPane.configured? visible += column_ratios[:finished] if KanbanPane::FinishedPane.configured? visible += column_ratios[:canceled] if KanbanPane::CanceledPane.configured? return ((column_ratios[column].to_f / visible) * 96).round(2) end def issue_url(issue) url_for(:controller => 'issues', :action => 'show', :id => issue) end def showing_current_user_kanban? @user == User.current end # Renders the title for the "Incoming" project. It can be linked as: # * New Issue jQuery dialog (user has permission to add issues) # * Link to the url configured in the plugin (plugin is configured with a url) # * No link at all def incoming_title if Setting.plugin_redmine_kanban['panes'].present? && Setting.plugin_redmine_kanban['panes']['incoming'].present? && Setting.plugin_redmine_kanban['panes']['incoming']['url'].present? href_url = Setting.plugin_redmine_kanban['panes']['incoming']['url'] incoming_project = extract_project_from_url(href_url) link_name = incoming_project.present? ? incoming_project.name : l(:kanban_text_incoming) else href_url = '' link_name = l(:kanban_text_incoming) end if User.current.allowed_to?(:add_issues, nil, :global => true) link_to(link_name, href_url, :class => 'new-issue-dialog') elsif href_url.present? link_to(link_name, href_url) else link_name end end # Given a url, extract the project record from it. # Will return nil if the url isn't a link to a project or the url can't be # recognized def extract_project_from_url(url) project = nil link_path = url. sub(request.host, ''). # Remove host sub(/https?:\/\//,''). # Protocol sub(/\?.*/,'') # Query string begin route = ActionController::Routing::Routes.recognize_path(link_path, :method => :get) if route[:controller] == 'projects' && route[:id] project = Project.find(route[:id]) end rescue ActionController::RoutingError # Parse failed, not a route end end def export_i18n_for_javascript strings = { 'kanban_text_error_saving_issue' => l(:kanban_text_error_saving_issue), 'kanban_text_issue_created_reload_to_see' => l(:kanban_text_issue_created_reload_to_see), 'kanban_text_issue_updated_reload_to_see' => l(:kanban_text_issue_updated_reload_to_see), 'kanban_text_notice_reload' => l(:kanban_text_notice_reload), 'kanban_text_watch_and_cancel_hint' => l(:kanban_text_watch_and_cancel_hint), 'kanban_text_issue_watched_reload_to_see' => l(:kanban_text_issue_watched_reload_to_see) } javascript_tag("var i18n = #{strings.to_json}") end def viewed_user return @user if @user.present? return User.current end def use_simple_issue_popup_form? # TODO: Hate how Settings is stored... @settings['simple_issue_popup_form'] && ( @settings['simple_issue_popup_form'] == '1' || @settings['simple_issue_popup_form'] == 1 || @settings['simple_issue_popup_form'] == true ) end # Load remote RJS/HTML data from url into dom_id def kanban_remote_data(url, dom_id) javascript_tag("Kanban.remoteData('#{url}', '#{dom_id}');") + content_tag(:span, l(:label_loading), :class => 'loading') end # Returns a list of pane names in the configured order. # # @param hash options Method options # @option options Array :only Filter the panes to only include these ones def ordered_panes(options={}) only = options[:only] || [] if only.present? KanbanPane.pane_order.select {|pane| only.include?(pane) } else KanbanPane.pane_order end end class UserKanbanDivHelper < BlockHelpers::Base include ERB::Util def self.parent superclass end def initialize(options={}) @column = options[:column] @user = options[:user] @project_id = options[:project_id] end def issues(issues) if issues.compact.empty? || issues.flatten.compact.empty? render :partial => 'kanbans/empty_issue' else render(:partial => 'kanbans/issue', :collection => issues.flatten, :locals => { :limit => Setting['plugin_redmine_kanban']["panes"][@column.to_s]["limit"].to_i }) end end def display(body) content_tag(:div, content_tag(:ol, body, :id => "#{@column}-issues-user-#{h(@user.id)}-project-#{h(@project_id)}", :class => "#{@column}-issues"), :id => "#{@column}-#{h(@user.id)}-project-#{h(@project_id)}", :class => "pane equal-column #{@column} user-#{h(@user.id)}", :style => "width: #{ helper.my_kanban_column_width(@column)}%") end end class KanbanContextualMenu < BlockHelpers::Base include ActionView::Helpers # 引入 Rails 视图方法 delegate :link_to, :content_tag, to: :@view_context def initialize(options={}) @view_context = options.delete(:view_context) || ActionController::Base.helpers super(options) @kanban = options[:kanban] @user = options[:user] end def color_help link_to_function(l(:kanban_text_color_help), "$('color-help').toggle();", :class => 'icon icon-info') end def kanban_board if User.current.allowed_to?(:view_kanban, nil, :global => true) link_to(l(:text_kanban_board), kanban_url, :class => 'icon icon-stats') end end def my_kanban_requests link_to(l(:text_my_kanban_requests_title), kanban_user_kanban_path(:id => User.current.id), :class => 'icon icon-user') end def assigned_requests link_to(l(:text_assigned_kanban_title), kanban_assigned_kanban_path(:id => User.current.id), :class => 'icon icon-user') end def new_issue if User.current.allowed_to?(:add_issues, nil, :global => true) if Setting.plugin_redmine_kanban['panes'].present? && Setting.plugin_redmine_kanban['panes']['incoming'].present? && Setting.plugin_redmine_kanban['panes']['incoming']['url'].present? incoming_url = Setting.plugin_redmine_kanban['panes']['incoming']['url'] incoming_project = extract_project_from_url(incoming_url) link_name = incoming_project.present? ? incoming_project.name : l(:label_issue_new) else link_name = l(:label_issue_new) end link_to_function(link_name, "void(0)", :class => 'new-issue-dialog icon icon-issue') end end def sync_kanban if User.current.allowed_to?(:edit_kanban, nil, :global => true) link_to(l(:kanban_text_sync), sync_kanban_url, :method => :put, :class => 'icon icon-reload') end end # @param [Hash] options # @option options [String] :url URL that the user switch form should post to # @option options [String] :label Text to use for the Switch User label (i18n'd already) # @option options [String] :users Users to allow switching to. Defaults to all active Users def user_switch(options={}) url = options[:url] label = options[:label] || l(:label_user_switch) users = options[:users] || User.active if kanban_settings["management_group"] && User.current.group_ids.include?(kanban_settings["management_group"].to_i) render :partial => 'kanbans/user_switch', :locals => {:url => url, :label => label, :users => users.sort} end end def display(body) content = call_hook(:view_user_kanbans_show_contextual_top, :user => @user, :kanban => @kanban).to_s content += body content += call_hook(:view_user_kanbans_show_contextual_bottom, :user => @user, :kanban => @kanban).to_s content_tag(:div, content, :class => "contextual") end end end
09-11
[17/Jun/2025 17:17:57] "GET / HTTP/1.1" 302 0 Internal Server Error: /login/ Traceback (most recent call last): File "C:\Users\17710\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.13_qbz5n2kfra8p0\LocalCache\local-packages\Python313\site-packages\django\core\handlers\exception.py", line 55, in inner response = get_response(request) File "C:\Users\17710\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.13_qbz5n2kfra8p0\LocalCache\local-packages\Python313\site-packages\django\core\handlers\base.py", line 197, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "C:\Users\17710\Supermarke\Member\views.py", line 11, in login return render(request, 'login.html') File "C:\Users\17710\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.13_qbz5n2kfra8p0\LocalCache\local-packages\Python313\site-packages\django\shortcuts.py", line 25, in render content = loader.render_to_string(template_name, context, request, using=using) File "C:\Users\17710\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.13_qbz5n2kfra8p0\LocalCache\local-packages\Python313\site-packages\django\template\loader.py", line 61, in render_to_string template = get_template(template_name, using=using) File "C:\Users\17710\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.13_qbz5n2kfra8p0\LocalCache\local-packages\Python313\site-packages\django\template\loader.py", line 19, in get_template raise TemplateDoesNotExist(template_name, chain=chain) django.template.exceptions.TemplateDoesNotExist: login.html [17/Jun/2025 17:17:57] "GET /login/ HTTP/1.1" 500 83493 解决这个问题
06-18
无法查找到HTML模板,我的程序如下from flask import Flask, render_template, request, jsonify, redirect, url_for from flask_sqlalchemy import SQLAlchemy from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user from werkzeug.security import generate_password_hash, check_password_hash import requests import json import os import uuid import redis import time from datetime import datetime, UTC from functools import wraps from dotenv import load_dotenv # 加载环境变量 load_dotenv() base_dir = os.path.abspath(os.path.dirname(__file__)) app = Flask(__name__, template_folder=os.path.join(base_dir, 'templates'), static_folder=os.path.join(base_dir, 'static')) print(f"当前文件位置: {os.path.abspath(__file__)}") print(f"项目根目录: {base_dir}") print(f"模板路径: {app.template_folder}") print(f"模板目录内容: {os.listdir(app.template_folder)}") app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'supersecretkey') app.config[ 'SQLALCHEMY_DATABASE_URI'] = f"mysql+mysqlconnector://{os.getenv('DB_USER')}:{os.getenv('DB_PASSWORD')}@{os.getenv('DB_HOST')}/{os.getenv('DB_NAME')}" app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['COZE_API_KEY'] = os.getenv('COZE_API_KEY') app.config['COZE_BOT_ID'] = os.getenv('COZE_BOT_ID') app.config['COZE_API_URL'] = "https://api.coze.cn/v3/chat" app.config['REDIS_URL'] = os.getenv('REDIS_URL', 'redis://localhost:6379/0') app.config['TOKEN_PRICE'] = 0.01 # 每个token的价格(美元) # 初始化扩展 db = SQLAlchemy(app) login_manager = LoginManager(app) login_manager.login_view = 'login' # 初始化Redis redis_client = redis.Redis.from_url(app.config['REDIS_URL']) # 数据库模型 class User(UserMixin, db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(50), unique=True, nullable=False) password = db.Column(db.String(255), nullable=False) email = db.Column(db.String(100), unique=True, nullable=False) is_admin = db.Column(db.Boolean, default=False) token_balance = db.Column(db.Integer, default=0) created_at = db.Column(db.DateTime, default=lambda: datetime.now(UTC)) api_keys = db.relationship('APIKey', backref='user', lazy=True) recharges = db.relationship('Recharge', backref='user', foreign_keys='Recharge.user_id', lazy=True) messages = db.relationship('MessageCache', backref='user', lazy=True) class APIKey(db.Model): __tablename__ = 'api_keys' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) api_key = db.Column(db.String(255), unique=True, nullable=False) is_active = db.Column(db.Boolean, default=True) created_at = db.Column(db.DateTime, default=lambda: datetime.now(UTC)) class Recharge(db.Model): __tablename__ = 'recharges' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) admin_id = db.Column(db.Integer, db.ForeignKey('users.id')) amount = db.Column(db.Integer, nullable=False) status = db.Column(db.String(20), default='pending') created_at = db.Column(db.DateTime, default=lambda: datetime.now(UTC)) processed_at = db.Column(db.DateTime) # 明确指定关系 admin = db.relationship('User', foreign_keys=[admin_id]) class MessageCache(db.Model): __tablename__ = 'message_cache' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) request = db.Column(db.Text, nullable=False) response = db.Column(db.Text, nullable=False) tokens_used = db.Column(db.Integer, nullable=False) created_at = db.Column(db.DateTime, default=lambda: datetime.now(UTC)) # Flask-Login 用户加载器 @login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id)) # 认证装饰器 def admin_required(f): @wraps(f) def decorated_function(*args, **kwargs): if not current_user.is_admin: return redirect(url_for('dashboard')) return f(*args, **kwargs) return decorated_function # 工具函数 def generate_api_key(): return str(uuid.uuid4()).replace('-', '') def format_to_openai(coze_response): """将Coze API响应转换为OpenAI格式""" choices = [] if 'messages' in coze_response: for msg in coze_response['messages']: if msg['role'] == 'assistant': choices.append({ "index": 0, "message": { "role": "assistant", "content": msg.get('content', '') }, "finish_reason": "stop" }) usage = coze_response.get('usage', {}) return { "id": f"chatcmpl-{int(time.time())}", "object": "chat.completion", "created": int(time.time()), "model": "coze-bot", "choices": choices, "usage": { "prompt_tokens": usage.get('prompt_tokens', 0), "completion_tokens": usage.get('completion_tokens', 0), "total_tokens": usage.get('total_tokens', 0) } } def deduct_tokens(user_id, amount): """扣除用户token""" user = User.query.get(user_id) if not user or user.token_balance < amount: return False user.token_balance -= amount db.session.commit() return True def add_tokens(user_id, amount): """增加用户token""" user = User.query.get(user_id) if not user: return False user.token_balance += amount db.session.commit() return True # 路由定义 @app.route('/') def index(): template_path = os.path.join(app.template_folder, 'index.html') if not os.path.exists(template_path): return f"模板文件不存在!路径: {template_path}", 500 try: return render_template('index.html') except Exception as e: return f"渲染错误: {str(e)}", 500 @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': username = request.form['username'] password = request.form['password'] user = User.query.filter_by(username=username).first() if user and check_password_hash(user.password, password): login_user(user) return redirect(url_for('dashboard')) return render_template('login.html', error='Invalid username or password') return render_template('login.html') @app.route('/register', methods=['GET', 'POST']) def register(): if request.method == 'POST': username = request.form['username'] email = request.form['email'] password = generate_password_hash(request.form['password']) admin_code = request.form.get('admin_code', '') # 检查用户名和邮箱是否已存在 if User.query.filter_by(username=username).first(): return render_template('register.html', error='Username already exists') if User.query.filter_by(email=email).first(): return render_template('register.html', error='Email already exists') is_admin = admin_code == '789456123' new_user = User( username=username, email=email, password=password, is_admin=is_admin ) db.session.add(new_user) db.session.commit() login_user(new_user) return redirect(url_for('dashboard')) return render_template('register.html') @app.route('/logout') @login_required def logout(): logout_user() return redirect(url_for('index')) @app.route('/dashboard') @login_required def dashboard(): return render_template('dashboard.html', user=current_user) @app.route('/admin/dashboard') @login_required @admin_required def admin_dashboard(): users = User.query.all() pending_recharges = Recharge.query.filter_by(status='pending').all() return render_template('admin_dashboard.html', users=users, recharges=pending_recharges) @app.route('/generate-api-key', methods=['POST']) @login_required def generate_api_key_route(): new_key = APIKey( user_id=current_user.id, api_key=generate_api_key() ) db.session.add(new_key) db.session.commit() return redirect(url_for('api_keys')) @app.route('/api-keys') @login_required def api_keys(): return render_template('api_keys.html', api_keys=current_user.api_keys) @app.route('/toggle-api-key/<int:key_id>', methods=['POST']) @login_required def toggle_api_key(key_id): api_key = APIKey.query.get_or_404(key_id) if api_key.user_id != current_user.id and not current_user.is_admin: return jsonify({"error": "Unauthorized"}), 403 api_key.is_active = not api_key.is_active db.session.commit() return redirect(url_for('api_keys')) @app.route('/recharge', methods=['POST']) @login_required def recharge(): try: amount = int(request.form['amount']) except ValueError: return render_template('dashboard.html', user=current_user, error='Invalid amount') if amount <= 0: return render_template('dashboard.html', user=current_user, error='Amount must be positive') new_recharge = Recharge( user_id=current_user.id, amount=amount ) db.session.add(new_recharge) db.session.commit() return render_template('dashboard.html', user=current_user, success='Recharge request submitted') @app.route('/admin/approve-recharge/<int:recharge_id>', methods=['POST']) @login_required @admin_required def approve_recharge(recharge_id): recharge = Recharge.query.get_or_404(recharge_id) if recharge.status != 'pending': return jsonify({"error": "Recharge already processed"}), 400 recharge.status = 'approved' recharge.processed_at = lambda: datetime.now(UTC) recharge.admin_id = current_user.id add_tokens(recharge.user_id, recharge.amount) db.session.commit() return redirect(url_for('admin_dashboard')) @app.route('/admin/reject-recharge/<int:recharge_id>', methods=['POST']) @login_required @admin_required def reject_recharge(recharge_id): recharge = Recharge.query.get_or_404(recharge_id) if recharge.status != 'pending': return jsonify({"error": "Recharge already processed"}), 400 recharge.status = 'rejected' recharge.processed_at = lambda: datetime.now(UTC) recharge.admin_id = current_user.id db.session.commit() return redirect(url_for('admin_dashboard')) @app.route('/admin/update-tokens/<int:user_id>', methods=['POST']) @login_required @admin_required def update_tokens(user_id): user = User.query.get_or_404(user_id) try: amount = int(request.form['amount']) except ValueError: return jsonify({"error": "Invalid amount"}), 400 user.token_balance = amount db.session.commit() return redirect(url_for('admin_dashboard')) @app.route('/admin/toggle-user/<int:user_id>', methods=['POST']) @login_required @admin_required def toggle_user(user_id): user = User.query.get_or_404(user_id) # 切换所有API密钥状态 for api_key in user.api_keys: api_key.is_active = not api_key.is_active db.session.commit() return redirect(url_for('admin_dashboard')) # API端点 @app.route('/v1/chat/completions', methods=['POST']) def chat_completion(): # 验证API密钥 auth_header = request.headers.get('Authorization') if not auth_header or not auth_header.startswith('Bearer '): return jsonify({"error": "Missing API key"}), 401 api_key = auth_header[7:] # 移除"Bearer " api_key_obj = APIKey.query.filter_by(api_key=api_key).first() if not api_key_obj or not api_key_obj.is_active: return jsonify({"error": "Invalid API key"}), 401 user = api_key_obj.user # 检查token余额 if user.token_balance <= 0: return jsonify({"error": "Insufficient token balance"}), 403 # 尝试从Redis缓存获取响应 cache_key = f"request:{api_key}:{hash(str(request.json))}" cached_response = redis_client.get(cache_key) if cached_response: return jsonify(json.loads(cached_response)) # 处理图片和文本 messages = request.json.get('messages', []) coze_messages = [] for msg in messages: if 'content' in msg: # 简单判断是否为图片(实际应用中可能需要更复杂的逻辑) content_type = 'image' if isinstance(msg['content'], dict) or msg['content'].startswith( 'data:image') else 'text' coze_messages.append({ "role": msg['role'], "content": msg['content'], "content_type": content_type }) # 构建Coze API请求 coze_data = { "bot_id": app.config['COZE_BOT_ID'], "user_id": str(user.id), "stream": False, "auto_save_history": True, "additional_messages": coze_messages } headers = { 'Authorization': f'Bearer {app.config["COZE_API_KEY"]}', 'Content-Type': 'application/json' } # 发送请求到Coze API try: response = requests.post( app.config['COZE_API_URL'], headers=headers, json=coze_data, timeout=30 ) response.raise_for_status() coze_response = response.json() except requests.exceptions.RequestException as e: app.logger.error(f"Coze API request failed: {str(e)}") return jsonify({"error": "Failed to communicate with Coze API"}), 500 except json.JSONDecodeError: app.logger.error("Failed to parse Coze API response") return jsonify({"error": "Invalid response from Coze API"}), 500 # 处理token消耗 tokens_used = coze_response.get('usage', {}).get('total_tokens', 100) # 默认100 # 扣除token if not deduct_tokens(user.id, tokens_used): return jsonify({"error": "Failed to deduct tokens"}), 500 # 转换为OpenAI格式 openai_response = format_to_openai(coze_response) # 缓存到Redis(1小时) redis_client.setex(cache_key, 3600, json.dumps(openai_response)) # 保存到数据库 new_cache = MessageCache( user_id=user.id, request=json.dumps(request.json), response=json.dumps(openai_response), tokens_used=tokens_used ) db.session.add(new_cache) db.session.commit() return jsonify(openai_response) # 初始化数据库 @app.before_first_request def create_tables(): db.create_all() # 创建初始管理员账户(如果不存在) admin_username = os.getenv('ADMIN_USERNAME', 'admin') admin_email = os.getenv('ADMIN_EMAIL', 'admin@example.com') admin_password = os.getenv('ADMIN_PASSWORD', 'adminpassword') if not User.query.filter_by(username=admin_username).first(): admin = User( username=admin_username, email=admin_email, password=generate_password_hash(admin_password), is_admin=True ) db.session.add(admin) db.session.commit() # 错误处理 @app.errorhandler(404) def page_not_found(e): return render_template('404.html'), 404 @app.errorhandler(500) def internal_server_error(e): return render_template('500.html'), 500 if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True)
07-07
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值