• 互联网菜鸟历险记之一


      在公司做一个Offline的服务管理系统,功能很简单主要是记录服务申请单,审批以及监控等。这个系统的架构是同事搭建的(像我这种菜鸟公司也不会让我搭建),架构很常规,三层架构,但是同事说另外加上了领域层(他也没有给我讲清楚,但是我看到在这一层上,都是一些log,metric的记录行为)。

      为了低耦合高内聚的目的,我们使用了IOC框架(通过IOC相当于初始化,省去了New,可以直接调用,但是IOC有个IOC容器在配置文件XML中,需要你将接口与其实现类一一对应好(MapTo),除了关系对应还有文件的路径,命名空间,构造函数参数等),Core层(也就是业务逻辑层,或者是服务接口层),在这一层在接口中定义服务方法,然后继承实现,所操作的实体对象都是接口类,这些实体接口类的实例化在Data层中,然而IOC实现了Core对Data层无依赖关系。在Controller中(也就是Web层)对服务进行调用是通过IOC实现的,通过IOC Resolver方法创建服务对象(IOC的初始化放在Global.asax文件的Application_Start()方法中,Global.asax文件首先被调用,其中包括着过滤器,错误日志处理,路由规则,还有就是IOC的初始化。),在创建服务对象时,将数据仓库注入到服务对象中,这样就可以直接使用服务对象调用仓库中的数据库处理方法了。因为仓库的接口类是在Core层中定义的,所以注入的时候是注入的仓库类接口对象,而仓库接口的具体实现是在Data层,操作着不同的数据实体类(具体实现的对象了),完成数据层的方法(无非是一些增删改查一类的,当然其中有一些函数,像对实体对象的增加,修改等,直接调用基类中已经封装好的就行),只有那些具体的需求的方法自己拼sql写即可。

      以上是整个系统的设计思路,其中的一些细节才是关键。

    1.如何写权限?因为在系统中分为管理员,审核人,用户这三种角色,我们是用enum类型定义

    public enum RoleType
    {
    Admin = 1,
    User = 2,
    Auditor =3
    }

    在web层的Controller中每个控制器都继承于一个BaseController(这个类是自己定义的),在这个类中,我们写上权限的判别方法。通过

    public static UserInfo CurrentUser
    {
    get
    {
    var obj = System.Web.HttpContext.Current.Session["user"];
    try
    {
    if (obj == null)
    {
    // Get windows user
    var loggedUser = System.Web.HttpContext.Current.User.Identity;
    if (loggedUser != null && loggedUser.IsAuthenticated)
    {
    string userName = loggedUser.Name;
    userName = userName.Substring(userName.IndexOf('\') + 1);

    UserInfo user = new UserInfo();
    user.UserName = userName;
    //to-do find role
    IUserService svc = IoC.Resolve<IUserService>();//这就是ioc通过用户服务接口实现用户服务对象的初始化
    var roleInfo = svc.GetUserInfo(userName);用户服务调用数据库查询,判断数据库中是否已经存在该用户
    if (roleInfo != null)
    {
    user.RoleId = roleInfo.RoleId;
    }
    else
    {

    //如果数据库没有该用户,则该用户是默认的用户即为User
    user.RoleId = (int)RoleType.User;
    var repo = IoC.Resolve<IUserRoleRepository>();//通过IOC将用户仓库类反转(相当于初始化,省去了New),repo可以直接调用IUserRoleRepository中的方法
    var entity = repo.GetEntityInstance();
    entity.RoleId = user.RoleId;
    entity.Operator = user.UserName;
    repo.Add(entity);
    }

    System.Web.HttpContext.Current.Session.Add("user", user);
    obj = user;
    }
    }
    }
    catch (Exception ex)
    {
    LogFormat.Write(LogFormat.BusinessError, "Auth-Exception", "获取用户验证信息时发生错误!", ex, LogErrorLevel.Error);
    }
    return (obj == null) ? null : obj as UserInfo;
    }
    }

    这个方法就是获取UserInfo类的对象CurrentUser,在如下的方法中进行调用:

    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
    ViewBag.UserName = CurrentUser.UserName;
    ViewBag.RoldId = CurrentUser.RoleId;
    base.OnActionExecuted(filterContext);
    }

    当然退出登录的方法也是写在里面:

    public void LogOff()
    {
    Session.Abandon();
    CasAuthentication.SingleSignOut();
    }

    这样在Controller中每个action上加上[Authorize][HttpGet],[ActionAuth(RoleType.User)]标签其中ActionAuth就是可以控制权限了

    2.在Global.asax文件中需要些路由配置,不然系统找不到你的action,RouteConfig.RegisterRoutes(RouteTable.Routes);所以在RouteConfig类中定义一个static的方法RegisterRoutes,将你用到的Controller中的Action都配置出来如下:

    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
    name: "BaseServiceMaintainanceIndex",
    url: "Maintain/BaseService/Index",
    defaults: new { controller = "Maintain", action = "SearchIndex"}//默认的参数
    );

    3.在Controller中使用System.Linq,可以方便写程序,例如你查询了一个List<T>,你需要按照时间降序排序,则可以用list.OrderByDescending(c => c.DataChange_LastTime).ToList();

    4.分页,分页的机制主要是url传入参数,页码,行数之后,通过数据端limt{0页码}{1行数} 把数据返回,对了,还要返回整体的总数据便于前端计算分多少页。在Controller中处理页码行数用ViewData["PagerRecord"] = pager;返回到View层Pager pager = ViewData["PagerRecord"] as Pager;,在View层通过 ViewDataDictionary viewData = new ViewDataDictionary(); viewData 对象封装页面需要传递的信息(这个类ViewDataDictionary 很重要,当两个页面cshtml之间数据进行传递时就会用到它),在A页中使用@Html.Partial("PagerPartial", pager, viewData),在页面传递时,有时会用到路由参数,因为页面传递时要刷新页面,要保证刷新页面不变,所以路由参数必须一致所以本次还用到RouteValueDictionary dict = new RouteValueDictionary();最后赋值完毕之后,将之赋值给viewData对象

    5.本次查询系统由于参数太多,所以在Core层使用了参数类,还有查询返回值也是用了结果类:其中Data就是返回的数据List,有一些操作也是用返回类实现的

    //查询结果类

    public class PagerResult<T>
    {
    public int PageIndex { get; set; }
    public int TotalCount { get; set; }

    public IList<T> Data { get; set; }
    }

    //操作结果类

    [Serializable]
    public class ModifyResult
    {

    private string _status = "success";
    private string _message = "提交成功!";

    /// <summary>
    /// 修改状态
    /// </summary>
    public virtual string status
    {
    get { return _status; }
    set { _status = value; }
    }

    /// <summary>
    /// 信息
    /// </summary>
    public virtual string message
    {
    get { return _message; }
    set { _message = value; }
    }
    }

    public class SuccessResult :ModifyResult
    {
    public override string message
    {
    get
    {
    return "提交成功!";
    }
    }

    public override string status
    {
    get
    {
    return "success";
    }
    }
    }

    6.编程需要注意一点,不要在数据库端写select all 然后在业务逻辑层使用foreach()进行循环遍历,这样既能拖垮数据库又会给业务层带来压力,最好的办法是通过参数传入数据端进行查询,举个例子:Appid与sid相同的记录不能存在两条,所以在新增或者更新时需要判断,写一个check函数,getListByAppid(string appid){}这样返回一个List 然后在业务层对resultList进行判断,使用Linq:resultList.Where(t=>t.sid ==e.sid);e.sid是你要判断数据的sid。实在不行你也可以写getListByXid(string appid,int sid){}判断有无返回值就可以了。

    7.关于多张表的插入,删除,更新操作,需要保持几张表同步更新/变化,所以你需要使用事务原则。在C#中我目前是用using (TransactionScope tsCope = new TransactionScope()) {}需要引入System.Transactions,在保证代码完整的完成事务操作的地方加上tsCope.Complete();

    using (TransactionScope tsCope = new TransactionScope())
    {
    data.DataChange_CreateTime = DateTime.Now;
    data.DataChange_LastTime = DateTime.Now;
    Object iresult = _dataRepo.Add(data);
    int iReturn = ConvertHelper.ToInt32(iresult);
    if (iReturn != 0)
    {
    string operatorid = data.Operator;
    //将操作信息写入Log库
    IOperatorLog ol = IoC.Resolve<IOperatorLog>();
    ol.WriteLogInfo(iReturn, operatorid, LogMessage.Commit_Service_Application);
    res = new SuccessResult();
    tsCope.Complete();
    }
    else
    {
    res = new FailedResult();
    }
    }

    8.因为Core层没有依赖Data层(这是解除耦合必须的),所以有时候你在Core想初始化Data层的数据实体,这是不可能的,怎么办呢?要处理数据不能一直把数据传递到Data层再做操作吧,那样多麻烦。遇到这样的问题你就要在Core层调用相应实体仓库接口中定义一个返回 数据实例化的方法,例如我在AppForm服务中调用AppFormEntity,但是没有办法new一个AppFormEntity,所以我在IAPPFormRepository(这个类还是在Core层)中写一个函数 IAppFormEntity CreateEntity();但是其实现类APPFormRepository在Data层了,哈哈,

    public IAppFormEntity CreateEntity()
    {
    return new AppFormEntity();
    }

    这就OK了吧!!当然比较明智的是,在IAPPFormRepository所继承的基类中写一个泛型接口方法并在其实现类中实现。例如IRepository接口中写:TEntity GetEntityInstance();

    在Repository中写

    public TInterface GetEntityInstance()
    {
    return new TEntity();
    }

    然后,IAPPFormRepository继承IRepository,其实现类APPFormRepository需要继承Repository与IAPPFormRepository

    9.数据库多参数查询时,需要拼sql,怎么拼比较好呢?我在这使用了一种很常规的方式,用List<string>拼接实现的,具体实现如下:

    public static StringBuilder GenerateSearchSql(SearchParams sp)
    {
    StringBuilder sql = new StringBuilder(ConstSqlStr.SQL_APP_COUNT);//这是SQL语句,联表查询,没有包含where条件
    List<string> wheres = new List<string>();
    List<StatementParameter> listParameter = new List<StatementParameter>();//参数话查询

    if (sp.Pid != 0)
    {
    wheres.Add(" a.Pid=@Pid ");
    listParameter.Add(new StatementParameter { Name = "@Pid", Direction = ParameterDirection.Input, DbType = DbType.Int32, Value = sp.Pid });
    }

    if (!string.IsNullOrEmpty(sp.AppId))
    {
    wheres.Add(" a.AppId = @AppId ");
    listParameter.Add(new StatementParameter { Name = "@AppId", Direction = ParameterDirection.Input, DbType = DbType.AnsiString, Value = sp.AppId });
    }

    if (!string.IsNullOrEmpty(sp.ApplicantId))
    {
    wheres.Add(" a.ApplicantId = @ApplicantId ");
    listParameter.Add(new StatementParameter { Name = "@ApplicantId", Direction = ParameterDirection.Input, DbType = DbType.AnsiString, Value = sp.ApplicantId });
    }
    if (!string.IsNullOrEmpty(sp.ServiceName))
    {
    wheres.Add(" s.ServiceName = @ServiceName ");
    listParameter.Add(new StatementParameter { Name = "@ServiceName", Direction = ParameterDirection.Input, DbType = DbType.AnsiString, Value = sp.ServiceName });
    }
    if (!string.IsNullOrEmpty(sp.UnitName))
    {
    wheres.Add(" r.UnitName = @UnitName ");
    listParameter.Add(new StatementParameter { Name = "@UnitName", Direction = ParameterDirection.Input, DbType = DbType.AnsiString, Value = sp.UnitName });
    }
    if (sp.Status != 0)
    {
    wheres.Add(" a.Status = @Status ");
    listParameter.Add(new StatementParameter { Name = "@Status", Direction = ParameterDirection.Input, DbType = DbType.Int32, Value = sp.Status });
    }
    //判断用户是否选择了条件
    if (wheres.Count > 0)//说明是有条件的查询
    {
    string wh = string.Join(" and ", wheres.ToArray());将条件用 and 拼接,
    sql.Append(" where " + wh);//当把所有的条件用and 连接完成之后,再在前面加上where 即可
    }
    //执行即可

    base.SelectList<T>(sql,listParameter).Cast<IT>().toList();//因为返回的是实体接口类,所以当把实体List查出来后还要做转换,将之转成其父类的形式用Cast<T>()方法,不能用一般的 as 方法,as 是做不到的。

    Cast<>()方法讲解可以参照http://www.cnblogs.com/ldp615/archive/2009/08/17/1548369.html

    }

    注意!!C#中Join函数有两种,如下,但是value值都是数组类型,所以需要将List<string>类型转换成Array类型

    [C#]
    public static string Join(
       string separator,
       string[] value
    );

    [C#]
    public static string Join(
       string separator,
       string[] value,
       int startIndex,
       int count
    );

    第一次写,先写一下后台的东西

  • 相关阅读:
    如何在EXCEL SHEET中 动态添加控件
    和菜鸟一起学OK6410之ADC模块
    和菜鸟一起学证券投资之消费物价指数CPI
    和菜鸟一起学证券投资之股市常见概念公式2
    作为软件工程师,你必须知道的20个常识
    和菜鸟一起学c++之虚函数
    和菜鸟一起学单片机之入门级led流水灯
    在国内各大软件下载网站上,“万能数据库查询分析器”已更新至 2.02 版本
    和菜鸟一起学证券投资之股市简单财务分析
    和菜鸟一起学OK6410之蜂鸣器buzzer字符驱动
  • 原文地址:https://www.cnblogs.com/walt/p/4889604.html
Copyright © 2020-2023  润新知