JavaEE 企业级分布式高级架构师(十)Nginx学习笔记(2)

Nginx核心配置

Nginx日志管理及自动切割

Nginx也提供了对日志的强大支持,通过日志可以查看到很多请求访问信息,及异常信息。

日志管理范围

首先,下面讲的这些日志相关属性可以配置在任意模块。但在不同的模块,记录的是不同请求的日志信息。即,日志记录的请求范围是不同的。日志一般可以指定三个范围:

  • http{}模块范围:若有请求通过http协议访问nginx,则就会有日志写入到该日志文件。
  • server{}模块范围:若有请求访问该server,则就会有日志写入到该日志文件。
  • location{}模块范围:若有请求访问该location,则就会有日志写入到该日志文件。

日志管理指令

下面以 http{} 模块下的日志为例来学习 Nginx 日志管理指令。
在这里插入图片描述

log_format
  • $remote_addr:获取访问者的 IP 地址。
  • $http_x_forwarded_for:获取客户端浏览器的IP。
  • $remote_user:获取访问者的用户名。
  • $time_local:获取请求访问的时间与时区,例如[19/May/2019:05:21:38 +0800],+0800表示时区为东8区。
  • $request:获取请求的相关信息,包含请求方式、请求的URI、及访问协议。
  • $status:后端服务器向其返回的状态码,例如200。
  • $body_bytes_sent:后端服务器向客户端发送的响应体内容字节数。
  • $http_referer:获取当前请求是从哪个页面过来的。其值在这里显示为杠(-)。
  • $http_user_agent:用户所使用的代理,一般为浏览器。例如,[Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3554.0 Safari/537.36]
access_log

该指令用于设置访问日志,指令格式包含三个参数:

  • 第一个参数是日志存放路径与日志文件名;
  • 第二个参数是日志格式名;
  • 第三个参数是指日志信息所使用的缓存大小;
  • access_log还可以跟一个参数off,用于关闭访问日志,即直接写 access_log off 即可以关闭访问日志。
error_log

该指令用于指定错误日志的路径与文件名。需要注意以下几点:

  • 其不能指定格式,因为其有默认格式。
  • 可以使用自己指定的错误日志文件,不过,将来的访问异常日志就不会再写入到默认的 logs/error.log 文件中了。所以关于错误日志,一般使用默认的即可。
  • 错误日志级别由低到高有:[debug | info | notice | warn | error | crit | alert | emerg],默认为 error,级别越高纪录的信息越少。
  • 错误日志默认是开启的。关闭错误日志不是 error_log off,这样会将错误日志写到当前目录中名称为 off 的文件中。关闭错误日志的正确写法是 error_log /dev/null
open_log_file_cache

该指令用于打开日志文件读缓存,将日志信息读取到缓存中,以加快日志解析系统对日志的访问。该功能默认为 off,即 open_log_file_cache off。

  • max:最大可以打开的日志文件个数。
  • inactive与min_uses要联用:在日志进入缓存 inactive 的时间内,应至少有 min_uses 次被访问。否则该日志将被移出缓存。
  • valid:缓存日志刷新时间。

默认的/favicon.ico请求

location / {
	root 		html;
	index	 index.html index.htm;
}
location /favicon.ico {
	root 		html;
}
  • 若将 favicon.ico 图标存放到 index.html 文件的存放目录,则无需添加 location{} 模块

日志自动切割

这里仅仅简单介绍一下日志切割的实现步骤,具体实现可以网上搜索。

  • 创建切割shell脚本文件:在Linux下创建一个实现日志切割的shell脚本文件,脚本文件的具体内容可以从网上查找,资源很多。例如,将该shell文件创建在Nginx安装目录下的logs目录中,并命名为cut_nginx_log.sh。
  • 为该文件添加可执行权限:该文件是要作为定时任务被执行的,所以该文件需要具有可执行权限。
  • 向crontab中添加一个定时任务:crontab是Linux中的一个定时任务文件,每一行都代表一项定时任务。每行由6个字段组成,前5个是时间设定段,第6个是任务段。具体格式如下:
