我的第一个微服务系列(二):创建 User.Api


  上一篇介绍了Ocelot并且搭建了一个集成Ocelot的.Net Core API项目,只是粗浅的集成了ocelot到项目中,本篇来搭建一个用户服务并且将其添加到Api GateWay中,外部只能访问GateWay来达到访问User.Api的目的。

  新建项目User.Api,在本系列中都是使用.Net Core 2.2版本构建项目。

  User.Api将实现用户资料获取,用户注册,更新用户信息等功能,在项目中使用了entity framework core 2.0 做持久化工具,数据库使用MySql。

  新建User和UserProperty两个实体类

public class User
{
    public User()
        {
            Properties = new List();
        }
    public int Id { get; set; }

    /// 
    /// 用户名称
    /// 
    public string Name { get; set; }

    /// 
    /// 公司名称
    /// 
    public string Company { get; set; }

    /// 
    /// 职位
    /// 
    public string Title { get; set; }

    /// 
    /// 手机号码
    /// 
    public string Phone { get; set; }

    /// 
    /// 头像地址
    /// 
    public string Avatar { get; set; }

    /// 
    /// 性别 1:男 0:女
    /// 
    public byte Gender { get; set; }

    /// 
    /// 详细地址
    /// 
    public string Address { get; set; }

    /// 
    /// 邮箱
    /// 
    public string Email { get; set; }

    /// 
    /// 公司电话
    /// 
    public string Tel { get; set; }

    /// 
    /// 省份Id
    /// 
    public int ProvinceId { get; set; }

    /// 
    /// 省名称
    /// 
    public string Province { get; set; }

    /// 
    /// 城市Id
    /// 
    public int CityId { get; set; }

    /// 
    /// 城市名称
    /// 
    public string City { get; set; }

    /// 
    /// 名片地址
    /// 
    public string NameCard { get; set; }

    /// 
    /// 用户属性列表
    /// 
    public List Properties { get; set; }


}
public class UserProperty
{
    private int? _requestedHashCode;

    public int UserId { get; set; }

    public string Key { get; set; }

    public string Text { get; set; }

    public string Value { get; set; }

    public override int GetHashCode()
        {
            if (!IsTransient())
            {
                if (!_requestedHashCode.HasValue)
                {
                    _requestedHashCode = (this.Key + this.Value).GetHashCode() ^ 31;
                }
                return _requestedHashCode.Value;
            }

            return base.GetHashCode();
        }

    public override bool Equals(object obj)
        {
            if(obj == null || !(obj is UserProperty))
            {
                return false;
            }
            if (Object.ReferenceEquals(this, obj))
            {
                return true;
            }
            UserProperty item = obj as UserProperty;
            if(item.IsTransient() || this.IsTransient())
            {
                return false;
            }
            else
            {
                return item.Key == this.Key && item.Value == this.Value;
            }

        }

    private bool IsTransient()
        {
            return string.IsNullOrEmpty(this.Key) || string.IsNullOrEmpty(this.Value);
        }

    public static bool operator ==(UserProperty left, UserProperty right)
        {
            if (Object.Equals(left, null))
            {
                return (Object.Equals(right, null)) ? true : false;
            }
            else
            {
                return left.Equals(right);
            }
        }

    public static bool operator !=(UserProperty left, UserProperty right)
        {
            return !(left == right);
        }
}

  添加EF配置

public class UserConfiguration : IEntityTypeConfiguration
{
    public void Configure(EntityTypeBuilder builder)
    {
        builder.ToTable("Users");
        builder.HasKey(u => u.Id);
    }
}

public class UserPropertyConfiguration : IEntityTypeConfiguration
{
    public void Configure(EntityTypeBuilder builder)
    {
        builder.ToTable("UserProperties");
        builder.HasKey(u => new { u.Key,u.UserId,u.Value });
        builder.Property(u => u.Key).HasMaxLength(100);
        builder.Property(u => u.Value).HasMaxLength(100);
    }
}

  创建UserContext

namespace User.API.Data
{
    public class UserContext : DbContext
    {
        public UserContext(DbContextOptions options) : base(options)
        {

        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            //modelBuilder.Entity()
            //    .ToTable("Users")
            //    .HasKey(u => u.Id);

            modelBuilder.ApplyConfiguration(new UserConfiguration());

            modelBuilder.ApplyConfiguration(new UserPropertyConfiguration());

            modelBuilder.DataSeed();

            base.OnModelCreating(modelBuilder);
        }

        public DbSet Users { get; set; }

        public DbSet UserProperties { get; set; }

    }
}

  在Startup中注入UserContext

services.AddDbContext(options => {
    options.UseMySql(Configuration.GetConnectionString("MysqlUser"));
});
 "ConnectionStrings": {
    "MysqlUser": "server=172.18.6.155;port=3306;database=users;userid=root;password=123456",
  },

  新建UserController

