从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式

这篇具有很好参考价值的文章主要介绍了从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本文介绍从零到一搭建基于netcore6.0版本的 webapi接口应用
包括swagger接口管理文档
jwt接口安全认证
aop接口调用轨迹日志
ef映射mysql 使用codefirst模式交互数据库

首先新建一个webapi应用
从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式
此次默认配置HTTPS默认是勾选的 此处没用到暂时不进行勾选
从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式
可以删除这2个默认的文件

从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式

首先我们要建立一个基础的控制器文件
打上路由的标记接口请求地址
继承ControllerBase基类
从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式
接下来写一个标准的CRUD 增删查改结构的接口
由于要用ef交互mysql数据库我们先建立一个model文件
从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式
table标记为mysql映射的表名,mysql一般表名都为小写
key标记字段为主键
column标记映射mysql表中的字段名称
maxlength为控制字段的最大长度

 /// <summary>
    /// 测试模型
    /// </summary>
    [Table("testtable")]
    public class TestModel
    {
        /// <summary>
        /// 主键
        /// </summary>
        [Key]
        [Column("id")]
        public Guid ID { get; set; }

        /// <summary>
        /// 名称
        /// </summary>
        [Display(Name = "名称")]
        [Column("name")]
        [MaxLength(45)]
        public string Name { get; set; }        
    }

第二步在配置文件中配置mysql的连接地址
首先在Nuget里面安装 Microsoft.EntityFrameworkCore
和Pomelo.EntityFrameworkCore.MySql 包文件

appsettings.json 为生产环境配置文件
Development 为开发环境配置文件
从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "MySql": "server=localhost;user=root;database=bridgedb;port=3306;password=123456"
  },
  "Jwt": {
    "SecretKey": "wubo199686wubo199686wubo199686",
    "Issuer": "WebAppIssuer",
    "Audience": "WebAppAudience"
  },
  "Log4Net": {
    "RepositoryName": "NETCoreRepository",
    "ConfigFilePath": "Log4Net/log4net.config"
  }
}

注册到程序中 修改 program文件
从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式

//新增数据库连接读取
builder.Services.AddDbContext<MyDbContext>(options => options.UseMySql(
    builder.Configuration.GetConnectionString("MySql"),
    ServerVersion.AutoDetect(builder.Configuration.GetConnectionString("MySql"))));

第三步建立数据上下文

从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式
从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式

public class MyDbContext : DbContext
	{
		/// <summary>
		/// 创建实体
		/// </summary>
		/// <param name="modelBuilder"></param>
		protected override void OnModelCreating(ModelBuilder modelBuilder)
		{
			base.OnModelCreating(modelBuilder);
		}

		public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
		{

		}

		/// <summary>
		/// 测试模型
		/// </summary>
		public DbSet<TestModel> ModelCustom { get; set; }

	}

第四步将表结构同步到mysql 生成数据库及表
打开Nuget控制台窗口程序
从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式

输入命令
Add-Migration Initial
然后执行
Update-Database
即可同步数据表结构

数据库及ef映射的相关工作都做好了该写我们的CRUD接口了
注入上下文 然后就可以使用linq或者lamda语法操作ef模型了
savechanges就是将更改提交到数据库

 /// <summary>
    /// api接口
    /// </summary>
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class TestController : ControllerBase
    {
        /// <summary>
        /// 数据上下文
        /// </summary>
        private readonly MyDbContext _coreDbContext;

        /// <summary>
        /// 注入上下文
        /// </summary>
        /// <param name="coreDbContext"></param>
        public TestController(MyDbContext coreDbContext)
        {
            _coreDbContext = coreDbContext;
        }

        /// <summary>
        /// 查询数据
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public List<TestModel> Get()
        {
            return _coreDbContext.Set<TestModel>().ToList();
        }

        /// <summary>
        /// 创建数据
        /// </summary>
        /// <param name="request">测试模型</param>
        /// <returns></returns>
        [HttpPost]
        public void Create([FromBody] TestModel request)
        {

            _coreDbContext.ModelCustom.Add(request);
            _coreDbContext.SaveChanges();
        }

        /// <summary>
        /// 删除数据
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpDelete]
        public void Delete([FromForm] Guid id)
        {
            var item = _coreDbContext.ModelCustom.Where(b => b.ID == id).FirstOrDefault();
            _coreDbContext.ModelCustom.Remove(item);
            _coreDbContext.SaveChanges();
        }

        /// <summary>
        /// 更新数据
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        [HttpPut]
        public void Update([FromBody] TestModel request)
        {
            var item = _coreDbContext.ModelCustom.Where(b => b.ID == request.ID).FirstOrDefault();
            item.Name = request.Name;
            _coreDbContext.SaveChanges();
        }


    }

