前端技术
HTML
CSS
Javascript
前端框架和UI库
VUE
ReactJS
AngularJS
JQuery
NodeJS
JSON
Element-UI
Bootstrap
Material UI
服务端和客户端
Java
Python
PHP
Golang
Scala
Kotlin
Groovy
Ruby
Lua
.net
c#
c++
后端WEB和工程框架
SpringBoot
SpringCloud
Struts2
MyBatis
Hibernate
Tornado
Beego
Go-Spring
Go Gin
Go Iris
Dubbo
HessianRPC
Maven
Gradle
数据库
MySQL
Oracle
Mongo
中间件与web容器
Redis
MemCache
Etcd
Cassandra
Kafka
RabbitMQ
RocketMQ
ActiveMQ
Nacos
Consul
Tomcat
Nginx
Netty
大数据技术
Hive
Impala
ClickHouse
DorisDB
Greenplum
PostgreSQL
HBase
Kylin
Hadoop
Apache Pig
ZooKeeper
SeaTunnel
Sqoop
Datax
Flink
Spark
Mahout
数据搜索与日志
ElasticSearch
Apache Lucene
Apache Solr
Kibana
Logstash
数据可视化与OLAP
Apache Atlas
Superset
Saiku
Tesseract
系统与容器
Linux
Shell
Docker
Kubernetes
[Flink时间窗口在推荐系统中的应用 ]的搜索结果
这里是文章列表。热门标签的颜色随机变换,标签颜色没有特殊含义。
点击某个标签可搜索标签相关的文章。
点击某个标签可搜索标签相关的文章。
Ruby
...编程就是让程序在同一时间执行多个任务。在Ruby中,我们可以用线程(Thread)来实现这一点。比如说啊,你正在倒腾一堆数据的时候,完全可以把它切成一小块一小块的,然后让每个线程去负责一块,这样一来,效率直接拉满,干活儿的速度蹭蹭往上涨! 但是,问题来了:并发编程虽然强大,但它并不是万能药。哎呀,经常会有这样的情况呢——自个儿辛辛苦苦改代码,还以为是在让程序变得更好,结果一不小心,又给它整出了新麻烦,真是“好心办坏事”的典型啊!接下来,我们来看几个具体的例子。 --- 3. 示例一 共享状态的混乱 场景描述: 假设你正在开发一个电商网站,需要统计用户的购买记录。你琢磨着干脆让多线程上阵,给这个任务提速,于是打算让每个线程各管一拨用户的活儿,分头行动效率肯定更高!看起来很合理对不对? 问题出现: 问题是,当你让多个线程共享同一个变量(比如一个全局计数器),事情就开始变得不可控了。Ruby 的线程可不是完全分开的,这就有点像几个人共用一个记事本,大家都能随便写东西上去。结果就是,这本子可能一会儿被这个写点,一会儿被那个划掉,最后你都不知道上面到底写了啥,数据就乱套了。 代码示例: ruby 错误的代码 counter = 0 threads = [] 5.times do |i| threads << Thread.new do 100_000.times { counter += 1 } end end threads.each(&:join) puts "Counter: {counter}" 分析: 这段代码看起来没什么问题,每个线程都只是简单地增加计数器。但实际情况却是,输出的结果经常不是期望的500_000,而是各种奇怪的数字。这就好比说,counter += 1 其实不是一步到位的简单操作,它得先“读一下当前的值”,再“给这个值加1”,最后再“把新的值存回去”。问题是,在这中间的每一个小动作,都可能被别的线程突然插队过来捣乱! 解决方案: 为了避免这种混乱,我们需要使用线程安全的操作,比如Mutex(互斥锁)。Mutex可以确保每次只有一个线程能够修改某个变量。 修正后的代码: ruby 正确的代码 require 'thread' counter = 0 mutex = Mutex.new threads = [] 5.times do |i| threads << Thread.new do 100_000.times do mutex.synchronize { counter += 1 } end end end threads.each(&:join) puts "Counter: {counter}" 总结: 这一段代码告诉我们,共享状态是一个雷区。如果你非要用共享变量,记得给它加上锁,不然后果不堪设想。 --- 4. 示例二 死锁的诅咒 场景描述: 有时候,我们会遇到更复杂的情况,比如两个线程互相等待对方释放资源。哎呀,这种情况就叫“死锁”,简直就像两只小猫抢一个玩具,谁都不肯让步,结果大家都卡在那里动弹不得,程序也就这样傻乎乎地停在原地,啥也干不了啦! 问题出现: 想象一下,你有两个线程,A线程需要获取锁X,B线程需要获取锁Y。想象一下,A和B两个人都想打开两把锁——A拿到了锁X,B拿到了锁Y。然后呢,A心想:“我得等B先把他的锁Y打开,我才能继续。”而B也在想:“等A先把她的锁X打开,我才能接着弄。”结果俩人就这么干等着,谁也不肯先放手,最后就成了“死锁”——就像两个人在拔河,谁都不松手,僵在那里啥也干不成。 代码示例: ruby 死锁的代码 lock_a = Mutex.new lock_b = Mutex.new thread_a = Thread.new do lock_a.synchronize do puts "Thread A acquired lock A" sleep(1) lock_b.synchronize do puts "Thread A acquired lock B" end end end thread_b = Thread.new do lock_b.synchronize do puts "Thread B acquired lock B" sleep(1) lock_a.synchronize do puts "Thread B acquired lock A" end end end thread_a.join thread_b.join 分析: 在这段代码中,两个线程都在尝试获取两个不同的锁,但由于它们的顺序不同,最终导致了死锁。运行这段代码时,你会发现程序卡住了,没有任何输出。 解决方案: 为了避免死锁,我们需要遵循“总是按照相同的顺序获取锁”的原则。比如,在上面的例子中,我们可以强制让所有线程都先获取锁A,再获取锁B。 修正后的代码: ruby 避免死锁的代码 lock_a = Mutex.new lock_b = Mutex.new thread_a = Thread.new do [lock_a, lock_b].each do |lock| lock.synchronize do puts "Thread A acquired lock {lock.object_id}" end end end thread_b = Thread.new do [lock_a, lock_b].each do |lock| lock.synchronize do puts "Thread B acquired lock {lock.object_id}" end end end thread_a.join thread_b.join 总结: 死锁就像一只隐形的手,随时可能掐住你的喉咙。记住,保持一致的锁顺序是关键! --- 5. 示例三 不恰当的线程池 场景描述: 线程池是一种管理线程的方式,它可以复用线程,减少频繁创建和销毁线程的开销。但在实际使用中,很多人会因为配置不当而导致性能下降甚至崩溃。 问题出现: 假设你创建了一个线程池,但线程池的大小设置得不合理。哎呀,这就好比做饭时锅不够大,菜都堆在那儿煮不熟,菜要是放太多呢,锅又会冒烟、潽得到处都是,最后饭也没做好。线程池也一样,太小了任务堆成山,程序半天没反应;太大了吧,电脑资源直接被榨干,啥事也干不成,还得收拾烂摊子! 代码示例: ruby 线程池的错误用法 require 'thread' pool = Concurrent::FixedThreadPool.new(2) 20.times do |i| pool.post do sleep(1) puts "Task {i} completed" end end pool.shutdown pool.wait_for_termination 分析: 在这个例子中,线程池的大小被设置为2,但有20个任务需要执行。哎呀,这就好比你请了个帮手,但他一次只能干两件事,其他事儿就得排队等着,得等前面那两件事儿干完了,才能轮到下一件呢!这种情况下,整个程序的执行时间会显著延长。 解决方案: 为了优化线程池的性能,我们需要根据系统的负载情况动态调整线程池的大小。可以使用Concurrent::CachedThreadPool,它会根据当前的任务数量自动调整线程的数量。 修正后的代码: ruby 使用缓存线程池 require 'concurrent' pool = Concurrent::CachedThreadPool.new 20.times do |i| pool.post do sleep(1) puts "Task {i} completed" end end sleep(10) 给线程池足够的时间完成任务 pool.shutdown pool.wait_for_termination 总结: 线程池就像一把双刃剑,用得好可以提升效率,用不好则会成为负担。记住,线程池的大小要根据实际情况灵活调整。 --- 6. 示例四 忽略异常的代价 场景描述: 并发编程的一个常见问题是,线程中的异常不容易被察觉。如果你没有妥善处理这些异常,程序可能会因为一个小错误而崩溃。 问题出现: 假设你有一个线程在执行某个操作时抛出了异常,但你没有捕获它,那么整个线程池可能会因此停止工作。 代码示例: ruby 忽略异常的代码 threads = [] 5.times do |i| threads << Thread.new do raise "Error in thread {i}" if i == 2 puts "Thread {i} completed" end end threads.each(&:join) 分析: 在这个例子中,当i == 2时,线程会抛出一个异常。哎呀糟糕!因为我们没抓住这个异常,程序直接就挂掉了,别的线程啥的也别想再跑了。 解决方案: 为了防止这种情况发生,我们应该在每个线程中添加异常捕获机制。比如,可以用begin-rescue-end结构来捕获异常并进行处理。 修正后的代码: ruby 捕获异常的代码 threads = [] 5.times do |i| threads << Thread.new do begin raise "Error in thread {i}" if i == 2 puts "Thread {i} completed" rescue => e puts "Thread {i} encountered an error: {e.message}" end end end threads.each(&:join) 总结: 异常就像隐藏在暗处的敌人,稍不注意就会让你措手不及。学会捕获和处理异常,是成为一个优秀的并发编程者的关键。 --- 7. 结语 好了,今天的分享就到这里啦!并发编程确实是一项强大的技能,但也需要谨慎对待。大家看看今天这个例子,是不是觉得有点隐患啊?希望能引起大家的注意,也学着怎么避开这些坑,别踩雷了! 最后,我想说的是,编程是一门艺术,也是一场冒险。每次遇到新挑战,我都觉得像打开一个神秘的盲盒,既兴奋又紧张。不过呢,光有好奇心还不够,还得有点儿耐心,就像种花一样,得一点点浇水施肥,不能急着看结果。相信只要我们不断学习、不断反思,就一定能写出更加优雅、高效的代码! 祝大家编码愉快!
2025-04-25 16:14:17
32
凌波微步
转载文章
...联系我们,我们将第一时间进行核实并删除相应内容。 目录 一、建模背景及目的及数据源说明 二、描述性分析 2.1 连续自变量与连续因变量的相关性分析 2.2 二分类变量与连续变量的相关性分析 2.3 多分类变量与连续变量的相关性分析 三、模型建立与诊断 3.1 一元线形回归及模型解读 3.2 残差可视化分析 3.3 多元线性回归 一、建模背景及目的及数据源说明 本案例数据来源于常国珍等人的《Python数据科学》一书第7章中的信用卡公司客户申请信息(年龄、收入、地区等信息)以及已有开卡客户的申请信息和信用卡消费信息数据,案例希望通过对该数据的分析和建模,根据已有的开卡用户的用户信息和消费来线形回归模型,来预测未开卡用户的消费潜力。数据下载见如下链https://download.csdn.net/download/baidu_26137595/85101874 数据读入及示例: raw = pd.read_csv('./data/creditcard_exp.csv', skipinitialspace = True)raw.head() 数据字段及说明: Acc: 是否开卡, 为0说明未开卡,对应的 avg_exp 为NaN;为1说明已开卡,对应avg_exp有值 avg_exp: 月均信用卡支出 avg_exp_ln:月均信用卡支出的对熟 gender : 性别 Ownrent: 是否自有住房 Selfempl: 是否自谋职业 Income:收入 dist_home_val: 所住小区均价 w dist_avg_income: 当地人均收入 age2: 年龄的平方 high_avg: 高出当地平均收入 edu_class:教育等级,0、1、2、3 依次是小学、初中、高中、大学 二、描述性分析 首先可筛选Acc为1的数据,分别以avg_exp为因变量,其余变量为自变量进行数据探索,主要是发现自变量和因变量是否有线形关系。 raw_1 = raw[raw['Acc'] == 1] 2.1 连续自变量与连续因变量的相关性分析 首先对连续变量和目标变量进行相关性分析,因变量avg_exp为连续变量,一般可以用相关系数来看其线形相关性。 cons_vasr = ['avg_exp', 'avg_exp_ln', 'Age', 'Income', 'dist_home_val', 'dist_avg_income', 'age2', 'high_avg']raw_1[cons_vasr].corr()vg']].corr() 结果如下,可以看到收入 Income 和当地人均收入 dist_avg_income这两个变量和avg_exp月均信用卡支出有较强的相关性,同时观察自变量间的相关性可发现人均收入 Income 和当地人均收入 dist_avg_income 之间也有较强的相关性,相关系数为0.99,说明接下来我们可以把这两个变量加入模型,但要注意可能会存在多重共线性。 2.2 二分类变量与连续变量的相关性分析 分类变量和连续变量之间的相关性可以用t检验进行,接下来以是否自有住房 Ownrent 变量 和 月均收入之间进行相关性检验。首先查看Ownrent 不同取值的数量以及avg_exp均值分布情况如何: pd.pivot_table(raw_1, values = ['avg_exp'], index = ['Ownrent'], aggfunc = {'avg_exp': ['count', np.mean]}) 接着分别对 Ownrent 为0、1的 avg_exp 进行t检验: import scipy.stats as st 引入scipy.stats进行t检验 创建变量Ownrent_0 = raw_1[raw_1['Ownrent'] == 0]['avg_exp'].valuesOwnrent_1 = raw_1[raw_1['Ownrent'] == 1]['avg_exp'].valuesst.ttest_ind(Ownrent_0, Ownrent_1, equal_var = True) p值为0.01 < 0.05,可以拒绝原假设,即认为是否自有住房和月均信用卡支出是相关的。 2.3 多分类变量与连续变量的相关性分析 多分类变量和连续变量之间的相关性检验可以用多次t检验进行,但较为繁琐,用方差分析进行快速检验相关性,然后再运用多重检验查看具体是哪些处理之间存在差异。以教育水平edu_class为例进行分析,同理首先查看分布 raw_1.pivot_table(index = 'edu_class', values = ['avg_exp'], aggfunc={'avg_exp': ['count', np.mean]}) 可以看到不同教育水平之间消费水平有明显差异,接下来通过方差分析进行检验差异是否明显。 from statsmodels.stats.anova import anova_lm 引入anova_lm进行方差分析from ststsmodels.stats.formula import ols 引入ols进行线性回归建模lm = ols('avg_exp~C(edu_class)', data = raw_1).fit() C(edu_class) 将数值型的变量指定为分类型anova_lm(lm, typ = 2) 可以看到不同教育水平之间的月均消费支出之间的差异是显著的,继续用多重检验来看哪些处理之间是显著的。 from statsmodels.stats.multicomp import MultiComparison 引入MultiComparison进行tukey多重检验mc = MultiComparison(raw_1['avg_exp'],raw_1['edu_class'])tukey_result = mc.tukeyhsd(alpha = 0.5)print(tukey_result) 结果是每个处理之间因变量差异的显著性,最后一列reject都为True说明各组之间均存在显著差异。 三、模型建立与诊断 3.1 一元线性回归及模型解读 以Income为自变量,以avg_exp为因变量建立一元线形回归并对模型结果进行解释 lm_1 = ols('avg_exp ~ Income', data = raw_1).fit()print(lm_1.summary()) 首先从第一部分可以看到R^2为0.454,整个模型的F检验p值小于0.05,说明模型通过显著性检验。 其次模型结果的第二块也表明自变量和截距也通过显著性检验。 最后一部分主要是对残差进行检验,左侧Omnibus、Prob(Omnibus)主要是对偏度Skew和峰度Kurtosis进行检验,正态分布的偏度为0,峰度为3,模型的Prob(Omnibus)值为0.156大于0.05,说明不能拒绝残差符合正态分布。 右侧Durbin-Watson主要是对残差的自相关性进行检(改检验可表示为,为残差之间的相关系数),Durbin-Watson的取值范围是0-4,越接近2说明残差不存在自相关性,越接近0说明存在正相关,越接近4说明存在负相关性。 右侧Jarque-Bera (JB)、Prob(JB)是对残差正态性检验,可以用来判断残差是否符合正态分布,本案例中Prob(JB)值为0.173 > 0.05,基不能拒绝残差服从正态分布。 右侧Cond. No.是多重共线性检验,该值越大,共线性越严重。 整体上看模型虽然拟合效果没那么好,但是显著性通过了检验。接下来看一下模型具体的系数,Income的系数为97.7说明模型收入越高信用卡消费越高,是符合业务预期的。 3.2 残差可视化分析 接下来对残差进一步进行可视化分析,主要看残差是否满足以下几个假定,并尝试通过对自变量、因变量进行调整来优化模型。首先来回顾一下残差需要满足的几个假定: a.残差的要服从均值为0,方差为的正态分布; b.残差之间要相互独立 c.残差和自变量没有相关性 (1)通过残差图进行模型优化 模型avg_exp ~ Income的自变量与残差分布图、残差qq图、模型拟合情况图即自变量与因变量及其预测值的图像 lm_1 = ols('avg_exp ~ Income', data = raw_1).fit() 建模raw_1['resid_1'] = lm_1.resid 模型残差raw_1['resid_1_rank'] = raw_1['resid_1'].rank(ascending = False, pct = True) 计算残差的百分位数raw_1['pred_1'] = lm_1.predict() 添加预测值plt.figure(figsize = (20, 6)) 自变量与残差分布图ax1 = plt.subplot(131)ax1.scatter('Income', 'resid', data = raw_1)ax1.set_title('Income & resid') 残差的qq图ax2 = plt.subplot(132)stats.probplot(raw_1['resid_1_rank'], dist = 'norm', plot = ax2) 模型拟合情况图,自变量与因变量以及模型预测值ax3 = plt.subplot(133)ax3.scatter('Income', 'avg_exp', data = raw_1)ax3.plot('Income', 'pred_1', data = raw_1, color = 'red')ax3.legend()ax3.text(12, 1920, 'pred func R^2: %.2f'% lm_1.rsquared)ax3.set_title('Income & avg_exp') 从第一个自变量和残差散点图可以看出,残差基本符合对称分布,但随着自变量增大,残差也在变大,存在方差不齐的情况。第二个图残差的qq图可以看出,残差近似正态分布。第三个图可以看模型的拟合效果并不是很好,R^2只有0.45。对avg_exp取对数,能够改善预测值越大残差越大的情况,但由于只对因变量取对数导致模型不好解释,对自变量Income同时取对数,代码和以上类似,只是改变因变量和自变量形式而已,以下是残差图,可以看到残差的异方差现象被有效的抑制,并且R^2也得到了提高。 (2)通过残差图发现强影响点 仔细观察以上图像结果,左下侧有两个较为异常的数据,对模型的拟和效果有较大的影响, 对于这种影响较大的可将其进行删除并重新建模: 计算学生化残差raw_1['resid_t'] = (raw_1['resid_2'] - raw_1['resid_2'].mean())/raw_1['resid_2'].std() raw_1[abs(raw_1['resid_t']) > 2] 将残差大于2的筛选出来 将强影响点删除后,得到的结果如下,模型结果更稳定。 3.3 多元线性回归 上一篇文章有说到多重共线性会对模型产生致命的影响,用方差膨胀因子来处理的话会非常繁琐。通过正则化处理如Lasso回归,能够产生某些严格等于0的系数,从而达到变量筛选的目的。接下来以Lasso为例,首先用LassoCV来找到最优的alpha。由于statsmodels中的ols的fit_regularized方法没有很好的实现,所以用sklearn中linear_model模块来进行建模 from sklearn.preprocessing import StandardScaler sklearn进行线性回归前必须要进行标准化from sklearn.linear_model import LassoCV Lasso的交叉验证方法con_xcols = ['Age', 'Income', 'dist_home_val', 'dist_avg_income']scaler = StandardScaler()X = scaler.fit_transform(raw_1[con_xcols])y = raw_1['avg_exp_ln']lasso_alphas = np.logspace(-3, 0, 100, base = 10)lcv = LassoCV(alphas = lasso_alphas, cv = 10)lcv.fit(X, y)print('best alpha %.4f' % lcv.alpha_)print('the r-square %.4f' % lcv.score(X, y)) 接下来画出不同alpha下的岭迹图,来看alpha值对系数的影响 from sklearn.linear_model import Lassocoefs = []lasso = Lasso()for i in lasso_alphas:lasso.set_params(alpha = i)lasso.fit(X, y)coefs.append(lasso.coef_)ax = plt.gca()ax.plot(lasso_alphas, coefs)ax.set_xscale('log')ax.set_xlabel('$\\alpha$')ax.set_ylabel('coefs value') 从图中可以看到随着alpha的增大,系数不断在减小,有些系数会优先收缩为0,再继续增大时所欲系数都会为0,通过该特性从而达到变量筛选的目的。将LassoCV得到的系数打印出来,可以看到用户月均信用卡支出和当地小区均价、当地人均收入成正比,当地人均收入水平的影响更大。 以上就是线形回归在应用时的注意事项。 本篇文章为转载内容。原文链接:https://blog.csdn.net/baidu_26137595/article/details/123766191。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-11-23 15:52:56
106
转载
Kafka
...为分布式消息中间件的应用场景愈发广泛。特别是在微服务架构日益普及的背景下,Kafka因其高吞吐量、低延迟的特点,成为了企业级数据流处理的首选方案。然而,这也带来了新的挑战。例如,国内某大型电商企业在双十一促销活动中,由于订单峰值激增,其基于Kafka构建的实时交易系统一度面临消息堆积的问题。经过紧急排查,发现主要是由于分区数量不足导致的负载不均。为此,该企业迅速调整了分区策略,并优化了消息生产和消费逻辑,最终顺利应对了高峰流量。 与此同时,国外科技巨头也对Kafka进行了持续改进。近日,Confluent公司宣布推出Kafka 3.6版本,该版本引入了多项新特性,包括增强型事务API、更高效的压缩算法以及对多租户环境的支持。这些更新旨在帮助企业更好地满足复杂业务场景的需求,同时也反映了Kafka社区对于技术创新的不懈追求。 此外,关于Kafka与ZooKeeper的关系,业界普遍关注其未来的演进方向。尽管Confluent正在推动KRaft(Kafka Raft-based Controller)项目,试图完全摆脱ZooKeeper的依赖,但在短期内,ZooKeeper仍将在许多传统部署环境中占据主导地位。因此,对于正在使用Kafka的企业而言,如何平衡现有基础设施与新技术之间的过渡,成为了一个值得深思的问题。 从长远来看,Kafka的成功离不开开源社区的支持。正如Apache软件基金会所倡导的理念,“开放、协作、共享”始终是推动技术创新的核心动力。在未来,随着更多企业和开发者加入到Kafka生态中,我们有理由相信,这一技术将继续保持旺盛的生命力,并在更多领域发挥重要作用。
2025-04-05 15:38:52
95
彩虹之上
转载文章
...联系我们,我们将第一时间进行核实并删除相应内容。 1,背景 博客停了好久,主要是最近工作太忙了,还有就是身体状况没有以前那么好了,乘着国庆长假的空档,写下这篇一直想写的文章。 运营平台是我主要致力的一个项目,这个项目分为四个大部分,个人中心,充值中心,客服中心,家长监护,最近主要忙着个人中心的重写和丰富,关于个人中心,无非就是对平台用户信息的自我管理,以及一些对用户帐号的安全保护措施,下图的菜单非常简要的说明了个人中心的功能。个人觉得最值得关注的就是密保设置和修改头像,因为之前没有处理过类似的问题,本文主要记录对头像的处理过程以及思考,希望给碰到类似问题的苦逼程序员一点借鉴。 个人中心整体功能一览 2,头像处理xmind 叽歪一句,个人碰到问题的时候,首先会分析问题,在分析问题的基础上,得到整体的解决方案,然后一步步分解步骤,去实现,首先奉上我的解决方案,也许不是最优的,但是按照个人的知识和技能水平,绝对是可以实现的。 修改头像mind 3,实现步骤 按照我的mind,首先是上传图片,先上效果图,然后给出实现的代码。首先是整体的结构图,做的比较丑,别喷哥··· 修改头像整体效果图 下面按照mind一步步实现, 首先:点击修改头像,弹出一个层, 第一步:弹出上传图片的层,上传图片到服务器 对实现细节不感冒的屌丝可以看看代码(结合哥的mind看可以事半功倍): 分层实现细节 Html结构层这个可以免了,一般都可以弄出来 Js连接层 首先是弹出一个上传图片的层,然后上传图片到服务器端。 $("editHead").bind("click", function () { showUploadDiv(); }); function showUploadDiv() { $("uploadMsg").empty(); $.fancybox({ type:'inline', width:400, href:'uploadUserHead' }); }//fancybox弹出层 上传的处理代码 Servlet服务端处理层(commonupload实现)服务器端处理代码 上传的处理代码 $(function () { $("uploadFrom").ajaxForm({ beforeSubmit:checkImg, error:function(data,status){ alert(status+' , '+data); $("uploadMsg").html('上传文件超过1M!'); }, success:function (data,status) { try{ var msg = $.parseJSON(data); if (msg.code == 200) { //如果成功提交 javascript:$.fancybox.close(); $("uploadUserHead").hide(); var data = msg.object; $("editImg").attr("src", data.path).show(); $("preview1").attr("src", data.path).show(); $(".zoom").show(); $("width").val(data.width); $("height").val(data.height); $("oldImgPath").val(data.realPath); $("imgFileExt").val(data.fileExt); var api, jcrop_api, boundx, boundy; $('editImg').Jcrop({ onChange:updatePreview, onSelect:updatePreview, aspectRatio:1, bgOpacity:0.5, bgColor:'white', addClass:'jcrop-light' }, function () { api = this; api.setSelect([130, 65, 130 + 350, 65 + 285]); api.setOptions({ bgFade:true }); api.ui.selection.addClass('jcrop-selection'); var bounds = this.getBounds(); boundx = bounds[0]; boundy = bounds[1]; jcrop_api = this; }); function updatePreview(c) { if (parseInt(c.w) > 0) { var rx = 80 / c.w; var ry = 80 / c.h; $('preview1').css({ width:Math.round(rx boundx) + 'px', height:Math.round(ry boundy) + 'px', marginLeft:'-' + Math.round(rx c.x) + 'px', marginTop:'-' + Math.round(ry c.y) + 'px' }); } jQuery('x').val(c.x); jQuery('y').val(c.y); jQuery('x2').val(c.x2); jQuery('y2').val(c.y2); jQuery('w').val(c.w); jQuery('h').val(c.h); } } if (msg.code == 204) { $("uploadMsg").html(msg.msg); } }catch (e){ $("uploadMsg").html('上传文件超过1M!'); } } }); }); //服务器端处理代码 String tempSavePath = ConfigurationUtils.get("user.resource.dir"); //上传的图片零时保存路径 String tempShowPath = ConfigurationUtils.get("user.resource.url"); //用户保存的头像路径 if(tempSavePath.equals("/img")) { tempSavePath=sc.getRealPath("/")+tempSavePath; } Msg msg = new Msg(); msg.setCode(204); msg.setMsg("上传头像失败!"); String type = request.getParameter("type"); if (!Strings.isNullOrEmpty(type) && type.equals("first")) { request.setCharacterEncoding("utf-8"); DiskFileItemFactory factory = new DiskFileItemFactory(); ServletFileUpload servletFileUpload = new ServletFileUpload(factory); try { List items = servletFileUpload.parseRequest(request); Iterator iterator = items.iterator(); while (iterator.hasNext()) { FileItem item = (FileItem) iterator.next(); if (!item.isFormField()) { { File tempFile = new File(item.getName()); File saveTemp = new File(tempSavePath+"/tempImg/"); String getItemName=tempFile.getName(); String fileName = UUID.randomUUID()+"." +getItemName.substring(getItemName.lastIndexOf(".") + 1, getItemName.length()); File saveDir = new File(tempSavePath+"/tempImg/", fileName); //如果目录不存在,创建。 if (saveTemp.exists() == false) { if (!saveTemp.mkdir()) { // 创建失败 saveTemp.getParentFile().mkdir(); saveTemp.mkdir(); } else { } } if (saveDir.exists()) { log.info("存在同名文件···"); saveDir.delete(); } item.write(saveDir); log.info("上传头像成功!"+saveDir.getName()); msg.setCode(200); msg.setMsg("上传头像成功!"); Image image = new Image(); BufferedImage bufferedImage = null; try { bufferedImage = ImageIO.read(saveDir); } catch (IOException e) { e.printStackTrace(); } image.setHeight(bufferedImage.getHeight()); image.setWidth(bufferedImage.getWidth()); image.setPath(tempShowPath+ "/tempImg/" + fileName); log.info(image.getPath()); image.setRealPath(tempSavePath+"/tempImg/"+ fileName); image.setFileExt(fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length())); msg.setObject(image); } } else { log.info("" + item.getFieldName()); } } } catch (Exception ex) { log.error("上传用户头像图片异常!"); ex.printStackTrace(); } finally { AppHelper.returnJsonAjaxForm(response, msg); } } 上传成功后,可以看到照片和照片的预览效果。看图: 上传头像之后的效果 Friday, October 05, 2012 第二步:编辑和保存头像 选中图中的区域,保存头像,就完成头像的修改。 修改之后的效果入下: 修改之后的头像(因为传了一张动态图片,得到的跟上图有些不同) 实现细节: 首先用了一个js控件:Jcrop,有兴趣的屌丝可以去搜一下,然后,利用上传之后的图片和之前的选定区域,完成了一个截图,保存为用户的头像。 连接层的js: $("saveHead").bind("click", function () { var width = $("width").val(); var height = $("height").val(); var oldImgPath = $("oldImgPath").val(); var imgFileExt = $("imgFileExt").val(); var x = $('x').val(); var y = $('y').val(); var w = $('w').val(); var h = $('h').val(); $.ajax({ url:'/imgCrop', type:'post', data:{x:x, y:y, w:w, h:h, width:width, height:height, oldImgPath:oldImgPath, fileExt:imgFileExt}, datatype:'json', success:function (msg) { if (msg.code == 200) { $("avatar").attr("src", msg.object); forword('/nav', 'index'); } else { alert(msg.msg); } } }); }); function checkImg() { //限制上传文件的大小和后缀名 var filePath = $("input[name='uploadImg']").val(); if (!filePath) { $("uploadMsg").html("请选择上传文件!").show(); return false; } else { var extStart = filePath.lastIndexOf("."); var ext = filePath.substring(extStart, filePath.length).toUpperCase(); if (ext != ".PNG" && ext != ".GIF" && ext != ".JPG") { $("uploadMsg").html("图片限于png,gif,jpg格式!").show(); return false; } } return true; } 服务器端处理代码: String savePath = ConfigurationUtils.get("user.resource.dir"); //上传的图片保存路径 String showPath = ConfigurationUtils.get("user.resource.url"); //显示图片的路径 if(savePath.equals("/img")) { savePath=sc.getRealPath("/")+savePath; } int userId = AppHelper.getUserId(request); String userName=AppHelper.getUserName(request); Msg msg = new Msg(); msg.setCode(204); msg.setMsg("剪切图片失败!"); if (userId <= 0) { msg.setMsg("请先登录"); return; } // 用户经过剪辑后的图片的大小 Integer x = (int)Float.parseFloat(request.getParameter("x")); Integer y = (int)Float.parseFloat(request.getParameter("y")); Integer w = (int)Float.parseFloat(request.getParameter("w")); Integer h = (int)Float.parseFloat(request.getParameter("h")); //获取原显示图片路径 和大小 String oldImgPath = request.getParameter("oldImgPath"); Integer width = (int)Float.parseFloat(request.getParameter("width")); Integer height = (int)Float.parseFloat(request.getParameter("height")); //图片后缀 String imgFileExt = request.getParameter("fileExt"); String foldName="/"+ DateUtils.nowDatetoStrToMonth()+"/"; String imgName = foldName + UUID.randomUUID()+userName + "." + imgFileExt; //组装图片真实名称 String createImgPath = savePath + imgName; //进行剪切图片操作 ImageCut.abscut(oldImgPath,createImgPath, xwidth/300, yheight/300, wwidth/300, hheight/300); File f = new File(createImgPath); if (f.exists()) { msg.setObject(imgName); //把显示路径保存到用户信息下面。 UserService userService = userServiceProvider.get(); int rel = userService.updateUserAvatar(userId, showPath+imgName); if (rel >= 1) { msg.setCode(200); msg.setMsg("剪切图片成功!"); log.info("剪切图片成功!"); //记录日志,更新session log(showPath+imgName,userName); UserObject userObject= userService.getUserObject(userName); request.getSession().setAttribute("userObject", userObject); if (userObject != null && Strings.isNullOrEmpty(userObject.getHeadDir())) userObject.setHeadDir("/images/geren_right_01.jpg"); } else { msg.setCode(204); msg.setMsg("剪切图片失败!"); log.info("剪切图片失败!"); } } AppHelper.returnJson(response, msg); File file=new File(oldImgPath); boolean deleteFile= file.delete(); if(deleteFile==true) { log.info("删除原来图片成功"); } / 图像切割(改) @param srcImageFile 源图像地址 @param dirImageFile 新图像地址 @param x 目标切片起点x坐标 @param y 目标切片起点y坐标 @param destWidth 目标切片宽度 @param destHeight 目标切片高度 / public static void abscut(String srcImageFile, String dirImageFile, int x, int y, int destWidth, int destHeight) { try { Image img; ImageFilter cropFilter; // 读取源图像 BufferedImage bi = ImageIO.read(new File(srcImageFile)); int srcWidth = bi.getWidth(); // 源图宽度 int srcHeight = bi.getHeight(); // 源图高度 if (srcWidth >= destWidth && srcHeight >= destHeight) { Image image = bi.getScaledInstance(srcWidth, srcHeight, Image.SCALE_DEFAULT); // 改进的想法:是否可用多线程加快切割速度 // 四个参数分别为图像起点坐标和宽高 // 即: CropImageFilter(int x,int y,int width,int height) cropFilter = new CropImageFilter(x, y, destWidth, destHeight); img = Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(image.getSource(), cropFilter)); BufferedImage tag = new BufferedImage(destWidth, destHeight, BufferedImage.TYPE_INT_RGB); Graphics g = tag.getGraphics(); g.drawImage(img, 0, 0, null); // 绘制缩小后的图 g.dispose(); // 输出为文件 ImageIO.write(tag, "JPEG", new File(dirImageFile)); } } catch (Exception e) { e.printStackTrace(); } } 最后一个处理的比较好的地方就是图片的存储路径问题: 我在服务器端的nginx中做了一个图片的地址映射,把图片放到了跟程序不同的路径中,每次存储图片都是存到图片路径中,客户端拿到图片的地址确实经过nginx映射过的地址。 还有就是关于限制上传图片的大小的问题: 我在服务器端显示了资源的最大大小为1M,当上传的资源超过1M,服务器自动报错413,通过异常处理,可以在客户端得到正确的提示信息。 4,总结优点和不足。 关于修改头像,这么做下来确实达到了目的,用户可以从容的修改头像,性能也还可以。但是,上传图片的大小判断是依靠服务器端来判断的,等待的时间比较久,改进的方向是使用flash控件来限制,使用flash来上传,也不会出现弹出层,这样比较大众化,更容易为用户接受一点。我会不断改进。 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_39849287/article/details/111489534。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-07-18 10:58:17
268
转载
转载文章
...联系我们,我们将第一时间进行核实并删除相应内容。 目录 1. 作者介绍 2. 算法介绍 2.1 阿里云介绍 2.2 证件照生成背景 2.3 图像分割算法 3.调用阿里云API进行证件照生成实例 3.1 准备工作 3.2 实验代码 3.3 实验结果与分析 参考(可供参考的链接和引用文献) 1. 作者介绍 王逸腾,男,西安工程大学电子信息学院,2022级硕士研究生 研究方向:三维手部姿态和网格估计 电子邮件:2978558373@qq.com 路治东,男,西安工程大学电子信息学院,2022级研究生,张宏伟人工智能课题组 研究方向:机器视觉与人工智能 电子邮件:2063079527@qq.com 2. 算法介绍 2.1 阿里云介绍 阿里云创立于2009年,是全球领先的云计算及人工智能科技公司,致力于以在线公共服务的方式,提供安全、可靠的计算和数据处理能力,让计算和人工智能成为普惠科技。阿里云服务着制造、金融、政务、交通、医疗、电信、能源等众多领域的领军企业,包括中国联通、12306、中石化、中石油、飞利浦、华大基因等大型企业客户,以及微博、知乎、锤子科技等明星互联网公司。在天猫双11全球狂欢节、12306春运购票等极富挑战的应用场景中,阿里云保持着良好的运行纪录 阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本 猿辅导、中泰证券、小米、媛福达、Soul和当贝,这些我们耳熟能详的APP或企业中,阿里云给他们提供了性能强大、安全、稳定的云产品与服务。 计算,容器,存储,网络与CDN,安全、中间件、数据库、大数据计算、人工智能与机器学习、媒体服务、企业服务与云通信、物联网、开发工具、迁移与运维管理和专有云等方面,阿里云都做的很不错。 2.2 证件照生成背景 传统做法:通常是人工进行P图,不仅费时费力,而且效果也很难保障,容易有瑕疵。 机器学习做法:通常利用边缘检测算法进行人物轮廓提取。 深度学习做法:通常使用分割算法进行人物分割。例如U-Net网络。 2.3 图像分割算法 《BiHand: Recovering Hand Mesh with Multi-stage Bisected Hourglass Networks》里的SeedNet网络是很经典的网络,它把分割任务转变成多个任务。作者的思想是:尽可能的通过多任务学习收拢语义,这样或许会分割的更好或姿态估计的更好。其实这个模型就是多阶段学习网络的一部分,作者想通过中间监督来提高网络的性能。 我提取bihand网络中的SeedNet与训练权重,进行分割结果展示如下 我是用的模型不是全程的,是第一阶段的。为了可视化出最好的效果,我把第一阶段也就是SeedNet网络的输出分别采用不同的方式可视化。 从左边数第一张图为原图,第二张图为sigmoid后利用plt.imshow(colored_mask, cmap=‘jet’)进行彩色映射。第三张图为网络输出的张量经过sigmoid后,二色分割图,阀闸值0.5。第四张为网络的直接输出,利用直接产生的张量图进行颜色映射。第五张为使用sigmoid处理张量后进行的颜色映射。第六张为使用sigmoid处理张量后进行0,1分割掩码映射。使用原模型和网络需要添加很多代码。下面为修改后的的代码: 下面为修改后的net_seedd代码: Copyright (c) Lixin YANG. All Rights Reserved.r"""Networks for heatmap estimation from RGB images using Hourglass Network"Stacked Hourglass Networks for Human Pose Estimation", Alejandro Newell, Kaiyu Yang, Jia Deng, ECCV 2016"""import numpy as npimport torchimport torch.nn as nnimport torch.nn.functional as Ffrom skimage import io,transform,utilfrom termcolor import colored, cprintfrom bihand.models.bases.bottleneck import BottleneckBlockfrom bihand.models.bases.hourglass import HourglassBisectedimport bihand.utils.func as funcimport matplotlib.pyplot as pltfrom bihand.utils import miscimport matplotlib.cm as cmdef color_mask(output_ok): 颜色映射cmap = plt.cm.get_cmap('jet') 将张量转换为numpy数组mask_array = output_ok.detach().numpy() 创建彩色图像cmap = cm.get_cmap('jet')colored_mask = cmap(mask_array)return colored_mask 可视化 plt.imshow(colored_mask, cmap='jet') plt.axis('off') plt.show()def two_color(mask_tensor): 将张量转换为numpy数组mask_array = mask_tensor.detach().numpy() 将0到1之间的值转换为二值化掩码threshold = 0.5 阈值,大于阈值的为白色,小于等于阈值的为黑色binary_mask = np.where(mask_array > threshold, 1, 0)return binary_mask 可视化 plt.imshow(binary_mask, cmap='gray') plt.axis('off') plt.show()class SeedNet(nn.Module):def __init__(self,nstacks=2,nblocks=1,njoints=21,block=BottleneckBlock,):super(SeedNet, self).__init__()self.njoints = njointsself.nstacks = nstacksself.in_planes = 64self.conv1 = nn.Conv2d(3, self.in_planes, kernel_size=7, stride=2, padding=3, bias=True)self.bn1 = nn.BatchNorm2d(self.in_planes)self.relu = nn.ReLU(inplace=True)self.maxpool = nn.MaxPool2d(2, stride=2)self.layer1 = self._make_residual(block, nblocks, self.in_planes, 2self.in_planes) current self.in_planes is 64 2 = 128self.layer2 = self._make_residual(block, nblocks, self.in_planes, 2self.in_planes) current self.in_planes is 128 2 = 256self.layer3 = self._make_residual(block, nblocks, self.in_planes, self.in_planes)ch = self.in_planes 256hg2b, res1, res2, fc1, _fc1, fc2, _fc2= [],[],[],[],[],[],[]hm, _hm, mask, _mask = [], [], [], []for i in range(nstacks): 2hg2b.append(HourglassBisected(block, nblocks, ch, depth=4))res1.append(self._make_residual(block, nblocks, ch, ch))res2.append(self._make_residual(block, nblocks, ch, ch))fc1.append(self._make_fc(ch, ch))fc2.append(self._make_fc(ch, ch))hm.append(nn.Conv2d(ch, njoints, kernel_size=1, bias=True))mask.append(nn.Conv2d(ch, 1, kernel_size=1, bias=True))if i < nstacks-1:_fc1.append(nn.Conv2d(ch, ch, kernel_size=1, bias=False))_fc2.append(nn.Conv2d(ch, ch, kernel_size=1, bias=False))_hm.append(nn.Conv2d(njoints, ch, kernel_size=1, bias=False))_mask.append(nn.Conv2d(1, ch, kernel_size=1, bias=False))self.hg2b = nn.ModuleList(hg2b) hgs: hourglass stackself.res1 = nn.ModuleList(res1)self.fc1 = nn.ModuleList(fc1)self._fc1 = nn.ModuleList(_fc1)self.res2 = nn.ModuleList(res2)self.fc2 = nn.ModuleList(fc2)self._fc2 = nn.ModuleList(_fc2)self.hm = nn.ModuleList(hm)self._hm = nn.ModuleList(_hm)self.mask = nn.ModuleList(mask)self._mask = nn.ModuleList(_mask)def _make_fc(self, in_planes, out_planes):bn = nn.BatchNorm2d(in_planes)conv = nn.Conv2d(in_planes, out_planes, kernel_size=1, bias=False)return nn.Sequential(conv, bn, self.relu)def _make_residual(self, block, nblocks, in_planes, out_planes):layers = []layers.append( block( in_planes, out_planes) )self.in_planes = out_planesfor i in range(1, nblocks):layers.append(block( self.in_planes, out_planes))return nn.Sequential(layers)def forward(self, x):l_hm, l_mask, l_enc = [], [], []x = self.conv1(x) x: (N,64,128,128)x = self.bn1(x)x = self.relu(x)x = self.layer1(x)x = self.maxpool(x) x: (N,128,64,64)x = self.layer2(x)x = self.layer3(x)for i in range(self.nstacks): 2y_1, y_2, _ = self.hg2b[i](x)y_1 = self.res1[i](y_1)y_1 = self.fc1[i](y_1)est_hm = self.hm[i](y_1)l_hm.append(est_hm)y_2 = self.res2[i](y_2)y_2 = self.fc2[i](y_2)est_mask = self.mask[i](y_2)l_mask.append(est_mask)if i < self.nstacks-1:_fc1 = self._fc1[i](y_1)_hm = self._hm[i](est_hm)_fc2 = self._fc2[i](y_2)_mask = self._mask[i](est_mask)x = x + _fc1 + _fc2 + _hm + _maskl_enc.append(x)else:l_enc.append(x + y_1 + y_2)assert len(l_hm) == self.nstacksreturn l_hm, l_mask, l_encif __name__ == '__main__':a = torch.randn(10, 3, 256, 256) SeedNetmodel = SeedNet() output1,output2,output3 = SeedNetmodel(a) print(output1,output2,output3)total_params = sum(p.numel() for p in SeedNetmodel.parameters())/1000000print("Total parameters: ", total_params)pretrained_weights_path = 'E:/bihand/released_checkpoints/ckp_seednet_all.pth.tar'img_rgb_path=r"E:\FreiHAND\training\rgb\00000153.jpg"img=io.imread(img_rgb_path)resized_img = transform.resize(img, (256, 256), anti_aliasing=True)img256=util.img_as_ubyte(resized_img)plt.imshow(resized_img)plt.axis('off') 关闭坐标轴plt.show()''' implicit HWC -> CHW, 255 -> 1 '''img1 = func.to_tensor(img256).float() 转换为张量并且进行标准化处理''' 0-mean, 1 std, [0,1] -> [-0.5, 0.5] '''img2 = func.normalize(img1, [0.5, 0.5, 0.5], [1, 1, 1])img3 = torch.unsqueeze(img2, 0)ok=img3print(img.shape)SeedNetmodel = SeedNet()misc.load_checkpoint(SeedNetmodel, pretrained_weights_path)加载权重output1, output2, output3 = SeedNetmodel(img3)mask_tensor = torch.rand(1, 64, 64)output=output2[1] 1,1,64,64output_1=output[0] 1,64,64output_ok=torch.sigmoid(output_1[0])output_real=output_1[0].detach().numpy()直接产生的张量图color_mask=color_mask(output_ok) 显示彩色分割图two_color=two_color(output_ok)显示黑白分割图see=output_ok.detach().numpy() 使用Matplotlib库显示分割掩码 plt.imshow(see, cmap='gray') plt.axis('off') plt.show() print(output1, output2, output3)images = [resized_img, color_mask, two_color,output_real,see,see]rows = 1cols = 4 创建子图并展示图像fig, axes = plt.subplots(1, 6, figsize=(30, 5)) 遍历图像列表,并在每个子图中显示图像for i, image in enumerate(images):ax = axes[i] if cols > 1 else axes 如果只有一列,则直接使用axesif i ==5:ax.imshow(image, cmap='gray')else:ax.imshow(image)ax.imshowax.axis('off') 调整子图之间的间距plt.subplots_adjust(wspace=0.1, hspace=0.1) 展示图像plt.show() 上述的代码文件是在bihand/models/net_seed.py中,全部代码链接在https://github.com/lixiny/bihand。 把bihand/models/net_seed.p中的代码修改为我提供的代码即可使用作者训练好的模型和进行各种可视化。(预训练模型根据作者代码提示下载) 3.调用阿里云API进行证件照生成实例 3.1 准备工作 1.找到接口 进入下面链接即可快速访问 link 2.购买试用包 3.查看APPcode 4.下载代码 5.参数说明 3.2 实验代码 !/usr/bin/python encoding: utf-8"""===========================证件照制作接口==========================="""import requestsimport jsonimport base64import hashlibclass Idphoto:def __init__(self, appcode, timeout=7):self.appcode = appcodeself.timeout = timeoutself.make_idphoto_url = 'https://idp2.market.alicloudapi.com/idphoto/make'self.headers = {'Authorization': 'APPCODE ' + appcode,}def get_md5_data(self, body):"""md5加密:param body_json::return:"""md5lib = hashlib.md5()md5lib.update(body.encode("utf-8"))body_md5 = md5lib.digest()body_md5 = base64.b64encode(body_md5)return body_md5def get_photo_base64(self, file_path):with open(file_path, 'rb') as fp:photo_base64 = base64.b64encode(fp.read())photo_base64 = photo_base64.decode('utf8')return photo_base64def aiseg_request(self, url, data, headers):resp = requests.post(url=url, data=data, headers=headers, timeout=self.timeout)res = {"status_code": resp.status_code}try:res["data"] = json.loads(resp.text)return resexcept Exception as e:print(e)def make_idphoto(self, file_path, bk, spec="2"):"""证件照制作接口:param file_path::param bk::param spec::return:"""photo_base64 = self.get_photo_base64(file_path)body_json = {"photo": photo_base64,"bk": bk,"with_photo_key": 1,"spec": spec,"type": "jpg"}body = json.dumps(body_json)body_md5 = self.get_md5_data(body=body)self.headers.update({'Content-MD5': body_md5})data = self.aiseg_request(url=self.make_idphoto_url, data=body, headers=self.headers)return dataif __name__ == "__main__":file_path = "图片地址"idphoto = Idphoto(appcode="你的appcode")d = idphoto.make_idphoto(file_path, "red", "2")print(d) 3.3 实验结果与分析 原图片 背景为红色生成的证件照 背景为蓝色生成的证件照 另外尝试了使用柴犬照片做实验,也生成了证件照 原图 背景为红色生成的证件照 参考(可供参考的链接和引用文献) 1.参考:BiHand: Recovering Hand Mesh with Multi-stage Bisected Hourglass Networks(BMVC2020) 论文链接:https://arxiv.org/pdf/2008.05079.pdf 本篇文章为转载内容。原文链接:https://blog.csdn.net/m0_37758063/article/details/131128967。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-07-11 23:36:51
131
转载
转载文章
...联系我们,我们将第一时间进行核实并删除相应内容。 写在开头的话 从很久之前就开始构思,一直到今天才开始动笔。这篇文章是送给那些希望更深刻地了解这个世界的人,然而,知道的多了,也不一定是件好事。另外注明,本篇文章全部为作者的个人观点。 引子 现在人工智能越来越火,我们的故事,要从它开始说起。 相信很多人都听说过王者荣耀,即使自己没有亲自玩过,应该也对这个游戏不陌生。我曾经也沉迷这个游戏,当时我有一个朋友,特别喜欢玩小号1。她说,玩小号匹配出来的对手都特别垃圾,玩起来就像切萝卜一样爽。当时我的感觉是玩小号匹配出来的都是新手,技术不高也不足为奇,然而在接下来的事情当中,我发现事情并没有这么简单。首先有网友反映在匹配时遇到了自己多年不用的小号2,也有网友反映,在连输很多局之后,会有一局赢得异常简单,就好像对手都是机器人一样3。 网友称这一类玩家为“ 电脑人”,还总结出了“ 电脑人”的若干特征: 4 1.加载速度非常快 2.进入游戏后在泉水的行为,走路姿势差不多,行动路线也差不多 3.查不到战绩 4.无法沟通 5.很怪异或者说无法解释的名字 但是,真正的玩家和所谓的“电脑人”仅靠这些特征真的可以完全的分辨开吗?这些“电脑人”,究竟是一些行为怪异的玩家,还是天美给玩家的一个惊喜,这并不是我这篇文章所想要讲述的重点。我想说的是,在人类日复一日研究人工智能,希望其更接近人类的同时,已经开始出现了人类与人工智能分不清的现象。更严重的情况是,已经开始了怀疑人类为人工智能的现象。 不难想象,在科技足够发达的未来,这一现象会愈加严重,人类究竟与人工智能有什么本质上的区别,在以下的内容中我将给出解释。 灵魂的存在 自古以来就有一个强大的神话:人类拥有永恒的灵魂。虽然肉体会消失,但是灵魂是永存的。尽管这一神话有人相信,有人不信,但是它确确实实的影响着我们的现实生活,是我们现有的法律,政治的经济制度的重要支柱。 如果灵魂确实存在的话,那么它作为区别人与人工智能的本质区别再合适不过了。但是,灵魂究竟是什么东西,或者说,它究竟存在与哪里。至今为止,科学家研究了动植物和人类的各个角落,也没有发现类似“灵魂”的东西。 又或许说,灵魂根本就是看不见,摸不着的。那灵魂又是如何产生的呢?从最开始的宇宙开始形成,灵魂显然是不存在的。而灵魂又是不可分割的,永恒不变的,那么在生物一步步进化的过程中,究竟是在那一刻,灵魂突然出现。由达尔文的《进化论》,由最初的单细胞生物到最后的人,都可以用基因突变来解释,但是究竟在那一代,突变产生了第一个具有灵魂的生物?人们不得而知。当然也有可能,灵魂是在某个时刻,由“上帝”加入到这个世界的。 本篇文章中,“灵魂”作为我们的唯一存在来描述,下文我会具体的解释。 心流的存在 与灵魂的存在相反,心灵的存在,是一个不争的事实,是一个我们每时每刻都在接受的明确的现实。心流包含两方面:感觉和欲望。 我们可以非常明确的知道,我们自身,是有感觉和欲望的,以及,人工智能,是不具备感觉和欲望。在这里,我想我需要简述一下笛卡尔的心灵哲学5,笛卡尔认为,人不是机器,但是动物是机器,只有人类才拥有感觉和欲望,其他动物都是没有心灵的自动物。所以当有人踢狗的时候,狗会自动的退缩,躲避,并开始狂吠,但是没有任何的感觉和欲望,就像自动贩卖机一样,按下开关,出来商品。所以人类对待动物,也很少有怜悯。早期17世纪的医生和学者对活狗进行解剖,观察其内脏器官如何运作,但完全不用麻醉,他们也不会感到不安。因为在他们眼中这没有什么不对,就像现在人们把机器拆开看看内部的电路是如何工作一样6。 当然,现在有了很多的动物保护者,他们认为动物和人类是平等的,也有自己的意识,也有喜怒哀乐。在《剑桥意识宣言》中提到:“各种证据均指出,非人类动物拥有构成意识所需的神经结构,神经化学及神经生理基础物质,并且能展现出有意图的行为。因此,证据已充分显示,负责产生意识的神经基础物质并非人类所独有。非人类动物,包括所有哺乳动物,鸟类,以及章鱼等其他生物,均拥有这些神经基础物质。” 确实,我承认心流并不只存在与人类,而是存在与所有生物之中。但是笛卡尔的理念也并不是完全错误的,因为心流虽然是生命的特质,但不是人类的特质,我想笛卡尔的理论中把心灵换做灵魂可能会更妥当一些,尽管灵魂的存在目前还是个未知数。或许我说完接下来的例子,会解释的更充分些。 对于心流的存在,生物学家给出了一个简单的不能再简单的解释,那就是,如果没有感觉和欲望,那么就无法解释生物的各种行为。拿人来做例子或许会比较难以理解,但是拿动物做例子却简单的过分,那就是:当人去踢狗的时候,如果狗没有感到疼痛,愤怒,产生躲避的欲望,那么它就会因此而受到伤害。也就是说,这些种种的感觉与欲望,是那些最原始的东西,即进化论为了使生命更好的活着而产生的,只因人类把自己放在比动物高很多个层次的阶级上,而忽略了这个很简单的问题。 心流的产生 问题的关键,在于心流的产生。这样稍微改动下,上文所提到的笛卡尔的理论或许会更合理些:人与动物都存在感觉与欲望,但是动物的感觉与欲望是依靠自身结构在外界的输入下产生的一种内部输出,而人类的感觉和欲望则是一种可以被称作“灵魂”的东西控制下产生的。从而确立了人类高于动物的地位。 前者很容易理解,现在的科学研究也已经很透彻了。例如兔子见到狮子,电信号便从眼睛传到大脑,刺激某些神经元,又结合之前的记忆神经元,放出更多的信号,整条线路的神经元一一受到刺激,最后指令传到肾上腺,让肾上腺素传遍全身,心脏的跳动也随之加快,肾上腺素也使信号的传递速度更快了些,同时在运动中枢的神经元也向腿部肌肉发出信号,让肌肉随着信号有序的完成伸展和收缩。外在的表现就是兔子从狮子旁边逃之夭夭。至于其中的恐惧的感觉和想要逃跑的欲望,都只不过是内部神经元信号的一种状态。 而对于后者,则难以解释。正因为对前者的理解透彻,对后者的解释才显得很难说通。两个过程本来是相同的过程,只是后者多了对于每个人有且唯一的“灵魂”的存在的介入,但是,它究竟何时介入,如何介入,正如前者所描述的,在这样一个信号的传递网络里,究竟有哪一步,是需要“灵魂”来控制的。思前想后,好像并没有必须存在的那么一个步骤。也就是可能,前者所描述的那个信号传递步骤,适用于所有生物,当然也包括人类。 简单的总结 简单的总结一下,关于确定存在的心流和不确定存在的灵魂。 首先,心流是确定存在,并且存在与所有生物当中,是生物进化产生的,为了更好的活着。其中,记忆储存的是之前的心流状态,当然不是全部的心流状态;感觉是当时的生物内部信号的一种状态,成为现态;欲望是一种内部输出,欲望,感觉和记忆相结合再结合会产生对外部的输出。 其次,“灵魂”在这里表示为一个个体的有且唯一的存在。它不参与生物的任何过程,但是却有选择的监视生物的心流。也可以这样说,生物体本身有选择的展示一部分心流以供灵魂检阅,灵魂也是从生物所展示的心流中有选择的检阅。这才是人类的特质。我们真正的自我,就是这样一个有且唯一的灵魂,它无法介入它所在的生物体的任何事情,但是可以在一定程度上知道它所在的生物体的状态。 也可以这样理解,生物体本身是一个封装的很好的复杂程序,心流则是程序的内部变量,程序不断的接收外部输入并向外部输出,我们本身的灵魂所在则置身于程序之外,就像我们坐在电脑前,无法知道这个复杂程序究竟是如何运行的,但是通过它输出在显示屏中的一些内部变量,即心流的一些数据,我们可以大致的判断出,程序在干些什么。对于这样的解释你可能难以接受,接下来的两个例子或许会让你接受这一事实。 现在科学家只要扫描人脑,就能在测试者自己有所感知之前,预测他们会有什么欲望,会做出怎样的决定。例如,在一次实验中,受试者躺在一台巨大的脑部设备里,两手各自拿着一个开关,受试者可以随机的选择在何时按下那个开关。而科学家通过观察受试者的大脑神经活动,就能在受试者做决定之前知道受试者做了怎样的决定。也就是说,当这些内部输出被外部观测者“灵魂”所察觉的时候,心流自身已经做出了决定。7 或许你没有亲自做过这个实验,并不相信实验的结论,但是还有一个实验,你现在就可以给自己做一个测试。相信对于大家心算100以内的乘法没有什么问题,那么请各位充分运用自己的自由意志,即本文中的“灵魂”去控制你的大脑心算5672,注意在计算的过程中不要让自己的大脑去思考其他的任何事情,用尽快的速度计算出结果。当然,你会发现你根本做不到,无论如何你都无法控制那先奇奇怪怪的想法出现在你的大脑里,至于大脑为什么会像你控制的那样去计算5672,接下来我会给出人类的大脑思维模型。 生物的模型 生物的模型分为两部分,一部分我称为确定机,一部分我称为概率机。 确定机 确定机是指只要输入确定,那么就会产生确定输出的部分,而对于输入的概率性则不予考虑。例如,当生物多次看到同一个画面的时候会在大脑里形成同样的图像,因为每次输入的光信号都是一样的,在生物内部进行的信号传递过程也是一样的,所以在大脑里形成的图像输出也是一样的。现在人类所生产的绝大多数工具就是一个确定机的模型,如果相同的输入,不管输入多少次都会得到相同的输出。确定机也是生物模型的基础部分,构成生物的绝大部分,实际上,除了大脑,生物的任何部分都是一个确定机的模型,而大脑也有一部分的确定机模型。对于确定机,所有的内部过程和输出都不会被“灵魂”检阅,当然生物上可以通过解剖或其他更先进的方式去检查生物内部确定机的工作状态。 概率机 概率机是指即使输入确定,输出的确定性也指限制在一定的概率范围之内,会以不同但是给定的概率输出多个输出。当然给定的概率可以是确定机给出的确定概率(只在输入确定的情况下才确定),也可以是概率机给出的概率概率。概率机构成生物的大脑部分,当然一部分低等生物只由确定机构成。对于概率机,有一部分输出会被“灵魂”检阅,而“灵魂”是否检阅取决于“灵魂”本身,当然,对于概率机的工作状态,也可以通过解剖或其他更先进的方式去检查。 生物思考的过程 对于不同的生物,大脑可以同时进行的事情是有限的。就像现在的电脑手机一样,有严格的内存限制,对于大脑来说,同时启用着多个线程,每个线程所占用的内存不同,但是所有线程所占用的内存总和不得超限。对于每个线程,会随机的考虑一些事件,这些事件包括记忆中的事件,和当时正在发生的事件,对于每个事件出现在线程中的概率不同。 不同事件的概率遵循的规律大致有以下几条: 1.对记忆中的事件,事件越久远概率越低。 2.对当时正在发生的事件,概率大致相同。 3.与当时线程中事件有关的事件概率高,无关的概率低。 4.与线程中的事件相关的个数越多,概率越高 5.对不同的心流状态,概率分配有所不同。 6.每个个体对不同的事件有不同的概率分配方案。 7.待补充。 可以说,大脑中的一切过程都是随机的。那这样的话,生物的思考过程究竟如何进行呢?其实很简单,单个概率可能代表随机,但是多个概率就有可能表示必然。我还是举那个5672的例子,为什么你会真的去心算这个结果,大致的过程是这样的,如果大脑的思考频率以毫秒计的话,假设看5672用了200毫秒,其中每毫秒除了这一事件,还有其他的99个事件,那么刚看完就开始计算的概率为1-0.99200=0.8660203251,看完后1秒之内还没有开始计算的概率为0.991000= 4.31712474107 e-5,可以说即使大脑中随机的杂念再多,思考的过程也会如约开始。假设线程中与事件相关的事件出现的概率为0.3,同理,在开始计算后1秒内大部分时间都在思考与计算有关的内容,当然也有可能会走神,即出现大范围的无关事件,但是这只会影响最后计算出结果的时间先后,并不会影响整个过程的进行。这也就是说,大脑的思考过程,其实就是由多个概率所确定的必然事件。 灵魂的旁观者 综上所述,作为个体唯一存在的“灵魂”处在一个旁观者的位置,而所谓的自由意识,主观意识不过是概率机的产物。那么这样就产生了两个问题。 第一个问题,你不觉得“灵魂”所在的肉体更像是一个囚笼吗?“灵魂”可以偶尔窥探外界,但无法做任何事情,只能默默得看着一切发生。尴尬的以为是自己做的,实际上就像看电影,每次看电影的时候,我都会以为我处在电影里面的世界。而现实就是,因为“灵魂”只能看肉体主演的这部“电影”,所以看的入迷了。其实,人类从解放双手,开发智力,使用工具,到探索宇宙,最大的进步莫过于发现自己其实仍处于囚笼之中。要怪就怪这囚笼建造地太过美好。而创建这一囚笼的“上帝”,把我们关在肉体这个囚笼里面,并且把我们的感知限制在有限的范围内,有限的嗅觉,16至20000赫兹的听觉,400纳米到700纳米的视觉,在感知中隔绝了我们对我们的唯一存在——“灵魂”的感知。 第二个问题,对于自己本身来说,表征自己存在的“灵魂”自己是可以确定的,而对于其他人,因为限制了对“灵魂”的感知,所以无法确认别人,别的生物体内这一旁观者的存在。也可以这么理解,你知道自己被关在一间囚笼里面,而不知道隔壁囚笼是否也关了一个存在。那么世界这个大监狱里面,可能只有一小部分,甚至只有你一个孤独的存在。而究竟为何我们或我被困于此,我不得而知,可能就像我们做研究的时候的小白鼠一样,“上帝”也在观察着我们或我的一举一动,这也是我这篇文章取这个题目的原因。小白鼠的逆袭,一开始我只是平凡的活着,说实在的其实做一个平凡人安安稳稳的一生还是很不错的,但是知道了这个囚笼的存在,就总想着打破它,因为在想到可能只有自己一个存在的时候,会是多么的孤独。就像一个人去看电影,哪怕电影的内容再精彩,再引人入胜,但当电影结束的时候,你才发现,原来我是一个人来的呀。 联系作者 有志向联系读者的:1612860@mail.nankai.edu.cn 未完待续。。。 本篇文章相当于《小白鼠的逆袭》的导读,下一篇我会出逆袭第一步:《思考的最简单模型及其编程实现》,可能用C++,也可能用Java,Python,看作者的心情吧。预计近几个月出吧,快则个把月,多则不知道了,毕竟作者本身还是比较忙的,忙七忙八也不知道在忙什么,嗯,就这样。 小号:在有多个游戏账号的前提下,等级高的号叫作大号,等级较低或者新创建的号叫作小号。 ↩︎ https://baijiahao.baidu.com/s?id=1586028525096880374&wfr=spider&for=pc. ↩︎ http://tieba.baidu.com/p/5127924201. ↩︎ http://tieba.baidu.com/p/5127924201. ↩︎ http://www.lwlm.com/sixiangzhexue/201704/840820.htm. ↩︎ 详细讨论请参见:《未来简史:从智人到智神》第三章:人类的特质。 ↩︎ “Unconscious determinants of free decisions in the human brain” in nature neuroscience, http://www.rifters.com/real/articles/NatureNeuroScience_Soon_et_al.pdf. ↩︎ 本篇文章为转载内容。原文链接:https://blog.csdn.net/qq_39384184/article/details/79288150。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-01-02 11:30:59
620
转载
转载文章
...联系我们,我们将第一时间进行核实并删除相应内容。 😎 知识点概览 为了方便后续回顾该项目时能够清晰的知道本章节讲了哪些内容,并且能够从该章节的笔记中得到一些帮助,所以在完成本章节的学习后在此对本章节所涉及到的知识点进行总结概述。 本章节为【学成在线】项目的 day15 的内容 根据 课程ID 搜索该课程已发布的课程信息,并返回该课程的所有课程计划信息。 将指定课程 发布时 所的课程计划的媒资信息保存到 teachplan_media_publish 表中, 根据 课程计划id 搜索该课程计划所对应的媒资信息,需要用到的是该课程计划对应的 m3u8 地址,用于在线播放视频,该接口在课程管理服务中开发,供学习服务进行远程调用。 在学习服务中远程调用 课程计划媒资信息查询接口,获取该课程计划的视频播放的 m3u8 url地址,并返回给前端,前端使用该 url 进行视频的在线播放。 在线学习完整的测试流程:媒资信息的上传、选择、发布到前端门户、搜索门户测试,在线学习的播放视频。 目录 内容会比较多,小伙伴门可以根据目录进行按需查阅。 文章目录 😎 知识点概览 目录 一、学习页面:查询课程计划 0x01 需求分析 0x02 Api接口 0x03 服务端开发 Controller Service 测试 0x04 前端开发 配置NGINX虚拟主机 前端 API 方法 前端 API 方法调用 测试 二、学习页面:获取视频播放地址 0x01 需求分析 0x02 课程发布:储存媒资信息 需求分析 数据模型 Dao Service 测试 0x03 Logstash:扫描课程计划媒资 创建索引 创建模板文件 配置 mysql.conf 启动 logstash.bat Logstash多实例运行 0x04 搜素服务:查询课程媒资接口 需求分析 Api接口定义 Service Controller 测试 三、在线学习:接口开发 0x01 需求分析 0x02 搭建开发环境 0x03 Api接口 0x04 服务端开发 需求分析 搜索服务注册Eureka 搜索服务客户端 自定义错误代码 Service Controller 测试 0x05 前端开发 需求分析 api方法 配置代理 视频播放页面 简单的测试 完整的测试 1、上传文件 一些问题 ~~方案1:删除本地分块文件重新尝试上传~~ 方案2:检查前端提交的MD5值是否正确 2、为课程计划选择媒资信息 3、前端门户测试 四、待完善的一些功能 😁 认识作者 一、学习页面:查询课程计划 0x01 需求分析 到目前为止,我们已可以编辑课程计划信息并上传课程视频,下一步我们要实现在线学习页面动态读取章节对应的视频并进行播放。在线学习页面所需要的信息有两类: 课程计划信息 课程学习信息(视频地址、学习进度等) 如下图: 在线学习集成媒资管理的需求如下: 1、在线学习页面显示课程计划 2、点击课程计划播放该课程计划对应的视频 本章节实现学习页面动态显示课程计划,进入不同课程的学习页面右侧动态显示当前课程的课程计划。 0x02 Api接口 课程计划信息从哪里获取? 在课程发布完成后会自动发布到一个 course_pub 的表中,logstash 会自动将课程发布后的信息自动采集到 ES 索引库中,这些信息也包含课程计划信息。 所以考虑性能要求,课程发布后对课程的查询统一从 ES 索引库中查询。 前端通过请求 搜索服务 获取课程信息,需要单独在 搜索服务 中定义课程信息查询接口。 本接口接收课程id,查询课程所有信息返回给前端。 我们在搜素服务 API 下添加以下方法 @ApiOperation("根据id搜索课程发布信息")public Map<String,CoursePub> getdetail(String id); 返回的课程信息为 json 结构:key 为课程id,value 为课程内容。 0x03 服务端开发 在搜索服务中开发查询课程信息接口。 Controller 在搜素服务下添加以下方法 / 根据id搜索课程发布信息 @param id 课程id @return JSON数据/@Override@GetMapping("/getdetail/{id}")public Map<String, CoursePub> getdetail(@PathVariable("id")String id) {return esCourseService.getdetail(id);} Service / 根据id搜索课程发布信息 @param id 课程id @return JSON数据/public Map<String, CoursePub> getdetail(String id) {//设置索引SearchRequest searchRequest = new SearchRequest(es_index);//设置类型searchRequest.types(es_type);//创建搜索源对象SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();//设置查询条件,根据id进行查询searchSourceBuilder.query(QueryBuilders.termQuery("id",id));//这里不使用source的原字段过滤,查询所有字段// searchSourceBuilder.fetchSource(new String[]{"name", "grade", "charge","pic"}, newString[]{});//设置搜索源对象searchRequest.source(searchSourceBuilder);//执行搜索SearchResponse searchResponse = null;try {searchResponse = restHighLevelClient.search(searchRequest);} catch (IOException e) {e.printStackTrace();}//获取搜索结果SearchHits hits = searchResponse.getHits();SearchHit[] searchHits = hits.getHits(); //获取最优结果Map<String,CoursePub> map = new HashMap<>();for (SearchHit hit: searchHits) {//从搜索结果中取值并添加到coursePub对象Map<String, Object> sourceAsMap = hit.getSourceAsMap();String courseId = (String) sourceAsMap.get("id");String name = (String) sourceAsMap.get("name");String grade = (String) sourceAsMap.get("grade");String charge = (String) sourceAsMap.get("charge");String pic = (String) sourceAsMap.get("pic");String description = (String) sourceAsMap.get("description");String teachplan = (String) sourceAsMap.get("teachplan");CoursePub coursePub = new CoursePub();coursePub.setId(courseId);coursePub.setName(name);coursePub.setPic(pic);coursePub.setGrade(grade);coursePub.setTeachplan(teachplan);coursePub.setDescription(description);//设置map对象map.put(courseId,coursePub);}return map;} 测试 使用 swagger-ui 或 postman 测试查询课程信息接口。 0x04 前端开发 配置NGINX虚拟主机 学习中心的二级域名为 ucenter.xuecheng.com ,我们在 nginx 中配置 ucenter 虚拟主机。 学成网用户中心server {listen 80;server_name ucenter.xuecheng.com;个人中心location / {proxy_pass http://ucenter_server_pool;} } 前端ucenterupstream ucenter_server_pool{server 127.0.0.1:7081 weight=10;server 127.0.0.1:13000 weight=10;} 在学习中心要调用搜索的 API,使用 Nginx 解决代理,如下图: 在 ucenter 虚拟主机下配置搜索 Api 代理路径 后台搜索(公开api)upstream search_server_pool{server 127.0.0.1:40100 weight=10;} 学成网用户中心server {listen 80;server_name ucenter.xuecheng.com;个人中心location / {proxy_pass http://ucenter_server_pool;}后端搜索服务location /openapi/search/ {proxy_pass http://search_server_pool/search/;} } 前端 API 方法 在学习中心 xc-ui-pc-leanring 对课程信息的查询属于基础常用功能,所以我们将课程查询的 api 方法定义在base 模块下,如下图: 在system.js 中定义课程查询方法: import http from './public'export const course_view = id => {return http.requestGet('/openapi/search/course/getdetail/'+id);} 前端 API 方法调用 在 learning_video.vue 页面中调用课程信息查询接口得到课程计划,将课程计划json 串转成对象。 xc-ui-pc-leanring/src/module/course/page/learning_video.vue 1、定义视图 课程计划 <!--课程计划部分代码--><div class="navCont"><div class="course-weeklist"><div class="nav nav-stacked" v-for="(teachplan_first, index) in teachplanList"><div class="tit nav-justified text-center"><i class="pull-left glyphicon glyphicon-th-list"></i>{ {teachplan_first.pname} }<i class="pull-right"></i></div><li v-if="teachplan_first.children!=null" v-for="(teachplan_second, index) in teachplan_first.children"><i class="glyphicon glyphicon-check"></i><a :href="url" @click="study(teachplan_second.id)">{ {teachplan_second.pname} }</a></li><!-- <div class="tit nav-justified text-center"><i class="pull-left glyphicon glyphicon-th-list"></i>第一章<i class="pull-right"></i></div><li ><i class="glyphicon glyphicon-check"></i><a :href="url" >第一节</a></li>--><!--<li><i class="glyphicon glyphicon-unchecked"></i>为什么分为A、B、C部分</li>--></div></div></div> 课程名称 <div class="top text-center">{ {coursename} }</div> 定义数据对象 data() {return {url:'',//当前urlcourseId:'',//课程idchapter:'',//章节Idcoursename:'',//课程名称coursepic:'',//课程图片teachplanList:[],//课程计划playerOptions: {//播放参数autoplay: false,controls: true,sources: [{type: "application/x-mpegURL",src: ''}]},} } 在 created 钩子方法中获取课程信息 created(){//当前请求的urlthis.url = window.location//课程idthis.courseId = this.$route.params.courseId//章节idthis.chapter = this.$route.params.chapter//查询课程信息systemApi.course_view(this.courseId).then((view_course)=>{if(!view_course || !view_course[this.courseId]){this.$message.error("获取课程信息失败,请重新进入此页面!")return ;} let courseInfo = view_course[this.courseId]console.log(courseInfo)this.coursename = courseInfo.nameif(courseInfo.teachplan){let teachplan = JSON.parse(courseInfo.teachplan);this.teachplanList = teachplan.children;} })}, 测试 在浏览器请求:http://ucenter.xuecheng.com//learning/4028e581617f945f01617f9dabc40000/0 4028e581617f945f01617f9dabc40000:第一个参数为课程 id,测试时从 ES索引库找一个课程 id 0:第二个参数为课程计划 id,此参数用于点击课程计划播放视频。 如果出现跨域问题,但是确定已经配置了跨域,请尝试结束所以 nginx.exe 的进程 和 清空浏览器缓存。 如果还没有解决?重启电脑试试。 二、学习页面:获取视频播放地址 0x01 需求分析 用户进入在线学习页面,点击课程计划将播放该课程计划对应的教学视频。 业务流程如下: 业务流程说明: 1、用户进入在线学习页面,页面请求搜索服务获取课程信息(包括课程计划信息)并且在页面展示。 2、在线学习请求学习服务获取视频播放地址。 3、学习服务校验当前用户是否有权限学习,如果没有权限学习则提示用户。 4、学习服务校验通过,请求搜索服务获取课程媒资信息。 5、搜索服务请求ElasticSearch获取课程媒资信息。 为什么要请求 ElasticSearch 查询课程媒资信息? 出于性能的考虑,公开查询课程信息从搜索服务查询,分摊 mysql 数据库的访问压力。 什么时候将课程媒资信息存储到 ElasticSearch 中? 课程媒资信息是在课程发布的时候存入 ElasticSearch,因为课程发布后课程信息将基本不再修改。 0x02 课程发布:储存媒资信息 需求分析 课程媒资信息是在课程发布的时候存入 ElasticSearch 索引库,因为课程发布后课程信息将基本不再修改,具体的业务流程如下。 1、课程发布,向课程媒资信息表写入数据。 1)根据课程 id 删除 teachplanMediaPub 中的数据 2)根据课程 id 查询 teachplanMedia 数据 3)将查询到的 teachplanMedia 数据插入到 teachplanMediaPub 中 2、Logstash 定时扫描课程媒资信息表,并将课程媒资信息写入索引库。 数据模型 在 xc_course 数据库创建课程计划媒资发布表: CREATE TABLE teachplan_media_pub (teachplan_id varchar(32) NOT NULL COMMENT '课程计划id',media_id varchar(32) NOT NULL COMMENT '媒资文件id',media_fileoriginalname varchar(128) NOT NULL COMMENT '媒资文件的原始名称',media_url varchar(256) NOT NULL COMMENT '媒资文件访问地址',courseid varchar(32) NOT NULL COMMENT '课程Id',timestamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT'logstash使用',PRIMARY KEY (teachplan_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8 数据模型类如下: package com.xuecheng.framework.domain.course;import lombok.Data;import lombok.ToString;import org.hibernate.annotations.GenericGenerator;import javax.persistence.;import java.io.Serializable;import java.util.Date;@Data@ToString@Entity@Table(name="teachplan_media_pub")@GenericGenerator(name = "jpa-assigned", strategy = "assigned")public class TeachplanMediaPub implements Serializable {private static final long serialVersionUID = -916357110051689485L;@Id@GeneratedValue(generator = "jpa-assigned")@Column(name="teachplan_id")private String teachplanId;@Column(name="media_id")private String mediaId;@Column(name="media_fileoriginalname")private String mediaFileOriginalName;@Column(name="media_url")private String mediaUrl;@Column(name="courseid")private String courseId;@Column(name="timestamp")private Date timestamp;//时间戳} Dao 创建 TeachplanMediaPub 表的 Dao,向 TeachplanMediaPub 存储信息采用先删除该课程的媒资信息,再添加该课程的媒资信息,所以这里定义根据课程 id 删除课程计划媒资方法: public interface TeachplanMediaPubRepository extends JpaRepository<TeachplanMediaPub, String> {//根据课程id删除课程计划媒资信息long deleteByCourseId(String courseId);} 从TeachplanMedia查询课程计划媒资信息 //从TeachplanMedia查询课程计划媒资信息public interface TeachplanMediaRepository extends JpaRepository<TeachplanMedia, String> {List<TeachplanMedia> findByCourseId(String courseId);} Service 编写保存课程计划媒资信息方法,并在课程发布时调用此方法。 1、保存课程计划媒资信息方法 本方法采用先删除该课程的媒资信息,再添加该课程的媒资信息,在 CourseService 下定义该方法 //保存课程计划媒资信息private void saveTeachplanMediaPub(String courseId){//查询课程媒资信息List<TeachplanMedia> byCourseId = teachplanMediaRepository.findByCourseId(courseId);if(byCourseId == null) return; //没有查询到媒资数据则直接结束该方法//将课程计划媒资信息储存到待索引表//删除原有的索引信息teachplanMediaPubRepository.deleteByCourseId(courseId);//一个课程可能会有多个媒资信息,遍历并使用list进行储存List<TeachplanMediaPub> teachplanMediaPubList = new ArrayList<>();for (TeachplanMedia teachplanMedia: byCourseId) {TeachplanMediaPub teachplanMediaPub = new TeachplanMediaPub();BeanUtils.copyProperties(teachplanMedia, teachplanMediaPub);teachplanMediaPubList.add(teachplanMediaPub);}//保存所有信息teachplanMediaPubRepository.saveAll(teachplanMediaPubList);} 2、课程发布时调用此方法 修改课程发布的 coursePublish 方法: ....//保存课程计划媒资信息到待索引表saveTeachplanMediaPub(courseId);//页面urlString pageUrl = cmsPostPageResult.getPageUrl();return new CoursePublishResult(CommonCode.SUCCESS,pageUrl);..... 测试 测试课程发布后是否成功将课程媒资信息存储到 teachplan_media_pub 中,测试流程如下: 1、指定一个课程 2、为课程计划添加课程媒资 3、执行课程发布 4、观察课程计划媒资信息是否存储至 teachplan_media_pub 中 注意:由于此测试仅用于测试发布课程计划媒资信息的功能,可暂时将 cms页面发布的功能暂时屏蔽,提高测试效率。 测试结果如下 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vrzs5589-1595567273126)(https://qnoss.codeyee.com/20200704_15/image7)] 0x03 Logstash:扫描课程计划媒资 Logstash 定时扫描课程媒资信息表,并将课程媒资信息写入索引库。 创建索引 1、创建 xc_course_media 索引 2、并向此索引创建如下映射 POST: http://localhost:9200/xc_course_media/doc/_mapping {"properties" : {"courseid" : {"type" : "keyword"},"teachplan_id" : {"type" : "keyword"},"media_id" : {"type" : "keyword"},"media_url" : {"index" : false,"type" : "text"},"media_fileoriginalname" : {"index" : false,"type" : "text"} }} 索引创建成功 创建模板文件 在 logstach 的 config 目录文件 xc_course_media_template.json 文件路径为 %ES_ROOT_DIR%/logstash6.8.8/config/xc_course_media_template.json %ES_ROOT_DIR% 为 ElasticSearch 和 logstash 的安装目录 内容如下: {"mappings" : {"doc" : {"properties" : {"courseid" : {"type" : "keyword"},"teachplan_id" : {"type" : "keyword"},"media_id" : {"type" : "keyword"},"media_url" : {"index" : false,"type" : "text"},"media_fileoriginalname" : {"index" : false,"type" : "text"} }},"template" : "xc_course_media"} } 配置 mysql.conf 在logstash的 config 目录下配置 mysql_course_media.conf 文件供 logstash 使用,logstash 会根据 mysql_course_media.conf 文件的配置的地址从 MySQL 中读取数据向 ES 中写入索引。 参考https://www.elastic.co/guide/en/logstash/current/plugins-inputs-jdbc.html 配置输入数据源和输出数据源。 input {stdin {} jdbc {jdbc_connection_string => "jdbc:mysql://localhost:3306/xc_course?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC" 数据库信息jdbc_user => "root"jdbc_password => "123123" MYSQL 驱动地址,修改为maven仓库对应的位置jdbc_driver_library => "D:/soft/apache-maven-3.5.4/repository/mysql/mysql-connector-java/5.1.40/mysql-connector-java-5.1.40.jar" the name of the driver class for mysqljdbc_driver_class => "com.mysql.jdbc.Driver"jdbc_paging_enabled => "true"jdbc_page_size => "50000"要执行的sql文件statement_filepath => "/conf/course.sql"statement => "select from teachplan_media_pub where timestamp > date_add(:sql_last_value,INTERVAL 8 HOUR)"定时配置schedule => " "record_last_run => truelast_run_metadata_path => "D:/soft/elasticsearch/logstash-6.8.8/config/xc_course_media_metadata"} } output {elasticsearch {ES的ip地址和端口hosts => "localhost:9200"hosts => ["localhost:9200","localhost:9202","localhost:9203"]ES索引库名称index => "xc_course_media"document_id => "%{teachplan_id}"document_type => "doc"template => "D:/soft/elasticsearch/logstash-6.8.8/config/xc_course_media_template.json"template_name =>"xc_course_media"template_overwrite =>"true"} stdout {日志输出codec => json_lines} } 启动 logstash.bat 启动 logstash.bat 采集 teachplan_media_pub 中的数据,向 ES 写入索引。 logstash.bat -f ../config/mysql_course_media.conf 课程发布成功后,Logstash 会自动参加 teachplan_media_pub 表中新增的数据,效果如下 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ILPBxfXi-1595567273134)(https://qnoss.codeyee.com/20200704_15/image10)] Logstash多实例运行 由于之前我们还启动了一个 Logstash 对课程的发布信息进行采集,所以如果想两个 logstash 实例同时运行,因为每个实例都有一个.lock文件,所以不能使用同一个目录来存放数据,所以我们需要使用 --path.data= 为每个实例指定单独的数据目录,具体的代码如下: 该配置是在windows下进行的 课程发布实例 logstash_start_course_pub.bat @title logstash in course_publogstash.bat -f ..\config\mysql.conf --path.data=../data/course_pub 课程计划媒体发布实例 logstash_start_teachplan_media.bat @title logstash i n teachplan_media_publogstash.bat -f ../config/mysql_course_media.conf --path.data=../data/teachplan_media/ 同时运行效果如下 0x04 搜素服务:查询课程媒资接口 需求分析 搜索服务 提供查询课程媒资接口,此接口供学习服务调用。 Api接口定义 @ApiOperation("根据课程计划查询媒资信息")public TeachplanMediaPub getmedia(String teachplanId); Service 1、配置课程计划媒资索引库等信息 在 application.yml 中配置 xuecheng:elasticsearch:hostlist: ${eshostlist:127.0.0.1:9200} 多个结点中间用逗号分隔course:index: xc_coursetype: docsource_field: id,name,grade,mt,st,charge,valid,pic,qq,price,price_old,status,studymodel,teachmode,expires,pub_time,start_time,end_timemedia:index: xc_course_mediatype: docsource_field: courseid,media_id,media_url,teachplan_id,media_fileoriginalname 2、service 方法开发 在 课程搜索服务 中定义课程媒资查询接口,为了适应后续需求,service 参数定义为数组,可一次查询多个课程计划的媒资信息。 / 根据一个或者多个课程计划id查询媒资信息 @param teachplanIds 课程id @return QueryResponseResult/public QueryResponseResult<TeachplanMediaPub> getmedia(String [] teachplanIds){//设置索引SearchRequest searchRequest = new SearchRequest(media_index);//设置类型searchRequest.types(media_type);//创建搜索源对象SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();//源字段过滤String[] media_index_arr = media_field.split(",");searchSourceBuilder.fetchSource(media_index_arr, new String[]{});//查询条件,根据课程计划id查询(可以传入多个课程计划id)searchSourceBuilder.query(QueryBuilders.termsQuery("teachplan_id", teachplanIds));searchRequest.source(searchSourceBuilder);SearchResponse searchResponse = null;try {searchResponse = restHighLevelClient.search(searchRequest);} catch (IOException e) {e.printStackTrace();}//获取结果SearchHits hits = searchResponse.getHits();long totalHits = hits.getTotalHits();SearchHit[] searchHits = hits.getHits();//数据列表List<TeachplanMediaPub> teachplanMediaPubList = new ArrayList<>();for(SearchHit hit:searchHits){TeachplanMediaPub teachplanMediaPub =new TeachplanMediaPub();Map<String, Object> sourceAsMap = hit.getSourceAsMap();//取出课程计划媒资信息String courseid = (String) sourceAsMap.get("courseid");String media_id = (String) sourceAsMap.get("media_id");String media_url = (String) sourceAsMap.get("media_url");String teachplan_id = (String) sourceAsMap.get("teachplan_id");String media_fileoriginalname = (String) sourceAsMap.get("media_fileoriginalname");teachplanMediaPub.setCourseId(courseid);teachplanMediaPub.setMediaUrl(media_url);teachplanMediaPub.setMediaFileOriginalName(media_fileoriginalname);teachplanMediaPub.setMediaId(media_id);teachplanMediaPub.setTeachplanId(teachplan_id);//将对象加入到列表中teachplanMediaPubList.add(teachplanMediaPub);}//构建返回课程媒资信息对象QueryResult<TeachplanMediaPub> queryResult = new QueryResult<>();queryResult.setList(teachplanMediaPubList);queryResult.setTotal(totalHits);return new QueryResponseResult<TeachplanMediaPub>(CommonCode.SUCCESS,queryResult);} Controller / 根据课程计划id搜索发布后的媒资信息 @param teachplanId @return/@GetMapping(value="/getmedia/{teachplanId}")@Overridepublic TeachplanMediaPub getmedia(@PathVariable("teachplanId") String teachplanId) {//为了service的拓展性,所以我们service接收的是数组作为参数,以便后续开发查询多个ID的接口String[] teachplanIds = new String[]{teachplanId};//通过service查询ES获取课程媒资信息QueryResponseResult<TeachplanMediaPub> mediaPubQueryResponseResult = esCourseService.getmedia(teachplanIds);QueryResult<TeachplanMediaPub> queryResult = mediaPubQueryResponseResult.getQueryResult();if(queryResult!=null&& queryResult.getList()!=null&& queryResult.getList().size()>0){//返回课程计划对应课程媒资return queryResult.getList().get(0);} return new TeachplanMediaPub();} 测试 使用 swagger-ui 和 postman 测试课程媒资查询接口。 三、在线学习:接口开发 0x01 需求分析 根据下边的业务流程,本章节完成前端学习页面请求学习服务获取课程视频地址,并自动播放视频。 0x02 搭建开发环境 1、创建数据库 创建 xc_learning 数据库,学习数据库将记录学生的选课信息、学习信息。 导入:资料/xc_learning.sql 2、创建学习服务工程 参考课程管理服务工程结构,创建学习服务工程: 导入:资料/xc-service-learning.zip 项目工程结构如下 0x03 Api接口 此 api 接口是课程学习页面请求学习服务获取课程学习地址。 定义返回值类型: package com.xuecheng.framework.domain.learning.response;import com.xuecheng.framework.model.response.ResponseResult;import com.xuecheng.framework.model.response.ResultCode;import lombok.Data;import lombok.NoArgsConstructor;import lombok.ToString;@Data@ToString@NoArgsConstructorpublic class GetMediaResult extends ResponseResult {public GetMediaResult(ResultCode resultCode, String fileUrl) {super(resultCode);this.fileUrl = fileUrl;}//媒资文件播放地址private String fileUrl;} 定义接口,学习服务根据传入课程 ID、章节 Id(课程计划 ID)来取学习地址。 @Api(value = "录播课程学习管理",description = "录播课程学习管理")public interface CourseLearningControllerApi {@ApiOperation("获取课程学习地址")public GetMediaResult getMediaPlayUrl(String courseId,String teachplanId);} 0x04 服务端开发 需求分析 学习服务根据传入课程ID、章节Id(课程计划ID)请求搜索服务获取学习地址。 搜索服务注册Eureka 学习服务要调用搜索服务查询课程媒资信息,所以需要将搜索服务注册到 eureka 中。 1、查看服务名称是否为 xc-service-search 注意修改application.xml中的服务名称:spring:application:name: xc‐service‐search 2、配置搜索服务的配置文件 application.yml,加入 Eureka 配置 如下: eureka:client:registerWithEureka: true 服务注册开关fetchRegistry: true 服务发现开关serviceUrl: Eureka客户端与Eureka服务端进行交互的地址,多个中间用逗号分隔defaultZone: ${EUREKA_SERVER:http://localhost:50101/eureka/,http://localhost:50102/eureka/}instance:prefer-ip-address: true 将自己的ip地址注册到Eureka服务中ip-address: ${IP_ADDRESS:127.0.0.1}instance-id: ${spring.application.name}:${server.port} 指定实例idribbon:MaxAutoRetries: 2 最大重试次数,当Eureka中可以找到服务,但是服务连不上时将会重试,如果eureka中找不到服务则直接走断路器MaxAutoRetriesNextServer: 3 切换实例的重试次数OkToRetryOnAllOperations: false 对所有操作请求都进行重试,如果是get则可以,如果是post,put等操作没有实现幂等的情况下是很危险的,所以设置为falseConnectTimeout: 5000 请求连接的超时时间ReadTimeout: 6000 请求处理的超时时间 3、添加 eureka 依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring‐cloud‐starter‐netflix‐eureka‐client</artifactId></dependency> 4、修改启动类,在class上添加如下注解: @EnableDiscoveryClient 搜索服务客户端 在 学习服务 创建搜索服务的客户端接口,此接口会生成代理对象,调用搜索服务: package com.xuecheng.learning.client;import com.xuecheng.framework.domain.course.TeachplanMediaPub;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;@FeignClient(value = "xc‐service‐search")public interface CourseSearchClient {@GetMapping(value="/getmedia/{teachplanId}")public TeachplanMediaPub getmedia(@PathVariable("teachplanId") String teachplanId);} 自定义错误代码 我们在 com.xuecheng.framework.domain.learning.response 包下自定义一个错误消息模型 package com.xuecheng.framework.domain.learning.response;import com.xuecheng.framework.model.response.ResultCode;import lombok.ToString;@ToStringpublic enum LearningCode implements ResultCode {LEARNING_GET_MEDIA_ERROR(false,23001,"学习中心获取媒资信息错误!");//操作代码boolean success;//操作代码int code;//提示信息String message;private LearningCode(boolean success, int code, String message){this.success = success;this.code = code;this.message = message;}@Overridepublic boolean success() {return success;}@Overridepublic int code() {return code;}@Overridepublic String message() {return message;} } 该消息模型基于 ResultCode 来实现,代码如下 package com.xuecheng.framework.model.response;/ Created by mrt on 2018/3/5. 10000-- 通用错误代码 22000-- 媒资错误代码 23000-- 用户中心错误代码 24000-- cms错误代码 25000-- 文件系统/public interface ResultCode {//操作是否成功,true为成功,false操作失败boolean success();//操作代码int code();//提示信息String message(); 从 ResultCode 中我们可以看出,我们约定了用户中心的错误代码使用 23000,所以我们定义的一些错误信息的代码就从 23000 开始计数。 Service 在学习服务中定义 service 方法,此方法远程请求课程管理服务、媒资管理服务获取课程学习地址。 package com.xuecheng.learning.service.impl;import com.netflix.discovery.converters.Auto;import com.xuecheng.framework.domain.course.TeachplanMediaPub;import com.xuecheng.framework.domain.learning.response.GetMediaResult;import com.xuecheng.framework.exception.ExceptionCast;import com.xuecheng.framework.model.response.CommonCode;import com.xuecheng.learning.client.CourseSearchClient;import com.xuecheng.learning.service.LearningService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Servicepublic class LearningServiceImpl implements LearningService {@AutowiredCourseSearchClient courseSearchClient;/ 远程调用搜索服务获取已发布媒体信息中的url @param courseId 课程id @param teachplanId 媒体信息id @return/@Overridepublic GetMediaResult getMediaPlayUrl(String courseId, String teachplanId) {//校验学生权限,是否已付费等//远程调用搜索服务进行查询媒体信息TeachplanMediaPub mediaPub = courseSearchClient.getmedia(teachplanId);if(mediaPub == null) ExceptionCast.cast(CommonCode.FAIL);return new GetMediaResult(CommonCode.SUCCESS, mediaPub.getMediaUrl());} } Controller 调用 service 根据课程计划 id 查询视频播放地址: @RestController@RequestMapping("/learning/course")public class CourseLearningController implements CourseLearningControllerApi {@AutowiredLearningService learningService;@Override@GetMapping("/getmedia/{courseId}/{teachplanId}")public GetMediaResult getMediaPlayUrl(@PathVariable String courseId, @PathVariable String teachplanId) {//获取课程学习地址return learningService.getMedia(courseId, teachplanId);} } 测试 使用 swagger-ui 或postman 测试学习服务查询课程视频地址接口。 0x05 前端开发 需求分析 需要在学习中心前端页面需要完成如下功能: 1、进入课程学习页面需要带上 课程 Id参数及课程计划Id的参数,其中 课程 Id 参数必带,课程计划 Id 可以为空。 2、进入页面根据 课程 Id 取出该课程的课程计划显示在右侧。 3、进入页面后判断如果请求参数中有课程计划 Id 则播放该章节的视频。 4、进入页面后判断如果 课程计划id 为0则需要取出本课程第一个 课程计划的Id,并播放第一个课程计划的视频。 进入到模块 xc-ui-pc-leanring/src/module/course api方法 let sysConfig = require('@/../config/sysConfig')let apiUrl = sysConfig.xcApiUrlPre;/获取播放地址/export const get_media = (courseId,chapter) => {return http.requestGet(apiUrl+'/api/learning/course/getmedia/'+courseId+'/'+chapter);} 配置代理 在 Nginx 中的 ucenter.xuecheng.com 虚拟主机中配置 /api/learning/ 的路径转发,此url 请转发到学习服务。 学习服务upstream learning_server_pool{server 127.0.0.1:40600 weight=10;}学成网用户中心server {listen 80;server_name ucenter.xuecheng.com;个人中心location / {proxy_pass http://ucenter_server_pool;}后端搜索服务location /openapi/search/ {proxy_pass http://search_server_pool/search/; }学习服务location ^~ /api/learning/ {proxy_pass http://learning_server_pool/learning/;} } 视频播放页面 1、如果传入的课程计划id为0则取出第一个课程计划id 在 created 钩子方法中完成 created(){//当前请求的urlthis.url = window.location//课程idthis.courseId = this.$route.params.courseId//章节idthis.chapter = this.$route.params.chapter//查询课程信息systemApi.course_view(this.courseId).then((view_course)=>{if(!view_course || !view_course[this.courseId]){this.$message.error("获取课程信息失败,请重新进入此页面!")return ;}let courseInfo = view_course[this.courseId]console.log(courseInfo)this.coursename = courseInfo.nameif(courseInfo.teachplan){console.log("准备开始播放视频")let teachplan = JSON.parse(courseInfo.teachplan);this.teachplanList = teachplan.children;//开始学习if(this.chapter == "0" || !this.chapter){//取出第一个教学计划this.chapter = this.getFirstTeachplan();console.log("第一个教学计划id为 ",this.chapter);this.study(this.chapter);}else{this.study(this.chapter);} }})}, 取出第一个章节 id,用户未输入课程计划 id 或者输入为 0 时,播放第一个。 //取出第一个章节getFirstTeachplan(){for(var i=0;i<this.teachplanList.length;i++){let firstTeachplan = this.teachplanList[i];//如果当前children存在,则取出第一个返回if(firstTeachplan.children && firstTeachplan.children.length>0){let secondTeachplan = firstTeachplan.children[0];return secondTeachplan.id;} }return ;}, 开始学习: //开始学习study(chapter){// 获取播放地址courseApi.get_media(this.courseId,chapter).then((res)=>{if(res.success){let fileUrl = sysConfig.videoUrl + res.fileUrl//播放视频this.playvideo(fileUrl)}else if(res.message){this.$message.error(res.message)}else{this.$message.error("播放视频失败,请刷新页面重试")} }).catch(res=>{this.$message.error("播放视频失败,请刷新页面重试")});}, 2、点击右侧课程章节切换播放 在原有代码基础上添加 click 事件,点击调用开始学习方法(study)。 <li v‐if="teachplan_first.children!=null" v‐for="(teachplan_second, index) inteachplan_first.children"><i class="glyphicon glyphicon‐check"></i><a :href="url" @click="study(teachplan_second.id)">{ {teachplan_second.pname} }</a></li> 3、地址栏路由url变更 这里需要注意一个问题,在用户点击课程章节切换播放时,地址栏的 url 也应该同步改变为当前所选择的课程计划 id 4、在线学习按钮 将 learnstatus 默认更改为 1,这样就能显示出马上学习的按钮,方便我们后续的集成测试。 文件路径为 xc-ui-pc-static-portal/include/course_detail_dynamic.html 部分代码块如下 <script>var body= new Vue({ //创建一个Vue的实例el: "body", //挂载点是id="app"的地方data: {editLoading: false,title:'测试',courseId:'',charge:'',//203001免费,203002收费learnstatus: 1 ,//课程状态,1:马上学习,2:立即报名、3:立即购买course:{},companyId:'template',company_stat:[],course_stat:{"s601001":"","s601002":"","s601003":""} }, 简单的测试 访问在线学习页面:http://ucenter.xuecheng.com//learning/课程id/课程计划id 通过 url 传入两个参数:课程id 和 课程计划id 如果没有课程计划则传入0 测试项目如下: 1、传入正确的课程id、课程计划id,自动播放本章节的视频 2、传入正确的课程id、课程计划id传入0,自动播放第一个视频 3、传入错误的课程id 或 课程计划id,提示错误信息。 4、通过右侧章节目录切换章节及播放视频。 访问: http://ucenter.xuecheng.com//learning/4028e58161bcf7f40161bcf8b77c0000/4028e58161bd18ea0161bd1f73190008 传入正确的课程id、课程计划id,自动播放本章节的视频 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ef0xxym7-1595567273153)(https://qnoss.codeyee.com/20200704_15/image17)] 传入正确的课程id、课程计划id传入0,自动播放第一个视频 访问 http://ucenter.xuecheng.com//learning/4028e58161bcf7f40161bcf8b77c0000/0 识别出第一个课程计划的 id 需要注意的是这里的 chapter 参数是我自己在 study 函数里加上去的,可以忽略。 传入错误的课程id或课程计划id,提示错误信息。 通过右侧章节目录切换章节及播放视频。 点击章节即可播放,但是点击制定章节后 url 没有发生改变,这个问题暂时还没有解决,关注笔记后面的内容。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TOGdxwb4-1595567273158)(https://qnoss.codeyee.com/20200704_15/image20)] 完整的测试 准备工作 启动 RabbitMQ,启动 Logstash、ElasticSearch 建议把所有后端服务都开起来 启动 前端静态门户、启动 nginx 、启动课程管理前端 我们整理一下测试的流程 上传两个媒资视频文件,用于测试 进入到课程管理,为课程计划选择媒资信息 发布课程,等待 logstash 将数据采集到 ElasticSearch 的索引库中 进入学成网主页,点击课程,进入到搜索门户页面 搜索课程,进入到课程详情页面 点击开始学习,进入到课程学习页面,选择课程计划中的一个章节进行学习。 1、上传文件 首先我们使用之前开发的媒资管理模块,上传两个视频文件用于测试。 第一个文件上传成功 一些问题 在上传第二个文件时,发生了错误,我们来检查一下问题出在了哪里 在媒体服务的控制台中可以看到,在 mergeChunks 方法在校验文件 md5 时候抛出了异常 我们在 MD5 校验这里打个断点,重新上传文件,分析一下问题所在。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OpEMZGI8-1595567273166)(https://qnoss.codeyee.com/20200704_15/image23)] 单步调试后发现,合并文件后的MD5值与用户上传的源文件值不相等 方案1:删除本地分块文件重新尝试上传 考虑到可能是在用户上传完 视频的分块文件时发生了一些问题,导致合并文件后与源文件的大小不等,导致MD5也不相同,这里我们把这个视频上传到本地的文件全部删除,在媒资上传页面重新上传文件。 对比所有分块文件的字节大小和本地源文件的大小,完全是相等的 删除所有文件后重新上传,md5值还是不等,考虑从调试一下文件合并的代码。 方案2:检查前端提交的MD5值是否正确 在查阅是否有其他的MD5值获取方案时,发现了一个使用 windows 本地命令获取文件MD5值的方法 certutil -hashfile .\19-在线学习接口-集成测试.avi md5 惊奇的发现,TM的原来是前端那边转换的MD5值不正确,后端这边是没有问题的。 从前面的图可以看出,本地和后端转换的都是以一个 f6f0 开头的MD5值 那么问题就出现在前端了,还需要花一些时间去分析一下,这里暂时就先告一段落,因为上传了几个文件测试中只有这一个文件出现了问题。 2、为课程计划选择媒资信息 进入到一个课程的管理页面 http://localhost:12000//course/manage/baseinfo/4028e58161bcf7f40161bcf8b77c0000 将刚才我们上传的媒资文件的信息和课程计划绑定 选择效果如下 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-epKaqzCD-1595567273178)(https://qnoss.codeyee.com/20200704_15/image29)] 2、发布课程,等待 logstash 从 course_pub 以及 teachplan_media_pub 表中采集数据到 ElasticSearch 当中 发布成功后,我们可以从 teachplan_media_pub 表中看到刚才我们发布的媒资信息 再观察 Logstash 的控制台,发现两个 Logstash 的实例都对更新的课程发布信息进行了采集 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hTUve2ik-1595567273183)(https://qnoss.codeyee.com/20200704_15/image32)] 3、前端门户测试 打开我们的门户主站 http://www.xuecheng.com/ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4wZe9R84-1595567273185)(https://qnoss.codeyee.com/20200704_15/image33)] 点击导航栏的课程,进入到我们的搜索门户页面 如果无法进入到搜索门户,请检查你的 xc-ui-pc-portal 前端工程是否已经启动 进入到搜索门户后,可以看到一些初始化时搜索的课程数据,默认是搜索第一页的数据,每页2个课程。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BJ1AKoJb-1595567273187)(https://qnoss.codeyee.com/20200704_15/image34)] 我们可以测试搜索一下前面我们选择媒资信息时所用的课程 点击课程,进入到课程详情页面,然后再点击开始学习。 点击马上学习后,会进入到该课程的在线学习页面,默认自动播放我们第一个课程计划中的视频。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tcuLWnf2-1595567273193)(https://qnoss.codeyee.com/20200704_15/image37)] 我们可以在右侧的目录中选择第二个课程计划,会自动播放所选的课程计划所对应的媒资视频播放地址,该 播放地址正是我们刚才通过 Logstash 自动采集到 ElasticSearch 的索引信息,效果图如下 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cvi9Dr0Y-1595567273195)(https://qnoss.codeyee.com/20200704_15/image38)] 四、待完善的一些功能 课程发布前,校验课程计划里面是否包含二级课程计划 课程发布前,校验课程计划信息里面是否全部包含媒资信息 删除媒资信息,并且同步删除ES中的索引 在获取该课程的播放地址时校验用户的合法、 在线学习页面,点击右侧目录中的课程计划同时改变url中的课程计划地址 视频文件 19-在线学习接口-集成测试.avi 前端上传时提交的MD5值不正确 😁 认识作者 作者:👦 LCyee ,全干型代码🐕 自建博客:https://www.codeyee.com 记录学习以及项目开发过程中的笔记与心得,记录认知迭代的过程,分享想法与观点。 CSDN 博客:https://blog.csdn.net/codeyee 记录和分享一些开发过程中遇到的问题以及解决的思路。 欢迎加入微服务练习生的队伍,一起交流项目学习过程中的一些问题、分享学习心得等,不定期组织一起刷题、刷项目,共同见证成长。 本篇文章为转载内容。原文链接:https://blog.csdn.net/codeyee/article/details/107558901。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-12-16 12:41:01
73
转载
转载文章
...联系我们,我们将第一时间进行核实并删除相应内容。 文章目录 一、处理不信任的SSL证书的网站 二、cookie 三、session 一、处理不信任的SSL证书的网站 SSL证书 数字证书的一种 SSL服务器证书 遵守SSL协议 具有服务器身份验证和数据传输加密功能 在爬虫时可能会遇到这样的报错(SSLError)这说明我们要爬取的网站没有SSL证书 处理:res = requests.get(url,verify=False) 二、cookie 通过记录用户信息来确定身份 1 模拟登陆 人人网保持登陆状态import requestsurl = 'http://www.renren.com/976686556/profile' 个人主界面headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36','Cookie':'anonymid=knvqe21amc6ghy; depovince=ZGQT; _r01_=1; taihe_bi\_sdk_uid=c2bd353cea6830a73eb74760fbc9fd5c; taihe_bi_sdk_session=9a91c\62f18e74ee26c3145bb49b4eb9e; ick_login=286c45d0-e571-4fb7-918a-46a9706\18110; first_login_flag=1; ln_uact=17315371375; ln_hurl=http://head.xiao\nei.com/photos/0/0/men_main.gif; wp_fold=0; jebecookies=ee811760-7bc0-43a9-\883c-0d041cb1baf0|||||; _de=A4C6B1A20CD5F525F9DA27654C2D2FDA; p=f5239823cd0af743a5f015652568b6036; t=42783075a815b6cef9f651ca18ff5c166; societyguester=42783075a815b6cef9f651ca18ff5c166; id=976686556; xnsid=f72459d7; ver=7.0; loginfrom=null'}res = requests.get(url,headers=headers) res 响应对象 html = res.textwith open('rr.html','w',encoding='utf-8') as file_obj:file_obj.write(res.text) 2 反反爬机制 12306查票import requests import json json.loads -- json类型的str -> python类型的字典def query():headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36','Cookie':'_uab_collina=159490169403897938828076; JSESSIONID=090F384AC50BE0F1AFA3892BE3F6DBE9; _jc_save_wfdc_flag=dc; _jc_save_fromStation=%u957F%u6C99%2CCSQ; _jc_save_toStation=%u5317%u4EAC%2CBJP; RAIL_DEVICEID=bbXqzYOPTc-SPgujxnGkCBr9t3sq0JQoMSYUdg-FxjyQ5IkfcPCNoreXmBAIh2HSrM9Z9awDR5onIQwy4EZ8pAhaGXWYBAH6etIlFc4dyxLudz525GAcRgVX5HLIxOE1orODUNSb9wvTBAJptPms1z5Pz5K6FXES; RAIL_EXPIRATION=1619479086609; _jc_save_toDate=2021-04-23; BIGipServerpool_passport=182714890.50215.0000; route=6f50b51faa11b987e576cdb301e545c4; _jc_save_fromDate=2021-04-26; BIGipServerportal=3067347210.16671.0000; BIGipServerotn=1725497610.50210.0000'}response = requests.get('https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2021-\04-26&leftTicketDTO.from_station=CSQ&leftTicketDTO.to_station=BJP&purpose_codes=ADULT',headers=headers) print(response.content.decode('utf-8'))return response.json()['data']['result']for i in query(): print(i)tem_list = i.split('|') 定义一个标记 给每个数据做个标记 j = 0 技术特别 for n in tem_list: print(j,n) j += 1 通过以上的测试我们知道了 列出是下标索引为3的数据 软卧是下标索引为23的数据if tem_list[23] != '无' and tem_list[23] != '':print(tem_list[3],'有票',tem_list[23])else:print(tem_list[3],'无票') 三、session Session与cookie功能效果相同。Session与Cookie的区别在于Session是记录在服务端的,而Cookie是记录在客户端的。 由于cookie 是存在用户端,而且它本身存储的尺寸大小也有限,最关键是用户可以是可见的,并可以随意的修改,很不安全。那如何又要安全,又可以方便的全局读取信息呢?于是,这个时候,一种新的存储会话机制:session 诞生了 突破12306验证码import requestsreq = requests.session() 保持会话def login(): 笔记本 win7 python3.6 获取验证码图片pic_response = req.get('https://kyfw.12306.cn/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand')codeImage = pic_response.contentfn = open('code2.png','wb')fn.write(codeImage)fn.close() 从验证码图片的左上角 (0,0)codeStr = input('请输入验证码坐标:')headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36'}data = {'answer': codeStr,'rand': 'sjrand','login_site': 'E'}response = req.post('https://kyfw.12306.cn/passport/captcha/captcha-check',data=data,headers=headers)print(response.text)login() base64伪加密 根本不算是一种加密算法 只不过它的数据看上去更像密文而已 64个字符来表示任意的二进制数据的方法 使用 A-Z A-Z 0 - 9 + / 这64个字符进行加密 import base64url = '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'img_data = base64.b64decode(url) 返回的是二进制数据print(type(img_data))fn = open('code.png','wb')fn.write(img_data)fn.close()'''我们打开了一个有base64加密的图片数据''' 本篇文章为转载内容。原文链接:https://blog.csdn.net/httpsssss/article/details/116136614。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-03-01 12:40:55
563
转载
HTML
...息展示,一些具有操作系统的功能开始出现在前端,如上述HTML关机程序就是一个例子。然而,这引发了一系列关于Web安全的问题。尽管这种技术在某些场景下可能有用,比如用于服务器维护或应急关闭,但在一般用户环境中,它可能会带来潜在的安全风险。 首先,允许网页直接执行关机命令可能会被恶意利用,黑客可以通过恶意网站或恶意软件利用这一漏洞对用户的电脑进行远程控制。其次,未经授权的关机操作可能导致数据丢失或系统不稳定。此外,这种功能可能会违背用户隐私和预期行为,因为用户可能期望他们的电脑在没有明确指示的情况下不会被随意关机。 因此,Web开发者在设计这类功能时,必须充分考虑到安全性。例如,可以采用沙箱模式限制JavaScript的权限,或者提供更安全的API接口供用户控制。同时,教育用户识别和避免潜在的安全威胁也至关重要。在未来,随着浏览器和Web标准的进步,我们期待看到更加精细的权限管理和更严格的安全规范,以确保此类功能既能提升用户体验,又能在保障安全的前提下得以实现。
2024-04-14 15:58:57
571
程序媛
JQuery
...dow DOM的广泛应用也为DOM元素的获取与操作提供了新的思路。Shadow DOM允许开发者封装组件内部结构和样式,通过其提供的shadowRoot接口,可以更精准地定位到组件内部特定属性的元素。 另外,在前端框架如React、Vue.js中,它们采用虚拟DOM技术来优化元素操作性能,虽然查询元素的方式与jQuery有所不同,但同样支持属性选择器式的逻辑,比如在React中可以通过refs或context API结合useState、useRef等 hooks 来实现类似功能。 同时,无障碍性(Accessibility)的重要性日益凸显,开发者在根据元素属性获取元素时,不仅要考虑技术实现,还要关注是否符合WCAG 2.1等无障碍标准,确保所有用户都能顺畅地与页面交互。 总的来说,无论是在jQuery还是原生JavaScript,乃至现代前端框架中,依据元素属性获取元素都是一个核心且常青的话题。随着Web技术的日新月异,我们应当持续关注和学习最新的DOM操作方法,以提升用户体验和开发效率。
2023-07-19 14:26:01
372
编程狂人
Maven
...中,情感智能是指AI系统理解和模拟人类情绪、感知他人情绪状态并据此做出适当反应的能力。这一领域涵盖了对情绪的识别、理解、生成与表达等多个层次的技术挑战,目前AI尚无法达到与人类相同水平的情感理解及自然流露。 创造性思维 , 创造性思维是人工智能试图模拟的一种高级认知能力,它涉及独立思考、创新解决问题和产生新颖且有价值的想法或作品的过程。当前AI虽然能在一定程度上进行文本创作,但还无法像人一样具备深度联想、灵感闪现以及基于复杂情境构建独特观点的创造性思维过程。 GPT-3模型 , GPT-3(Generative Pre-training Transformer 3)是由OpenAI开发的一款先进的预训练语言模型,通过大规模无监督学习和深度神经网络架构,能够在各种任务中生成高质量的文本内容。尽管GPT-3在某些场景下可以生成看似具有情感色彩和创造性的文本片段,但其仍受限于算法本身的机制,无法真正实现人类般深层次的情感理解和创新思考。
2023-12-17 20:55:11
58
山涧溪流_t
Docker
...3 COPY . /应用 WORKDIR /应用 CMD ["python3", "app.py"] 可以看到,这个Dockerfile首先从ubuntu:latest镜像作为出发点,然后装置了Python 3,接着粘贴了此刻文件夹下的所有文件到/应用目录,并且设置/应用目录为工作目录。最后,运行了一个Python 3应用程序。这个Dockerfile是一个简单的范例,你可以根据自己的需求进行修改。 除了Dockerfile之外,Docker文件夹中还包含了其他必要的文件,比如.dockerignore文件,用来指定哪些文件不需要被粘贴到Docker容器中;以及docker-compose.yml文件,用来设定多个Docker容器之间的关系。 总的来说,Docker文件夹是Docker应用程序的重要组成部分,它能够帮助我们快速制作、操控和安置Docker容器。
2024-04-07 16:13:15
555
电脑达人
Java
...以进一步探索更广阔的应用场景和实践策略。近期,随着JDK 17的发布,对内存管理和字符串处理的优化更加凸显了正确使用“==”和equals()方法的重要性。例如,在JDK 17引入的String类的内部优化中,对于相同的字符串字面量,其“==”比较的结果在更多情况下将表现为true,这是由于对字符串常量池进行了更为高效的管理。 此外,对于自定义类的对象比较,不仅需要重写equals()方法以实现内容比较,还应遵循约定,同时重写hashCode()方法以保持equals()和hashCode()的一致性原则。这在诸如HashMap、HashSet等集合类的使用场景中至关重要,因为这些类会依赖hashCode()来快速定位元素,而equals()则用于最终确定元素是否相等。 实践中,对于复杂的对象结构,如多层嵌套的对象属性,可以采用Apache Commons Lang库中的EqualsBuilder和HashCodeBuilder工具类进行深度内容比较。这些工具提供了链式调用的方式,能确保即使对象结构复杂也能准确地判断内容是否相等,从而避免因属性遗漏而导致的比较错误。 总之,理解并有效运用Java中的对象比较方式是编程过程中的基石之一,它不仅关乎程序逻辑的准确性,也在很大程度上影响着应用程序的性能与健壮性。紧跟技术发展趋势,结合实际项目需求,灵活选择和定制合适的比较策略,是每个Java开发者不断提升技能的重要环节。
2023-06-27 10:13:01
314
键盘勇士
Python
...的链接的功能。在实际应用中,这种模仿 - 模拟点击的方式可以用于各种自动化 - 自动化的操作,比如通过点击来实现站点 - 网页的自动登录,自动发送邮件等等。
2024-05-01 16:24:58
245
编程狂人
JQuery
...,但在日益复杂的前端应用场景下,结合最新技术和最佳实践来优化按钮交互逻辑,已成为提升网站品质与用户体验的关键所在。
2023-06-09 14:51:42
158
键盘勇士
CSS
...下划线状点划线,可以应用text-decoration属性。可以将属性设定配置为“underline”,代表下划线,而属性设定为“dotted”,代表点划线。以下是示例程序: 这是一段带下划线状点划线的文字。 在pre标签中配置文字下划线状点划线: 如果希望在pre标签中配置文字下划线状点划线,一种常见的方法是应用border-bottom属性。以下是示例程序: 这是一段带下划线状点划线的文字。 需要注意的是,在border-bottom属性中配置样式时,要指定边框的宽度和样式。在以上示例中,我们将边框宽度配置为1像素,样式配置为“dotted”。 综上所述,通过text-decoration和border-bottom属性,我们可以轻松地达成网页中文字下划线状点划线的效果。同时,我们还可以通过p标签和pre标签的调用方式来使代码更加规范和易读。
2023-06-30 08:06:33
403
代码侠
Redis
...工智能的设计、开发、应用过程中应遵循的一系列道德原则和行为规范,旨在确保AI技术的安全、公平、透明以及对人类尊严、隐私权和自主权的尊重。文中提到的AI伦理问题主要体现在AI生成内容的准确性和公正性,以及依赖大数据训练可能带来的信息泄露风险等方面。
2023-12-27 18:58:01
420
月影清风_t
CSS
...,它确定了当多个格式应用于同一个组件时哪个格式将被应用。当有多个格式同时应用于同一个组件时,这些格式将根据它们的重要性进行排列,重要性高的格式将取代重要性低的格式。 重要性的计算规则如下: 1. 使用!important定义的格式重要性最高,无法被取代。 2. 根据选择器的类型、ID特性、类特性和组件名依次计算,每多出一个ID特性或类特性,重要性加100,每多出一个组件名,重要性加10,选择器类型不计算重要性。 3. 当多个选择器重要性相同时,后定义的格式将取代先定义的格式。 例如,以下CSS代码中有两个格式应用于同一个组件: p { color: red; font-size: 14px; } content p { color: blue; } 其中第一个格式选择器为p,第二个格式选择器为content p。由于第二个格式选择器中包含了ID特性,因此它的重要性比第一个格式选择器高100,即总重要性为101。因此在应用格式时,组件的文字颜色为蓝色。 需要注意的是,CSS格式选择器的重要性只确定了格式的应用顺序,不代表效果的重要性。在编写CSS格式表时,应根据页面需求和设计风格合理使用格式,而不是纠结于重要性的细节。
2023-11-06 08:37:41
535
键盘勇士
CSS
...效的轮播效果。在实际应用中,设计师和开发者可以根据项目需求,结合JavaScript进行更复杂的交互控制,比如自定义动画过渡、触摸滑动切换等。 值得注意的是,为了保证不同设备和屏幕尺寸下的兼容性和显示效果,开发人员在实现横向铺满轮播图时还需关注响应式设计原则,运用媒体查询(Media Queries)调整轮播图在移动端或其他小屏设备上的布局和行为。 此外,随着Web性能优化理念的普及,如何在保证视觉效果的同时减少资源加载和渲染负担,也成为衡量一个优秀轮播图组件的重要指标。通过懒加载、预加载等技术手段,以及对图片大小、格式的合理选择,可以使CSS横向铺满轮播图在提升用户体验的同时,也能兼顾页面性能表现。
2023-01-15 14:17:14
469
算法侠
VUE
...)等Vue高级特性的应用场景,帮助开发者提升组件化开发效率。 另外,InfoQ的一篇报道《Vue.js在大型项目中的CSS类管理策略》也值得一看,文中不仅回顾了v-bind:class的基本用法,还分享了一些实际项目中如何结合模块化、CSS预处理器等工具进行复杂场景下的class动态管理,这对于面临大规模应用架构挑战的前端开发者具有很高的参考价值。 最后,Vue社区的一些教程如"Vue Conditional Classes: The Complete Guide",提供了大量实战案例,引导读者逐步掌握条件class的各种绑定技巧,包括但不限于基于状态切换、事件驱动、以及与其他Vue指令如v-if、v-for等协同工作的方法,为读者深化Vue技能树提供有力支撑。
2023-07-15 17:19:02
197
键盘勇士
JSON
...务和API接口的广泛应用,JSON作为数据交换的标准格式,其解析与操作的重要性日益凸显。 例如,2023年5月,Node.js社区发布了一篇关于优化JSON性能的文章,其中详述了如何利用最新版本V18中的JSON.parse()方法的新特性提高大数据量JSON解析速度。通过引入新的Streaming API和改进的内存管理机制,开发者可以更高效地处理大规模JSON数据流,并动态获取嵌套数组或对象的名字及其对应值。 另外,对于那些需要深度访问JSON结构的应用场景,如配置文件解析、复杂状态管理等,JavaScript提供了诸如Lodash这样的工具库,提供了诸如_.get()、_.set()等便捷方法,使得根据路径字符串动态获取或设置JSON任意层级的数据成为可能,大大提升了开发效率及代码可读性。 同时,针对安全性考量,在实际项目中处理JSON时应确保进行有效的数据验证和错误处理,防止因恶意构造或意外损坏的JSON数据导致的安全漏洞。例如,使用AJV等JSON Schema验证库,可以在数据解析前对其进行严格校验,从而降低潜在风险。 综上所述,对JSON数组名值获取的基础理解是前端乃至全栈开发者的必备技能之一,而随着技术发展和安全需求的提升,掌握更多先进的JSON处理策略与工具将为开发者应对各种复杂应用场景提供有力支持。
2023-10-30 12:28:39
512
编程狂人
HTML
...之一。随着现代Web应用日趋复杂和交互性增强,这一需求变得更加普遍且重要。近期,Vue.js、React和Angular等主流前端框架在模板渲染方面提供了更为强大且灵活的解决方案。 例如,Vue.js采用MVVM(Model-View-ViewModel)模式,并支持模板语法,开发者可以直接在HTML中插入可响应的数据绑定表达式,如{ {name} }和v-bind指令,框架会自动将其转化为对应的HTML字符串,实现数据与视图的实时同步更新。 同时,React推崇JSX语法,它允许开发者直接在JavaScript中编写类似HTML的结构,通过Babel编译器将其转化为React.createElement函数调用序列,最终生成HTML字符串。这种将模板与逻辑紧密耦合的方式有利于提升代码的可维护性和复用性。 深入研究,还可以发现诸如lit-html这样的轻量级库,它利用模板字面量和HTML模板化功能,结合高效的差异更新算法,在保证性能的同时简化了将JavaScript转为HTML字符串的过程。 总之,在当前前端开发领域,将JavaScript转换为HTML字符串不仅停留在原始的字符串拼接或模板字符串阶段,而是融入到各类现代框架的核心机制之中,以更高效、便捷的方式服务于复杂的Web应用开发实践。不断跟进和掌握这些新方法和技术趋势,有助于开发者提升项目质量和开发效率。
2023-11-22 11:28:15
476
电脑达人
站内搜索
用于搜索本网站内部文章,支持栏目切换。
知识学习
实践的时候请根据实际情况谨慎操作。
随机学习一条linux命令:
tar -cvzf archive.tar.gz dir
- 压缩目录至gzip格式的tar包。
推荐内容
推荐本栏目内的其它文章,看看还有哪些文章让你感兴趣。
2023-04-28
2023-08-09
2023-06-18
2023-04-14
2023-02-18
2023-04-17
2024-01-11
2023-10-03
2023-09-09
2023-06-13
2023-08-07
2023-03-11
历史内容
快速导航到对应月份的历史文章列表。
随便看看
拉到页底了吧,随便看看还有哪些文章你可能感兴趣。
时光飞逝
"流光容易把人抛,红了樱桃,绿了芭蕉。"