当前位置:首页 > 技术分析 > 正文内容

东京码农CTO的面试之旅_京东码农待遇怎么样

ruisui883个月前 (02-08)技术分析16

前几天参加了一家公司的面试(技术测试),跟大家共享一下。

个人的简历

我是2001年从国内大学毕业之后就直接来的日本,一直从事IT行业,20多年了。来日本之后工作经历大概是这样的。

  1. 日系,中资系中小软件公司。主要是派遣和社内开发。
  2. 日本埃森哲。主要是大客户的社内系统的维护保守,提案。也负责跟大连开发中心联系。
  3. 创业公司 CTO。创业期加入。公司整体规模从初期的5人发展到100人左右。开发团队也从初期1人(我自己)发展到20人。主要是B2B的开发,公司内部开发团队的组建,管理。
  4. 创业公司 CTO。主要负责越南的开发团队和日本社内的开发团队的管理,当然自己也在做开发。
  5. 创业公司 联合创始人。创业公司B出售给上市公司之后,公司内的开发团队和一部分的营业团队,带着一部分项目,独立出来成立了这个公司。日本这边大概有20多个人,越南那里也有20多号人。

这次想换工作的背景

  1. 跟他人一块成立的创业公司进展不是太顺利,合伙人是4个,有点时候也感觉有劲使不到一块去。对公司前景有点灰心。

由于这个原因,并且岁数也有点大了(40多了),就打算找一个稳定点的公司,老老实实上班了。

工资情况

可能好多朋友也对在日本上班的IT人的工资情况有兴趣,就把我的情况简单分享一下。

  1. 刚来日本的时候是年收入300万日元,后来在派遣公司换来换去大概从第4年的时候,年收入达到600万日元
  2. 在埃森哲的时候,因为加班多少对收入会有很大的影响,我当时是分配到了运用维护的项目,项目预算控制得很严,不像新开发项目有那么大的加班空余,所以年收入当时是700万到800万日元上下浮动
  3. 创业公司,刚进去的时候是年收入800万日元,到后来公司做大,开发团队也壮大之后,基本上2年左右上调一次。800万到1000万到1200万日元。在这个公司待了5年。
  4. 创业公司,刚进去的时候年收入是1450万日元,一直到现在。

大概就是这个情况了,简单总结一下就是,20岁后半达到年收入600万日元,30岁中间之前达到年收入800万日元,40岁之前达到年收入1400万日元这样的过程。但是也不是所有的人都是这个情况,我是后期(最近10年)一直做公司的CTO,带团队开发。所以个人感觉应该比正常的IT行业的开发人员(比如说编码的,做上流设计或者项目管理的)的收入高一些。

日本这几年创业公司风起,行情比以前好很多很多,所以开发人员比较好找工作。比如说800万年收入上下的工作就比以前好找多了,但是要找很高工资的话还得是,开发组的负责人,技术负责人(Tech Lead),或者CTO(VPoE)才行。

单纯搞开发的话,达到2000万日元年收入以上的就比较少了,个别的一些公司(尤其是外企)能提供,但是都会要求很强悍的技术(在一个行业要有一定的知名度)和管理背景,人脉关系等等。

对方公司的情况

这次面试的是一家日本的超大银行(传统的金融行业)和互联网金融公司合资开的一个创业公司。具体提供的服务就不讲了。对方公司现在正在设计服务和组建开发团队。

这次的职位能够提供的薪水范围是900万日元到2000万日元(这个范围比较大,有可能有猫腻),但是因为是对方的负责人直接发过来的邀请邮件,所以就打算去试一下看看。

面试经过

第一次面试通过之后,二次面试之前来了个编程考试,让在二次面试之前把代码完成。第二次面试的时候会以完成的代码为话题来考核,然后会在第二面的时候,再现场进行一次技术框架设计的考核。

技术考试(Coding Test)

日语内容是这样的