接口写好了我们需要使用swagger来对接口做管理和说明
由于6.0是自动继承了swagger的包的所以无需单独配置 而且6.0将startup文件和program文件合并了 我们注册的时候只需要对program文件进行操作即可
从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式

builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo
    {
        Version = "v1",
        Title = "API",
        Description = "API说明"
    });
    var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
});

将此应用属性里面生成 勾选生成api文档
从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式

此处运行就成功了
从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式

但是接口的话我们要考虑到安全性对于前后端分离的项目来说jwt认证是比较适用的

首先安装Nuget包
System.IdentityModel.Tokens.Jwt
Microsoft.AspNetCore.Authentication.JwtBearer

然后新建一个jwt的帮助类

 /// <summary>
    /// JWT工具类
    /// </summary>
    public class JwtHelper
    {
        private readonly IConfiguration _configuration;

        public JwtHelper(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        public string CreateToken()
        {
            // 1. 定义需要使用到的Claims
            var claims = new[]
            {
               new Claim(ClaimTypes.Name, "u_admin"), //HttpContext.User.Identity.Name
               new Claim(ClaimTypes.Role, "r_admin"), //HttpContext.User.IsInRole("r_admin")
               new Claim(JwtRegisteredClaimNames.Jti, "admin"),
               new Claim("Username", "Admin"),
               new Claim("Name", "超级管理员")
            };

            // 2. 从 appsettings.json 中读取SecretKey
            var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:SecretKey"]));

            // 3. 选择加密算法
            var algorithm = SecurityAlgorithms.HmacSha256;

            // 4. 生成Credentials
            var signingCredentials = new SigningCredentials(secretKey, algorithm);

            // 5. 根据以上,生成token
            var jwtSecurityToken = new JwtSecurityToken(
                _configuration["Jwt:Issuer"],     //Issuer
                _configuration["Jwt:Audience"],   //Audience
                claims,                          //Claims,
                DateTime.Now,                    //notBefore
                DateTime.Now.AddDays(1),    //expires
                signingCredentials               //Credentials
            );

            // 6. 将token变为string
            var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);

            return token;
        }
    }

配置文件中配置jwt的相关配置
注意SecretKey设置不可过短不然在生成token的时候会有异常 最少16个字符以上
从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式

注册到程序中
从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式
从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式

从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式
从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式

var builder = WebApplication.CreateBuilder(args);

var configuration = builder.Configuration;

//jwt认证
builder.Services.AddSingleton(new JwtHelper(configuration));

// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddControllers();

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo
    {
        Version = "v1",
        Title = "API",
        Description = "API说明"
    });
    var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));

    //定义JwtBearer认证方式一
    options.AddSecurityDefinition("JwtBearer", new OpenApiSecurityScheme()
    {
        Description = "这是方式一(直接在输入框中输入认证信息,不需要在开头添加Bearer)",
        Name = "Authorization",//jwt默认的参数名称
        In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)
        Type = SecuritySchemeType.Http,
        Scheme = "bearer"
    });

    //定义JwtBearer认证方式二
    //options.AddSecurityDefinition("JwtBearer", new OpenApiSecurityScheme()
    //{
    //    Description = "这是方式二(JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}(注意两者之间是一个空格))",
    //    Name = "Authorization",//jwt默认的参数名称
    //    In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)
    //    Type = SecuritySchemeType.ApiKey
    //});

    //声明一个Scheme,注意下面的Id要和上面AddSecurityDefinition中的参数name一致
    var scheme = new OpenApiSecurityScheme()
    {
        Reference = new OpenApiReference() { Type = ReferenceType.SecurityScheme, Id = "JwtBearer" }
    };
    //注册全局认证(所有的接口都可以使用认证)
    options.AddSecurityRequirement(new OpenApiSecurityRequirement()
    {
        [scheme] = new string[0]
    });
});

