【asp.net core 系列】- 11 Service层的实现样板

0.前言

在《asp.net core 系列》之实战系列中,我们在之前的篇幅中对项目有了一个大概的认知,也搭建了一个基础的项目骨架。那么就让我们继续完善这个骨架,让它更加丰满。这一篇,我将带领小伙伴们一起实现用户管理功能。

1. 数据表

一般情况下,我们会把用户表和登录信息表放在两个表里。为什么会这样设计呢?出于以下几种考虑:

  • 使功能分割,用户信息管理是用户管理,登录是登录
  • 增加安全,降低无关信息的查询,例如访问登录接口不会连带检索用户的普通信息,当进行用户信息管理的时候,不会把登录信息也带过来

等等

废话不多说,直接上代码:

namespace Data.Enums
{
    /// <summary>
    /// 登录类型
    /// </summary>
    public enum LoginType : byte
    {
        /// token登录
        Token,
        /// 用户名密码
        Password
    }
    /// <summary>
    /// 性别
    /// </summary>
    public enum SexEnum
    {
        /// 男
        Male,
        /// 女
        Female,
        /// 隐私
        None
    }
}

SysUserAuthEntity.cs

using Data.Enums;
using Data.Infrastructure;

namespace Data.Models
{
    public class SysUserAuthEntity : BaseEntity<int>
    {
        public string UserName { get; set; }
        public string Password { get; set; }
        
        public LoginType LoginType { get; set;}
    }
}

SysUserInfoEntity.cs

using System;
using Data.Enums;
using Data.Infrastructure;

namespace Data.Models
{

    public class SysUserInfoEntity : BaseEntity<int>
    {
        public string NickName { get; set; }
        public string ImageUrl { get; set; }
        public SexEnum Sex { get; set; }
        public DateTime? BirthDay { get; set; }

        public int SysUserAuthId { get; set; }

        public virtual SysUserAuthEntity SysUserAuth { get; set; }
    }
}

这里并没有使用数据库Sql语句作为数据库描述,而是使用了Entity类作为描述,这是因为数据库到实体类之间还是有一层转换,对于开发而言接触更多的是实体类,而不是数据表。

2. 生成 Repository相关

使用工具代码的方式有很多,我在这里推荐一种, Test项目中,添加一个测试类,具体代码如下:

using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Text;
using Utils.Develop;

namespace Test
{
    public class DevelopTest
    {
        [Test]
        public void TetDevelop()
        {
            var d = Develop.CurrentDirect;
            Console.WriteLine(d);
            Assert.IsTrue(d.Contains("template"));
            var entities = Develop.LoadEntities();
            foreach (var item in entities)
            {
                Console.WriteLine(item.FullName);
            }
        }
        [Test]
        public void TestCreateDevelop()
        {
            var entities = Develop.LoadEntities();
            foreach (var item in entities)
            {
                Develop.CreateRepositoryInterface(item);
                Develop.CreateRepositoryImplement(item);
                Develop.CreateEntityTypeConfig(item);
            }
            Assert.Pass();
        }
    }
}

具体的命令行执行比较麻烦,会执行所有的测试单元:

cd Test/
dotnet test

当然了,IDE支持单个测试单元的执行,具体操作这里就不做过多的介绍了。

3. Service 接口和实现类

通常Service接口会提供一个简单Crud的Service接口,然后其他业务接口继承该接口。这样可以减少代码的重复,因为重复的代码在开发过程中是非常讨厌的一种情况,因为一旦一处发生变更,其他的也有可能发生变更。所以遇到重复代码一般都会进行一定程度的封装:

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Data.Infrastructure;

namespace Service.Insfrastructure
{
    public interface BaseService<T>
    {
        T Get(object key);
        T Get(Expression<Func<T, bool>> predicate);

        PageModel<T> SearchPage(PageCondition<T> condition);

        void Delete(Expression<Func<T, bool>> predicate);

        void Update(T entity);

        List<T> Search(Expression<Func<T, bool>> predicate);

    }
}

暂时就提供了这些最常见的请求方法。

在Service.Implements项目中:

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Data.Infrastructure;
using Domain.Insfrastructure;
using Service.Insfrastructure;

namespace Service.Implements.Insfrastructure
{
    public abstract class BaseServiceImpl<T> : BaseService<T>
    {
        private IRepository<T> LocalRepository { get; }

        protected BaseServiceImpl(IRepository<T> repository)
        {
            LocalRepository = repository;
        }
        
        
        public T Get(object key)
        {
            return LocalRepository.Get(key);
        }

        public T Get(Expression<Func<T, bool>> predicate)
        {
            return LocalRepository.Get(predicate);
        }

        public PageModel<T> SearchPage(PageCondition<T> condition)
        {
            return LocalRepository.Search(condition);
        }

        public void Delete(Expression<Func<T, bool>> predicate)
        {
            LocalRepository.Delete(predicate);
        }

        public void Update(T entity)
        {
            LocalRepository.Update(entity);
        }

        public List<T> Search(Expression<Func<T, bool>> predicate)
        {
            return LocalRepository.Search(predicate);
        }
    }
}

这个类设置为抽象类。

4. 用户管理的接口

先创建了两个简单的示范接口:

using Data.Models;
using Service.Insfrastructure;

namespace Service
{
    public interface ISysUserService : BaseService<SysUserInfoEntity>
    {
        void Register(SysUserAuthEntity auth, SysUserInfoEntity info);

        void ChangePassword(int userId, string oldPwd, string newPwd);
    }
}

实现类:

using System;
using Data.Models;
using Domain.Repository;
using Service.Implements.Insfrastructure;

namespace Service.Implements
{
    public class SysUserServiceImpl : BaseServiceImpl<SysUserInfoEntity>, ISysUserService
    {
        protected ISysUserAuthRepository AuthRepository { get; }
        protected ISysUserInfoRepository InfoRepository { get; }

        public SysUserServiceImpl(ISysUserAuthRepository authRepository, ISysUserInfoRepository infoRepository) : base(
            infoRepository)
        {
            AuthRepository = authRepository;
            InfoRepository = infoRepository;
        }

        public void Register(SysUserAuthEntity auth, SysUserInfoEntity info)
        {
            var authItem = AuthRepository.Get(p => p.LoginType == auth.LoginType && p.UserName == auth.UserName);
            if (authItem != null)
            {
                throw new Exception("用户信息已经存在");
            }

            info.SysUserAuth = auth;
            InfoRepository.Insert(info);
        }

        public void ChangePassword(int userId, string oldPwd, string newPwd)
        {
            var info = InfoRepository.Get(userId);
            var auth = AuthRepository.Get(info.SysUserAuthId);
            if (oldPwd == null || oldPwd != auth?.Password)
            {
                throw new Exception("原密码错误");
            }

            auth.Password = newPwd;

        }
    }
}

这里没对密码进行加密处理,直接使用明文。这在正式开发中是不允许的,密码不能使用明文保存。当然,这也不是最终代码,下一篇我们将介绍一下.net core中常见的加密实现。

5. 总结

这一篇通过几个简单的示例为大家介绍了一下Service层的开发逻辑以及理念。下一篇将为大家介绍一下.net core中几种简单的加密实现。

更多内容烦请关注我的博客《高先生小屋》

file
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270