minute(0-59) hour(0-23) day(0-31) month(1-12) week(0-6) command

执行如下命令后会打开文本编辑器,然后输入如下文本内容即可。

[root@localhost logs]# crontab -e
# 每天23点59分时执行日志分割
59 23 * * * /usr/local/nginx/logs/cut_nginx_log.sh

静态代理

Nginx静态代理是指,将所有的静态资源,例如,css、js、html、jpg 等资源存放到 Nginx 服务器,而不存放在应用服务器 Tomcat 中。

扩展名拦截

  • 修改配置文件:
location ~.*\.(jpg|png|css|js|html)$ {
    root /opt/apps/statics;
}
  • 创建目录:在 /opt 目录下创建 apps/statics 目录,再在/opt/apps/statics目录中创建 css、js、images 目录。
  • 在/opt/apps/statics/images 目录中上传一些图片,例如01.jpg、02.jpg、03.jpg、04.jpg、05.jpg
  • 重启nginx,测试访问地址:http://192.168.254.120/images/01.jpg

目录名拦截

  • 修改配置文件:
#location ~.*\.(jpg|png|css|js|html)$ {
#    root /opt/apps/statics;
#}

location ~.*(css|js|images).+ {
    root /opt/apps/statics;
}
  • 重启nginx,测试访问地址:http://192.168.254.120/images/01.jpg

页面压缩

浏览器常见的压缩协议

浏览器中最常用的压缩算法有:

  • deflate:是一种过时的压缩算法,是 huffman 编码的一种加强。
  • gzip:是目前大多数浏览器都支持的一种压缩算法,是对 deflate 的改进。
  • sdch:谷歌开发的一种压缩算法,一种全新的压缩思路。deflate 与 gzip 的压缩思想是修改传输数据的编码格式以达到减少体量的目的,其最终传输的数据并没有减少。而sdch压缩算法的思想是,让冗余的数据仅出现一次,其最终传输的数据减少了。
  • Zopfli:谷歌开发的一种压缩算法,deflate压缩算法的改进。比标准的gzip-9要小 3%-8%,但压缩用时是 gzip-9 的 80 多倍。
  • br:即Brotli,谷歌开发的一种压缩算法,是一种全新的数据格式。与 Zopfli 相比,压缩率能够降低 20%-26%。Brotli-1有着与 gzip-9 相近的压缩比和更快的压缩解压速度。
常用设置
gzip  on;
gzip_min_length 5k;
gzip_comp_level 4;
gzip_buffers 4 16k;
gzip_vary on;
gzip_types text/html text/css text/xml application/x-javascript;
  • gzip on;:开启 gzip 压缩,默认为 off。
  • gzip_min_length 5k;:指定最小启用压缩的文件大小。
  • gzip_comp_level 4;:指定压缩级别,取值为1-9,数字越大,压缩比越高,但压缩所用时间会越长。默认1,建议使用4。
  • gzip_buffers 4 16k;:“4”表示的是缓存颗粒数量【一般设置为workers的数量】,而“16k”表示的是缓存颗粒大小【一般设置为需要压缩的最大文件的大小】。
  • gzip_vary on;:开启动态压缩。
  • gzip_types mimeType;:通过 MIME 类型来指定要压缩的文件类型。对于 Nginx 所支持的 MIME 类型,可以通过与 nginx.conf 同目录的 mime.types 文件查看,默认值 text/html。

反向代理

通过在 location{} 中添加通行代理 proxy_pass 可以指定当前 Nginx 所要代理的真正的服务器。

反向代理百度

在 http{} 的 server{} 中添加如下内容,当然,需要将原来的根的跳转注释掉。需要注意,Nginx 做反向代理时的匹配路径只能写为根(/)。