//新增数据库连接读取
builder.Services.AddDbContext<MyDbContext>(options => options.UseMySql(
    builder.Configuration.GetConnectionString("MySql"),
    ServerVersion.AutoDetect(builder.Configuration.GetConnectionString("MySql"))));

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters()
    {
        ValidateIssuer = true, //是否验证Issuer
        ValidIssuer = configuration["Jwt:Issuer"], //发行人Issuer
        ValidateAudience = true, //是否验证Audience
        ValidAudience = configuration["Jwt:Audience"], //订阅人Audience
        ValidateIssuerSigningKey = true, //是否验证SecurityKey
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:SecretKey"])), //SecurityKey
        ValidateLifetime = true, //是否验证失效时间
        ClockSkew = TimeSpan.FromSeconds(30), //过期时间容错值,解决服务器端时间不同步问题(秒)
        RequireExpirationTime = true,
    };
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

如果全局注册的话 所有接口都需要认证
如果不全局注册的话在需要认证的接口打上标记即可
从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式

获取token的方法

/// <summary>
    /// 认证控制器
    /// </summary>
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class AccountController : ControllerBase
    {
        private readonly JwtHelper _jwtHelper;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="jwtHelper"></param>
        public AccountController(JwtHelper jwtHelper)
        {
            _jwtHelper = jwtHelper;
        }

        /// <summary>
        /// 获取token
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public ActionResult<string> GetToken()
        {
            return _jwtHelper.CreateToken();
        }
    }

加了认证后 不带token请求的话是请求不通的
从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式

测试接口的话先将token请求出来
从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式

从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式

将token输入
再次请求接口即可访问成功
如果是用postman请求的话将token带入请求头即可
从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式
从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式

现在swagger+jwt+数据库交互都已经完成 剩下最后就是日志了 毕竟接口调用轨迹还是比较重要的 如果是每个接口都写日志的相关代码实在是过于麻烦
此次用log4net+aop自定义属性切面记录日志

首先安装log4net的包
从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式

新建log4net相关配置文件
从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式

<?xml version="1.0" encoding="utf-8"?>
<configuration>
	<log4net>
		<!-- 错误日志类-->
		<logger name="Error">
			<level value="ALL" />
			<appender-ref ref="ErrorAppender" />
		</logger>
		<!-- 错误日志附加介质-->
		<appender name="ErrorAppender" type="log4net.Appender.RollingFileAppender">
			<!--日志文件路径-->
			<param name="File" value="Logs\\Error\\" />
			<!--是否是向文件中追加日志-->
			<param name="AppendToFile" value="true" />
			<!--log保留天数-->
			<param name="MaxSizeRollBackups" value="1000" />
			<!--最大文件大小-->
			<param name="MaxFileSize" value="10240" />
			<!--日志文件名是否是固定不变的-->
			<param name="StaticLogFileName" value="false" />
			<!--日志文件名格式为:2008-08-31.log-->
			<param name="DatePattern" value="yyyy-MM-dd.'log'" />
			<!--日志根据日期滚动-->
			<param name="RollingStyle" value="Date" />
			<!--信息日志布局-->
			<layout type="log4net.Layout.PatternLayout">
				<param name="ConversionPattern" value="%n==========%n【日志级别】:%-5level%n【记录时间】:%date %n【执行时间】:[%r]毫秒%n%message%n" />
			</layout>
		</appender>

		<!-- 信息日志类 -->
		<logger name="Info">
			<level value="ALL" />
			<appender-ref ref="InfoAppender" />
		</logger>
		<!-- 信息日志附加介质-->
		<appender name="InfoAppender" type="log4net.Appender.RollingFileAppender">
			<!--日志文件路径-->
			<param name="File" value="Logs\\Info\\" />
			<!--是否是向文件中追加日志-->
			<param name="AppendToFile" value="true" />
			<!--log保留天数-->
			<param name="MaxSizeRollBackups" value="100" />
			<param name="MaxFileSize" value="1" />
			<!--日志文件名是否是固定不变的-->
			<param name="StaticLogFileName" value="false" />
			<!--日志文件名格式为:2008-08-31.log-->
			<param name="DatePattern" value="yyyy-MM-dd.'log'" />
			<!--日志根据日期滚动-->
			<param name="RollingStyle" value="Date" />
			<!--信息日志布局-->
			<layout type="log4net.Layout.PatternLayout">
				<param name="ConversionPattern" value="%n==========%n【日志级别】:%-5p%n【记录时间】:%d [%t]%n【信息详情】:%m%n"  />
			</layout>
		</appender>
	</log4net>
