From b1c10d66f9cfb0e057bd8f517048c8f9a2784a4e Mon Sep 17 00:00:00 2001 From: xxq250 Date: Thu, 2 Mar 2023 13:49:50 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8C=BA=E5=9D=97=E9=93=BE=E7=A1=AE=E6=9D=83?= =?UTF-8?q?=E7=9B=B8=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/application_controller.rb | 319 +++++++++++++++ app/controllers/issues_controller.rb | 13 +- app/controllers/journals_controller.rb | 7 +- app/controllers/projects_controller.rb | 3 +- app/controllers/pull_requests_controller.rb | 53 ++- app/controllers/users_controller.rb | 383 +++++++++++++++++- app/forms/base_form.rb | 8 + app/forms/projects/create_form.rb | 5 +- app/helpers/tag_chosen_helper.rb | 3 +- app/models/concerns/watchable.rb | 205 ++++++++-- app/queries/application_query.rb | 49 +++ app/queries/blockchain/balance_query.rb | 31 ++ .../blockchain/balance_query_one_project.rb | 12 + app/queries/blockchain/repo_basic_info.rb | 13 + app/queries/projects/list_my_query.rb | 6 + app/services/application_service.rb | 144 +++++-- app/services/blockchain/create_issue.rb | 28 ++ app/services/blockchain/create_trade.rb | 29 ++ app/services/blockchain/fix_issue.rb | 28 ++ .../blockchain/invoke_blockchain_api.rb | 34 ++ app/services/blockchain/transfer_service.rb | 36 ++ app/services/projects/create_service.rb | 14 +- config/configuration.yml.example | 3 + config/routes.rb | 15 + ...30070048_add_use_blockchain_to_projects.rb | 5 + ...81736_add_blockchain_token_num_to_issue.rb | 5 + 26 files changed, 1354 insertions(+), 97 deletions(-) create mode 100644 app/queries/blockchain/balance_query.rb create mode 100644 app/queries/blockchain/balance_query_one_project.rb create mode 100644 app/queries/blockchain/repo_basic_info.rb create mode 100644 app/services/blockchain/create_issue.rb create mode 100644 app/services/blockchain/create_trade.rb create mode 100644 app/services/blockchain/fix_issue.rb create mode 100644 app/services/blockchain/invoke_blockchain_api.rb create mode 100644 app/services/blockchain/transfer_service.rb create mode 100644 db/migrate/20201230070048_add_use_blockchain_to_projects.rb create mode 100644 db/migrate/20210421081736_add_blockchain_token_num_to_issue.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 8129df8f1..f98c3c456 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -832,6 +832,325 @@ class ApplicationController < ActionController::Base HotSearchKeyword.add(keyword) end + # author: zxh + # blockchain相关项目活动调用函数 + # return true: 表示上链操作成功; return false: 表示上链操作失败 + def push_activity_2_blockchain(activity_type, model) + if activity_type == "issue_create" + + project_id = model['project_id'] + project = Project.find(project_id) + if project['use_blockchain'] == 0 || project['use_blockchain'] == false + # 无需执行上链操作 + return true + end + + id = model['id'] + + owner_id = project['user_id'] + owner = User.find(owner_id) + ownername = owner['login'] + identifier = project['identifier'] + + author_id = project['user_id'] + author = User.find(author_id) + username = author['login'] + + action = 'opened' + + title = model['subject'] + content = model['description'] + created_at = model['created_on'] + updated_at = model['updated_on'] + + # 调用区块链接口 + params = { + "request-type": "upload issue info", + "issue_id": "gitlink-" + id.to_s, + "repo_id": "gitlink-" + project_id.to_s, + "issue_number": 0, # 暂时不需要改字段 + "reponame": identifier, + "ownername": ownername, + "username": username, + "action": action, + "title": title, + "content": content, + "created_at": created_at, + "updated_at": updated_at + }.to_json + resp_body = Blockchain::InvokeBlockchainApi.call(params) + if resp_body['status'] == 10 + raise Error, resp_body['message'] + elsif resp_body['status'] != 0 + raise Error, "区块链接口请求失败." + end + + elsif activity_type == "issue_comment_create" + issue_comment_id = model['id'] + issue_id = model['journalized_id'] + parent_id = model['parent_id'].nil? ? "" : model['parent_id'] + + issue = Issue.find(issue_id) + issue_classify = issue['issue_classify'] # issue或pull_request + project_id = issue['project_id'] + project = Project.find(project_id) + + if project['use_blockchain'] == 0 || project['use_blockchain'] == false + # 无需执行上链操作 + return + end + + identifier = project['identifier'] + owner_id = project['user_id'] + owner = User.find(owner_id) + ownername = owner['login'] + + author_id = model['user_id'] + author = User.find(author_id) + username = author['login'] + + action = 'created' + + content = model['notes'] + created_at = model['created_on'] + + if issue_classify == "issue" + params = { + "request-type": "upload issue comment info", + "issue_comment_id": "gitlink-" + issue_comment_id.to_s, + "issue_comment_number": 0, # 暂时不需要 + "issue_number": 0, # 暂时不需要 + "issue_id": "gitlink-" + issue_id.to_s, + "repo_id": "gitlink-" + project.id.to_s, + "parent_id": parent_id.to_s, + "reponame": identifier, + "ownername": ownername, + "username": username, + "action": action, + "content": content, + "created_at": created_at, + }.to_json + elsif issue_classify == "pull_request" + params = { + "request-type": "upload pull request comment info", + "pull_request_comment_id": "gitlink-" + issue_comment_id.to_s, + "pull_request_comment_number": 0, # 不考虑该字段 + "pull_request_number": 0, # 不考虑该字段 + "pull_request_id": "gitlink-" + issue_id.to_s, + "parent_id": parent_id.to_s, + "repo_id": "gitlink-" + project.id.to_s, + "reponame": identifier, + "ownername": ownername, + "username": username, + "action": action, + "content": content, + "created_at": created_at, + }.to_json + end + + # 调用区块链接口 + resp_body = Blockchain::InvokeBlockchainApi.call(params) + if resp_body['status'] == 10 + raise Error, resp_body['message'] + elsif resp_body['status'] != 0 + raise Error, "区块链接口请求失败." + end + elsif activity_type == "pull_request_create" + # 调用区块链接口 + project_id = model['project_id'] + project = Project.find(project_id) + if project['use_blockchain'] == 0 || project['use_blockchain'] == false + # 无需执行上链操作 + return + end + + pull_request_id = model['id'] + identifier = project['identifier'] + owner_id = project['user_id'] + owner = User.find(owner_id) + ownername = owner['login'] + + action = 'opened' + + title = model['title'] + content = model['body'] + + source_branch = model['head'] + source_repo_id = model['fork_project_id'].nil? ? project_id : model['fork_project_id'] + + target_branch = model['base'] + target_repo_id = project_id + + author_id = model['user_id'] + author = User.find(author_id) + username = author['login'] + + created_at = model['created_at'] + updated_at = model['updated_at'] + + # 查询pull request对应的commit信息 + commits = Gitea::PullRequest::CommitsService.call(ownername, identifier, model['gitea_number']) + if commits.nil? + raise Error, "区块链接口请求失败" # 获取pr中变更的commit信息失败 + end + commit_shas = [] + commits.each do |c| + commit_shas << c["Sha"] + end + params = { + "request-type": "upload pull request info", + "pull_request_id": "gitlink-" + pull_request_id.to_s, + "pull_request_number": 0, # trustie没有该字段 + "repo_id": "gitlink-" + project_id.to_s, + "ownername": ownername, + "reponame": identifier, + "username": username, + "action": action, + "title": title, + "content": content, + "source_branch": source_branch, + "target_branch": target_branch, + "reviewers": [], # trustie没有该字段 + "commit_shas": commit_shas, + "merge_user": "", # trustie没有该字段 + "created_at": created_at, + "updated_at": updated_at + }.to_json + resp_body = Blockchain::InvokeBlockchainApi.call(params) + if resp_body['status'] == 9 + raise Error, resp_body['message'] + elsif resp_body['status'] != 0 + raise Error, "区块链接口请求失败." + end + elsif activity_type == "pull_request_merge" + # 调用区块链接口 + project_id = model['project_id'] + project = Project.find(project_id) + if project['use_blockchain'] == 0 || project['use_blockchain'] == false + # 无需执行上链操作 + return + end + + pull_request_id = model['id'] + identifier = project['identifier'] + owner_id = project['user_id'] + owner = User.find(owner_id) + ownername = owner['login'] + + action = 'merged' + + created_at = model['created_at'] + updated_at = model['updated_at'] + + # 查询pull request对应的commit信息 + commits = Gitea::PullRequest::CommitsService.call(ownername, identifier, model['gitea_number']) + if commits.nil? + raise Error, "区块链接口请求失败" # 获取pr中变更的commit信息失败 + end + commit_shas = [] + commits.each do |c| + commit_shas << c["Sha"] + end + + # 将pull request相关信息写入链上 + params = { + "request-type": "upload pull request info", + "pull_request_id": "gitlink-" + pull_request_id.to_s, + "pull_request_number": 0, # trustie没有该字段 + "repo_id": "gitlink-" + project_id.to_s, + "ownername": ownername, + "reponame": identifier, + "username": username, + "action": action, + "title": title, + "content": content, + "source_branch": source_branch, + "target_branch": target_branch, + "reviewers": [], # trustie没有该字段 + "commit_shas": commit_shas, + "merge_user": "", # trustie没有该字段 + "created_at": created_at, + "updated_at": updated_at + }.to_json + resp_body = Blockchain::InvokeBlockchainApi.call(params) + if resp_body['status'] == 9 + raise Error, resp_body['message'] + elsif resp_body['status'] != 0 + raise Error, "区块链接口请求失败." + end + + + # 将commit相关信息写入链上 + commit_shas.each do |commit_sha| + commit_diff = Gitea::Commit::DiffService.call(ownername, identifier, commit_sha, owner['gitea_token']) + commit = Gitea::Commit::InfoService.call(ownername, identifier, commit_sha, owner['gitea_token']) + params = { + "request-type": "upload commit info", + "commit_hash": commit_sha, + "repo_id": "gitlink-" + project_id.to_s, + "author": commit['commit']['author']['name'], + "author_email": commit['commit']['author']['email'], + "committer": commit['commit']['committer']['name'], + "committer_email": commit['commit']['committer']['email'], + "author_time": commit['commit']['author']['date'], + "committer_time": commit['commit']['committer']['date'], + "content": commit['commit']['message'], + "commit_diff": commit_diff['Files'].to_s + }.to_json + resp_body = Blockchain::InvokeBlockchainApi.call(params) + if resp_body['status'] == 7 + raise Error, resp_body['message'] + elsif resp_body['status'] != 0 + raise Error, "区块链接口请求失败." + end + end + + elsif activity_type == "pull_request_refuse" + + # 调用区块链接口 + project_id = model['project_id'] + project = Project.find(project_id) + if project['use_blockchain'] == 0 || project['use_blockchain'] == false + # 无需执行上链操作 + return true + end + + pull_request_id = model['id'] + identifier = project['identifier'] + owner_id = project['user_id'] + owner = User.find(owner_id) + ownername = owner['login'] + + action = 'refused' + + # 将pull request相关信息写入链上 + params = { + "request-type": "upload pull request info", + "pull_request_id": "gitlink-" + pull_request_id.to_s, + "pull_request_number": 0, # trustie没有该字段 + "repo_id": "gitlink-" + project_id.to_s, + "ownername": ownername, + "reponame": identifier, + "username": username, + "action": action, + "title": title, + "content": content, + "source_branch": source_branch, + "target_branch": target_branch, + "reviewers": [], # trustie没有该字段 + "commit_shas": commit_shas, + "merge_user": "", # trustie没有该字段 + "created_at": created_at, + "updated_at": updated_at + }.to_json + resp_body = Blockchain::InvokeBlockchainApi.call(params) + if resp_body['status'] == 9 + raise Error, resp_body['message'] + elsif resp_body['status'] != 0 + raise Error, "区块链接口请求失败." + end + end + end def find_atme_receivers @atme_receivers = User.where(login: params[:receivers_login]) end diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index 67fe65641..dfc551e3c 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -151,6 +151,7 @@ class IssuesController < ApplicationController end @issue.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: "create") + @issue.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: ProjectTrend::CLOSE) if params[:status_id].to_i == 5 @@ -159,6 +160,15 @@ class IssuesController < ApplicationController # 新增时向grimoirelab推送事件 IssueWebhookJob.set(wait: 5.seconds).perform_later(@issue.id) + # author: zxh + # 扣除发起人的token + if @issue.blockchain_token_num > 0 + Blockchain::CreateIssue.call(user_id: @issue.author_id, project_id: @issue.project_id, token_num: @issue.blockchain_token_num) + end + + # 调用上链API存证 + push_activity_2_blockchain("issue_create", @issue) + render json: {status: 0, message: "创建成功", id: @issue.id} else normal_status(-1, "创建失败") @@ -546,7 +556,8 @@ class IssuesController < ApplicationController branch_name: params[:branch_name].to_s, issue_classify: "issue", author_id: current_user.id, - project_id: @project.id + project_id: @project.id, + blockchain_token_num: params[:blockchain_token_num] } end diff --git a/app/controllers/journals_controller.rb b/app/controllers/journals_controller.rb index 6dc1e29c9..ff5e28cc1 100644 --- a/app/controllers/journals_controller.rb +++ b/app/controllers/journals_controller.rb @@ -47,10 +47,15 @@ class JournalsController < ApplicationController Rails.logger.info "[ATME] maybe to at such users: #{@atme_receivers.pluck(:login)}" AtmeService.call(current_user, @atme_receivers, journal) if @atme_receivers.size > 0 # @issue.project_trends.create(user_id: current_user.id, project_id: @project.id, action_type: "journal") + + # author: zxh + # 调用上链API + push_activity_2_blockchain("issue_comment_create", journal) + render :json => { status: 0, message: "评论成功", id: journal.id} - # normal_status(0, "评论成功") else normal_status(-1, "评论失败") + raise ActiveRecord::Rollback end end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 52c776477..053c89e02 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -273,7 +273,8 @@ class ProjectsController < ApplicationController private def project_params params.permit(:user_id, :name, :description, :repository_name, :website, :lesson_url, :default_branch, :identifier, - :project_category_id, :project_language_id, :license_id, :ignore_id, :private) + :project_category_id, :project_language_id, :license_id, :ignore_id, :private, + :blockchain, :blockchain_token_all, :blockchain_init_token) end def mirror_params diff --git a/app/controllers/pull_requests_controller.rb b/app/controllers/pull_requests_controller.rb index 2b9bbbe6f..5a6742037 100644 --- a/app/controllers/pull_requests_controller.rb +++ b/app/controllers/pull_requests_controller.rb @@ -6,6 +6,7 @@ class PullRequestsController < ApplicationController before_action :find_pull_request, except: [:index, :new, :create, :check_can_merge,:get_branches,:create_merge_infos, :files, :commits] before_action :load_pull_request, only: [:files, :commits] before_action :find_atme_receivers, only: [:create, :update] + include TagChosenHelper include ApplicationHelper @@ -73,6 +74,11 @@ class PullRequestsController < ApplicationController SendTemplateMessageJob.perform_later('ProjectPullRequest', current_user.id, @pull_request&.id) if Site.has_notice_menu? Rails.logger.info "[ATME] maybe to at such users: #{@atme_receivers.pluck(:login)}" AtmeService.call(current_user, @atme_receivers, @pull_request) if @atme_receivers.size > 0 + + # author: zxh + # 调用上链API + push_activity_2_blockchain("pull_request_create", @pull_request) + else render_error("create pull request error: #{@gitea_pull_request[:status]}") raise ActiveRecord::Rollback @@ -119,7 +125,7 @@ class PullRequestsController < ApplicationController end else return normal_status(-1, "请输入正确的标记。") - end + end end if params[:status_id].to_i == 5 @issue.issue_times.update_all(end_time: Time.now) @@ -149,12 +155,16 @@ class PullRequestsController < ApplicationController ActiveRecord::Base.transaction do begin colsed = PullRequests::CloseService.call(@owner, @repository, @pull_request, current_user) - if colsed === true + # author: zxh + # 调用上链API + push_activity_2_blockchain("pull_request_refuse", @pull_request) + + if colsed === true @pull_request.project_trends.create!(user: current_user, project: @project,action_type: ProjectTrend::CLOSE) # 合并请求下issue处理为关闭 @issue&.update_attributes!({status_id:5}) SendTemplateMessageJob.perform_later('PullRequestClosed', current_user.id, @pull_request.id) if Site.has_notice_menu? - normal_status(1, "已拒绝") + normal_status(1, "已拒绝") else normal_status(-1, '合并失败') end @@ -198,6 +208,43 @@ class PullRequestsController < ApplicationController # @pull_request.project_trend_status! @pull_request.project_trends.create!(user: current_user, project: @project,action_type: ProjectTrend::MERGE) @issue&.custom_journal_detail("merge", "", "该合并请求已被合并", current_user&.id) + + # author: zxh + # 调用上链API + push_activity_2_blockchain("pull_request_merge", @pull_request) + + # 查看是否fix了相关issue,如果fix就转账 + if params["fix_issue_id"].nil? || params["fix_issue_id"] == "" + else + issue = Issue.find_by(id: params["fix_issue_id"]) + if issue.nil? + normal_status(-1, "关联issue失败") + raise ActiveRecord::Rollback + else + token_num = issue.blockchain_token_num + token_num = token_num.nil? ? 0 : token_num + owner = User.find_by(login: params["owner"]) + pr = PullRequest.find_by(id: params["pull_request"]["id"]) + if owner.nil? || pr.nil? + normal_status(-1, "关联issue失败") + raise ActiveRecord::Rollback + else + project = Project.find_by(user_id: owner.id, identifier: params["project_id"]) + if project.nil? + normal_status(-1, "关联issue失败") + raise ActiveRecord::Rollback + else + author_id = pr.user_id + if token_num > 0 + Blockchain::FixIssue.call({user_id: author_id.to_s, project_id: project.id.to_s, token_num: token_num}) + end + # update issue to state 5 + issue.update(status_id: 5) + end + end + end + end + # 合并请求下issue处理为关闭 @issue&.update_attributes!({status_id:5}) SendTemplateMessageJob.perform_later('PullRequestMerged', current_user.id, @pull_request.id) if Site.has_notice_menu? diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 33fd93f83..6aee4b584 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -2,8 +2,8 @@ class UsersController < ApplicationController include ApplicationHelper include Ci::DbConnectable - before_action :load_user, only: [:show, :homepage_info, :sync_token, :sync_gitea_pwd, :projects, :watch_users, :fan_users, :hovercard] - before_action :check_user_exist, only: [:show, :homepage_info,:projects, :watch_users, :fan_users, :hovercard] + before_action :load_user, only: [:show, :homepage_info, :sync_token, :sync_gitea_pwd, :projects, :watch_users, :fan_users, :hovercard, :hovercard4proj] + before_action :check_user_exist, only: [:show, :homepage_info,:projects, :watch_users, :fan_users, :hovercard, :hovercard4proj] before_action :require_login, only: %i[me sync_user_info] before_action :connect_to_ci_db, only: [:get_user_info] before_action :convert_image!, only: [:update, :update_image] @@ -81,9 +81,134 @@ class UsersController < ApplicationController @watchers = paginate(watchers) end + def contribution_perc + project_id = params[:project_id] + @project = Project.find(project_id) + user_id = params[:id] + @user = User.find(user_id) + + def cal_perc(count_user, count_all) + (count_user * 1.0 / (count_all + 0.000000001)).round(5) + end + + if @project['use_blockchain'] == true or @project['use_blockchain'] == 1 + balance_user = Blockchain::BalanceQueryOneProject.call({"user_id": user_id, "project_id": project_id}) + balance_all = Blockchain::RepoBasicInfo.call({"project_id": project_id})["cur_supply"] + scores = { + "final" => cal_perc(balance_user, balance_all), + "blockchain" => true + } + else + # 获取所有行为对应的项目内记录总数和个人记录数 + features = { + "requirement" => {}, + "development" => {}, + "review" => {} + } + + # 1. issue创建 + issues = Issue.where(project_id: project_id, issue_classify: 'issue') + issue_all = issues.count + issue_user = issues.where(author_id: user_id).count + features["requirement"] = features["requirement"].merge({"issue" => {"all" => issue_all, "perc" => cal_perc(issue_user, issue_all)}}) + # 2. 里程碑创建 + milestones = Version.where(project_id: project_id) + milestone_all = milestones.count + milestone_user = milestones.where(user_id: user_id).count + features["requirement"] = features["requirement"].merge({"milestone" => {"all" => milestone_all, "perc" => cal_perc(milestone_user, milestone_all)}}) + # 3. issue评论 + issue_comments = Journal.joins("INNER JOIN issues on journals.journalized_id=issues.id").where("issues.project_id=#{project_id} and journalized_type='Issue' and issues.issue_classify='issue'") + issue_comment_all = issue_comments.count + issue_comment_user = issue_comments.where("journals.user_id=#{user_id}").count + features["requirement"] = features["requirement"].merge({"issue_comment" => {"all" => issue_comment_all, "perc" => cal_perc(issue_comment_user, issue_comment_all)}}) + # 4. 合并请求 + prs = PullRequest.where(project_id: project_id) + pr_all = prs.count + pr_user = prs.where(user_id: user_id).count + features["development"] = features["development"].merge({"pr" => {"all" => pr_all, "perc" => cal_perc(pr_user, pr_all)}}) + # 5. pr评论 + pr_comments = Journal.joins("INNER JOIN issues on journals.journalized_id=issues.id").where("issues.project_id=#{project_id} and journalized_type='Issue' and issues.issue_classify='pull_request'") + pr_comment_all = pr_comments.count + pr_comment_user = pr_comments.where("journals.user_id=#{user_id}").count + features["review"] = features["review"].merge({"pr_comment" => {"all" => pr_comment_all, "perc" => cal_perc(pr_comment_user, pr_comment_all)}}) + # 6. 代码行评论 + line_comments = Journal.joins("INNER JOIN pull_requests on journals.journalized_id=pull_requests.id").where("pull_requests.project_id=#{project_id} and journalized_type='PullRequest'") + line_comment_all = line_comments.count + line_comment_user = line_comments.where("journals.user_id=#{user_id}").count + features["review"] = features["review"].merge({"line_comment" => {"all" => line_comment_all, "perc" => cal_perc(line_comment_user, line_comment_all)}}) + # 7. 代码行、commit贡献统计 + code_contributions = Api::V1::Projects::CodeStats::ListService.call(@project, {ref: nil}) + commit_all = code_contributions["commit_count"] + addition_all = code_contributions["additions"] + deletion_all = code_contributions["deletions"] + + commit_user = 0 + addition_user = 0 + deletion_user = 0 + code_contributions["authors"].each do |author| + if author["name"] == @user.login + commit_user = author["commits"] + addition_user = author["additions"] + deletion_user = author["deletions"] + end + end + + features["development"] = features["development"].merge({"commit" => {"all" => commit_all, "perc" => cal_perc(commit_user, commit_all)}}) + features["development"] = features["development"].merge({"addition" => {"all" => addition_all, "perc" => cal_perc(addition_user, addition_all)}}) + features["development"] = features["development"].merge({"deletion" => {"all" => deletion_all, "perc" => cal_perc(deletion_user, deletion_all)}}) + + def cal_weight(features) + weights = {} # 计算每一项的权重 + categories = [] + features.each do |key, _| + categories << key + weights[key] = Hash.new + end + count_all = 0 + counts = {} + categories.each do |category| + count_1 = 0 + features[category].each do |_, value| + count_1 += value["all"] + end + count_all += count_1 + counts[category] = count_1 + features[category].each do |key, value| + weight = cal_perc(value["all"], count_1) + weights[category] = weights[category].merge({key => weight}) + end + end + categories.each do |category| + weight = cal_perc(counts[category], count_all) + weights[category] = weights[category].merge({"category_weight" => weight}) + end + return weights + end + + weights_categories = cal_weight(features) + scores = { + "final" => 0.0, + "blockchain" => false + } + features.each do |category, value_1| + category_score = 0.0 + value_1.each do |action, value_2| + category_score += weights_categories[category][action] * value_2["perc"] + end + scores["final"] += weights_categories[category]["category_weight"] * category_score.round(4) + scores = scores.merge({category => category_score.round(4)}) + end + end + render json: { scores: scores } + end + def hovercard end + # author: zxh, 查询贡献者的贡献度 + def hovercard4proj + end + def update return render_not_found unless @user = User.find_by(login: params[:id]) || User.find_by_id(params[:id]) return render_forbidden unless User.current.logged? && (current_user&.admin? || current_user.id == @user.id) @@ -94,11 +219,11 @@ class UsersController < ApplicationController end end - def update_image - return render_not_found unless @user = User.find_by(login: params[:id]) || User.find_by_id(params[:id]) + def update_image + return render_not_found unless @user = User.find_by(login: params[:id]) || User.find_by_id(params[:id]) return render_forbidden unless User.current.logged? && (current_user&.admin? || current_user.id == @user.id) - Util.write_file(@image, avatar_path(@user)) + Util.write_file(@image, avatar_path(@user)) return render_ok({message: '头像修改成功'}) rescue Exception => e uid_logger_error(e.message) @@ -284,6 +409,252 @@ class UsersController < ApplicationController @projects = paginate(scope) end + + # query all projects with tokens by a user + def blockchain_balance + #is_current_admin_user = User.current.logged? && (current_user&.admin? || current_user.id == params['user_id'].to_i) + is_current_admin_user = true + results = Blockchain::BalanceQuery.call(params, is_current_admin_user) + if results[:status] == 0 + @total_count = results[:projects].size + @projects = results[:projects] + else + @total_count = -1 + @projects = [] + end + + render json: { status: results[:status], projects: @projects, total_count: @total_count } + end + + # query one balance + def blockchain_balance_one_project + #is_current_admin_user = User.current.logged? && (current_user&.admin? || current_user.id == params['user_id'].to_i) + is_current_admin_user = true + if is_current_admin_user + owner = User.find_by(login: params['owner_login']) + if owner.nil? + normal_status(-1, "创建者无法找到") + else + p = Project.find_by(user_id: owner.id, identifier: params['project_identifier']) + balance = Blockchain::BalanceQueryOneProject.call({"user_id": params['user_id'].to_i, "project_id": p.id.to_i}) + render json: { status: 0, balance: balance} + end + else + normal_status(-1, "缺少权限") + end + + end + + + def blockchain_transfer + #is_current_admin_user = User.current.logged? && (current_user&.admin? || current_user.id == params['payer_id'].to_i) + is_current_admin_user = true + + if is_current_admin_user + Blockchain::TransferService.call(params) + render json: {status: 2} # 重新查询余额 + else + normal_status(-1, "缺少权限") + end + rescue Exception => e + normal_status(-1, e.to_s) + end + + # exchange money + def blockchain_exchange + #is_current_admin_user = User.current.logged? && (current_user&.admin? || current_user.id == params['user_id'].to_i) + #require 'alipay' + ## setup the client to communicate with either production API or sandbox API + ## https://openapi.alipay.com/gateway.do (Production) + ## https://openapi.alipaydev.com/gateway.do (Sandbox) + #api_url = 'https://openapi.alipay.com/gateway.do' + # + ## setup your own credentials and certificates + #app_id = '2021002140631434' + #app_private_key="-----BEGIN RSA PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCPDsgc0n0RWRnbe9OMqtUbde8gu88OyjuZm8cJXeiSING18HX56C5zV4DsHQ6K9/JmQi/NYCc7/2Prh66Bei0L4Xm5TysJTPYi90+WlbJJESFF6fqULi8sSqZXUtaoMKRcUyeR144l2GQgXbZWBbVSQeZW5DqUDlurTF0I7vQ21wGqxQqBjQK8PSVw5hF+aIsNOfAY9M1tzdD5Jzo2Y3FJdsbXIJNMyhJJCZ+7KHFqma7lpjkXdCoyh/nOISkQdGtFI29gI94sqewI2AMU84uxuBIE3h90iLT+8xrd2dQKdBS7qfhQ3PgkBPVNs5jxsVBqSFdSFT6zcqFdHJzulCUJAgMBAAECggEAWocAGz0X5+J6emnhdSKluLrol85BORrAnHP3f/XtNouOKZQBFCPZQSQecUvx5/7/ZbZ8iXpPWahDkshJpaWq29nTLXDryvboyze1JZWVPKeaZqOp7htLvrt+h8PkEoq1d7cnUyMU0N4eflzPBaCXHXaWTGYgq5Bqcfvg48ZSxGBYeHt5WWU2+GW5fpsaVBBYkdyxxGMoy/bzYzGhvfSJkexqnl0XkAAODa02mu3WsHrzRid6Mf+3syYbq/MfUodU6Vng2tbCqwnWrHCyrD4RYl6rg1TKuAv2YAfBhLzwBxHYVC4SRqzjs+8AaOQoF+lCjr4JklPhEuRThzD31YwIAQKBgQDAg4S7ciMmxvbNxR+1eimoAYLLF9jPpPU6w3VFNPb4rDu4tX77bNf082YplfJX5TYtZKjKPKyYG87K6slGunyPL4AFRW81WPB9u1uP26dihyPCBSUE01jKRPPfRrQnnru5fpm8LL3L03V3yA6J+GyQ8wltRJJi1eBSSm+IWRsZewKBgQC+PBD9J1LYOEIVeK9KvN8BpkA1ZIkk//VuJaoGfVXn+1EzM1yFB05fnsEQkHFynisvuCIr7pH63HcdyffQhe1YOnw9iDCG1zPjsi5uTe9WAM0dnb7xdsaLPr/Q2LyoDOTN9344Qovy1AAnpWtGTn6omgHst5nZpp/mHOuBlKiqSwKBgBKRXM77fjpyPEGyfpFxW+0xYB0YirfUUDa/vWLUbfGkIwp4ruuvHtEoXLUsGjiyCdys9b6zxW3SWMqnhIxG1la1HSLlBInfryphVL52UBmnsSI4fs6NV+YCaocheaTMoYyNkmRc6F1tYsoPyJ80D7yXRFR+paPUvxMQzNsYxQ1bAoGAHd2uSSBQWFPUxCwzUQd/93FTaU6EXYO103okTGqG/ymsoN4ya0wvWMHCy8fxl64PV6mP69fDoV/Vb57SwjEUhyJ/eOWVwMWuhtPliDnCFn1/tmOao6wjFZ9fW/l6/OMxVMjDTy/bat8vuwm0YtBWAEBVhwV4KPyI5AasTqa5KCsCgYB/usnqhVx2zt+MxpBt2Q9Vxc0zXcZxMDs69UUdTY86gjcJyCFGe3bbumUcyfSJzIznC2hfFX5ZyS0oMwiAzWtslRMh9LRh3kofD/6BogL3RKOlBk3iekvQ8Gn0tbwk2Qzr4WJgfA7A4GTf5r7Y+bvOfazzsUQAfSK6nUTIlOj2Ew==\n-----END RSA PRIVATE KEY-----\n" + #alipay_public_key="-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgHXLD1BshMymbqqtZVKNyo95FNfxzXzaw3P1eI0KeO6RaL+JzrWxzIBFfTjkWv/8WM9u/NcXMOFt2QO9q5KIDx6PkqjRDTd1hgP/cTgdjOHQqnVSihCrQDVCDBSOXIujC8Lk/P4pFVRhQkYeZqEb1qb8b/2tzTY8g9PKKBSCQv7SfgL2TBcpAVbb+9xdJ6VainC/wYGk8T+c+st1hXnuBJSS0m7LFxJOsYkNk0wbA0tfdZLrO3us2F7sjC9t4h/05nr+gSuDkzo+1kCEefYLqScexN+vnQiLoylp/C82wNiP6okxfhmHz3EcYfUqUyGTN/oFaFcPFPpUtFNS8jFV9QIDAQAB\n-----END PUBLIC KEY-----\n" + # + ## initialize a client to communicate with the Alipay API + #@alipay_client = Alipay::Client.new( + # url: api_url, + # app_id: app_id, + # app_private_key: app_private_key, + # alipay_public_key: alipay_public_key + #) + # + #return_result = @alipay_client.page_execute_url( + # method: 'alipay.trade.page.pay', + # biz_content: JSON.generate({ + # out_trade_no: '20210420104600', + # product_code: 'FAST_INSTANT_TRADE_PAY', + # total_amount: '0.01', + # subject: 'test' + # }, ascii_only: true), # ascii_only is important! + # timestamp: '2021-04-20 10:46:00' + #) + #render json: { pay_url: return_result } + # + + # 替代解决方案 + # 读取所有交易信息 + end + # # + # # def blockchain_create_trade + # # #is_current_admin_user = User.current.logged? && (current_user&.admin? || current_user.id == params['user_id'].to_i) + # # is_current_admin_user = true + # # if is_current_admin_user + # # user_id = params['user_id'].to_i + # # project_id = params['project_id'].to_i + # # money = params['money'].to_f + # # #description = params['description'] + # # token_num = params['token_num'].to_i + # # # 锁仓 + # # result = Blockchain::CreateTrade.call({user_id: user_id, project_id: project_id, token_num: token_num}) + # # if result == false + # # normal_status(-1, "创建交易失败") + # # else + # # bt = BlockchainTrade.new(user_id: user_id, project_id: project_id, token_num: token_num, money: money, state: 0) # state=0表示创建交易; state=1表示执行中; state=2表示执行完成 + # # bt.save() + # # status = 2 # 交易创建成功 + # # render json: { status: status } + # # end + # # else + # # normal_status(-1, "缺少权限") + # # end + # # end + # # + # # + # # def blockchain_get_trades + # # trades = BlockchainTrade.where(state: 0).all() + # # results = [] + # # trades.each do |t| + # # project_id = t.project_id + # # project = Project.find_by(id: project_id) + # # if !project.nil? + # # owner = User.find_by(id: project.user_id) + # # else + # # owner = nil + # # end + # # user_id = t.user_id + # # creator = User.find_by(id: user_id) + # # if project.nil? || owner.nil? || creator.nil? + # # else + # # results << [creator, owner, project, t] + # # end + # # end + # # render json: { results: results } + # # end + # # + # # def blockchain_trade + # # #is_current_admin_user = User.current.logged? && (current_user&.admin? || current_user.id == params['user_id'].to_i) + # # is_current_admin_user = true + # # if is_current_admin_user + # # user_id2 = params['user_id2'].to_i + # # trade_id = params['trade_id'].to_i + # # BlockchainTrade.find(trade_id).update(user_id2: user_id2, state: 1) # state=1表示锁定了,等待线下卖家发货 + # # render json: {status: 2} # window.location.reload() + # # else + # # normal_status(-1, "缺少权限") + # # end + # # end + # + # + # def blockchain_verify_trade + # #is_current_admin_user = User.current.logged? && (current_user&.admin? || current_user.id == params['user_id'].to_i) + # is_current_admin_user = true + # if is_current_admin_user + # trade_id = params['trade_id'].to_i + # BlockchainTrade.find(trade_id).update(state: 2) # state=2表示确认收货 + # render json: {status: 2} # window.location.reload() + # else + # normal_status(-1, "缺少权限") + # end + # end + # + # def blockchain_get_verify_trades + # #is_current_admin_user = User.current.logged? && (current_user&.admin? || current_user.id == params['user_id'].to_i) + # is_current_admin_user = true + # if is_current_admin_user + # trades = BlockchainTrade.where(state: 1).all() + # results = [] + # trades.each do |t| + # project_id = t.project_id + # project = Project.find_by(id: project_id) + # if !project.nil? + # owner = User.find_by(id: project.user_id) + # else + # owner = nil + # end + # user_id = t.user_id + # creator = User.find_by(id: user_id) + # user_id2 = t.user_id2 + # buyer = User.find_by(id: user_id2) + # if project.nil? || owner.nil? || creator.nil? || buyer.nil? + # else + # results << [creator, owner, project, t, buyer] + # end + # end + # render json: { results: results } + # else + # normal_status(-1, "缺少权限") + # end + # end + # + # def blockchain_get_history_trades + # #is_current_admin_user = User.current.logged? && (current_user&.admin? || current_user.id == params['user_id'].to_i) + # is_current_admin_user = true + # if is_current_admin_user + # trades = BlockchainTrade.where(state: 2).all() + # results = [] + # trades.each do |t| + # project_id = t.project_id + # project = Project.find_by(id: project_id) + # if !project.nil? + # owner = User.find_by(id: project.user_id) + # else + # owner = nil + # end + # user_id = t.user_id + # creator = User.find_by(id: user_id) + # user_id2 = t.user_id2 + # buyer = User.find_by(id: user_id2) + # if project.nil? || owner.nil? || creator.nil? || buyer.nil? + # else + # results << [creator, owner, project, t, buyer] + # end + # end + # render json: { results: results } + # else + # normal_status(-1, "缺少权限") + # end + # end + + + def blockchain_get_issue_token_num + issue_id = params["issue_id"]['orderId'].to_i + issue = Issue.find_by(id: issue_id) + project = Project.find(issue.project_id) + if project[:use_blockchain] + render json: {"blockchain_token_num": issue.blockchain_token_num} + else + render json: {"blockchain_token_num": -1} # 未使用blockchain + end + end + + + def blockchain_get_unclosed_issue_list + ownername = params["ownername"] + identifier = params["reponame"] + owner = User.find_by(login: ownername) + project = Project.find_by(user_id: owner.id, identifier: identifier) + unclosed_issues = Issue.where(project_id: project.id, issue_classify: "issue").where.not(status_id: 5) + results = [] + unclosed_issues.each do |i| + results << [i.id, i.subject] + end + render json: {unclosed_issues: results} + end + # TODO 其他平台登录时同步修改gitea平台对应用户的密码 # 该方法主要用于:别的平台初次部署对接forge平台,同步用户后,gitea平台对应的用户密码与forge平台用户密码不一致是问题 def sync_gitea_pwd @@ -322,7 +693,7 @@ class UsersController < ApplicationController end end - def email_search + def email_search return render_error('请输入email') if params[:email].blank? @user = User.find_by(mail: params[:email]) end diff --git a/app/forms/base_form.rb b/app/forms/base_form.rb index ca82fa4bb..db745e8d8 100644 --- a/app/forms/base_form.rb +++ b/app/forms/base_form.rb @@ -30,6 +30,14 @@ class BaseForm raise "项目名称已被使用." if Project.where(user_id: user_id, name: project_name.strip).exists? end + def check_blockchain_token_all(blockchain_token_all) + raise "请正确填写项目token总数." if (Float(blockchain_token_all) rescue false) == false or blockchain_token_all.to_i < 0 or Float(blockchain_token_all) != blockchain_token_all.to_i + end + + def check_blockchain_init_token(blockchain_init_token) + raise "请正确填写项目创始人token占比." if (Float(blockchain_init_token) rescue false) == false or blockchain_init_token.to_i < 0 or blockchain_init_token.to_i > 100 or Float(blockchain_init_token) != blockchain_init_token.to_i + end + def check_reversed_keyword(repository_name) raise "项目标识已被占用." if ReversedKeyword.check_exists?(repository_name) end diff --git a/app/forms/projects/create_form.rb b/app/forms/projects/create_form.rb index c51c2c60f..c9fcb44e6 100644 --- a/app/forms/projects/create_form.rb +++ b/app/forms/projects/create_form.rb @@ -1,6 +1,7 @@ class Projects::CreateForm < BaseForm attr_accessor :user_id, :name, :description, :repository_name, :project_category_id, - :project_language_id, :ignore_id, :license_id, :private, :owner + :project_language_id, :ignore_id, :license_id, :private, :owner, + :blockchain, :blockchain_token_all, :blockchain_init_token validates :user_id, :name, :repository_name, presence: true validates :repository_name, format: { with: CustomRegexp::REPOSITORY_NAME_REGEX, multiline: true, message: "只能含有数字、字母、下划线且不能以下划线开头和结尾" } @@ -15,6 +16,8 @@ class Projects::CreateForm < BaseForm check_project_language(project_language_id) check_project_name(user_id, name) unless name.blank? check_repository_name(user_id, repository_name) unless repository_name.blank? + check_blockchain_token_all(blockchain_token_all) unless blockchain_token_all.blank? + check_blockchain_init_token(blockchain_init_token) unless blockchain_init_token.blank? end def check_license diff --git a/app/helpers/tag_chosen_helper.rb b/app/helpers/tag_chosen_helper.rb index 520b868fa..784efcf29 100644 --- a/app/helpers/tag_chosen_helper.rb +++ b/app/helpers/tag_chosen_helper.rb @@ -21,7 +21,8 @@ module TagChosenHelper "issue_tag": render_issue_tags(project), "issue_type": render_issue_species, "all_issues": all_issues, - "branches": render_branches(project) + "branches": render_branches(project), + "use_blockchain": project['use_blockchain'] } end diff --git a/app/models/concerns/watchable.rb b/app/models/concerns/watchable.rb index 008d382f3..9eb0172d7 100644 --- a/app/models/concerns/watchable.rb +++ b/app/models/concerns/watchable.rb @@ -1,45 +1,160 @@ -module Watchable - extend ActiveSupport::Concern - - included do - has_many :watchers, as: :watchable, dependent: :destroy - has_many :watcher_users, through: :watchers, source: :user, validate: false - - scope :watched_by, -> (user_id) { includes(:watchers).where(watchers: { user_id: user_id }) } - scope :following, -> (user_id) { watched_by(user_id) } - end - - def watched?(watchable) - watchable.watchers.exists?(user: self) - end - - def watch!(watchable) - watchable.watchers.create!(user: self, created_at: Time.current) - end - - def unwatch!(watchable) - obj = watchable.watchers.find_by(user: self) - obj.destroy! if obj.present? - end - - # 我正在关注的、我追随的 - def following - User.following(self.id) - end - - def following_count - following.size - end - - # 关注我的、我的粉丝、我的追随者 - def followers - watcher_users - end - - def followers_count - followers.size - end - - module ClassMethods - end -end +module Watchable + extend ActiveSupport::Concern + + included do + has_many :watchers, as: :watchable, dependent: :destroy + has_many :watcher_users, through: :watchers, source: :user, validate: false + + scope :watched_by, -> (user_id) { includes(:watchers).where(watchers: { user_id: user_id }) } + scope :following, -> (user_id) { watched_by(user_id) } + end + + def watched?(watchable) + watchable.watchers.exists?(user: self) + end + + def watch!(watchable) + watchable.watchers.create!(user: self, created_at: Time.current) + end + + def unwatch!(watchable) + obj = watchable.watchers.find_by(user: self) + obj.destroy! if obj.present? + end + + # 我正在关注的、我追随的 + def following + User.following(self.id) + end + + def following_count + following.size + end + + def contribution_perc + project_identifier = params[:project_identifier] + user_login = params[:id] + @project = Project.find_by_identifier(project_identifier) + @user = User.find_by_login(user_login) + + def cal_perc(count_user, count_all) + (count_user * 1.0 / (count_all + 0.000000001)).round(5) + end + + if @project['use_blockchain'] == true or @project['use_blockchain'] == 1 + balance_user = Blockchain::BalanceQueryOneProject.call({"user_id": @user.id, "project_id": @project.id}) + balance_all = Blockchain::RepoBasicInfo.call({"project_id": @project.id})["cur_supply"] + score = cal_perc(balance_user, balance_all) + else + # 获取所有行为对应的项目内记录总数和个人记录数 + features = { + "requirement" => {}, + "development" => {}, + "review" => {} + } + + # 1. issue创建 + issues = Issue.where(project_id: @project.id, issue_classify: 'issue') + issue_all = issues.count + issue_user = issues.where(author_id: @user.id).count + features["requirement"] = features["requirement"].merge({"issue" => {"all" => issue_all, "perc" => cal_perc(issue_user, issue_all)}}) + # 2. 里程碑创建 + milestones = Version.where(project_id: @project.id) + milestone_all = milestones.count + milestone_user = milestones.where(user_id: @user.id).count + features["requirement"] = features["requirement"].merge({"milestone" => {"all" => milestone_all, "perc" => cal_perc(milestone_user, milestone_all)}}) + # 3. issue评论 + issue_comments = Journal.joins("INNER JOIN issues on journals.journalized_id=issues.id").where("issues.project_id=#{@project.id} and journalized_type='Issue' and issues.issue_classify='issue'") + issue_comment_all = issue_comments.count + issue_comment_user = issue_comments.where("journals.user_id=#{@user.id}").count + features["requirement"] = features["requirement"].merge({"issue_comment" => {"all" => issue_comment_all, "perc" => cal_perc(issue_comment_user, issue_comment_all)}}) + # 4. 合并请求 + prs = PullRequest.where(project_id: @project.id) + pr_all = prs.count + pr_user = prs.where(user_id: @user.id).count + features["development"] = features["development"].merge({"pr" => {"all" => pr_all, "perc" => cal_perc(pr_user, pr_all)}}) + # 5. pr评论 + pr_comments = Journal.joins("INNER JOIN issues on journals.journalized_id=issues.id").where("issues.project_id=#{@project.id} and journalized_type='Issue' and issues.issue_classify='pull_request'") + pr_comment_all = pr_comments.count + pr_comment_user = pr_comments.where("journals.user_id=#{@user.id}").count + features["review"] = features["review"].merge({"pr_comment" => {"all" => pr_comment_all, "perc" => cal_perc(pr_comment_user, pr_comment_all)}}) + # 6. 代码行评论 + line_comments = Journal.joins("INNER JOIN pull_requests on journals.journalized_id=pull_requests.id").where("pull_requests.project_id=#{@project.id} and journalized_type='PullRequest'") + line_comment_all = line_comments.count + line_comment_user = line_comments.where("journals.user_id=#{@user.id}").count + features["review"] = features["review"].merge({"line_comment" => {"all" => line_comment_all, "perc" => cal_perc(line_comment_user, line_comment_all)}}) + # 7. 代码行、commit贡献统计 + code_contributions = Api::V1::Projects::CodeStats::ListService.call(@project, {ref: nil}) + commit_all = code_contributions["commit_count"] + addition_all = code_contributions["additions"] + deletion_all = code_contributions["deletions"] + + commit_user = 0 + addition_user = 0 + deletion_user = 0 + code_contributions["authors"].each do |author| + if author["name"] == @user.login + commit_user = author["commits"] + addition_user = author["additions"] + deletion_user = author["deletions"] + end + end + + features["development"] = features["development"].merge({"commit" => {"all" => commit_all, "perc" => cal_perc(commit_user, commit_all)}}) + features["development"] = features["development"].merge({"addition" => {"all" => addition_all, "perc" => cal_perc(addition_user, addition_all)}}) + features["development"] = features["development"].merge({"deletion" => {"all" => deletion_all, "perc" => cal_perc(deletion_user, deletion_all)}}) + + def cal_weight(features) + weights = {} # 计算每一项的权重 + categories = [] + features.each do |key, _| + categories << key + weights[key] = Hash.new + end + count_all = 0 + counts = {} + categories.each do |category| + count_1 = 0 + features[category].each do |_, value| + count_1 += value["all"] + end + count_all += count_1 + counts[category] = count_1 + features[category].each do |key, value| + weight = cal_perc(value["all"], count_1) + weights[category] = weights[category].merge({key => weight}) + end + end + categories.each do |category| + weight = cal_perc(counts[category], count_all) + weights[category] = weights[category].merge({"category_weight" => weight}) + end + return weights + end + + weights_categories = cal_weight(features) + score = 0.0 + features.each do |category, value_1| + category_score = 0.0 + value_1.each do |action, value_2| + category_score += weights_categories[category][action] * value_2["perc"] + end + score += weights_categories[category]["category_weight"] * category_score.round(4) + end + end + score + (score * 100).round(1).to_s + "%" + end + + # 关注我的、我的粉丝、我的追随者 + def followers + watcher_users + end + + def followers_count + followers.size + end + + module ClassMethods + end +end diff --git a/app/queries/application_query.rb b/app/queries/application_query.rb index c66af94c0..fdef1b71d 100644 --- a/app/queries/application_query.rb +++ b/app/queries/application_query.rb @@ -6,4 +6,53 @@ class ApplicationQuery def strip_param(key) params[key].to_s.strip.presence end + + # find all the repos that a user has tokens + def find_repo_with_token(user_id) + params = { + "request-type": "query user balance of all repos", + "username": user_id.to_s + }.to_json + resp_body = Blockchain::InvokeBlockchainApi.call(params) + if resp_body['status'] != 0 + raise "区块链接口请求失败." + else + token_list = resp_body['UserBalanceList'].nil? ? [] : resp_body['UserBalanceList'] + return token_list + end + end + + + # find one repo that a user has tokens + def find_one_balance(user_id, project_id) + # return 3 statuses: UnknownErr/ResUserNotExisted/Success + params = { + "request-type": "query user balance of single repo", + "username": user_id.to_s, + "token_name": project_id.to_s + }.to_json + resp_body = Blockchain::InvokeBlockchainApi.call(params) + if resp_body['status'] == 0 + return resp_body['balance'] + elsif resp_body['status'] == 100 + return 0 # 找不到用户返回0 + else + raise "区块链接口请求失败." + end + end + + # query the basic info of a repository + def find_blockchain_repo_info(project_id) + params = { + "request-type": "query repo basic info", + "token_name": project_id.to_s + }.to_json + resp_body = Blockchain::InvokeBlockchainApi.call(params) + if resp_body['status'] == 0 + return resp_body + else + raise "区块链接口请求失败." + end + end + end \ No newline at end of file diff --git a/app/queries/blockchain/balance_query.rb b/app/queries/blockchain/balance_query.rb new file mode 100644 index 000000000..669370a54 --- /dev/null +++ b/app/queries/blockchain/balance_query.rb @@ -0,0 +1,31 @@ +class Blockchain::BalanceQuery < ApplicationQuery + + attr_reader :params, :is_current_admin_user + + def initialize(params,is_current_admin_user) + @params = params + @is_current_admin_user = is_current_admin_user + end + + def call + if is_current_admin_user + token_list = find_repo_with_token(params["user_id"]) + result_list = [] + token_list.each do |t| + project = Project.find_by(id: t['token_name'].to_i) + if project.nil? + next + end + owner = User.find_by(id: project.user_id) + if owner.nil? || project.nil? + else + balance = t['balance'] + result_list << [owner, project, balance] + end + end + results = {"status": 0, "projects": result_list} + else + results = {"status": 1} # query failed + end + end +end diff --git a/app/queries/blockchain/balance_query_one_project.rb b/app/queries/blockchain/balance_query_one_project.rb new file mode 100644 index 000000000..c4ec19e21 --- /dev/null +++ b/app/queries/blockchain/balance_query_one_project.rb @@ -0,0 +1,12 @@ +class Blockchain::BalanceQueryOneProject < ApplicationQuery + + attr_reader :params, :is_current_admin_user + + def initialize(params) + @params = params + end + + def call + find_one_balance(params[:user_id].to_s, params[:project_id].to_s) + end +end diff --git a/app/queries/blockchain/repo_basic_info.rb b/app/queries/blockchain/repo_basic_info.rb new file mode 100644 index 000000000..df17b7a04 --- /dev/null +++ b/app/queries/blockchain/repo_basic_info.rb @@ -0,0 +1,13 @@ +class Blockchain::RepoBasicInfo < ApplicationQuery + + attr_reader :params, :is_current_admin_user + + def initialize(params) + @params = params + end + + def call + info = find_blockchain_repo_info(params[:project_id].to_s) + return info + end +end diff --git a/app/queries/projects/list_my_query.rb b/app/queries/projects/list_my_query.rb index 6c7f38d9c..7e555b529 100644 --- a/app/queries/projects/list_my_query.rb +++ b/app/queries/projects/list_my_query.rb @@ -38,6 +38,12 @@ class Projects::ListMyQuery < ApplicationQuery # projects = projects.visible.joins(:members).where(members: { user_id: user.id }) # elsif params[:category].to_s == "private" # projects = projects.is_private.joins(:members).where(members: { user_id: user.id }) + elsif params[:category].to_s == "blockchain_token" # 所有钱包中有token的项目有哪些 + token_list = find_repo_with_token(user.id) + project_names = token_list.map { |x| x['token_name'] } + projects = projects.where(name: project_names) + tokens = token_list.map { |x| x['balance'] } + puts "pause" end if params[:is_public].present? diff --git a/app/services/application_service.rb b/app/services/application_service.rb index 9c70bbeb2..df06f3960 100644 --- a/app/services/application_service.rb +++ b/app/services/application_service.rb @@ -1,35 +1,109 @@ -class ApplicationService - include Callable - - Error = Class.new(StandardError) - - def regix_emoji content - " " if content.blank? - regex = /[^a-zA-Z0-9\u4E00-\u9FFF]/ - content.gsub(regex, '') - end - - protected - def try_lock(key) - raise Error, "请稍后再试!" unless $redis_cache.set(key, 1, nx: true, ex: 60.seconds) - end - - def unlock(key) - $redis_cache.del(key) - end - - private - - def strip(str) - str.to_s.strip.presence - end - - def str_to_boolean str - ActiveModel::Type::Boolean.new.cast str - end - - def phone_mail_type value - value =~ /^1\d{10}$/ ? 1 : 0 - end - -end +class ApplicationService + include Callable + + Error = Class.new(StandardError) + + def regix_emoji content + " " if content.blank? + regex = /[^a-zA-Z0-9\u4E00-\u9FFF]/ + content.gsub(regex, '') + end + + protected + def try_lock(key) + raise Error, "请稍后再试!" unless $redis_cache.set(key, 1, nx: true, ex: 60.seconds) + end + + def unlock(key) + $redis_cache.del(key) + end + + private + + def strip(str) + str.to_s.strip.presence + end + + def str_to_boolean str + ActiveModel::Type::Boolean.new.cast str + end + + # params: params from index.js page + def create_repo_on_blockchain(params, project) + username = params['user_id'].to_s + token_name = project.id.to_s + total_supply = params['blockchain_token_all'].to_i + token_balance = [[username, (total_supply * params['blockchain_init_token'].to_i / 100).to_i]] + + params = { + "request-type": "create repo", + "username": username, + "token_name": token_name, + "total_supply": total_supply, + "token_balance": token_balance + }.to_json + resp_body = Blockchain::InvokeBlockchainApi.call(params) + if resp_body['status'] != 0 + raise "区块链接口请求失败." + end + end + + + def transfer_balance_on_blockchain(payer, payee, token_name, amount) + + params = { + "request-type": "transfer amount", + "payer": payer, + "payee": payee, + "token_name": token_name, + "amount": amount + }.to_json + resp_body = Blockchain::InvokeBlockchainApi.call(params) + if resp_body['status'] == 5 or resp_body['status'] == 106 or resp_body['status'] == 105 + raise resp_body['message'] + elsif resp_body['status'] != 0 + raise "区块链接口请求失败." + else + end + end + + + def lock_balance_on_blockchain(username, tokenname, amount) + + params = { + "request-type": "lock user balance", + "username": username, + "token_name": tokenname, + "amount": amount + }.to_json + resp_body = Blockchain::InvokeBlockchainApi.call(params) + if resp_body['status'] == 5 or resp_body['status'] == 106 or resp_body['status'] == 103 + raise resp_body['message'] + elsif resp_body['status'] != 0 + raise "区块链接口请求失败." + else + end + end + + + def unlock_balance_on_blockchain(username, tokenname, amount) + + params = { + "request-type": "unlock user balance", + "username": username, + "token_name": tokenname, + "amount": amount + }.to_json + resp_body = Blockchain::InvokeBlockchainApi.call(params) + if resp_body['status'] == 5 or resp_body['status'] == 106 or resp_body['status'] == 104 + raise resp_body['message'] + elsif resp_body['status'] != 0 + raise "区块链接口请求失败." + else + end + end + def phone_mail_type value + value =~ /^1\d{10}$/ ? 1 : 0 + end + +end diff --git a/app/services/blockchain/create_issue.rb b/app/services/blockchain/create_issue.rb new file mode 100644 index 000000000..c3ab5ad49 --- /dev/null +++ b/app/services/blockchain/create_issue.rb @@ -0,0 +1,28 @@ +class Blockchain::CreateIssue < ApplicationService + + attr_reader :params + + def initialize(params) + @params = params + end + + def call + ActiveRecord::Base.transaction do + username = @params[:user_id].to_s + token_name = @params[:project_id].to_s + amount = @params[:token_num].to_i + + # 调用token锁仓函数 + lock_balance_on_blockchain(username, token_name, amount) + end + rescue => e + puts "转账失败: #{e.message}" + raise Error, e.message + end + + private + + def no_use + puts "this function does not have any usage" + end +end \ No newline at end of file diff --git a/app/services/blockchain/create_trade.rb b/app/services/blockchain/create_trade.rb new file mode 100644 index 000000000..a4de98783 --- /dev/null +++ b/app/services/blockchain/create_trade.rb @@ -0,0 +1,29 @@ +class Blockchain::CreateTrade < ApplicationService + + attr_reader :params + + def initialize(params) + @params = params + end + + def call + ActiveRecord::Base.transaction do + username = @params[:user_id].to_s + token_name = @params[:project_id].to_s + amount = @params[:token_num].to_i + + # 调用token锁仓函数 + results = lock_balance_on_blockchain(username, token_name, amount) + return results + end + rescue => e + puts "转账失败: #{e.message}" + raise Error, e.message + end + + private + + def no_use + puts "this function does not have any usage" + end +end \ No newline at end of file diff --git a/app/services/blockchain/fix_issue.rb b/app/services/blockchain/fix_issue.rb new file mode 100644 index 000000000..332202792 --- /dev/null +++ b/app/services/blockchain/fix_issue.rb @@ -0,0 +1,28 @@ +class Blockchain::FixIssue < ApplicationService + + attr_reader :params + + def initialize(params) + @params = params + end + + def call + ActiveRecord::Base.transaction do + username = @params[:user_id].to_s + token_name = @params[:project_id].to_s + amount = @params[:token_num].to_i + + # 调用token锁仓函数 + unlock_balance_on_blockchain(username, token_name, amount) + end + rescue => e + puts "关联issue失败: #{e.message}" + raise Error, e.message + end + + private + + def no_use + puts "this function does not have any usage" + end +end \ No newline at end of file diff --git a/app/services/blockchain/invoke_blockchain_api.rb b/app/services/blockchain/invoke_blockchain_api.rb new file mode 100644 index 000000000..d485e171c --- /dev/null +++ b/app/services/blockchain/invoke_blockchain_api.rb @@ -0,0 +1,34 @@ +class Blockchain::InvokeBlockchainApi < ApplicationService + # 调用blockchain + + attr_reader :params + + def initialize(params) + @params = params + end + + def call + begin + uri = Blockchain.blockchain_config[:api_url] + uri = URI.parse(URI.encode(uri.strip)) + res = Net::HTTP.start(uri.host, uri.port) do |http| + req = Net::HTTP::Post.new(uri) + req['Content-Type'] = 'application/json' + req.body = @params + http.request(req) + end + if res.code.to_i != 200 + raise "区块链接口请求失败." + else + res_body = JSON.parse(res.body) + if res_body.has_key?("status") + else + raise "区块链接口请求失败." + end + end + return res_body + rescue Exception => e + raise "区块链接口请求失败." + end + end +end \ No newline at end of file diff --git a/app/services/blockchain/transfer_service.rb b/app/services/blockchain/transfer_service.rb new file mode 100644 index 000000000..4f7425e5d --- /dev/null +++ b/app/services/blockchain/transfer_service.rb @@ -0,0 +1,36 @@ +class Blockchain::TransferService < ApplicationService + + attr_reader :params + + def initialize(params) + @params = params + end + + def call + ActiveRecord::Base.transaction do + transfer_amount = params['transfer_amount'] + if (Float(transfer_amount) rescue false) == false or transfer_amount.to_i < 0 or Float(transfer_amount) != transfer_amount.to_i + raise Error, "请输入正确的转账金额" + end + transfer_amount = params['transfer_amount'].to_i + transfer_login = params['transfer_login'] + payer = params['payer_id'].to_s + payee = User.find_by(login: transfer_login) + if payee.nil? + raise Error, "未找到接收转账的用户" + else + payee = payee.id.to_s + token_name = params['project_id'].to_s + # 调用token转移函数 + transfer_balance_on_blockchain(payer, payee, token_name, transfer_amount) + end + end + end + + private + + def no_use + puts "this function does not have any usage" + end + +end \ No newline at end of file diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index ff36dfe52..c4f892f7f 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -18,6 +18,9 @@ class Projects::CreateService < ApplicationService ProjectUnit.init_types(@project.id) Repositories::CreateService.new(user, @project, repository_params).call upgrade_project_category_private_projects_count + if repo_use_blockchain + create_repo_on_blockchain(params, @project) + end else Rails.logger.info("#############___________create_project_erros______###########{@project.errors.messages}") end @@ -30,7 +33,7 @@ class Projects::CreateService < ApplicationService private - def upgrade_project_category_private_projects_count + def upgrade_project_category_private_projects_count # 如果为空或者项目为公有项目直接返回 return unless params[:project_category_id].present? return if repo_is_public @@ -38,7 +41,7 @@ class Projects::CreateService < ApplicationService project_category.increment!(:private_projects_count, 1) end - def authroize_user_id_success + def authroize_user_id_success (user.id == params[:user_id].to_i) || (user.organizations.find_by_id(params[:user_id]).present?) end @@ -53,7 +56,8 @@ class Projects::CreateService < ApplicationService ignore_id: params[:ignore_id], license_id: params[:license_id], website: params[:website], - identifier: params[:repository_name] + identifier: params[:repository_name], + use_blockchain: repo_use_blockchain # 新增,zxh } end @@ -72,4 +76,8 @@ class Projects::CreateService < ApplicationService def repo_is_public params[:private].blank? ? true : !(ActiveModel::Type::Boolean.new.cast(params[:private]).nil? ? false : ActiveModel::Type::Boolean.new.cast(params[:private])) end + + def repo_use_blockchain + params[:blockchain].blank? ? false : params[:blockchain] + end end diff --git a/config/configuration.yml.example b/config/configuration.yml.example index f056dee1b..6ae32e4f3 100644 --- a/config/configuration.yml.example +++ b/config/configuration.yml.example @@ -75,6 +75,9 @@ default: &default forum: domain: '' base_url: '/api' + # 区块链相关配置 + blockchain: + api_url: 'blockchain service url' production: <<: *default diff --git a/config/routes.rb b/config/routes.rb index 3488bacb4..2a837190f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -123,6 +123,20 @@ Rails.application.routes.draw do put 'commons/unhidden', to: 'commons#unhidden' delete 'commons/delete', to: 'commons#delete' + # blockchain related routes + get 'users/blockchain/balance', to: 'users#blockchain_balance' + post 'users/blockchain/balance_project', to: 'users#blockchain_balance_one_project' + post 'users/blockchain/transfer', to: 'users#blockchain_transfer' + post 'users/blockchain/exchange', to: 'users#blockchain_exchange' + post 'users/blockchain/create_trade', to: 'users#blockchain_create_trade' + get '/users/blockchain/get_trades', to: 'users#blockchain_get_trades' + post '/users/blockchain/trade', to: 'users#blockchain_trade' + get '/users/blockchain/get_verify_trades', to: 'users#blockchain_get_verify_trades' + post '/users/blockchain/verify_trade', to: 'users#blockchain_verify_trade' + get '/users/blockchain/get_history_trades', to: 'users#blockchain_get_history_trades' + post '/blockchain/issue/get_token_num', to: 'users#blockchain_get_issue_token_num' + get '/projects/blockchain/get_unclosed_issue_list', to: 'users#blockchain_get_unclosed_issue_list' + resources :owners, only: [:index, :show] scope module: :organizations do @@ -243,6 +257,7 @@ Rails.application.routes.draw do get :watch_users get :fan_users get :hovercard + get :hovercard4proj # author: zxh, 获取用户对项目的贡献情况 put :update_image get :get_image end diff --git a/db/migrate/20201230070048_add_use_blockchain_to_projects.rb b/db/migrate/20201230070048_add_use_blockchain_to_projects.rb new file mode 100644 index 000000000..f7e2fdde0 --- /dev/null +++ b/db/migrate/20201230070048_add_use_blockchain_to_projects.rb @@ -0,0 +1,5 @@ +class AddUseBlockchainToProjects < ActiveRecord::Migration[5.2] + def change + add_column :projects, :use_blockchain, :boolean, default: 0 + end +end diff --git a/db/migrate/20210421081736_add_blockchain_token_num_to_issue.rb b/db/migrate/20210421081736_add_blockchain_token_num_to_issue.rb new file mode 100644 index 000000000..1391667e5 --- /dev/null +++ b/db/migrate/20210421081736_add_blockchain_token_num_to_issue.rb @@ -0,0 +1,5 @@ +class AddBlockchainTokenNumToIssue < ActiveRecord::Migration[5.2] + def change + add_column :issues, :blockchain_token_num, :integer + end +end