#location / {
#    root   html;
#    index  index.html index.htm;
#}
location / {
    proxy_pass  https://www.baidu.com;
}

反向代理Tomcat服务器

定义一个web工程

定义一个 Maven Web 工程,并命名为 web-demo。其包含一个JSP页面,及一个Servlet。

  • 修改pom.xml
<dependencies>
    <!-- Servlet依赖 -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>

    <!-- JSP依赖 -->
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>javax.servlet.jsp-api</artifactId>
        <version>2.2.1</version>
        <scope>provided</scope>
    </dependency>
</dependencies>
  • 定义index.jsp
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
    <title>webdemo</title>
</head>
<body background="/images/bj.jpg">
    Nginx World Welcome You!<br>
    Nginx Addr = ${pageContext.request.remoteAddr} <br>
    Tomcat Addr = ${pageContext.request.localAddr} <br>
</body>
</html>
  • 定义SomeServlet
@WebServlet("/some")
public class SomeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        PrintWriter writer = resp.getWriter();
        writer.println("Nginx Ip = " + req.getRemoteAddr());
        writer.println("Tomcat Ip = " + req.getLocalAddr());
    }
}
Tomcat部署工程
  • tomcat机器:192.168.254.130
  • 将 web-demo 工程打包后部署到 Tomcat 的 webapps 目录中。
  • 启动tomcat
[root@centos130 apache-tomcat-8.5.41]# bin/startup.sh | tail -f logs/catalina.out
  • 访问测试:http://192.168.254.130:8080/webdemo/
    在这里插入图片描述
修改Nginx配置文件
location ~.*(/|/some) {
    proxy_pass http://192.168.254.130:8080;
}
  • 测试访问地址:http://192.168.254.120/webdemo/some
    在这里插入图片描述

反向代理的属性设置

反向代理的属性设置,一定是设置在反向代理的 location{} 模块中。

  • client_max_body_size 100k;:Nginx允许客户端请求的单文件最大大小,单位字节。
  • proxy_buffering on;:开启“后端被代理服务器(即后端服务器)”的响应内容缓冲区,默认值 on。
  • proxy_buffers 4 8k;:该指令用于设置后端服务器的响应内容缓冲区的数量与大小。
  • proxy_busy_buffers_size 16k;:高负荷下缓存大小,其默认值一般为单个proxy_buffers 的 2 倍。
  • proxy_connect_timeout 60s;:Nginx跟后端服务器连接超时时间,默认 60 秒。
  • proxy_read_timeout 60s;:Nginx发出请求后等待后端服务器响应的最长时限,默认 60 秒。

负载均衡

负载均衡分类

软硬件分类

负载均衡可以通过负载均衡软件实现,也可以通过硬件负载均衡器实现。

硬件负载均衡

硬件负载均衡的性能稳定,且有生产厂商作为专业的服务团队。但其成本很高,一台硬件负载均衡器的价格一般都在十几万到几十万,甚至上百万。知名的负载均衡器有 F5、Array、深信服、梭子鱼等。
在这里插入图片描述

软件负载均衡

软件负载均衡成本几乎为零,基本都是开源软件。例如:LVS、HAProxy、Nginx等。

负载均衡工作层分类(面试点)

负载均衡就其所工作的OSI(开放系统互联模型)层次,在生产应用层面分为四类:

  • 七层负载均衡L7:应用层,基于HTTP协议的负载均衡,通过虚拟URL将请求分配到真实的服务器。Nginx就是L7负载均衡器,应用场景一般为 B/S 架构的系统。
  • 四层负载均衡L4:传输层,基于TCP协议的负载均衡器,通过虚拟IP+端口号的形式将请求分配到真实的服务器。应用场景一般为 C/S 架构的系统。LVS、F5都是基于L4的负载均衡器。Nginx Plus(Nginx商业版)提供了基于 L4 的负载均衡功能。
  • 三层负载均衡L3:网络层,基于IP协议的负载均衡,通过虚拟IP形式将请求分配到真实的服务器。有些DNS提供L3的负载均衡功能。
  • 二层负载均衡L2:链路层,通过虚拟MAC的形式将请求分配到真实服务器。有些DNS提供L2的负载均衡功能。