</configuration>
 public class Log4NetConfig
    {
        public static string RepositoryName { get; set; }

        public static void Init(IConfiguration configuration)
        {
            var repositoryName = configuration.GetSection("Log4Net:RepositoryName").Value;
            if (string.IsNullOrWhiteSpace(repositoryName))
            {
                throw new Exception("必须在配置文件中添加 Log4Net > RepositoryName 节点");
            }

            RepositoryName = repositoryName;

            var configFilePath = configuration.GetSection("Log4Net:ConfigFilePath").Value;
            if (string.IsNullOrWhiteSpace(configFilePath))
            {
                configFilePath = "log4net.config";
            }

            var file = new FileInfo(configFilePath);
            var repository = LogManager.CreateRepository(repositoryName);
            XmlConfigurator.Configure(repository, file);
        }
    }

从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式

新增一个LogFilter的过滤器

  /// <summary>
    /// 日志过滤器
    /// </summary>
    public class LogFilter : ActionFilterAttribute
    {
        private static readonly ILog InfoLog = LogManager.GetLogger(Log4NetConfig.RepositoryName, "Info");

        /// <summary>
        /// Action方法调用之前执行
        /// </summary>
        /// <param name="context"></param>
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            var descriptor = context.ActionDescriptor as ControllerActionDescriptor;
            string param = string.Empty;
            string globalParam = string.Empty;

            foreach (var arg in context.ActionArguments)
            {
                string value = JsonConvert.SerializeObject(arg.Value);
                param += $"{arg.Key} : {value} \r\n";
                globalParam += value;
            }
            InfoLog.Info($"webapi方法名称:【{descriptor.ActionName}】接收到参数为:{param}");
        }
        /// <summary>
        /// Action 方法调用后,Result 方法调用前执行
        /// </summary>
        /// <param name="context"></param>
        public override void OnActionExecuted(ActionExecutedContext context) { }

        /// <summary>
        /// Result 方法调用前执行
        /// </summary>
        /// <param name="context"></param>
        public override void OnResultExecuting(ResultExecutingContext context) { }

        /// <summary>
        /// Result 方法调用后执行
        /// </summary>
        /// <param name="context"></param>
        public override void OnResultExecuted(ResultExecutedContext context)
        {
            var descriptor = context.ActionDescriptor as ControllerActionDescriptor;
            string result = string.Empty;
            if (context.Result is ObjectResult)
            {
                result = Newtonsoft.Json.JsonConvert.SerializeObject(((ObjectResult)context.Result).Value);
            }

            InfoLog.Info($"webapi方法名称【{descriptor.ActionName}】执行的返回值 :  {result}");
        }
    }

然后注册到program
从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式
需要记录日志的在接口打上[LogFilter]标记即可
此次从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式就已经完成 希望可以和大家共同交流学习文章来源地址https://www.toymoban.com/news/detail-444178.html

