require 'net/https' require 'uri' require 'json' require 'yaml' require 'lhj/command/yapi/formatters/service' module Lhj class Command # generate model from yapi class Yapi < Command self.summary = '通过yapi接口生成请求' self.description = '更新 ~/.lhj/yapi.yml 文件配置后执行`lhj api`生成接口模型' def self.options [ %w[--id api的id], %w[--model-pre 模型的前缀], %w[--save 保存生成文件] ] end def initialize(argv) @id = argv.option('id') @model_pre_name = argv.option('model-pre') @save = argv.flag?('save', false) @debug = argv.flag?('debug', false) @http_url = '' @http_headers = [] @config_id = '' @config_model_pre = 'ML' @model_default_suffix = 'Model' @type_trans = {} @config_model_names = [] @model_names = [] # <<<===== model for template @result_model_name = '' @param_model_name = '' @model_template = '' @desc = '' @path = '' @path_name = '' @path_key = '' @method = 'GET' @title = '' @username = '' @req_body_is_json_schema = false # =====>>> super end def begin_title '读取配置文件~/.lhj/yapi.yml' end def handle process(yml_file) if File.exist?(yml_file) end def process(config_file) load_config(config_file) res_body = req_api_model return unless res_body && res_body['errcode'].to_i.zero? && !res_body.empty? # 1.print request result print_res_body_model(res_body) # 2.print request json body print_req_body_model(res_body) # 3.print request param print_req_query(res_body['data']) # 4.print request method print_http_method(res_body['data']) # 5.save to file save_to_file if @save end def test_ding require 'net/http' require 'uri' body = { 'msgtype' => 'text', 'text' => { 'content' => 'error:上传蒲公英超时失败!' } }.to_json Net::HTTP.post(URI('https://oapi.dingtalk.com/robot/send?access_token=6a3519057170cdb1b7274edfe43934c84a0062ffe2c9bcced434699296a7e26e'), body, 'Content-Type' => 'application/json') end def puts_h(str) puts str.magenta @h_file_array ||= [] @h_file_array << str end def puts_h_red(str) puts str.red @h_file_array ||= [] @h_file_array << str end def puts_m(str) puts str.blue @m_file_array ||= [] @m_file_array << str end def save_to_file @model_names = [] file_name = gen_model_name('') h_file = File.join('.', "#{file_name}.h") m_file = File.join('.', "#{file_name}.m") File.write(h_file, @h_file_array.join("\n")) if @h_file_array.count.positive? File.write(m_file, @m_file_array.join("\n")) if @m_file_array.count.positive? puts "\n\n生成文件成功!所在路径:\n#{File.expand_path(h_file)} \n#{File.expand_path(m_file)}".green end def url_str "#{@http_url}#{api_id}" end def yml_file File.join(Lhj::Config.instance.home_dir, 'yapi.yml') end def load_config(file) config = YAML.load_file(file) config.each do |k, v| @http_headers << "#{k}=#{v}" if k.eql?('__wpkreporterwid_') || k.eql?('_yapi_token') || k.eql?('_yapi_uid') end @http_url = config['url'] @config_id = config['id'] @config_model_pre = config['model_pre'] @config_model_suffix = config['model_suffix'] @config_model_names = config['model_names'] @type_trans = config['type_trans'] end def api_id @id || @config_id.to_s end def model_pre @model_pre_name || @config_model_pre end def model_suffix @config_model_suffix || @model_default_suffix end def req_api_model uri = URI.parse(url_str) req = Net::HTTP::Get.new(uri) req['Cookie'] = @http_headers.join('; ') res = Net::HTTP.start(uri.hostname, uri.port) do |http| http.request(req) end res_json = JSON.parse(res.body) puts res.body unless res_json['errcode'].to_i.zero? puts res.body if @debug res_json end def print_res_body_model(res_json) res_body = fetch_res_boy(res_json) return unless res_body puts "\n<===============打印返回数据模型-Begin=====================>\n".green models = [] handle_model(res_body) do |model| models << model end print_models(models) print_models_impl(models) @result_model_name = models.last[:name] puts "\n<===============打印返回数据模型-End=====================>\n".green end def print_req_body_model(res_json) req_body = fetch_req_body(res_json) return unless req_body puts "\n<===============打印请求模型-Begin=====================>\n".green models = [] handle_model(req_body) do |model| models << model end print_models(models) print_models_impl(models) @param_model_name = models.last[:name] puts "\n<===============打印请求模型-End=====================>\n".green end def fetch_res_boy(res_json) return if !res_json || !res_json['data'] || !res_json['data']['res_body'] res_body = JSON.parse(res_json['data']['res_body']) return if !res_body['properties'] || !res_body['properties']['detailMsg'] result = res_body['properties']['detailMsg'] return unless result['type'] == 'object' || result['type'] == 'array' result['name'] = gen_model_name('') result end def fetch_req_body(res_json) return if !res_json || !res_json['data'] || !res_json['data']['req_body_other'] result = JSON.parse(res_json['data']['req_body_other']) result['name'] = gen_model_name('') result end def gen_model_name(name) n = name.gsub(/vo|model|list/i, '').gsub(/(.*)s$/, '\1').gsub(/^\w/) { $&.upcase } if n.length <= 0 n = @config_model_names.detect { |c| @model_names.none? { |na| na.gsub(/#{model_pre}(.*)Model/, '\1').eql?(c) } } end model_name = "#{model_pre}#{n}#{model_suffix}" @model_names << model_name model_name end def handle_model(model, &block) p_type = model['type'] p_name = model['name'] p_properties = model['properties'] p_model = { name: p_name } properties = [] case p_type when 'object' p_properties.each do |k, v| c_type = @type_trans[v['type']] || v['type'] c_model = { key: k, type: c_type, description: v['description'], default: '' } if v['type'].eql?('object') || v['type'].eql?('array') o = v['items'] || v o['name'] = gen_model_name(k) if v['type'].eql?('array') && v['items']['type'].eql?('string') c_model[:type_name] = 'NSString' else c_model[:type_name] = o['name'] handle_model(o, &block) end end properties << c_model end p_model[:properties] = properties block[p_model] if block_given? when 'array' t = model['items'] t['name'] = p_name handle_model(t, &block) end end def print_models(models) models.each do |model| model_name = model[:name] || '' model_properties = model[:properties] puts_h "@interface #{model_name} : NSObject" model_properties.each do |m| print_model(m) end puts_h "@end\n\n\n" end end def print_models_impl(models) models.each do |model| puts_m "@implementation #{model[:name]}" str = model[:properties].filter { |p| p[:type].eql?('array') && !p[:type_name].eql?('NSString') }.map { |p| "@\"#{p[:key]}\": #{p[:type_name]}.class" }.join(', ') if str && str.length.positive? puts_m '+(NSDictionary *)modelContainerPropertyGenericClass {' puts_m " return @{#{str}};" puts_m '}' end puts_m "@end\n" puts "\n\n" end end def print_model(m) key = m[:key] type_name = m[:type_name] type = m[:type] des = m[:description] || '' des.gsub!(/\n/, ' ') default = m[:default] puts_h "///#{des} #{default}" case type when 'integer' puts_h "@property (nonatomic, assign) NSInteger #{key};" if des.include?('分') || des.include?('0.01') puts_h_red '/////////==========删掉其中一个属性' puts_h_red "@property (nonatomic, strong) MLCentNumber *#{key};" end when 'cent' puts_h "@property (nonatomic, strong) MLCentNumber *#{key};" when 'string' puts_h "@property (nonatomic, copy) NSString *#{key};" when 'number' puts_h "@property (nonatomic, strong) NSNumber *#{key};" when 'float' puts_h "@property (nonatomic, assign) CGFloat #{key};" when 'double' puts_h "@property (nonatomic, assign) double #{key};" when 'boolean' puts_h "@property (nonatomic, assign) BOOL #{key};" when 'object' puts_h "@property (nonatomic, strong) #{type_name} *#{key};" when 'array' puts_h "@property (nonatomic, strong) NSArray<#{type_name} *> *#{key};" else puts_h "@property (nonatomic, copy) NSString *#{key};" end end # @param [Object] data def print_req_query(data) return unless data && data['req_query'] puts_h '@interface MLParamModel : NSObject' data['req_query'].each do |h| des = h['desc'] puts_h "///#{des} #{h['example']}" puts_h "@property (nonatomic, copy) NSString *#{h['name']};" end puts_h '@end' puts "\n\n" end def print_http_method(data) return unless data @path = data['path'] @method = data['method'] @title = data['title'] @username = data['username'] @desc = data['desc'] @method = 'JSON' if data['req_body_is_json_schema'] @path_name = @path.split('/').map { |s| s.gsub(/[^A-Za-z0-9]/, '').gsub(/^\w/) { $&.upcase } }.join('') if @path @path_key = "k#{@path_name}URL" puts "\n<===============方法调用=====================>\n".green @model_template = Lhj::ErbFormatter::Service.new(self).render('model') puts Lhj::ErbFormatter::Service.new(self).render('yapi').blue end end end end