负载均衡的实现

总体规划
  • 该集群包含一台 Nginx 服务器,两台 Tomcat 服务器。前面打包过的 web 工程直接部署到两台 Tomcat 主机上,然后,在Nginx 服务器上设置对这两台 Tomcat 主机的负载均衡。
  • 两台Tomcat主机:192.168.254.120、192.168.254.130。
配置Nginx主机

修改Nginx配置文件:
在这里插入图片描述

测试

测试访问地址:http://192.168.254.120/webdemo/some
在这里插入图片描述

Nginx负载均衡策略

Nginx内置了三种负载均衡策略,另外,其还支持第三方的负载均衡。而每种负载均衡主机根据负载均衡策略的不同,又可设置很多性能相关的属性。

轮询

默认的负载均衡策略,其是按照各个主机的权重比例依次进行请求分配的。该策略使用的场景是:根据主机性能设置不同权重。对于每台主机,除了像 weight 一样可以设置的属性外,还可以设置如下属性:

upstream tomcat.yw.com {
	server tomcat1:8080 weight=2 fail_timeout=20 max_fail=3;
	server tomcat2:8080 weight=1 fail_timeout=20 max_fail=3;
	server tomcat3:8080 backup;
	server tomcat4:8080 down;
}
  • fail_timeout:表示当前主机被 Nginx 认定为停机的最长失联时间,默认为 10 秒。常与 max_fails 联合使用。
  • max_fails:表示在 fail_timeout 时间内最多允许的失败次数。
  • backup:表示当前服务器为备用服务器。
  • down:表示当前服务器永久停机。
ip_hash

指定负载均衡按照基于客户端IP的分配方式,该策略确保了相同的客户端的请求一直发送到相同的服务器,以保证 session 会话,解决了 session 不能跨服务器的问题。

upstream tomcat.yw.com {
	ip_hash;
	server tomcat1:8080 weight=2 fail_timeout=20 max_fail=3;
	server tomcat2:8080 weight=1 fail_timeout=20 max_fail=3;
	server tomcat3:8080 weight=2 fail_timeout=20 max_fail=3;
	server tomcat4:8080 weight=1 fail_timeout=20 max_fail=3;
}

对于该策略需要注意以下几点:

  • 在 nginx1.3.1版本之前,该策略中不能指定 weight 属性。
  • 该策略不能与 backup 同时使用。
  • 此策略适合有状态服务,比如 session。
  • 当有服务器宕机,必须手动指定 down 属性,否则请求仍是会落到该服务器。
least_conn

把请求转发到连接数最少的服务器。

upstream tomcat.yw.com {
	least_conn;
	server tomcat1:8080 weight=2 fail_timeout=20 max_fail=3;
	server tomcat2:8080 weight=1 fail_timeout=20 max_fail=3;
	server tomcat3:8080 backup;
	server tomcat4:8080 down;
}

Nginx Plus的四层负载均衡实现

  • Nginx Plus 是 Nginx 的商业版,其官网为:https://nginx.com
  • 同样是修改 nginx.conf 文件,添加一个 stream 模块,其与 events、http 等模块同级。在其中配置 upstream{} 与 server{} 模块。此时需要注意,通行代理配置在 server{} 中(前面的是配置在 server 模块的 location 模块中),且不能再是 http:// 开头了,因为其负载均衡协议不再是 HTTP 协议了。
// ...
events {
	// ...
}
stream {
	upstream app {
		server 192.168.254.120:1234;
		server 192.168.254.130:1234;
	}
	server {
		listen 1234;
		proxy_pass app;
	}
}
http {
	// ...
}

动静分离