到了这里,关于从零到一搭建netcore6.0 webapi+swagger+jwt认证+aop日志+mysql codefirst模式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 长安链---从零到一部署Chainmaker-2.0.0

    长安链部署 长安链·ChainMaker具备自主可控、灵活装配、软硬一体、开源开放的突出特点,由北京微芯研究院、清华大学、北京航空航天大学、腾讯、百度和京东等知名高校、企业共同研发。取名“长安链”,喻意“长治久安、再创辉煌、链接世界”。 长安链作为区块链开源

    2023年04月08日
    浏览(54)
  • zookeeper单节点部署,手把手从零到一

    kafka戳这里:kafka单节点部署,手把手从零到一 事前准备: 1、一台Linux服务器或者是一台虚拟机 2、准备好JDK环境 3、安装好wget(当然也可以不用这个,只是用于下载安装包的一个工具,所以能下载好包就是没问题的) 4、需要了解vim的一些基础操作,不懂得可自行百度 1.1、

    2023年04月15日
    浏览(54)
  • 开发一个Android应用:从零到一的实践指南

    在这篇博文中,我们将逐步探讨如何从头开始构建一个Android应用。我们将从最基本的环境搭建开始,然后深入讨论组件、布局和其他核心概念。在完成整个过程后,你将会掌握一个简单但完整的Android应用开发流程。让我们开始吧! 准备开发环境 创建项目 理解项目结构 设计

    2024年02月08日
    浏览(87)
  • 如何制作流程图?教你从零到一制作

    如何 制作流程图 ? 在当今快节奏、信息化的社会,流程图已经成为了一种非常重要的沟通工具。它能够帮助我们清晰地表达复杂的过程、系统和思路。那么,如何从零开始制作流程图呢?本文将为你提供一份详细的指南。 一、明确目的和内容 在开始制作流程图之前,首先

    2024年01月24日
    浏览(68)
  • Vue+Electron打包桌面应用(从零到一完整教程)

    切记,整个项目的json文件不能有注释,及时没报错也不行,否则运行命令时还是有问题 参考此视频 1- 1.创建项目 1- 2. 安装依赖运行项目 1- 3.配置Electron 1- 4.修改配置文件 1) vite.config.js 2)main.js(项目根目录新增) 此为electron运行的入口文件 3)preload.js(项目根目录下新增) 4)package

    2024年02月07日
    浏览(54)
  • 【30天python从零到一】---第七天:列表和元组

    🍎 博客主页:🌙@披星戴月的贾维斯 🍎 欢迎关注:👍点赞🍃收藏🔥留言 🍇系列专栏:🌙 Python专栏 🌙请不要相信胜利就像山坡上的蒲公英一样唾手可得,但是请相信,世界上总有一些美好值得我们全力以赴,哪怕粉身碎骨!🌙 🍉一起加油,去追寻、去成为更好的自己

    2023年04月19日
    浏览(52)
  • 入职从零到一:如何快速学习Git以适应工作环境

    本文并非面向完全的 Git 初学者,也不会详细介绍每一个 Git 命令和它的所有选项。相反,本文的目标读者是那些已经有一些基础,至少知道如何在本地仓库进行基本的版本控制操作,包括 git add , git commit 和 git log ,但是还没有在企业环境中真正使用 Git 进行过项目开发的开

    2024年02月11日
    浏览(79)
  • 从零到一的方法:学习视频剪辑与嵌套合并技巧

    随着社交媒体和数字技术的快速发展,视频制作已是常见的工作。那么如何批量嵌套合并视频呢?下面一起来看云炫AI智剪如何批量合并的方法。 嵌套合并后的视频截图查看。 批量嵌套合并的操作: 操作1、在云炫AI智剪上选择“嵌套合并”功能,切换相应的版块。 操作2、把

    2024年02月02日
    浏览(69)
  • 纯编程从零到一创建图书管理系统V1.0

    上一篇文章 答应了道友们通过ChatGpt制作图书管理系统程序,今天第一版LMS(Library Management System)他来了,完整版代码放在在文末,有基础的道友可以 点此跳转,在安装中遇到问题,可以点击此处查看解决方案 效果图 功能列表 1、搜索图书只显示符合条件的图书所在行 2、添加

    2024年01月17日
    浏览(58)
  • 从零到一,接入广告后如何让你的APP顺利实现冷启动?

    ​很多APP在上线后却面临着变现难的问题。其中一个主要原因就是缺乏有效的广告变现经验。而当这些APP开始接入广告时,又面临着如何顺利冷启动的问题。 本文将为您介绍一些关键的步骤和技巧,帮助您的APP在接入广告后成功实现冷启动。 提高用户留存率 广告变现不仅仅

    2024年02月06日
    浏览(65)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包