namespace User.API.Controllers
{
    [Route("api/[controller]")]
    //[ApiController] //加了该特性,请求ContentType需要为json类型,参数可以省略掉[FromBody],但是string类型又拿不到,去掉该特性可以通过Form-Data提交数据,string类型可以获取到,但是json类型需要加[FromBody]
    public class UserController : BaseController
    {
        #region Member
        private UserContext _userContext;
        private ILogger _logger;#endregion

        #region Ctor

        public UserController(UserContext userContext
            , ILogger logger)
        {
            _userContext = userContext;
            _logger = logger;
        }

        #endregion
#region 获取当前用户的资料
        /// 
        /// 获取当前用户的资料
        /// 
        /// 
        [Route("")]
        [HttpGet]
        public async Task Get()
        {
            var user = await _userContext.Users
                .AsNoTracking()
                .Include(u => u.Properties)
                .SingleOrDefaultAsync(u => u.Id == UserIdentity.UserId);
            if (user == null)
            {
                throw new UserOperationException($"错误的用户上下文Id {UserIdentity.UserId}");
            }

            return Json(user);

        }

        #endregion

        #region 更新用户信息

        /// 
        /// 更新用户信息,使用JsonPatch 具体操作可参考:
        /// [
        ///   {
        ///     "op": "replace",
        ///     "path": "/company",
        ///     "value": "yubay.com"
        ///   },
        ///   {
        ///     "op": "replace",
        ///     "path": "/title",
        ///     "value": "软件工程师"
        ///   },
        ///    {
        ///     "op": "replace",
        ///     "path": "/properties",
        ///     "value": [{
        ///         "key":"fin_stage",
        ///         "value":"A轮",
        ///         "text":"A轮"
        ///     },{
        ///         "key":"fin_stage",
        ///         "value":"B轮",
        ///         "text":"B轮"
        ///     },
        ///     {
        ///         "key": "fin_stage",
        ///         "text": "C轮",
        ///         "value": "C轮"
        ///     }]
        ///   }
        /// ]
        /// https://docs.microsoft.com/en-us/aspnet/core/web-api/jsonpatch?view=aspnetcore-2.2
        /// 
        /// 
        /// 
        [HttpPatch]
        public async Task Patch([FromBody]JsonPatchDocument patch)
        {
            var user = await _userContext.Users.SingleOrDefaultAsync(u => u.Id == UserIdentity.UserId);
            patch.ApplyTo(user);

            foreach (var property in user.Properties)
            {
                _userContext.Entry(property).State = EntityState.Detached;
            }

            var originProperties = await _userContext.UserProperties.AsNoTracking().Where(u => u.UserId == UserIdentity.UserId).ToListAsync();
            var allProperties = originProperties.Union(user.Properties).Distinct();

            var removeProperties = originProperties.Except(user.Properties);
            var newProperties = allProperties.Except(originProperties);

            foreach (var property in removeProperties)
            {
                //_userContext.Entry(property).State = EntityState.Deleted;
                _userContext.Remove(property);
            }

            foreach (var property in newProperties)
            {
                _userContext.Add(property);
            }

            using(var trans = _userContext.Database.BeginTransaction())
            {
                _userContext.Users.Update(user);
                await _userContext.SaveChangesAsync();
                
                trans.Commit();
            }
            

            return Json(user);
        }
        #endregion

        #region 检查或者创建用户(当用户手机号码不存在的时候则创建用户)
        /// 
        /// 检查或者创建用户(当用户手机号码不存在的时候则创建用户)
        /// 
        /// 
        /// 
        [Route("check-or-create")]
        [HttpPost]
        public async Task CheckOrCreate(string phone)
        {
            //Todo Check Phone Valid

            var user = await _userContext.Users.SingleOrDefaultAsync(u => u.Phone == phone);

            if (user == null)
            {
                user = new Models.User { Phone = phone };
                _userContext.Users.Add(user);

                await _userContext.SaveChangesAsync();
            }

            return Ok(new {
                user.Id,
                user.Name,
                user.Company,
                user.Title,
                user.Avatar
            });
        }
        #endregion

      
        #region 根据手机号码查找用户资料
        /// 
        /// 根据手机号码查找用户资料
        /// 
        /// 
        /// 
        [HttpPost]
        [Route("search")]
        public async Task Search(string phone)
        {
            return Json(await _userContext.Users.Include(u => u.Properties).SingleOrDefaultAsync(u => u.Phone == phone));
        }
        #endregion

        [HttpGet]
        [Route("baseinfo/{userId}")]
        public async Task GetBaseInfoAsync(int userId)
        {
            //To do 检查用户
            var user = await _userContext.Users.SingleOrDefaultAsync(u => u.Id == userId);
            if(user == null)
            {
                return NotFound();
            }

            return Ok(new
            {
                UserId = user.Id,
                user.Name,
                user.Company,
                user.Title,
                user.Avatar
            });
        }
    }
}

   至此,User.Api基本的功能已完成,下篇就来搭建IdentityServer认证服务,通过IdentityServer来调用User.Api实现用户认证。