dotnetcore vue+elementUI 前后端分离架二(后端篇)
前言
最近几年前后端分离架构大行其道,而且各种框架也是层出不穷。本文通过dotnetcore +vue 来介绍 前后端分离架构实战。
涉及的技术栈
服务端技术
mysql
本项目使用mysql 作为持久化层
本项目采用了 mysql 的示例 employees 数据库, 需要的朋友可以自行下载 。
http://www3.ntu.edu.sg/home/ehchua/programming/sql/SampleDatabases.html
orm
dapper 短小精悍,被称为orm中的瑞士军刀。作者之前使用EF 比较多,总感觉 EF 对一些复杂查询需要原生sql支持的不是那么好,EF 生成sql 不好控制,涉及对性能要求比较高的地方,优化起来不够理想。作者在最近的几个项目中接触到dapper,它对原生sql查询支持的相当给力,使用起来也比较简便,感觉真是爱不释手,嘿嘿。。。
https://github.com/StackExchange/Dapper架构
服务端整体架构采用仓储模式
Repository是一个独立的层,介于领域层与数据映射层(数据访问层)之间。它的存在让领域层感觉不到数据访问层的存在,它提供一个类似集合的接口提供给领域层进行领域对象的访问。Repository是仓库管理员,领域层需要什么东西只需告诉仓库管理员,由仓库管理员把东西拿给它,并不需要知道东西实际放在哪。
此处照搬了dudu同学对仓储模式的理解。
仓储模式的好处:领域依赖接口与DB进行交互,解除了二者之间的耦合,如果更换orm,DB层,采用这种模式,更换的代价最低。
前端技术
整体上SPA 结构
Vue:
渐进式 JavaScript 框架.
三大特点:易用,灵活, 高效。
详细信息见 https://cn.vuejs.org/VueRouter
vue-router是Vue.js官方的路由插件,它和vue.js是深度集成的,适合用于构建单页面应用。vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来。传统的页面应用,是用一些超链接来实现页面切换和跳转的。在vue-router单页面应用中,则是路径之间的切换,也就是组件的切换。
axios
axios 开始崛起, 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端。
ElementUI
网站快速成型工具
Element,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库
详细信息见 http://element-cn.eleme.io/#/zh-CN
服务端项目结构
服务端代码地址:https://github.com/hbb0b0/Hbb0b0.CMS/tree/master/Hbb0b0.CMS.WebAPI
摘取部分类予以说明。
CMS.Common
通用工具的封装
配置工具类
DB配置
Cors配置
WebApi配置
DB工具类
DapperContext
IRespository
ResponsitoryBase
包含获取列表,获取分页列表,增删改的通用操作的封装
SimpleCRUD
github上对Dapper的进一步分装
消息
数据转换
entity --> Dto
DTObase
PageList
CMS.DTO
返回给前端的数据对象,隔离DB model变化直接映射前端对象,使View与Model解耦。
CMS.Model
DB 中的表映射到 Model
CMS.IResponsity
DB 操作接口
CMS.Responsity
DB 操作接口的实现
CMS.IService
Serive接口,可以供给第三方调用,也是第三方调用的规范,同时也约束着服务实现。
目前提供给API层调用。
CMS.Service
Serive接口的实现
CMS.API
以WebAPI 形式提供给前台调用,并不是严格按照 WebApi 规范 post,get,put,delete 调用.使用了 core中的 mvc 方式。
- CMS.API 调用 IService 接口
- CMS.Service 调用 IResponsitory接口
- 各接口与实现之间通过Core中的构造函数注入方式联接
服务端代码说明:
- 配置
{
"Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Information"
}
},
"Console": {
"LogLevel": {
"Default": "Information"
}
}
},
"WebAPI": {
"DB": {
"ConnectionString": "server=localhost;database=employees;uid=root;pwd=sqlsa;charset='utf8';persistsecurityinfo=True;SslMode=none;Allow User Variables=True;",
"ReadingConnectionString": "server=localhost;database=employees;uid=root;pwd=sqlsa;charset='utf8';persistsecurityinfo=True;SslMode=none;Allow User Variables=True;",
"WritingConnectionString": "server=localhost;database=employees;uid=root;pwd=sqlsa;charset='utf8';persistsecurityinfo=True;SslMode=none;Allow User Variables=True;"
},
"Cors": {
"Name": "CMSCorsConfig",
"Original": "http://localhost:8080"
}
}
}
API
Startup
- 配置读取与接口实现配置
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CMS.Common;
using CMS.Common.Config;
using CMS.Common.DB;
using CMS.IRepository;
using CMS.IService;
using CMS.Repository;
using CMS.Service;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace CMS.WebApi
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
//序列化设置
.AddJsonOptions(options =>
{
//忽略循环引用
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
//不使用驼峰样式的key
//options.SerializerSettings.ContractResolver = new DefaultContractResolver();
//设置时间格式
options.SerializerSettings.DateFormatString = "yyyy-MM-dd hh:mm:ss";
}
);
services.AddOptions();
//读取配置
services.Configure<WebApiOption>(Configuration.GetSection("WebAPI"));
IServiceProvider serviceProvider = services.BuildServiceProvider();
//获取配置
WebApiOption config = serviceProvider.GetService<IOptions<WebApiOption>>().Value;
//Cors配置
AddCorsService(services,config);
//DB Service
AddDBService(services,config);
}
/// <summary>
/// AddDBService
/// </summary>
/// <param name="services"></param>
/// <param name="config"></param>
private void AddDBService(IServiceCollection services,WebApiOption config)
{
//设置全局配置
services.AddSingleton<IDapperContext>(_ => new DapperContext(
config
));
services.AddScoped<IDepartmentRep, DepartmentRep>();
services.AddScoped<IDepartmentService, DepartmentService>();
services.AddScoped<IEmployeeRep, EmployeeRep>();
services.AddScoped<IEmployeeService, EmployeeService>();
services.AddScoped<ITitleRep, TitleRep>();
services.AddScoped<ISalaryRep, SalaryRep>();
}
/// <summary>
/// AddCorsService
/// </summary>
/// <param name="services"></param>
/// <param name="config"></param>
private void AddCorsService(IServiceCollection services, WebApiOption config)
{
//添加cors 服务
services.AddCors(options =>
options.AddPolicy(WebApiOption.CORS_POLICY_NAME, p => p.WithOrigins(config.Cors.Original)
.AllowAnyMethod().AllowAnyHeader()));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action}/{no?}",
defaults: new { controller = "Home", action = "Index" });
});
//配置Cors
app.UseCors(WebApiOption.CORS_POLICY_NAME);
}
}
}
- ControllerBase
using CMS.Common.Config;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace CMS.WebApi.Common
{
[Route("api/[controller]")]
[EnableCors(WebApiOption.CORS_POLICY_NAME)]
public abstract class BaseController: Controller
{
protected readonly ILogger m_Logger;
public BaseController(ILogger<BaseController> logger)
{
m_Logger = logger;
}
}
}
- EmployeeController
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CMS.Common;
using CMS.Common.Config;
using CMS.Common.Message;
using CMS.DTO;
using CMS.Entity;
using CMS.IService;
using CMS.WebApi.Common;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
namespace CMS.WebApi.Controllers
{
public class EmployeeController : BaseController
{
private DBOption m_CmsOptions;
private IEmployeeService m_Service;
public EmployeeController(IOptions<DBOption> option, IEmployeeService employeeService, ILogger<EmployeeController> logger) : base(logger)
{
this.m_CmsOptions = option.Value;
m_Service = employeeService;
}
[Route("[action]/{pageNumber}/{rowsPerPage}")]
[HttpGet]
public ReponseMessage<PagedList<EmployeeDTO>> GetPagedList(int pageNumber, int rowsPerPage)
{
var result = m_Service.GetPagedList(pageNumber, rowsPerPage);
return m_Service.GetPagedList(pageNumber, rowsPerPage);
}
[Route("[action]")]
[HttpPost]
public ReponseMessage<PagedList<EmployeeDTO>> Query([FromBody]QueryCondition<EmployeeQuery> condition)
{
var result = m_Service.Query(condition);
m_Logger.LogInformation("GetPagedList:{0}", JsonConvert.SerializeObject(result));
return result;
}
[Route("[action]/{no}")]
[HttpGet]
public ReponseMessage<EmployeeDetailDto> GetDetail(string no)
{
var result = m_Service.GetDetail(no);
return result;
}
[Route("[action]")]
[HttpPost]
public ReponseMessage<int> Add([FromBody]EmployeeDTO dto)
{
var result = m_Service.Add(dto);
return result;
}
[Route("[action]/{empNo}")]
[HttpPost]
public ReponseMessage<int> Delete(int empNo)
{
var result = m_Service.Delete(empNo);
return result;
}
[Route("[action]/")]
[HttpPost]
public ReponseMessage<int> Update([FromBody]EmployeeDTO dto)
{
var result = m_Service.Update(dto);
return result;
}
}
}
- IService
using CMS.Common;
using CMS.Common.Message;
using CMS.DTO;
using CMS.Entity;
using CMS.Model;
using System;
using System.Collections.Generic;
namespace CMS.IService
{
public interface IEmployeeService
{
ReponseMessage<PagedList<EmployeeDTO>> GetPagedList(int pageNumber, int rowsPerPage);
ReponseMessage<PagedList<EmployeeDTO>> Query(QueryCondition<EmployeeQuery> condition);
ReponseMessage<EmployeeDetailDto> GetDetail(string emp_no);
ReponseMessage<int> Add(EmployeeDTO dto);
ReponseMessage<int> Update(EmployeeDTO dto);
ReponseMessage<int> Delete(int emp_No);
}
}
- Service 实现
using CMS.Common;
using CMS.Common.Message;
using CMS.DTO;
using CMS.Entity;
using CMS.IRepository;
using CMS.IService;
using CMS.Model;
using CMS.Utility;
using Dapper;
using System;
using System.Collections.Generic;
using System.Linq;
namespace CMS.Service
{
public class EmployeeService : IEmployeeService
{
private IEmployeeRep m_Rep;
private ITitleRep m_TitleRep;
private ISalaryRep m_SalaryRep;
public EmployeeService(IEmployeeRep rep,ITitleRep titleRep, ISalaryRep salaryRep)
{
m_Rep = rep;
m_TitleRep = titleRep;
m_SalaryRep = salaryRep;
}
public ReponseMessage<EmployeeDetailDto> GetDetail(string emp_no)
{
ReponseMessage<EmployeeDetailDto> result = new ReponseMessage<EmployeeDetailDto>();
EmployeeDetailDto data = new EmployeeDetailDto();
var emp = m_Rep.Get(emp_no);
data.Info = emp.JTransformTo<EmployeeDTO>();
var titleList = m_TitleRep.GetList(string.Format($"where emp_no = {emp_no}"));
var salaryList = m_SalaryRep.GetList(string.Format($"where emp_no = {emp_no} order by from_date "));
result.Data = data;
result.IsSuccess = true;
if (titleList != null)
{
result.Data.TitleList = titleList.JTransformTo<TitleDto>();
}
if (salaryList!=null)
{
result.Data.SalaryList = salaryList.JTransformTo<SalaryDto>();
}
return result;
}
public ReponseMessage<PagedList<EmployeeDTO>> GetPagedList(int pageNumber, int rowsPerPage)
{
int total = 0;
ReponseMessage<PagedList<EmployeeDTO>> result = new ReponseMessage<PagedList<EmployeeDTO>>();
result.Data = new PagedList<EmployeeDTO>();
var modelResult= m_Rep.GetPagedList(pageNumber, rowsPerPage, null, null, out total);
result.Data.TotalCount = total;
result.Data.Items=modelResult.Items.JTransformTo<EmployeeDTO>();
result.IsSuccess = true;
return result;
}
public ReponseMessage<PagedList<EmployeeDTO>> Query(QueryCondition<EmployeeQuery> condition)
{
ReponseMessage<PagedList<EmployeeDTO>> result = new ReponseMessage<PagedList<EmployeeDTO>>();
result.Data = new PagedList<EmployeeDTO>();
var modelResult = m_Rep.Query(condition);
result.Data.TotalCount = modelResult.TotalCount;
if (modelResult.TotalCount > 0)
{
result.Data.Items = modelResult.TotalCount>0 ? modelResult.Items.JTransformTo<EmployeeDTO>():new List<EmployeeDTO>();
}
result.IsSuccess = true;
return result;
}
public ReponseMessage<int> Add(EmployeeDTO dto)
{
ReponseMessage<int> result = new ReponseMessage<int>();
if (dto==null )
{
result.IsSuccess = false;
result.MessageInfo = new MessageInfo() { ErrorCode = -1, Message ="无效的参数" };
}
EmployeeModel entitity = dto.JTransformTo<EmployeeModel>();
int? opResult = m_Rep.CustomerAdd(entitity);
if(opResult.HasValue && opResult>0)
{
result.IsSuccess = true;
result.Data = opResult.Value;
}
return result;
}
public ReponseMessage<int> Update(EmployeeDTO dto)
{
ReponseMessage<int> result = new ReponseMessage<int>();
if (dto == null)
{
result.IsSuccess = false;
result.MessageInfo = new MessageInfo() { ErrorCode = -1, Message = "无效的参数" };
}
EmployeeModel entitity = dto.JTransformTo<EmployeeModel>();
int? opResult = m_Rep.Update(entitity);
if (opResult.HasValue && opResult > 0)
{
result.IsSuccess = true;
result.Data = opResult.Value;
}
return result;
}
public ReponseMessage<int> Delete(int empNo)
{
ReponseMessage<int> result = new ReponseMessage<int>();
int? opResult = m_Rep.Delete(empNo);
if (opResult.HasValue && opResult ==1)
{
result.IsSuccess = true;
result.Data = opResult.Value;
}
return result;
}
}
}
- IResponsitory
using CMS.Common;
using CMS.Common.DB;
using CMS.DTO;
using CMS.Entity;
using CMS.Model;
using System;
using System.Collections.Generic;
using System.Text;
namespace CMS.IRepository
{
public interface IEmployeeRep: IRepository<EmployeeModel>
{
PagedList<EmployeeDTO> Query(QueryCondition<EmployeeQuery> query);
int? Add(EmployeeModel entity);
int? CustomerAdd(EmployeeModel entity);
int? Delete(int empNo);
int? Update(EmployeeModel entity);
}
}
-Responsitory 实现
using CMS.Common;
using CMS.Common.DB;
using CMS.DTO;
using CMS.IRepository;
using CMS.Model;
using System;
using System.Collections.Generic;
using System.Text;
using Dapper;
namespace CMS.Repository
{
public class EmployeeRep : ReponsitoryBase, IEmployeeRep
{
private static object m_sync_Object = new object();
public EmployeeRep(IDapperContext dapper) : base(dapper)
{
}
public int? Add(EmployeeModel entity)
{
return this.Insert(entity);
}
new public int? Update(EmployeeModel entity)
{
return base.Update(entity);
}
public int? CustomerAdd(EmployeeModel entity)
{
int cmdResult = -1;
using (var connection = this.GetWritingConnection())
{
var sql = new StringBuilder(" set @maxNo=(select max(emp_no)+1 from employees) ;");
sql.AppendLine("insert into employees(emp_no,first_name,last_name,gender,birth_date,hire_date) values(@maxNo,@first_name,@last_name,@gender,@birth_date,@hire_date); ");
sql.AppendLine("select @maxNo;");
cmdResult = connection.ExecuteScalar<int>(sql.ToString(), entity);
}
return cmdResult;
}
public int? Delete(int empNo)
{
int cmdResult = -1;
using (var connection = this.GetWritingConnection())
{
cmdResult = connection.Execute("delete from employees where emp_no = @emp_no ;", new { emp_no = empNo });
}
return cmdResult;
}
public PagedList<EmployeeDTO> Query(QueryCondition<EmployeeQuery> query)
{
PagedList<EmployeeDTO> pagedList = new PagedList<EmployeeDTO>();
#region sql
var sql = new StringBuilder("SELECT SQL_CALC_FOUND_ROWS * from employees ");
#endregion
sql.AppendLine(" Where 1=1");
if (!string.IsNullOrEmpty(query.Param.First_Name))
{
sql.AppendLine(string.Format(" and First_Name like '{0}'", query.GetLikeValue(query.Param.First_Name)));
}
if (!string.IsNullOrEmpty(query.Param.Last_Name))
{
sql.AppendLine(string.Format(" and last_Name like '{0}'", query.GetLikeValue(query.Param.Last_Name)));
}
if (!string.IsNullOrEmpty(query.Param.Emp_No))
{
sql.AppendLine(string.Format(" and emp_no = @Emp_No"));
}
if (!string.IsNullOrEmpty(query.Param.Gender))
{
sql.AppendLine(string.Format(" and gender = @Gender"));
}
DateTime? hire_date_start = null;
DateTime? hire_date_end = null;
if (query.Param.Hire_Date_Range != null)
{
if (query.Param.Hire_Date_Range[0].HasValue)
{
hire_date_start = query.Param.Hire_Date_Range[0];
sql.AppendLine(string.Format(" and hire_date >= @Hire_Date_Range_Start"));
}
if (query.Param.Hire_Date_Range[1].HasValue)
{
hire_date_end = query.Param.Hire_Date_Range[1];
sql.AppendLine(string.Format(" and hire_date <= @Hire_Date_Range_End"));
}
}
DateTime? birth_date_start = null;
DateTime? birth_date_end = null;
if (query.Param.Birth_Date_Range != null)
{
if (query.Param.Birth_Date_Range[0].HasValue)
{
birth_date_start = query.Param.Birth_Date_Range[0];
sql.AppendLine(string.Format(" and birth_date >= @Birth_Date_Range_Start"));
}
if (query.Param.Birth_Date_Range[1].HasValue)
{
birth_date_end = query.Param.Birth_Date_Range[1];
sql.AppendLine(string.Format(" and birth_date <= @Birth_Date_Range_End"));
}
}
sql.AppendLine(" order by emp_no desc");
sql.AppendLine($" LIMIT {(query.pageInfo.PageIndex - 1) * query.pageInfo.PageSize},{query.pageInfo.PageSize}");
sql.Append(";");
using (var connection = this.GetReadingConnection())
{
var result = connection.Query<EmployeeDTO>(sql.ToString(),
new
{
Emp_No = query.Param.Emp_No,
Hire_Date_Range_Start = hire_date_start,
Hire_Date_Range_End = hire_date_end,
Birth_Date_Range_Start = birth_date_start,
Birth_Date_Range_End = birth_date_end,
Gender = query.Param.Gender
}).AsList();
pagedList.Items = result;
pagedList.TotalCount = connection.ExecuteScalar<int>("SELECT FOUND_ROWS();");
}
return pagedList;
}
}
}
目前测试 empoyee 30万条分页数据大致在400ms左右
dotnetcore vue+elementUI 前后端分离架二(后端篇)的更多相关文章
- dotnetcore+vue+elementUI 前后端分离 三(前端篇)
说明: 本项目使用了 mysql employees数据库,使用了vue + axois + element UI 2.0 ,演示了 单页程序 架构 ,vue router 的使用,axois 使用, ...
- Flask + vue 前后端分离的 二手书App
一个Flask + vue 前后端分离的 二手书App 效果展示: https://blog.csdn.net/qq_42239520/article/details/88534955 所用技术清单 ...
- .netcore+vue+elementUI 前后端分离---支持前端、后台业务代码扩展的快速开发框架
框架采用.NetCore + Vue前后端分离,并且支持前端.后台代码业务动态扩展,框架内置了一套有着20多种属性配置的代码生成器,可灵活配置生成的代码,代码生成器界面配置完成即可生成单表(主表)的增 ...
- 前后端分离项目获取后端跨控制器获取不到session
最近做前后端分离项目(.net core web api +vue)时,后台跨控制器不能获取到session.由于配置的是共享的session.本来以为是共享session出了问题,就在共享sess ...
- nginx反向代理前后端分离项目(后端多台)
目前软件架构都比较流行前后端分离,前后端的分离也实现了前后端架构的分离,带来的好处 —— 整个项目的开发权重往前移,实现真正的前后端解耦,动态资源和静态资源分离,提高了性能和扩展性. 通常Spring ...
- 关于flask(前后端分离)的后端开发的小白笔记整理(含postman,jwt,json,SQLAlchemy等)
首先是提醒自己的一些唠嗑: 学会劳逸结合,文档看累了可以看视频,动手操作很关键,遇到问题先动脑子冷静地想,不要跟着步骤都不带脑子,想不出来了再查一查!有时候打出来的代码很虚,但是实践不花钱,实践出真知 ...
- 工作中vue项目前后端分离,调用后端本地接口出现跨域问题的完美解决
在我们实际开发中,选择不错的前端框架可以为我们省掉很多时间,当然,有时我们也会遇到很多坑. 最近在做vue项目时就遇到了跨域问题,一般来说,出现跨域我们第一反应使用jsonp,但是这个只支持get请求 ...
- Springboot前后端分离中,后端拦截器拦截后,前端没有对应的返回码可以判断
项目登录流程如下 用户进入前端登录界面,输入账号密码等,输入完成之后前端发送请求到后端(拦截器不会拦截登录请求),后端验证账号密码等成功之后生成Token并存储到数据库,数据库中包含该Token过期时 ...
- Vue.js 源码分析(十二) 基础篇 组件详解
组件是可复用的Vue实例,一个组件本质上是一个拥有预定义选项的一个Vue实例,组件和组件之间通过一些属性进行联系. 组件有两种注册方式,分别是全局注册和局部注册,前者通过Vue.component() ...
随机推荐
- Android-AnsyncTask异步任务
同步和异步的概念区别: 同步,必须执行完成某个问题后才能继续执行其他的. 异步,我会去先执行其他问题,你执行完之后返回给我一个结果就可以. android中为什么要引用异步任务呢 android启动的 ...
- virtuoso数据库的安装方法
数据库virtuoso有两种安装配置方式 第一种就是直接在系统中默认安装,拷贝virtuoso的安装文件,直接默认安装. 第二种是配置安装方式 参考地址:http://vos.openlinksw.c ...
- JDBC详解系列(四)之建立Stament和执行SQL语句
建立Stament 在获得连接之后,我们就可以跟数据库进行交互了. 在JDBC中,我们发送SQL语句到数据库这些操作时通过Stament,Preparement,CallableStateme ...
- VM虚拟机安装centos,同网段,局域网能访问
VM虚拟机安装centos,同网段,局域网能访问. 首先下载虚拟机镜像文件,自行下载 安装,网络模式为桥接,设置dhcp为主机同网段 保持VM服务开启 开机就是同网段了
- Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型?
方法的重写Overriding和重载Overloading是Java多态性的不同表现.重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现.如 ...
- 接口自动化测试框架 (一) :APIAutoTest框架
前言 随着测试技术的发展,接口自动化测试逐渐成为各大公司投入产出比最高的测试技术.介入时间早,执行效率高,稳定性高的优点,让越来越多的公司引入接口自动化测试. 框架简介 APIAutoTest是处理A ...
- Django权限管理测试
测试内容:当我单击登录页面登录的时候页面会弹出当前用户的个人信息 当我点击提交的时候可以看到我当前用户的所有权限: 测试成功,接下来看一下后台的简单代码: class User(models.Mode ...
- Nexus 6P 解锁+TWRP+CM
// 这是一篇导入进来的旧博客,可能有时效性问题. 1. 需要用到的文件:Google USB驱动:adb和fastboot工具二进制文件(如果解锁时提示命令无效说明版本过低,需下载使用支持nexus ...
- 声音变调算法PitchShift(模拟汤姆猫) 附完整C++算法实现代码
上周看到一个变调算法,挺有意思的,原本计划尝试用来润色TTS合成效果的. 实测感觉还需要进一步改进,待有空再思考改进方案. 算法细节原文,移步链接: http://blogs.zynaptiq.com ...
- [51nod1357]密码锁
有一个密码锁,其有N位,每一位可以是一个0~9的数字,开启密码锁需要将锁上每一位数字转到解锁密码一致.这个类似你旅行用的行李箱上的密码锁,密码锁的每一位其实是一个圆形转盘,上面依次标了0,1,...9 ...