require 'net/https' require 'uri' require 'json' require 'yaml' require 'lhj/command/yapi/formatters/service' require 'lhj/helper/dingtalk_config' require 'lhj/helper/erb_template_helper' 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') @language = argv.option('lan', 'oc') @save = argv.flag?('save', false) @sync = argv.flag?('sync', false) @notify = argv.flag?('notify', false) @debug = argv.flag?('debug', false) @sync = true if @notify @save = true if @sync @http_headers = [] @config_id = '' @config_model_pre = 'ML' @model_default_suffix = 'Model' @type_trans = {} @config_model_names = [] @model_names = [] @http_url = '' super 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 unless res_body['errcode'].to_i.zero? puts '请执行 lhj yapi-init 初始化配置'.red return end return unless res_body && !res_body.empty? # 1.print request result result_model_name = print_res_body_model(res_body) # 2.print request json body param_model_name = print_req_body_model(res_body) # 3.print request param print_req_query(res_body['data']) # 4.print request method service_code = print_http_method(res_body['data'], result_model_name, param_model_name) # 5.save to file file_map = save_to_file(service_code) if @save # 6.push to git push_to_git if @sync # 7.notify robot notify_robot(file_map) if @notify 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 sub_folder_name return @sub_folder_name if @sub_folder_name time = Time.now @sub_folder_name ||= time.strftime('%Y%m%d%H%M%S') @sub_folder_name end def save_to_file(service_code) @model_names = [] model_name = @config_model_names.first file_name = gen_model_name('') unless File.exist?(File.expand_path(sub_folder_name, '.')) FileUtils.mkdir_p(File.expand_path(sub_folder_name, '.')) end h_file = File.join('.', sub_folder_name, "#{file_name}.h") m_file = File.join('.', sub_folder_name, "#{file_name}.m") service_file = File.join('.', sub_folder_name, "#{model_pre}#{model_name}Service.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? File.write(service_file, service_code) if service_code puts "\n\n生成文件成功!所在路径:\n#{File.expand_path(h_file)} \n#{File.expand_path(m_file)}".green { h_file: h_file, m_file: m_file, s_file: service_file } end def push_to_git Actions.sh('git checkout master', log: false) Actions.sh('git add .', log: false) Actions.sh("git commit -m 'generate yapi code'", log: false) Actions.sh('git push', log: false) end def notify_robot(template_vars) robot_url = 'https://oapi.dingtalk.com/robot/send?access_token=fe879fd3e7a3b5e59d5719b2384845b7884901919be5a78fe443cbf777869807' template = Lhj::ErbTemplateHelper.load('oc_code_notify') output = Lhj::ErbTemplateHelper.render(template, template_vars, '-') Lhj::Dingtalk.post_message_robot(robot_url, '生成代码', output) 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 case @language when 'oc' print_models(models) print_models_impl(models) when 'java' print_models_for_java(models) end puts "\n<===============打印返回数据模型-End=====================>\n".green models.last[:name] 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 case @language when 'oc' print_models(models) print_models_impl(models) when 'java' print_models_for_java(models) end puts "\n<===============打印请求模型-End=====================>\n".green models.last[:name] 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_for_java(models) models.each do |model| model_name = model[:name] || '' model_properties = model[:properties] puts_h "public class #{model_name} extends BaseModel implements BusinessEvent.ProductDataCollect {" model_properties.each do |m| print_model_for_java(m) end puts_h "}\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&.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};" 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 def print_model_for_java(m) key = m[:key] type_name = m[:type_name] type = m[:type] des = m[:description] || '' des.gsub!(/\n/, ' ') default = m[:default] case type when 'integer' puts_h "int #{key};//#{des} #{default}" when 'string' puts_h "String #{key};//#{des} #{default}" when 'number' puts_h "double #{key};//#{des} #{default}" when 'float' puts_h "double #{key};//#{des} #{default}" when 'double' puts_h "double #{key};//#{des} #{default}" when 'boolean' puts_h "boolean #{key};//#{des} #{default}" when 'object' puts_h "#{type_name} #{key};//#{des} #{default}" when 'array' puts_h "#{type_name}[] #{key};//#{des} #{default}" else puts_h "String #{key};//#{des} #{default}" end end # @param [Object] data def print_req_query(data) return unless data && data['req_query'] case @language when 'oc' 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" when 'java' end end def print_http_method(data, result_model_name, param_model_name) return unless data path = data['path'] path_name = path.split('/').map { |s| s.gsub(/[^A-Za-z0-9]/, '').gsub(/^\w/) { $&.upcase } }.join('') if path path_key = "k#{path_name}URL" mth = data['method'] mth = 'JSON' if data['req_body_is_json_schema'] case @language when 'oc' puts "\n<===============方法调用=====================>\n".green model_temp = Lhj::ErbTemplateHelper.load('oc_code_service_inner') model_temp_result = Lhj::ErbTemplateHelper.render(model_temp, { result_model_name: result_model_name }, '-') yapi_temp = Lhj::ErbTemplateHelper.load('oc_code_service') yapi_vars = { title: data['title'], desc: data['desc'], username: data['username'], path: path, path_key: path_key, path_name: path_name, result_model_name: result_model_name, param_model_name: param_model_name || 'MLParamModel', mth: mth, model_template: model_temp_result } yapi_temp_result = Lhj::ErbTemplateHelper.render(yapi_temp, yapi_vars, '-') puts yapi_temp_result.green return yapi_temp_result when 'java' end end end end end