diff --git a/app/models/concerns/dun_check_able.rb b/app/models/concerns/dun_check_able.rb new file mode 100644 index 00000000..c0d95eb4 --- /dev/null +++ b/app/models/concerns/dun_check_able.rb @@ -0,0 +1,50 @@ +module DunCheckAble + extend ActiveSupport::Concern + + included do + validate :check_text_able + end + + def check_text_able + dun_check_params = get_model + if dun_check_params[:is_change] + dun_check_params.delete(:is_change) + check_result = DunCheck::TextCheck.new(dun_check_params).call + if check_result[:status].to_i == -1 + errors.add(:base, "内容含有:#{check_result[:extra_params][:infos]},请修改") + # raise ActiveRecord::RecordInvalid.new(self) + end + end + end + + def get_model + dun_model = self.class.name + case dun_model + when "Issue" + check_params = { + title: self.subject, + content: self.description, + is_change: (self.subject_changed? || self.description_changed?) && self.subject.present? && self.description.present? + } + when "PullRequest" + check_params = { + title: "", + content: self.body, + is_change: self.body_changed? && self.body.present? + } + when "Journal" + check_params = { + title: "", + content: self.notes, + is_change: self.notes_changed? && self.notes.present? + } + when "Version" + check_params = { + title: self.name, + content: self.description, + is_change: (self.name_changed? || self.description_changed?) && self.name.present? && self.description.present? + } + end + return check_params + end +end diff --git a/app/models/concerns/dun_check_image_able.rb b/app/models/concerns/dun_check_image_able.rb new file mode 100644 index 00000000..d640a816 --- /dev/null +++ b/app/models/concerns/dun_check_image_able.rb @@ -0,0 +1,34 @@ +module DunCheckImageAble + extend ActiveSupport::Concern + + included do + def self.check_image_able(file) + + original_filename = file.original_filename + file_extention = original_filename.split(".").last + check_include = %w(jpg png bmp gif webp tiff jpeg) + + if file_extention && check_include.include?(file_extention) + base64_file = Base64.encode64(file.open.read.force_encoding(Encoding::UTF_8)) + check_params = [ + { + name: original_filename, + type: 2, + data: base64_file, + } + ] + check_result = DunCheck::ImageCheck.new(check_params).call + if check_result[:status].to_i == -1 + return {status: -1, message: check_result[:message]} + else + return {status: 1} + end + else + return {status: 1} + end + end + end + + + +end diff --git a/app/models/issue.rb b/app/models/issue.rb index d7dd3bbb..7e122827 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -1,5 +1,6 @@ class Issue < ApplicationRecord #issue_type 1为普通,2为悬赏 + include DunCheckAble belongs_to :project, :counter_cache => true belongs_to :tracker,optional: true has_many :project_trends, as: :trend, dependent: :destroy diff --git a/app/models/journal.rb b/app/models/journal.rb index f730878d..d7c4f2a4 100644 --- a/app/models/journal.rb +++ b/app/models/journal.rb @@ -1,4 +1,5 @@ class Journal < ApplicationRecord + include DunCheckAble belongs_to :user belongs_to :issue, foreign_key: :journalized_id, :touch => true has_many :journal_details, :dependent => :delete_all diff --git a/app/models/pull_request.rb b/app/models/pull_request.rb index d752bb85..b2d4dadf 100644 --- a/app/models/pull_request.rb +++ b/app/models/pull_request.rb @@ -1,5 +1,6 @@ class PullRequest < ApplicationRecord #status 0 默认未合并, 1表示合并, 2表示请求拒绝 + include DunCheckAble belongs_to :issue belongs_to :user belongs_to :project, :counter_cache => true diff --git a/lib/dun_check/image_check.rb b/lib/dun_check/image_check.rb new file mode 100644 index 00000000..77d19eaf --- /dev/null +++ b/lib/dun_check/image_check.rb @@ -0,0 +1,167 @@ +class DunCheck::ImageCheck + + #检测结果,0:通过,1:嫌疑,2:不通过 + # include ActionView::Helpers::LoopTextsHelper + + require 'uri' + require 'net/http' + + def initialize(image_params) + @image_params = image_params + end + + def call + begin + Rails.logger.info("==========@image_params===========#{@image_params}") + dun_params = check_dun_params(@image_params) + + api_url = Redmine::Configuration['dun']['image_api'] + + uri = URI.parse(api_url) + + http = Net::HTTP.new(uri.hostname, uri.port) + if api_url.include?("https://") + http.use_ssl = true + end + dun_params_str = URI.encode_www_form(dun_params) + + header = {'content-type':'application/x-www-form-urlencoded'} + response = http.post(uri.path, dun_params_str, header) + + response_body = JSON.parse(response.body) + if response_body["code"].to_i == 200 + response_body_result = response_body["antispam"] + response_lables = response_body_result[0]["labels"] + return_sub_lable = "" + if response_lables.present? + return_sub_lable = get_sub_labels(response_lables) + end + render_status = response_body_result[0]["action"].to_i == 0 ? 1 : -1 + tip_status(render_status, return_sub_lable.present? ? "图片含有: #{return_sub_lable}" : response_body["msg"] ) + else + tip_status(-1, response_body["msg"]) + end + rescue Exception => ex + Rails.logger.info "*** transaction abored!" + Rails.logger.info "*** errors: #{ex.message}" + tip_status(-1, "检测失败") + end + + end + + private + + def check_dun_params(image_params) + + dun_public_params = DunCheck::PublicParams.new("image") + check_params = { + version: "v4", + images: image_params.to_json + } + check_params.merge!(dun_public_params.call) + dun_params = dun_public_params.generate_sign(check_params) + + return dun_params + end + + def tip_status(status, message, msg_params={}) + return {status: status, message: message, extra_params: msg_params} + end + + def get_sub_labels(labels) + _sub_labels = [] + labels.each do |label| + error_label = label["subLabels"].present? ? label["subLabels"][0]["subLabel"] : "" + if error_label.present? + _sub_labels.push(sub_lables[:"#{error_label.to_s}"]) + end + end + return _sub_labels.present? ? _sub_labels.join(",") : "" + end + + def sub_lables + { + "10000": "色情", + "10001": "女下体", + "10002": "女胸", + "10003": "男下体", + "10004": "性行为", + "10005": "臀部", + "10006": "口交", + "10007": "卡通色情", + "10008": "色情人物", + "10009": "儿童色情", + "11000": "性感低俗", + "11001": "亲吻", + "11002": "腿部特写", + "11003": "非漏点赤膊", + "11004": "胸部", + "100001": "色情文字-色情其他", + "100002": "色情文字-色情传播", + "100003": "色情文字-色情性器官", + "100004": "色情文字-色情挑逗", + "100005": "色情文字-色情低俗段子", + "100006": "色情文字-色情性行为", + "100007": "色情文字-色情舆情事件", + "100008": "色情文字-色情交友类", + "20000": "广告", + "20001": "广告带文字", + "200009": "广告文字-商业推广", + "200010": "广告文字-广告法", + "200011": "刷量行为", + "200012": "广告其他", + "260052": "广告文字-广告法-涉医疗用语", + "260053": "广告文字-广告法-迷信用语", + "260054": "广告文字-广告法-需要凭证", + "260055": "广告文字-广告法-限时性用语", + "260056": "广告文字-广告法-涉嫌诱导消费者", + "260057": "广告文字-广告法-涉嫌欺诈消费者", + "260058": "广告文字-广告法-法律风险较高", + "260059": "广告文字-广告法-极限词", + "21000": "二维码", + "30000": "暴恐", + "30001": "暴恐图集", + "30002": "暴恐旗帜", + "30003": "暴恐人物", + "30004": "暴恐标识", + "30005": "暴恐场景", + "300016": "暴恐文字-暴恐其他", + "40000": "违禁", + "40001": "违禁图集", + "40002": "违禁品", + "40003": "特殊标识", + "40004": "血腥模型", + "40005": "公职服饰", + "40006": "不文明", + "40007": "违禁人物", + "40008": "违禁场景", + "40009": "火焰", + "40010": "骷髅", + "40011": "货币", + "40012": "毒品", + "400017": "违禁文字-违禁其他", + "600018": "违禁文字-谩骂其他", + "50000": "涉政", + "50001": "涉政图集", + "50002": "中国地图", + "50003": "涉政人物", + "50004": "涉政旗帜", + "50005": "涉政标识", + "50006": "涉政场景", + "500013": "涉政文字-涉政其他", + "500014": "涉政文字-敏感专项", + "500015": "涉政文字-严格涉政", + "500039": "涉政文字-时事报道", + "500040": "涉政文字-领导人相关", + "500041": "涉政文字-英雄烈士相关", + "500042": "涉政文字-邪教迷信", + "500043": "涉政文字-落马官员相关", + "500044": "涉政文字-热点舆情", + "500045": "涉政文字-涉政综合", + "90000": "其他", + "90002": "自定义用户名单", + "90003": "自定义IP名单", + "900020": "文字违规-其他" + } + end +end diff --git a/lib/dun_check/public_params.rb b/lib/dun_check/public_params.rb new file mode 100644 index 00000000..7b44cae4 --- /dev/null +++ b/lib/dun_check/public_params.rb @@ -0,0 +1,31 @@ +class DunCheck::PublicParams + def initialize(type) + @type = type + end + + def call + + public_params = { + secretId: Redmine::Configuration['dun']['secretId'], + businessId: Redmine::Configuration['dun']["#{@type}_businessId"], + timestamp: DateTime.current.strftime('%Q').to_i, + nonce: rand(10 ** 11).to_i + } + return public_params + end + + def generate_sign(params) + secretkey = Redmine::Configuration['dun']['secretKey'] + sort_params = params.sort.to_h + sign_str = "" + sort_params.each do |k,v| + sign_str += "#{k.to_s}#{v.to_s}" + end + sign_str += secretkey + md5_sign = Digest::MD5.hexdigest(sign_str.to_s.force_encoding("UTF-8")) + return sort_params.merge!(signature: md5_sign) + end + +end + + diff --git a/lib/dun_check/text_check.rb b/lib/dun_check/text_check.rb new file mode 100644 index 00000000..c7df9529 --- /dev/null +++ b/lib/dun_check/text_check.rb @@ -0,0 +1,164 @@ +class DunCheck::TextCheck + + # include DunCheck::PublicParams + #text_params = { + # content: "ccc", #内容 + # ip: "xxx", #用户ip + # account: "xxx", #登录login + # nickname: "xxx", #用户姓名 + # title: "xxx", #帖子的标题 + # } + + #检测结果,0:通过,1:嫌疑,2:不通过 + # include ActionView::Helpers::LoopTextsHelper + + require 'uri' + require 'net/http' + + def initialize(text_params) + @text_params = text_params + end + + def call + new_text_params = @text_params + text_long_array = [] + check_content = new_text_params[:content] + format_text(check_content,text_long_array) + (1..text_long_array.size).each do |i| + new_text_params.merge!(content: text_long_array[i-1]) + check_result = check_text(new_text_params) + if check_result[:status].to_i == -1 + return check_result + break + else + if i == text_long_array.size + return check_result + else + next + end + end + end + end + + def check_text(text_params) + begin + dun_params = check_dun_params(text_params) + + api_url = Redmine::Configuration['dun']['text_api'] + + uri = URI.parse(api_url) + + http = Net::HTTP.new(uri.hostname, uri.port) + if api_url.include?("https://") + http.use_ssl = true + end + dun_params_str = URI.encode_www_form(dun_params) + header = {'content-type':'application/x-www-form-urlencoded'} + response = http.post(uri.path, dun_params_str, header) + response_body = eval(response.body) + Rails.logger.info("======response========#{response_body}") + + if response_body[:code].to_i == 200 + response_body_result = response_body[:result] + response_body_labels = response_body_result[:labels].present? ? response_body_result[:labels][0] : [] + extra_params = { + action: response_body_result[:action], + taskId: response_body_result[:taskId], + infos: response_body_labels.present? ? sub_lables[:"#{response_body_labels[:subLabels][0][:subLabel]}"] : "" + } + render_status = response_body_result[:action].to_i == 0 ? 1 : -1 + tip_status(render_status, response_body[:msg], extra_params) + else + tip_status(-1, response_body[:msg]) + end + rescue Exception => ex + Rails.logger.info "*** transaction abored!" + Rails.logger.info "*** errors: #{ex.message}" + tip_status(-1, "检测失败") + end + + end + + private + + def check_dun_params(text_params) + dun_public_params = DunCheck::PublicParams.new("text") + rand_data_id = random_dataId + check_params = { + dataId: rand_data_id, + version: "v3.1", + callback: rand_data_id + }.merge(text_params) + + check_params.merge!(dun_public_params.call) + dun_params = dun_public_params.generate_sign(check_params) + return dun_params + end + + def format_text(text_long, text_long_array) + slice_content = text_long.slice(0..4998) + last_slice_content = text_long.slice(4999..-1) + text_long_array.push(slice_content) + if last_slice_content.present? + if last_slice_content.length > 4999 + format_text(last_slice_content, text_long_array) + else + text_long_array.push(last_slice_content) + end + end + end + + def random_dataId + Digest::MD5.hexdigest(rand(100000000).to_s) + end + + def check_labels + # 100:色情,200:广告,260:广告法,300:暴恐,400:违禁,500:涉政,600:谩骂,700:灌水 + %w(100 200 260 300 400 500 600 700).join(",") + end + + def tip_status(status, message, msg_params={}) + return {status: status, message: message, extra_params: msg_params} + end + + def sub_lables + { + "100001": "色情其他", + "100002": "色情传播", + "100003": "色情性器官", + "100004": "色情挑逗", + "100005": "色情低俗段子", + "100006": "色情性行为", + "100007": "色情舆情事件", + "100008": "色情交友类", + "200009": "商业推广", + "200010": "广告法", + "200011": "刷量行为", + "200012": "广告其他", + "260052": "广告法-涉医疗用语(非药品禁止宣传药效)", + "260053": "广告法-迷信用语", + "260054": "广告法-需要凭证(可以写但需要凭证证明)", + "260055": "广告法-限时性用语(可以写但必须有具体时间)", + "260056": "广告法-涉嫌诱导消费者", + "260057": "广告法-涉嫌欺诈消费者", + "260058": "广告法-法律风险较高", + "260059": "广告法-极限词(用语绝对化)", + "300016": "暴恐其他", + "400017": "违禁其他", + "400021": "违禁网监要求", + "500013": "涉政其他", + "500014": "敏感专项", + "500015": "严格涉政", + "500039": "时事报道", + "500040": "领导人相关", + "500041": "英雄烈士相关", + "500042": "邪教迷信", + "500043": "落马官员相关", + "500044": "热点舆情", + "500045": "涉政综合", + "600018": "谩骂其他", + "700019": "灌水其他", + "900020": "其他", + } + end +end