动静分离简介

在这里插入图片描述

Nginx动静分离的实现

  • 下面要搭建的Nginx环境中有三台Nginx主机:一台用于完成负载均衡,两台用于存放项目中的静态资源。另外,还包含前面的两台 Tomcat 主机。
  • Nginx服务器配置
192.168.254.120		负载均衡
192.168.254.128		存放静态资源bj.jpg
192.168.254.130		存放静态资源bj.jpg(与上面的不同)
  • Tomcat服务器配置
192.168.254.120:8080
192.168.254.130:8080
  • 192.168.254.128和192.168.254.130机器上分别在 /opt/statics/images 目录下添加bj.jpg图片,并修改nginx配置:
location ~.*(css|js|html|images).+ {
    root /opt/statics;
}
  • 修改负载均衡 Nginx 主机配置
    在这里插入图片描述
  • 测试访问地址:http://192.168.254.120/webdemo/
    在这里插入图片描述

虚拟主机

虚拟主机简介

  • 虚拟主机,就是将一台物理服务器虚拟为多个服务器来使用,从而实现在一台服务器上配置多个站点,即可以在一台物理主机上配置多个域名。Nginx中,一个 server 标签就是一台虚拟主机,配置多个 server 标签就虚拟出了多台主机。
  • Nginx 虚拟主机的实现方式有两种:域名虚拟方式端口虚拟方式。域名虚拟方式是指不同的虚拟机使用不同的域名,通过不同的域名虚拟出不同的主机;端口虚拟方式是指不同的虚拟机使用相同的域名不同的端口号,通过不同的端口号虚拟出不同的主机。基于端口号虚拟方式不常用。

总体规划

  • 现在很多生活服务类网络平台都具有这样的功能:不同城市的用户可以打开不同城市专 属的站点。用户首先打开的是平台总的站点,然后允许用户切换到不同的城市。其实,不同 的城市都是一个不同的站点。
  • 这里我们要实现的功能是为平台总站点、北京、上海两个城市站点分别创建一个虚拟主 机。每一个虚拟主机都具有两台 Tomcat 的负载均衡主机。由于有三个站点,所以共需六台 Tomcat 主机,克隆 Tomcat 主机太过麻烦,所以这六台 Tomcat 我们使用一台主机实现。在 一台主机中安装六个 Tomcat,它们分别使用六个不同的端口号。
  • 首先要创建一个 web 工程,其中就一个 index.jsp 页面,页面除了显示当前城市外,还 要显示城市切换的超链接。为了能够再明显的区分出当前访问的 Tomcat,再在页面中显示 出当前工程所在的主机名与端口号。

创建三个web工程

复制 web-demo 工程,每个工程仅需一个 jsp 即可。最终打包为三个 war 包,三个工程的 jsp 页面内容各不相同。
在这里插入图片描述

  • 总站:
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
    <title>entry68</title>
</head>
<body>
    这是68平台总站<br>
    站点切换:
    <a href="http://bj.68.com">北京</a>
    <a href="http://sh.68.com">上海</a> <br>

    <hr>
    Tomcat Addr = ${pageContext.request.localAddr} <br>
    Tomcat Port = ${pageContext.request.localPort} <br>
</body>
</html>
  • 北京站:
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
    <title>bj68</title>
</head>
<body>
    这是68平台【北京】站<br>
    站点切换:
    <a href="http://www.68.com">总站</a>
    <a href="http://sh.68.com">上海</a> <br>

    <hr>
    Tomcat Addr = ${pageContext.request.localAddr} <br>
    Tomcat Port = ${pageContext.request.localPort} <br>
</body>
</html>
  • 上海站:
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
    <title>sh68</title>
</head>
<body>
    这是68平台【上海】站<br>
    站点切换:
    <a href="http://www.68.com">总站</a>
    <a href="http://bj.68.com">北京</a> <br>

    <hr>
    Tomcat Addr = ${pageContext.request.localAddr} <br>
    Tomcat Port = ${pageContext.request.localPort} <br>