大概意思就是

  • 用户可以往系统里边登录银行交易的流水,但是累计金额不到超过1000,超过了1000的话就返回一个特定的代码(这个实际上讲的不是很清楚,实际上要返回什么代码,要去看他给的那个 Golang 开发的测试代码)
  • 附送的代码里边有API测试用的Go代码,这个代码不允许修改。完成的API必须通过这个测试。
  • 开发环境使用 docker-compose 搭建
  • API开发不限开发语言


要开发的内容实际上很简单,平常也一直用docker-compose。倒也没什么问题。但是Go开发语言是第一次用,怎么执行测试文件也不知道。稍微焦虑了一些,额外花了一些时间。

先看一下对方给的代码,可以从Github上直接下载。

下载完之后的文件夹构成就是这样的

╰─ tree -L 1                                                                                                                                                              ─╯
.
├── LICENSE
├── README.md
├── app
├── db
├── docker-compose.yml
├── go.mod
├── go.sum
└── main_test.go

2 directories, 6 files

1

app

放API代码的地方,开发语言不限。

2

db

MySQL的创建表格和测试数据的脚本在里边

3

docker-compose.yml

MySQL启动用的设定在里边

4

go.mod / go.sum

Goalng测试代码的依存关系

5

main_test.go

测试API的代码,用Goalng编写的。

虽然不懂Golang,但是以前C, C+++, C#,Java以前都用过,大概也能推测出来什么意思。


登录失败的时候:402: Payment Required

银行流水登录成功: 201: Created


再看一下docker-compose.yml文件的内容

version: "3.8"

services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 1
    volumes:
      - ./db:/docker-entrypoint-initdb.d
    ports:
      - 3306:3306
    hostname: db

  app:
    build:
      context: app
    ports:
      - 8888:8888

就是普普通通的一个docker-compose 设定文件,启动MySQL然后投入测试用的数据,然后再启动API服务。

我的任务就是开发这个API,然后放到那个app容器里边就好了。


说干就干,因为这7,8年都是用Ruby on Rails做开发,所以就用Rails来做这个API了。Rails里边也有一些专门的做API开发的Gem(比如说 grape ),但是这次的需求很简单,并没有提到那些东西,所以就打算用Rails的Controller+返回JSON数据来做了。

先把docker-compose.yml文件按照设想的修改一下

version: "3.8"

services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 1
    volumes:
      - ./db:/docker-entrypoint-initdb.d
    ports:
      - "3306:3306"
    hostname: db

  app:
    build:
      context: app
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - ./app:/app/
    ports:
      - "8888:3000"
    depends_on:
      - db
    hostname: app

  app_test:
    build:
      context: app_test
    depends_on:
      - app
    volumes:
      - ./app_test:/app_test/
    tty: true
    hostname: app_test

db就是那个MySQL了,这个是对方以前提前设定好的,不需要做任何改动。

app就是这次用Rails来开发API的那个容器。

app_test就是用来执行测试API的容器,因为对方给的是Golang的测试代码。我的本地机器上边并没有执行环境,也不打算污染自己的机器,所以就用docker-compose容器了。完事之后把Docker镜像都删掉就好了。

