LeoCache的博客

用工程师的浪漫照亮科技的未来

0%

Nginx+Nacos2.2.0搭建集群遇坑指南

这几天刚刚开始学习Nacos,恰好重磅升级的Nacos2.2.0正式版刚刚发布不久,我直接忽略了老师讲解使用的1.1.4版本和SpringCloudAlibaba2.2.9版本推荐的2.1.0,直接选择了2.2.0,后者选择2.1.0和2.2.0的关系倒不是很大,但是1.x和2.x有太多的不一样,导致我花了两整天的时间捣鼓遇到的一个又一个坑。

坑一:64位jdk8+

启动Nacos后通过start.out看到总是报错,错误信息是:

1
2
3
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'instanceOperatorClientImpl'
Caused by: java.lang.UnsatisfiedLinkError: libjawt.so: 无法打开共享对象文件: 没有那个文件或目录
此处省略无数异常 大概都是springboot启动的错误,各种Error creating”

我又安装了Nacos1.1.4尝试,结果成功启动。那这就很可能是环境问题,打开Nacos官网手册一看,首页就是:

image-20230108155841778

然后我就查了我在linux上的环境,也没有什么错误啊,直到过了很久才发现我的jdk是x86即32位,换了x64之后成功启动。

坑2:Nacos2.x使用grpc通信

在Nacos集群,Nginx反向代理都完成后,我测试了Nacos作为配置中心的功能,结果很完美。就当我测试最后一步作为服务注册中心功能时,微服务又报错:

1
com.alibaba.nacos.api.exception.NacosException: Client not connected, current status:STARTING

nginx反向代理配置:

1
2
3
4
5
6
7
8
9
10
11
12
upstream nacoscluster{
server 192.168.241.129:3333;
server 192.168.241.129:4444;
server 192.168.241.129:5555;
}
server{
listen 1111;
server_name 192.168.241.129;

location / {
proxy_pass http://nacoscluster;
}

微服务配置:

1
2
3
4
5
6
7
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: 192.168.241.129:1111

配置看起来和老师一模一样,那就还是版本新特性问题,其实Nacos2.x服务端是支持http2和grpc两种通信方式的,但是我们Maven中导入的客户端版本是2.1.0,client客户端在2.x版本中使用 grpc 调用,那显然我们使用nginx进行http代理是不靠谱的。

image-20230108161721489

解决方法一,使用http通信:

那第一种解决方法就是直接把客户端版本降低,替换成1.x版本,问题成功解决:

1
2
3
4
5
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.4.3</version>
</dependency>

但是,既然选择了Nacos2.x服务端,那最好还是用对应的2.x客户端,毕竟据官方宣传Nacos2.0的性能相较于1.x提升近10倍,2.x默认使用grpc调用通信原因就是grpc的低延迟和高吞吐量特性。

解决方法二,Nginx使用TCP代理:

官网有这样一句话使用VIP/nginx请求时,需要配置成TCP转发,不能配置http2转发,否则连接会被nginx断开。Nginx如何实现TCP转发有很多博文,这里就不写了,主要是需要添加一个插件–with-stream。

nginx添加配置(http的不用动,浏览器访问依旧需要):

1
2
3
4
5
6
7
8
9
10
11
stream{
upstream nacoscluster{
server 192.168.241.129:4333;
server 192.168.241.129:5444;
server 192.168.241.129:6555;
}
server{
listen 2111;
proxy_pass nacoscluster;
}
}

注意这个stream是和上面的http平级的。至于端口为什么比http里面的多1000,是因为我们上面提到的Nacos2.x客户端和服务端使用grpc调用,这里的端口是grpc端口,GRPC port = 主端口 + grpc端口偏移量,Nacos源码中设置偏移量默认1000。

微服务注册配置是不需要修改的,因为微服务注册配置的是主端口,所以还是1111。

1
2
3
4
5
6
7
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: 192.168.241.129:1111

最后,终于完成了Nacos2.2.0+Nginx实现Nacos集群的配置,成功将微服务注册:

image-20230108165109903

新版本选用问题

我在使用东西的时候总爱去使用一些较为新的版本,与老师使用的不同,很可能就会导致很多兼容问题。但是我感觉遇到这种问题通过自己一步步的解决可能比使用与老师一样的版本按部就班的要更有意义。因为在微服务时代,性能的优化是非常重要的,新版本肯定在性能上比旧版本有不小提升,就比如Nacos2.x比Nacos1.x有近10倍性能提升,但是新版本也不是随便使用的,你不能使用一个SpringCloudAlibaba低版本与最新的Nacos或者其他组件去搭建,那问题肯定会更多,还是要遵循官方的版本关系建议。

【最新敲简单】微信早安推送 +页面总控, JAVA版本,一键部署运行——保姆级教程

一、项目简介

​ 一个浪漫的早安推送小项目,采用Java+SpringBoot框架进行开发,SpringBoot内置tomcat使项目部署运行非常方便。此项目利用微信接口测试号实现了每天早上定时推送模板消息(天气+纪念日+每日一句,也可自己更改模板进行开发)。项目还添加了一个极简的前端页面,可以实现部署到服务器后不停止项目直接更改天气地址和直接点击立即发送。网上的使用Java开发此项目的非常少,也非常感谢这篇文章让我少绕了很多弯路,其实这个博主写的已经非常详细了,我的消息模板也是搬过来的这位博主的,此文就来丰富一下Java版本的选择吧。先来看几张结果:
![Alt](https://img-blog.csdnimg.cn/98929bdbf4cb42769a930cd0df26486c.png#pic_center#pic_center =300x400)在这里插入图片描述

二、本地克隆项目

​ 项目仓库在Gitee::link:https://gitee.com/li-haoaa/romantic-wechat-push,这里提供几种克隆方式:

1、直接打开链接,点击下载zip即可,然后解压缩,在idea或者eclipse里面打开项目就好了。

2、利用idea内嵌的组件,依次点击上方VCS、Get From Version Control,在弹框中粘贴上去上方的网址即可。不同版本idea基本一致。eclipse也有此功能,可直接网上搜。这里还是建议大家采用IDEA,在maven上有很大优势。
![在这里插入图片描述](https://img-blog.csdnimg.cn/bb47716806164a30b9f05b5e100ad8f2.png#pic_center =160x200)
3、打开\wechatPushing\src\main\resources\application.yml,配置文件如下图:我们接下来要填充下划线部分,记得把纪念日改了。
alt

三、申请各种接口

​ 简单介绍一下工作原理,你可以把刚才加载的项目理解为小的后台服务器,我们设定了机制,在每天设定的时间它会自动调用方法去第三方网站获取当日的天气和每日一句名言,没错,就是通过api调用。然后将请求过来的数据进行各种处理,转化成一个微信接口能听懂的语言发送过去,微信再进行模板处理把消息推送出去,我们这一步要做的就是申请这些第三方网站的api和微信接口api。

1、微信接口申请

  • 打开微信公众平台,扫码登录按照要求完成认证。之后得到这一步:
    在这里插入图片描述

appID和appsecret就是我们需要的填到上方提到的配置文件中。你可以简单理解为接口的账号和密码。

  • 然后你和要推送的对象(lover)扫码关注,下图中的微信号填到配置文件的receiver中,注意前面的-不要去掉,直接粘贴到双引号中即可。如果有第二个,就把下方的#删除直接粘贴即可。(如果你学过springboot就知道原因)

  • 在这里插入图片描述

  • 最后需要给你的微信添加消息模板,这里的模板是用的别人的,有想法的也可以自己修改。直接点击新增测试模板,把下面的复制进去,将生日名字改了就OK,这里面双括号包含的就是根据服务器发来的请求进行动态填充的部分,其他部分是静态不变的。你了解后可以充分想象自定义模板。然后把模板ID填到上面配置文件中的templateId中就好。
    在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{{first.DATA}}

城市:{{city.DATA}}

实况天气:{{weather.DATA}}
气温:{{minTemperature.DATA}} ~ {{maxTemperature.DATA}}
风速:{{wind.DATA}}
湿度:{{wet.DATA}}
今天~后天:{{day1_wea.DATA}},{{day2_wea.DATA}},{{day3_wea.DATA}}

♥在一起♥: {{togetherDate.DATA}}

距离XX生日:{{birthDate1.DATA}}
距离XX生日:{{birthDate2.DATA}}

{{note_En.DATA}}

{{note_Zh.DATA}}

2、天气API接口申请

点击这里跳转去申请,注册成功后会出现
在这里插入图片描述

把配置文件中的Weather模块appid和appsecret补充上去,把city进行修改,虽然提供了前端页面修改城市,但还是建议大家通过这里修改,因为api原因前端页面修改的有时会出现一些bug,我后期会考虑使用百度的天气api接口替换可以改善这个问题。

3、名言API接口申请

点击这里跳转注册登录,然后点击购买套餐,有一个免费的套餐,新用户也有一个1000次的可以免费购买。之后点击右上角头像,进入这个页面
在这里插入图片描述

复制下来Token,粘贴到配置文件中的名言警句token处,这个网站还提供了历史上的今天等很多实用的api,想要自己改消息模板的都可以使用。

四、项目测试

如果你本地jdk环境没有问题,idea也没有问题,那么你的项目现在就可以运行了,打开项目中的WechatPushingApplication,点击运行即可。
在这里插入图片描述

然后打开浏览器,输入“http://localhost:60001/”,只有你的项目运行起来这个界面才能访问进去。点击立即发送即可进行测试。如果你的微信能正常收到推送消息就说明没有问题了。
在这里插入图片描述

但是目前你只能手动点击发送,或者让项目在你的电脑上不停运行,每天定时自动推送,定时时间在MessageController中进行修改,下图是项目默认的每天7:30推送。三个数字分别是秒分时,其他的较为复杂的规则大家可以搜索@Scheduled用法。

在这里插入图片描述

五、云上部署运行

虽然已经成功,但是目前为止你必须手动点击发送或者让项目不停的在你的电脑上运行实现定时发送,这显然和你预期的“自动定时发送”不一致,要想实现自动定时发送需要我们将项目部署到云服务器上。

1、初始服务器

腾讯云特惠链接,趁着活动可以购买或者体验云服务器,建议选择轻量应用服务器,方便高效还便宜。镜像就选择宝塔Linux就好,宝塔面板自动搭建好非常的方便。
![在这里插入图片描述](https://img-blog.csdnimg.cn/87fcd8b3376e459fbdf1b9d0248dfe2f.png#pic_center =300x300)

等待几分钟初始化完成进入轻量应用服务器控制台点击你自己的实例就会到这个界面:
在这里插入图片描述

2、进入宝塔面板管理服务器

点击登录之后会一键登录到你的远程机,输入命令sudo /etc/init.d/bt default会获取到你的面板账号和密码
在这里插入图片描述

打开得到的外网面板地址,用账号和密码登录就进入了宝塔面板管理页面,第一次登录需要安装一些软件环境,根据指导安装即可。
在这里插入图片描述

然后进入软件商店,安装Java项目一键部署

在这里插入图片描述

然后从首页进入一键部署:

在这里插入图片描述

进去先安装tomcat8,虽然我们的springboot的jar包已经包含tomcat,我们在这里安装tomcat的目的是他会帮我们自动安装jdk8。
在这里插入图片描述

3、上传项目并部署

打开idea,右侧maven栏,选择clean和package然后点击上方运行进行打包。之后target中就会出现jar包,右键open in explore在文件夹中打开
![在这里插入图片描述](https://img-blog.csdnimg.cn/96e4552130e34b4486ee57a7186b0a68.png#pic =250x200)![在这里插入图片描述](https://img-blog.csdnimg.cn/d33fd2380f524d56a331b5806c7b5778.png#pic =250x200)

然后打开宝塔面板的文件,找到/www/wwwroot目录,将刚刚打好的jar包上传。
在这里插入图片描述

进入java一键部署页面,选择springboot,添加项目,选择好jdk,记住分配的端口号。(这里有时经常部署不上去,策略一:多试几次,策略二:复制执行命令,自己粘贴到服务器终端执行)
![在这里插入图片描述](https://img-blog.csdnimg.cn/bdd86a32baed4bd7a2f8ef96b7cff7ba.png#pic_center =500x400)

最后,进入腾讯云控制台,打开防火墙,将刚刚的端口添加进去即可,大功告成了。
在这里插入图片描述

这样就完成了自动推送,如果你想更改天气地址或者立即推送消息,进入:http://公网ip:端口号/,公网ip就是腾讯云控制台顶端的ip地址,端口号是上一步的端口号。这样你可以进入熟悉的页面。
在这里插入图片描述
在这里插入图片描述

六、项目完善

如果你有经验,完全可以丰富这个项目,比如说使用数据库系统,把每次推送记录进去,然后在前端页面做出推送记录模块。也可以自定义修改api,接入一些新的功能……

XDOJ-哈夫曼树、Huffman编码

一、问题描述

问题描述
假设用于通信的电文由n个字符组成,字符在电文中出现的频度(权值)为w1,w2,…,wn,试根据该权值序列构造哈夫曼树,并计算该树的带权路径长度。

输入说明
第1行为n的值,第2行为n个整数,表示字符的出现频度。

输出说明
输出所构造哈夫曼树的带权路径长度。

输入样例
8
7 19 2 6 32 3 21 10

输出样例
261

二、C代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include<stdio.h>
#include<stdlib.h>
typedef struct{
int weight;
int parent,lchild,rchild;
}HTNOTE,*HuffmanTree;
//typedef char** HuffmanCode;
void Select(HuffmanTree HT, int n, int* s1, int* s2);
void CreatHuffmanTree(HuffmanTree* HT,int *w,int n){
int i=0,s1=0,s2=0;
if(n<=1) return;
int m=2*n-1;
*HT=(HuffmanTree)malloc(sizeof(HTNOTE)*(m+1));
if(!*HT) exit(-1);
HuffmanTree adjust = NULL;
for(adjust=*HT+1,i=1;i<=n;i++,adjust++,w++){
adjust->weight=*w;
adjust->parent=0;
adjust->rchild=0;
adjust->lchild=0;
}
for(;i<=m;i++,adjust++){
adjust->weight=0;
adjust->parent=0;
adjust->rchild=0;
adjust->lchild=0;
}
for(i=n+1;i<=m;i++){
Select(*HT, i-1, &s1, &s2);
(*HT + s1)->parent = (*HT + s2)->parent = i;
(*HT + i)->lchild = s1;
(*HT + i)->rchild = s2;
(*HT + i)->weight = (*HT + s1)->weight + (*HT + s2)->weight;
}
}
int Min(HuffmanTree HT,int n){
int min=1000;
int flag;
for(int i=1;i<=n;i++){
if((HT+i)->weight<min&&(HT+i)->parent==0){
flag=i;
min=(HT+i)->weight;
}
}
(HT+flag)->parent=1;
return flag;
}
void Select(HuffmanTree HT, int n, int* s1, int* s2) {
*s1 = Min(HT, n);
*s2 = Min(HT, n);
}
int FindWay(HuffmanTree HT,int n,int *w){
int sum=0;
for(int i=1;i<=n;i++){
HuffmanTree q=HT+i;
int m=0;
while(q->parent!=0){
q=HT+q->parent;
m++;
}
sum+=m*w[i-1];
}
return sum;
}

int main(){
int n;
scanf("%d",&n);
int w[n];
for(int i=0;i<n;i++){
scanf("%d",&w[i]);
}
HuffmanTree HT;
CreatHuffmanTree(&HT,w,n);
int sum=FindWay(HT,n,w);
printf("%d",sum);
/*for(int i=1;i<=2*n-1;i++){
printf("%d ",(HT+i)->weight);
}*/
}

基于SSM的CRUD项目准备工作

​ 该项目是基于SSM的增删改查项目,前端淘汰了过时的jsp技术,通过Bootstrap+vue+Thymeleaf实现。完整项目下载链接

一、SSM文件配置

1、web.xml

  • 编码过滤器和处理请求方式过滤器
  • DispatcherServlet前端控制器(要引入SpringMVC.xml配置文件路径)
  • 引入Spring配置文件路径
  • Spring监听器ContextConfigLocation

2、SpringMVC.xml

  • 扫描控制层组件
  • 视图解析器Thymeleaf
  • 配置默认Servlet处理静态资源
  • 开启MVC注解驱动
  • 配置视图控制器

3、Spring.xml

  • 扫描除控制层以外组件
  • 配置数据源(先引用数据库配置文件)
  • 配置SqlSessionFactoryBean
  • 配置mapper接口的扫描

二、创建数据表使用逆向工程

1、创建数据表

1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE TABLE `tbl_stu` (
`stu_id` int NOT NULL AUTO_INCREMENT,
`stu_name` varchar(2295) DEFAULT NULL,
`gender` char(9) DEFAULT NULL,
`email` varchar(2295) DEFAULT NULL,
`d_id` int DEFAULT NULL,
PRIMARY KEY (`stu_id`)
)
CREATE TABLE `tbl_dept` (
`dept_id` int NOT NULL AUTO_INCREMENT,
`dept_name` varchar(765) DEFAULT NULL,
PRIMARY KEY (`dept_id`)
)

2、完成逆向工程,更改生成的mapper

​ 先完成generatorConfig.xml文件的配置,再使用逆向工程插件生成数据表对应的Pojo、Mapper和sql接口映射文件。因为自动生成的查询学生只能得到他所在的部门id,我们想让每次查询都能直接得到对应的部门信息,所以需要对自动生成的类进行一些修改,步骤如下:

  • Student类中添加Department属性
  • 原mapper接口中有方法

    1
    2
    List<Student> selectByExample(StudentExample example);
    Student selectByPrimaryKey(Integer stuId);

​ 我们再添加两个:

1
2
 List<Student> selectByExampleWithDept(StudentExample example);
Student selectByPrimaryKeyWithDept(Integer stuId);
  • StudentMapper.xml中进行修改,其实都是复制原有自动生成的,再进行稍微修改实现分步查询。
1
2
3
4
5
6
7
8
9
10
11
12
13
  <!--带上部门后的resultMap-->
<resultMap id="BaseResultMapWithDept" type="com.guoliang.ssm.pojo.Student" >
<id column="stu_id" property="stuId" jdbcType="INTEGER" />
<result column="stu_name" property="stuName" jdbcType="VARCHAR" />
<result column="gender" property="gender" jdbcType="CHAR" />
<result column="email" property="email" jdbcType="VARCHAR" />
<result column="d_id" property="dId" jdbcType="INTEGER" />
<association property="department"
select="com.guoliang.ssm.mapper.DepartmentMapper.selectByPrimaryKey"
column="d_id"></association>
</resultMap>
<select id="selectByExampleWithDept" resultMap="BaseResultMapWithDept" parameterType="com.guoliang.ssm.pojo.StudentExample" >
<select id="selectByPrimaryKeyWithDept" resultMap="BaseResultMapWithDept" parameterType="java.lang.Integer" >

这是学习JavaWeb过程中和以往相比做的比较复杂的小项目,是通过tymeleaf进行渲染的B/S(浏览器/服务器)小项目,课程链接:尚硅谷丨2022版JavaWeb教程(全新技术栈,全程实战),尚硅谷,yyds!!!
由于不想花过多的时间在前端上,所以HTML/CSS/JavaScript页面模板大多直接使用的课程资料,就自己模仿着做了一个添加日志和注册的页面。听视频介绍这个项目的主要目的就是手撕简约版Spring MVC框架,为了后面学习框架相对轻松一点。第一次实现了数据库、后台程序、前端页面的交互,还是很有意思,所以写下来理清自己的思路,为了自己更好的理解,也欢迎大家纠错更正。

一、开发环境:

jdk8+MySQL8+tomcat8.5(课程中用的数据库是MySQL5,由于版本不同有些细节不同也耽误了不少时间,下面会提到)

二、流程图:

在这里插入图片描述

三、主要模块

监听器和IOC容器:

一旦监听到服务器启动,开始调用BeanFactory创建容器,通过读取配置文件中的标签内容,将后续的Controller类、Service类、DAO类实例化保存。并且建立依赖关系,比如说操作日志相关内容的控制器TopicController里面用到了TopicService,那么直接将刚刚实例化的TopicService赋值到TopicController中,这就是“控制反转”和“依赖注入”。以往我们写程序,一个对象里面需要另一个对象我们就让前者自己直接new出来,但是这样会导致对象依赖和耦合严重,不利于代码维护。为了实现“高内聚,低耦合”的架构,我们现在把创建对象的权利全部交给第三方,即“控制反转”,然后再通过配置文件中的信息进行“依赖注入”,达到解除耦合的目的。

DispatcherServlet:

作为核心响应调度,它的工作是拦截获取到浏览器的(A.do?opetate=B)请求,通过字符串处理,在IOC容器中查找到处理该A请求的指定Controller类,再通过反射找到B方法,进行方法参数赋值调用方法。

把MVC中的V即“view‘也放在dispatcherServlet中,控制器进行操作后需要给dispatcherServlet返回一个字符串,dispatcherServlet通过该字符串判断下一步工作,是继续调用其他控制器,还是直接返回页面给浏览器。

Controller、Service、DAO:

Controller作为控制器提供一些方法供浏览器选择,比如说浏览器端需要执行日志(Topic)添加工作,那需要调用TopicController中的addTopic()方法,再比如需要执行登录操作,当用户点击“登录”按键时浏览器就调用了UserBasicController(UserBasic是用户信息类,登录时需要查询用户信息,所以调用它的Controller)中的login方法。Service就是业务方法,比如说Controller现在整理好了浏览器发来的日志添加内容,需要保存到数据库了,它就调用相应的Service方法,Service再看看有什么需要封装整理的,然后再调用DAO方法,至于DAO就是JDBC中的内容,应该很熟悉了。

四、遇到的主要问题

1、项目关联包问题

异常:java.lang.IllegalStateException: 启动子级时出错

注意操作顺序!一定要先关联上lib下的各种驱动包、添加上tomcat、然后再打包成Artifacts进行部署。刚开始就遇到了找不到MySQL驱动器的问题,原因是将新建的项目一开始就打包成了Artifacts,然后再关联,这样的话Artifacts进行部署时里面当然没有我们关联的驱动。出现这样的问题解决很简单,不需要你删除Artifacts后再添加,只需要Project Structer下面的problems就可以解决。

2、类加载器问题

异常:java.lang.NullPointerException
at java.util.Properties$LineReader.readLine(Properties.java:434)
at java.util.Properties.load0(Properties.java:353)
at java.util.Properties.load(Properties.java:341)

这个问题我解决了好久,抛出的异常就是在获取数据库链接的时候无法读到你的properties配置文件,无法获取链接。就是这一句出了问题:InputStream stream=ClassLoader.getSystemClassLoader().getResourceAsStream(“jdbc.properties”);

于是我做了个单元测试,在idea里面明明可以获取链接,但是到tomcat服务器上就不行。然后我在评论区下发现了一个留言,需要改成:InputStream stream =JDBCUtils.class.getClassLoader().getResourceAsStream(“jdbc.properties”),我一试果然成功,为什么出现这样的情况呢,说到底是路径问题:

你可以尝试在idea下和在tomcat环境下分别执行以上两句,你会发现在idea下两个得到的路径是相同的,都是当前项目的src下:

1
2
System.out.println(JDBCUtils.class.getClassLoader().getResource(""));
System.out.println(ClassLoader.getSystemClassLoader().getResource(""));

在这里插入图片描述
但是同样的代码在tomcat调用时:
在这里插入图片描述

会发现InputStream stream =JDBCUtils.class.getClassLoader().getResourceAsStream(“jdbc.properties”) 调用的路径是artifacts打包后的“src”目录,是正确的。而InputStream stream=ClassLoader.getSystemClassLoader().getResourceAsStream(“jdbc.properties”);得到的是null。

3、LocalDateTime类转化问题

异常:java.lang.IllegalArgumentException: Can not set java.util.Date field com.guoliang.qqzone.pojo.Topic.topicDate to java.time.LocalDateTime

当调用DAO时一旦发现异常can not set A to B,就是你不能把B类型赋值给A。

视频中老师讲的时候是没有这个问题的,因为老师用的是MySQL5,这个问题应该是MySQL8驱动引入的。该项目数据库中的Date全部是DateTime,对应的Java中是LocalDateTime或者TimeStamp,而我们Java中设定的全部是Date。

解决方法:1、将pojo类中的Date全部转化为LocalDateTime

​ 2、BaseDAO中加上以下代码进行转化

1
2
3
4
//LocalDateTime需要转化
if (propertyValue.getClass().toString().equals("class java.time.LocalDateTime")) {
propertyValue = Timestamp.valueOf((LocalDateTime) propertyValue);
}

4、tymeleaf渲染问题

写好的前端页面展示出来发现与我们所想不一样,这个问题老师也遇到了,再上一个项目中老师一直没有发现,但是这个项目老师成功解决了这个问题。问题根源在于既然使用了tymeleaf,你就不能直接调用网页,必须经过DispactureServlet中的视图模块进行调用渲染,否则你页面上的所有“th:”都无法识别。

六、部分结果展示:

登录
主页面
回复
添加日志

信号量实现进程同步

【实验目的】

进程同步是操作系统多进程/多线程并发执行的关键之一,进程 同步是并发进程为了完成共同任务采用某个条件来协调他们的活 动,这是进程之间发生的一种直接制约关系。本次试验是利用信号量进行进程同步。

【实验软硬件环境】

Linux,gcc

【实验内容】

生产者进程生产产品,消费者进程消费产品。当生产者进程生产产品时,如果没有空缓冲区可用,那么生产者进程必须等待消费者进程释放出一个缓冲区。当消费者进程消费产品时,如果缓冲区中没有产品,那么消费 者进程将被阻塞,直到新的产品被生产出来。

【实验程序及分析】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <time.h>
#include <stdlib.h>
#define MAXSEM 5
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
};
int fullid, emptyid,mutxid; //f 已有产品量 e 空余量
int main(){
struct sembuf P,V;
union semun arg;
//声明共享主存array, sum, get, set
int *array;
int *sum;
int *get;
int *set;
//将array,sum,set,get映射到共享主存
array =(int *)mmap(NULL,sizeof(int)*MAXSEM,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
sum=(int *)mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
set=(int *)mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
get=(int *)mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
//创建信号量fullid, emptyid,mutxid
fullid=semget(IPC_PRIVATE,1,IPC_CREAT|0666);
emptyid=semget(IPC_PRIVATE,1,IPC_CREAT|0666);
mutxid=semget(IPC_PRIVATE,1,IPC_CREAT|0666);
//初始化信号量
arg.val=0;
if(semctl(fullid,0,SETVAL,arg)==-1)perror("semctl setval error");
arg.val=MAXSEM;
if(semctl(emptyid,0,SETVAL,arg)==-1)perror("semctl setval error");
arg.val=1;
if(semctl(mutxid,0,SETVAL,arg)==-1)perror("semctl setval error");
//初始化P,V操作
V.sem_num=0;
V.sem_op=1;
V.sem_flg=SEM_UNDO;
P.sem_num=0;
P.sem_op=-1;
P.sem_flg=SEM_UNDO;
//创建生产者进程
pid_t fpid1=fork();
if(fpid1==0){
int i=0;
while(i<100){
//对emptyid ,mutxid执行P操作
semop(emptyid,&P,1);
semop(mutxid,&P,1);
array[*(set)%MAXSEM] = i+1; //生产产品
(*set)++;
printf("produce %d\n",i+1);
//对emptyid, mutxid执行V操作
semop(mutxid,&V,1);
semop(fullid,&V,1);
i++;
}
//休眠一段时间
sleep(1);
//打印生产者结束
printf("producer is over\n");
exit(0);
}else{
//创建消费者进程
pid_t fpid2=fork();
if(fpid2==0){
//消费者进程A
while(1){
//对fuild, mutxid执行P操作
semop(fullid,&P,1);
semop(mutxid,&P,1);
if(*get == 100)
break;
*sum += array[(*get)%MAXSEM];
printf("the comsumer A get number %d\n", array[(*get)%MAXSEM]);
(*get) ++;
if(*get == 100)
{printf("the sum is %d\n", *sum);
break;
}
//对emptyid, mutxid执行V操作
semop(mutxid,&V,1);
semop(emptyid,&V,1);
sleep(0.5);
}
printf("consumer A is over\n");
exit(0);
}else{
//消费者进程B
while(1){
//对fuild, mutxid执行P操作
semop(fullid,&P,1);
semop(mutxid,&P,1);
if(*get == 100)
break;
*sum += array[(*get)%MAXSEM];
printf("the comsumer B get number %d\n", array[(*get)%MAXSEM]);
(*get) ++;
if(*get == 100)
{printf("the sum is %d\n", *sum);
break;
}
//对emptyid, mutxid执行V操作
semop(mutxid,&V,1);
semop(emptyid,&V,1);
sleep(0.5);
}
printf("consumer B is over\n");
exit(0);
}

}

}

【程序分析】:

本次实验创建了一个生产者进程和两个消费者进程,主要是通过共享内存机制通过进行PV操作完成,设置了信号量fullid, emptyid,mutxid来实现对生产者,消费者的操作。生产者总共需要生产100个产品,消费者取出产品并进行输出产品编号。

​ 数码圈流传着这样一句话:“你可以不喜欢小米,但是你没有理由不钦佩雷军”。这位每次出现在大众视野基本都是穿着牛仔裤和衬衫标准”程序员“搭配的”雷布斯“,不仅担任着数家上市公司董事长,还是一位极具前瞻性的天使投资人,已经成为程序员天花板。

“有人说我写的代码 如诗一样优雅”

​ 这句话是雷军在一段访谈中对自己的称赞,作为程序员出身的雷军对自己的编程能力还是蛮自信的,事实究竟如何?有人扒出了一段雷军在1994年写的一段完整的汇编代码贴在了网上,无数网友回复”跪着读完“。这一段高质量的代码就算在今天也少有人能写的出来。

​ 1987年雷军进入武汉大学计算机系,他仅使用了两年的时间完成了所有大学内容和毕业设计。大学期间写过的很多代码早已被编入武汉大学的教材……在大学期间他就开始写软件出售来赚取利润。大四那年他开始了人生的第一次创业,仿制金山汉卡。然而他的第一次创业以失败告终,破产清算时仅仅获得了一台电脑。别急,传奇才刚刚开始。

扛起中国软件的大旗——金山

​ 谁也没想到,最初靠仿制金山赚钱的雷军一眼被“中国第一程序员”——求伯君看中,力邀他加入金山,几年的时间内,雷军就从一个新员工成为了金山总裁。当时金山面临的最大对手就是微软,微软的office利用与操作系统捆绑的优势成为后起之秀,抢占了WPS的大部分市场。雷军年少轻狂,带领团队做出了新一代产品”盘古“,里面融合了文字处理、词典、事务管理等多种功能,本想凭借这一产品重回巅峰,但惨淡的销售量给了雷军闷头一棒。

​ 这一盆冷水浇得雷军醒不过来,最后他递交了辞职信。但是求伯君极力挽留,只给他休假。半年后,雷军重回岗位,他这次回来显然已经制定好了转型战略,之后新版WPS强势上场,在微软的歼灭战中夹缝生存,重新赢得了不少的市场占有率。

​ 直到2007年,金山上市,雷军为这一刻付出了十几年的心血。金山无疑是中国软件的一面旗帜,在遭遇国外产品不公平竞争的时刻依旧逆势而上,并且获得了不菲的成绩!

“所有产品不超过5%的利润”——小米

​ 就在金山上市后两个月,雷军宣布离职,这让很多人不能理解。接下来的时间,雷军开始专注自己的投资事业,同时,雷军似乎拥有感应商业机会的本能,他在等待拥抱一个全新时代的到来——移动互联时代。

​ 在iPhone开始风靡全球的同时,他在思考安卓的开放性是不是更具有可玩性。他找到投资人、“同盟者”,向他们描述他大胆的想法。2010年四月,在北京一家小工作室里,十几个人喝了一锅小米粥,小米诞生。

​ 2011年,小米第一部手机发布,豪华的配置,1999的价格,赢得了一众好评,出现了一机难求的场面。2013年,红米首款手机发布,以极低的价格终结了山寨机泛滥的市场。

​ 2019年,小米成为了世界最年轻的500强,并且具有了完整的生态链体系。

​ 如今,雷军已经开启了自己人生的最后一次伟大创业——小米汽车,我们无法预测结果如何,但可以肯定的是雷军早已成为中国科技界的“传奇”!这位向往纯粹技术的工程师在为实现“科技走进千家万户”而努力。

因为简单纯粹,所以无所畏惧;因为无所畏惧,所以一往无前!——范海涛

​ 之所以要写Intel,是因为近日计组课涉及了指令集的内容,就忽然想到了《浪潮之巅》中对Intel的介绍。如果你有读过《浪潮之巅》,那么你没有必要浪费时间阅读这篇关于Intel的介绍,因为这篇介绍的大部分内容在这本书中都包含。如果你没有读过,那么也强烈希望你能在读完这篇介绍后阅读一下这本书,尤其是计算机从业者。

历史必然选择

​ 1968年摩尔和诺伊斯创办了英特尔公司,就是提出摩尔定律(每18个月集成电路复杂度会翻倍,有机会再细讲)的那个男人。起初的英特尔只是一个小婴儿,因为IBM(当时最大的计算机厂商之一)等计算机制造公司都是自己设计处理器,而英特尔设计的都是一些低端的微型处理器。70年代末,英特尔设计出了处理器8086.

​ 由于IBM公司当时为了与苹果快速抢占个人计算机(PC)市场,就索性直接采用了英特尔的8086,这一用直接奠定了英特尔的“不可撼动”的地位。原因就是IBM的PC机销售远超预期。之后兼容机(其实就是组装机啦)由于价格低廉也有不错的销量,而这些兼容机毫无疑问必须选择8086处理器和Windows才能实现兼容的目的。所以时至今日,IBM已经不再生产PC,但是英特尔和微软的地位无法撼动。

​ 再后来,英特尔也走上了高端路线,设计了奔腾系、酷睿系、牙膏系(狗头)……

指令集的战争

​ 这部分才是这篇想主要介绍的东西,可以作为课程知识的背景。

​ 先简要了解一下什么是指令集:就是CPU中用来计算和控制计算机系统的一套指令的集合。再了解一下两大指令集CISC(复杂指令集)和RISC(精简指令集)的区别,来自计组课本:

  1. 指令系统 CISC数量多,复杂;RISC数量少,精简。

  2. 使用频率 CISC相差大;RISC 接近。毕竟RISC就是选取了使用频率高的指令。

  3. CUP功耗 CISC高;RISC低。

    ……

​ 简单对比后就算你不知道什么是指令集你也一定会说RISC比较好。那么恭喜你和当时众多学者想法一样,未来将是RISC的天下。
​ 于是一大堆企业转战RISC,其中包括一些原本只是采购而自己不研发的企业开始自己研发RISC处理器,谁都想在风口上飞起来。英特尔也感受到了危机,但是做出了最正确的选择。英特尔继续坚持自己在基于CISC指令集的CPU上的领先地位,又开始着手RISC指令。之后英特尔开发出来几款RISC产品,但是都不成功。而RISC派这边,基本处于各自为战的状态,群龙无首,做出的机器兼容性可想而知。虽然RISC具有天然的性能优势,但是英特尔已经将自己的CISC优化的非常不错,但是绝大部分消费者在稳定兼容和性能稍稍领先上选择前者。
​ 这里还有一个小故事,在RISC提出之前。有一家企业可以和英特尔抗衡——摩托罗拉。当时IBM选择了英特尔,而苹果选择了摩托罗拉。但是RISC指令提出后由于摩托罗拉做出了错误的决定再加上管理层问题,丢失了以前的大部分订单,基本上放弃了CISC转战RISC,直到最后一棵稻草苹果也放弃它。
​ 虽然那个年代的指令集之争英特尔大获全胜,但是新世纪以来,由于智能手机的快速发展,基于ARM架构(采用RISC指令集)的手机处理器也换代发展迅速。当某一架构的硬件性能达到极限时,只能尝试改变,于是最近英特尔也正在进军RISC-V,毕竟RISC具有天然性能优势。

芯片帝国

​ 英特尔毫无疑问已经发展成为了一个芯片帝国,世界上超一半计算机都在采用英特尔芯片。它的成功离不开决策、时机、运气。英特尔历史上唯一的错误决定就是看轻了智能手机时代,将手机芯片拱手相让,错失了一次绝佳良机。现在好似又出现一家看似能和英特尔较量的公司——AMD。但是两者其实根本不在一个水平上,AMD为了抢占英特尔市场也只能跟在英特尔身后做出兼容芯片,起码我们消费者又多了一个非常不错的选择。

问题输入

一组数据,输入数据第1行为两个正整数m和n,m表示迷宫高度,n表示迷宫宽度,m<100,n<100;第2行为两个整数,分表表示起点的行列位置;第3为两个整数,分别表示终点的行列位置;其后为m行数据,每行n个整数,表示迷宫对应位置的状态,0表示通路,1表示障碍。

问题输出

以三元组形式(见P105)输出从起点到终点搜索到的第一条通路,没有则输出no

输入样例

8 8

1 1

8 8

0 0 1 0 0 0 1 0

0 0 1 1 0 0 1 0

0 0 0 0 1 1 0 0

0 1 1 1 0 0 0 0

0 0 0 1 1 0 0 0

0 1 0 0 0 1 0 0

0 1 1 1 0 1 1 0

1 1 0 0 0 0 0 0

输出样例

(1,1,1),(1,2,2),(2,2,2),(3,2,3),(3,1,2),(4,1,2),(5,1,1),(5,2,1),(5,3,2),(6,3,1),(6,4,1),(6,5,2),(7,5,2),(8,5,1),(8,6,1),(8,7,1),(8,8,1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#include<stdio.h>
#include<stdlib.h>
#define Maxsize 10000
int **map;
typedef struct{
int x;
int y;
int di;
}seat;
typedef struct{
seat *base;
seat *top;
int size;
}sqStack;
int InitStack(sqStack *S){
S->base=(seat *)malloc(sizeof(seat)*Maxsize);
if(!S->base) exit(0);
S->size=Maxsize;
S->top=S->base;
return 1;
}
int Push(sqStack *S,seat a){
*S->top++=a;
return 1;
}
int GetTop(sqStack *S,seat *a){
if(S->base==S->top) return 0;
else *a=*--S->top;
return 1;
}
void Map(int x,int y){
map=(int **)malloc(sizeof(int *)*x);
for(int i=0;i<x;i++){
map[i]=(int *)malloc(sizeof(int)*y);
}
for(int i=0;i<x;i++){
map[i][0]=1;
map[i][y-1]=1;
}
for(int j=0;j<y;j++){
map[0][j]=1;
map[x-1][j]=1;
}
for(int i=1;i<x-1;i++){
for(int j=1;j<y-1;j++){
scanf("%d",&map[i][j]);
}
}

}

int findPath(int x1,int y1,int x2,int y2){
sqStack q;
InitStack(&q);
seat a,b,c;
int d,m,n,f;
a.x=x1;
a.y=y1;
map[x1][y1]=-1;
a.di=0;
Push(&q,a);

while(q.top!=q.base){
if((q.top-1)->x==x2&&(q.top-1)->y==y2) break;
d=(q.top-1)->di;
f=0;
while(d<=4){
d++;
switch(d){
case 1: m=(q.top-1)->x; n=(q.top-1)->y+1; break;
case 2: m=(q.top-1)->x+1; n=(q.top-1)->y; break;
case 3: m=(q.top-1)->x; n=(q.top-1)->y-1; break;
case 4: m=(q.top-1)->x-1; n=(q.top-1)->y; break;
}
if(map[m][n]==0){
map[m][n]=-1;
(q.top-1)->di=d;
b.x=m;
b.y=n;
b.di=0;
Push(&q,b);
f=1;
break;
}

}
if(f==0){
GetTop(&q,&c);
map[c.x][c.y]=3;
}
}
if((q.top-1)->x==x2&&(q.top-1)->y==y2){
for(seat *i=q.base;i<(q.top-1);i++){
printf("(%d,%d,%d),",i->x,i->y,i->di);
}
printf("(%d,%d,%d)",(q.top-1)->x,(q.top-1)->y,1);
return 0;
}
else printf("no");
}
int main(){
int X,Y,ax,ay,bx,by;
scanf("%d %d %d %d %d %d",&X,&Y,&ax,&ay,&bx,&by);
Map(X+2,Y+2); //初始化迷宫,把迷宫四周围上1;
findPath(ax,ay,bx,by);
}

问题输入
一组数据,数据为一个字符串,表示一个待翻译的字符串。
问题输出
将字符串按规则翻译后输出。
输入样例
B(pxyzABhij)B
输出样例
tsaedsaepjpiphptsaedsaepsaepzpypxptsaedsae

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define maxsize 100
typedef struct{
char *base;
char *top;
int size;
}sqStack;
//构造栈
int InitStack(sqStack *S){
S->base=(char *)malloc(sizeof(char)*maxsize);
if(!S->base) exit(0);
S->size=maxsize;
S->top=S->base;
return 1;
}
//出栈
int GetTop(sqStack *S,char *c){
if(S->top==S->base) return 0;
else *c=*--S->top;
return 1;
}
//入栈
int Push(sqStack *S,char c){
*S->top++=c;
return 1;
}
typedef struct QNode * LNode;
struct QNode{
char data;
LNode next;
};
typedef struct{
LNode front; //队头指针
LNode rear; //队尾指针
}LinkQueue;
//构造队列
int InitQueue(LinkQueue *Q){
Q->front=Q->rear=(LNode)malloc(sizeof(struct QNode));
Q->front->next=NULL;
return 1;
}
//入队
int PushQueue(LinkQueue *Q,char c){
LNode p=(LNode)malloc(sizeof(struct QNode));
p->data=c;
p->next=NULL;
Q->rear->next=p;
Q->rear=p;
return 1;
}
//出队
int GetQueue(LinkQueue *Q,char *c){
if(Q->rear==Q->front) return 0;
LNode p=Q->front->next;
*c=p->data;
Q->front->next=p->next;
if(Q->rear==p) Q->rear=Q->front;
return 1;
}
int main(){
char st[1000],a;
scanf("%s",st);
//puts(st);
sqStack S1,S2;
LinkQueue L;
InitQueue(&L);
InitStack(&S1);
InitStack(&S2);
//将字符串从右向左依次入栈
for(int i=(strlen(st)-1);i>=0;i--){
Push(&S1,st[i]);
}
//处理括号内容
while(S1.base!=S1.top){
char r;
GetTop(&S1,&r);
if(r!=')'){
Push(&S2,r);
}
else if(r==')'){
char C;
GetTop(&S2,&a);
while(a!='('){
PushQueue(&L,a);
C=a;
GetTop(&S2,&a);
}
while(L.front->next!=L.rear){
char b;
GetQueue(&L,&b);
Push(&S2,C);
Push(&S2,b);
}
Push(&S2,C);
}

}
//B和A转化
while(S2.base!=S2.top){
char n;
char A[4]="sae";
char B[9]="tsaedsae";
GetTop(&S2,&n);
if(n!='A'&&n!='B'){
Push(&S1,n);
}
else if(n=='A'){
for(int i=2;i>=0;i--){
Push(&S1,A[i]);
}
}
else if(n=='B'){
for(int i=7;i>=0;i--){
Push(&S1,B[i]);
}
}
}
char m;
while(S1.base!=S1.top){
GetTop(&S1,&m);
printf("%c",m);
}
}

主要是考察栈和队列,建议画图解决。