</body>
</html>

修改hosts文件

  • 通过前面的三个 web 工程可知,这里需要三个域名。由于这三个域名都是虚拟在同一个 Nginx 上,所以这三个域名所使用的是作为虚拟主机的 Nginx 的 IP。
    在这里插入图片描述

配置Tomcat主机

总体规划:
tomcat8081、tomcat8082		部署 entry68.war
tomcat8083、tomcat8084		部署 bj68.war
tomcat8085、tomcat8086		部署 sh68.war
部署tomcat8081
  • 复制一份tomcat,重命名为tomcat8081,修改 server.xml 配置:
    在这里插入图片描述
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8001" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>
  <Service name="Catalina">
    <Connector port="8081" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <Connector port="8011" protocol="AJP/1.3" redirectPort="8443" />
    <Engine name="Catalina" defaultHost="localhost">
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
      </Host>
    </Engine>
  </Service>
</Server>
  • 上传 entry68.war 到 tomcat8081 的 webapps/ROOT 目录。
部署tomcat8082
  • 复制 tomcat8081,并重命名为tomcat8082。
  • 修改 server.xml 配置:
    在这里插入图片描述
    在这里插入图片描述
部署tomcat8083~tomcat8086
  • 和部署tomcat8081,tomcat8082类似,分别将项目 bj68.war 上传到tomcat8083、tomcat8084,sh68.war 上传到tomcat8085、tomcat8086。
  • 修改相应的 server.xml 配置。
  • 最终复制出 6 个 tomcat。
    在这里插入图片描述
启动
  • 启动脚本 start.sh:
./tomcat8081/bin/startup.sh
./tomcat8082/bin/startup.sh
./tomcat8083/bin/startup.sh
./tomcat8084/bin/startup.sh
./tomcat8085/bin/startup.sh
./tomcat8086/bin/startup.sh
  • 启动:
    在这里插入图片描述
  • 查看:
    在这里插入图片描述

配置虚拟主机

直接配置到 nginx.conf 中
// ...
	upstream www.68.com {
        server  192.168.254.120:8081    weight=1;
        server  192.168.254.120:8082    weight=1;
    }
    upstream bj.68.com {
        server  192.168.254.120:8083    weight=1;
        server  192.168.254.120:8084    weight=1;
    }
    upstream sh.68.com {
        server  192.168.254.120:8085    weight=1;
        server  192.168.254.120:8086    weight=1;
    }

    server {
        listen  80;
        server_name www.68.com;
        location / {
            proxy_pass http://www.68.com;
        }
    }
    server {
        listen  80;
        server_name bj.68.com;
        location / {
            proxy_pass http://bj.68.com;
        }
    }
    server {
        listen  80;
        server_name sh.68.com;
        location / {
            proxy_pass http://sh.68.com;
        }
    }
// ...
单独配置到 vhosts.conf 中
  • 在任意目录中定义任意名字的文件,该文件专门配置虚拟主机。这里在nginx安装目录的conf中定义一个名词为 vhosts.conf文件:
    在这里插入图片描述
  • 修改 nginx.conf 文件:
// ...
	upstream www.68.com {
        server  192.168.254.120:8081    weight=1;
        server  192.168.254.120:8082    weight=1;
    }
    upstream bj.68.com {
        server  192.168.254.120:8083    weight=1;
        server  192.168.254.120:8084    weight=1;
    }
    upstream sh.68.com {
        server  192.168.254.120:8085    weight=1;
        server  192.168.254.120:8086    weight=1;
    }

    include /usr/local/nginx/conf/vhosts.conf;
// ...

测试

  • 测试访问地址:http://www.68.com/
    在这里插入图片描述
  • http://bj.68.com/
    在这里插入图片描述
  • http://sh.68.com/
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

讲文明的喜羊羊拒绝pua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值