# 先把不要的东西都删掉
rm app/**
cd app
touch Gemfile
touch Gemfile.lock
touch entrypoint.sh
# 先这样就好,回头docker-compose up之后,通过docker-compose来操作

app容器的entrypoint.sh文件的内容,就是为了避免残留的文件影响Rails的启动。

#!/bin/bash
set -e

rm -f /app/tmp/pids/server.pid

exec "$@"

app容器的Dockerfile文件,这个也没什么特别。Ruby的版本指定后,然后适当的安装需要的东西就好

FROM ruby:2.7.5

RUN apt-get update && apt-get install -y curl apt-transport-https wget && \
    curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
    echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
    apt-get update && apt-get install -y yarn

RUN apt-get update -qq && apt-get install -y nodejs yarn

RUN mkdir /app
WORKDIR /app

COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock

RUN bundle config set paht "vendor/bundle"
RUN bundle install

RUN yarn install --check-files
RUN bundle exec rails webpacker:compile

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"]


启动容器

docker-compose up -d
docker-compose ps
# 或者docker ps
# 会看到三个容器

rails安装

# 要注意的是不要用run,要用exec
docker-compose exec app bundle install
docker-compose exec app bundle exec rails new . --database=mysql --skip-test
docker-compose exec app bundle exec rails webpacker:install
docker-compose exec app bundle config set path 'vendor/bundle'
docker-compose exec app bundle install

# 重启容器
docker-compose stop app
docker-compose start app
# 有点时候不知道什么原因,一些设定或者改动的文件没有反应到容器的话,就把容器删掉重新启动就好
docker-compose down
docker-compose up -d

调整Gemfile文件,把这次要用的Gem都放进去

source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '2.7.5'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main'
gem 'rails', '~> 6.1.5'
# Use mysql as the database for Active Record
gem 'mysql2', '~> 0.5'
# Use Puma as the app server
gem 'puma', '~> 5.0'
# Use SCSS for stylesheets
gem 'sass-rails', '>= 6'
# Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
gem 'webpacker', '~> 5.0'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.7'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use Active Model has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Active Storage variant
# gem 'image_processing', '~> 1.2'

# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.4', require: false

gem 'devise'

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]

  gem "brakeman", require: false
  gem "bundler-audit"

  gem "rails_best_practices"
  gem "rubocop", require: false
  gem 'rubocop-airbnb', require: false
  gem "rubocop-performance", require: false
  gem "rubocop-rails", require: false
end

group :development do
  gem "bullet"

  # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
  gem 'web-console', '>= 4.1.0'
  # Display performance information such as SQL time and flame graphs for each request in your browser.
  # Can be configured to work on production as well see: https://github.com/MiniProfiler/rack-mini-profiler/blob/master/README.md
  gem 'rack-mini-profiler', '~> 2.0'
  gem 'listen', '~> 3.3'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

主要加了这几个东西

  • devise
  • brakeman
  • bundler-audit
  • rails_best_practices
  • rubocop
  • rubocop-airbnb
  • rubocop-performance
  • rubocop-rails

因为跟容器是文件夹绑定的,所以在容器外边用别的编辑器直接修改就好。完事后再安装那些Gem。这次编写代码的时候用的是 vs code。但是我平常做公司项目的时候都是用 Rubymine 的

docker-compose exec app bundle install


环境都准备好了,现在就写代码就好了

主要的代码是下边的部分

  • model
  • controller
  • views(json输出)


用户(user.rb)

# frozen_string_literal: true

class User < ApplicationRecord
  has_many :transactions
end

流水(transaction.rb)

# frozen_string_literal: true

class Transaction < ApplicationRecord
  TOTAL_AMOUNT_LIMIT = 1000

  belongs_to :user
  validates :amount,
            presence: true,
            numericality: {
              only_integer: true,
              greater_than: 0,
              less_than_or_equal_to: Transaction::TOTAL_AMOUNT_LIMIT,
              allow_nil: true,
            }
  validates :description, presence: true, length: { maximum: 256 }
  validate :total_amount_must_less_than_or_equal_to_limit

  private

  def total_amount_must_less_than_or_equal_to_limit
    return if errors[:amount].present?

    permitted_total_amount = user.transactions.sum(:amount)
    return if permitted_total_amount + amount <= Transaction::TOTAL_AMOUNT_LIMIT

    errors.add :amount, :limit_exceeded, limit: Transaction::TOTAL_AMOUNT_LIMIT
  end
end

transactions_controller.rb

这个部分的实现,需要先看一下那个测试代码,要看一下他是怎么把认证代码(api_key)传过来的。

从这里能看到,是用Header来传的。

从这里能看到传过来的参数的名字。


# frozen_string_literal: true

class TransactionsController < ApplicationController
  skip_before_action :verify_authenticity_token

  before_action :load_user_from_api_key!
  before_action :check_user_id_and_api_key!

  layout false

  def create
    @transaction = @user.transactions.new(permitted_transaction_params.except(:user_id))
    # 因为这次仅仅是个代码考试,并没有提到高并发性的时候的锁定性能问题。
    # 所以没有用Redis setnx之类的,就只是用了Rails的记录锁定
    @user.with_lock do
      if @transaction.save
        @balance = @user.transactions.where(id: -Float::INFINITY..@transaction.id).sum(:amount)
        render status: :created
      else
        render status: :payment_required
      end
    end
  end

  private

  def permitted_transaction_params
    params.require(:transaction).permit(:user_id, :amount, :description)
  end

  # 从Request头里边取出api_key,然后去查找用户
  def load_user_from_api_key!
    api_key = request.headers['apikey'].presence

    return render body: nil, status: :forbidden if api_key.nil?

    @user = User.find_by(api_key: api_key)
    render body: nil, status: :forbidden if @user.nil?
  end

  # 检查用api_key查找到的用户是否也参数中的用户匹配
  def check_user_id_and_api_key!
    # 好多朋友都知道,两个字符串的比较是不安全的,所以用Devise提供的这个函数来比较
    return if Devise.secure_compare(@user.id.to_s, permitted_transaction_params[:user_id])

    render body: nil, status: :forbidden
  end
end

API的返回结果(
transactions/create.json.jbuilder)

# frozen_string_literal: true

if @transaction.errors.empty?
  json.transaction do
    json.extract! @transaction, :amount
    json.balance @balance
  end
else
  json.transaction do
    json.errors do
      json.array! @transaction.errors.full_messages
    end
  end
end

最后把路径设定一下(routes.rb)

Rails.application.routes.draw do
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
  resources :transactions, only: [:create]
end

databases.yml需要适当调整一下

  • host: db
  • database: 用你的这次的数据库的名字


这样的话,Rails程序就应该动起来了。

自己机器上并没有Golang环境,也不想安装,就用docker-compose了。因为第一次用Golang,怎么执行文件也不知道,查了半天。

最终的成品是这样的。除了Dockfile之外,剩下的都是他们提供的。main_test.go文件稍微改动了一些。DB和API的链接地址(因为是用Docker容器的)

把原先的127.0.0.1改成对应的Docker容器服务的名字


╰─ tree -L 1 ./app_test                                                                                                                                                   ─╯
./app_test
├── Dockerfile
├── go.mod
├── go.sum
└── main_test.go

Dockerfile的内容,很简单。Golang容器启动

FROM golang:1.16

RUN mkdir /app_test
WORKDIR /app_test

CMD [ "/bin/sh" ]


现在大功告成,进去Golang的容器执行测试就好了。

# 先检查一下代码里边有没有问题
docker-compose exec app bundle exec bundle-audit check --update
docker-compose exec app bundle exec brakeman
docker-compose exec app bundle exec rubocop
docker-compose exec app bundle exec rails_best_practices .

docker ps

# 找到app_test的容器ID
docker exec -it 容器ID bash

# 这样就进入到那个Golang的容器了
root@app_test:/app_test#

# 执行测试就好
root@app_test:/app_test# go version
go version go1.16.15 linux/amd64
root@app_test:/app_test# go test -run TestCreate
go: downloading github.com/go-sql-driver/mysql v1.6.0
PASS
ok  	github.com/........	2.021s
root@app_test:/app_test# go test -v -run TestCreate
=== RUN   TestCreate
    main_test.go:69: {"transaction":{"amount":100,"balance":100}}
    main_test.go:69: {"transaction":{"amount":100,"balance":100}}
    main_test.go:69: {"transaction":{"amount":100,"balance":200}}
    main_test.go:69: {"transaction":{"amount":100,"balance":200}}
    main_test.go:69: {"transaction":{"amount":100,"balance":300}}
    main_test.go:69: {"transaction":{"amount":100,"balance":300}}
    main_test.go:69: {"transaction":{"amount":100,"balance":400}}
    main_test.go:69: {"transaction":{"amount":100,"balance":400}}
    main_test.go:69: {"transaction":{"amount":100,"balance":500}}
    main_test.go:69: {"transaction":{"amount":100,"balance":500}}
    main_test.go:69: {"transaction":{"amount":100,"balance":600}}
    main_test.go:69: {"transaction":{"amount":100,"balance":600}}
    main_test.go:69: {"transaction":{"amount":100,"balance":700}}
    main_test.go:69: {"transaction":{"amount":100,"balance":700}}
    main_test.go:69: {"transaction":{"amount":100,"balance":800}}
    main_test.go:69: {"transaction":{"amount":100,"balance":800}}
    main_test.go:69: {"transaction":{"amount":100,"balance":900}}
    main_test.go:69: {"transaction":{"amount":100,"balance":1000}}
    main_test.go:69: {"transaction":{"amount":100,"balance":900}}
    main_test.go:69: {"transaction":{"amount":100,"balance":1000}}
    main_test.go:69: {"transaction":{"errors":["Amount Transaction limit(1000) exceeded"]}}
    main_test.go:69: {"transaction":{"errors":["Amount Transaction limit(1000) exceeded"]}}
    main_test.go:69: {"transaction":{"errors":["Amount Transaction limit(1000) exceeded"]}}
    main_test.go:69: {"transaction":{"errors":["Amount Transaction limit(1000) exceeded"]}}
    main_test.go:87: 1 1000
    main_test.go:87: 2 1000
--- PASS: TestCreate (1.29s)
PASS
ok  	github.com/........	1.297s



大功告成!!!

再简单分享一下我用到的两个工具,vs code 和 iTerm2 的截图。



扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/1801.html

标签: json_extract
分享给朋友:

“东京码农CTO的面试之旅_京东码农待遇怎么样” 的相关文章

Vue组件通信之props深入详解!

props 是 Vue 组件中一个很重要的概念。它是用来从父组件向子组件传递数据的。为什么需要props?这是因为在Vue中,组件是相互隔离的。每个组件都有自己的作用域,子组件无法直接访问父组件的状态或值。通过props,父组件可以将数据传递给子组件。使用props的步骤:1. 在子组件中定义pro...

前后端分离自动化运维平台开发

运维平台采用前后端分离:前端vue,框架vue-element-admin;后端python,框架django-rest-framework.目前运维平台模块如下:1、 CMDB管理应用管理、环境管理、开发语言管理、产品项目管理、资产管理2、 构建发布持续构建、持续部署、Jar工程依赖构建3、 容器...

祸害阿里云宕机3小时的IO HANG究竟是什么?

本文来自微信公号“CSDN”(ID:CSDNnews),作者 | 王知无, 责编| 郭 芮。2019年3月3日凌晨,微博炸锅,有网友反映说阿里云疑似出现宕机,华北很多互联网公司受到暴击伤害,APP、网站全部瘫痪,我自己的朋友圈和微信群里也有好友反馈,刚刚从被窝被叫起来去修Bug,结果发现服务器登不上...

双子座应用程序推出模型切换器以在Android上访问2.0

#头条精品计划# 快速导读谷歌推出了Gemini 2.0 Flash实验版,现已在其安卓应用中可用,之前仅在gemini.google.com网站上提供。新版本的15.50包含模型切换器,用户可以在设置中选择不同模型,包括1.5 Pro、1.5 Flash和2.0 Flash实验版。谷歌提醒,2.0...

Vue2的16种传参通信方式

前言先直入主题列出有哪些传参方式,下面再通过事例一一讲解。props(父传子)$emit与v-on (子传父)EventBus (兄弟传参).sync与update: (父子双向)v-model (父子双向)ref$children与$parent$attrs与$listeners (爷孙双向)pr...

从 Vue2.0 到 React17——React 开发入门

作者:佚名来源:前端大全前言找工作时发现有一些公司是以React作为技术栈的,而且薪资待遇都不错,为了增加生存的筹码,所以还是得去学一下React,增加一项求生技能。因为我用Vue2.0开发项目已经四年了,故用Vue2.0开发项目的思路来学习React。前端项目是由一个个页面组成的,对于Vue来说,...