|
pcn1arotx1v6406105715.gif
2 f! J& I1 B: p: \点击上方蓝色字体,关注我们
" E. P+ G# C( N7 \很多计算机专业大学生经常和我交流:毕业设计没思路、不会做、论文不会写、太难了......& q* H5 ^- c& ?; B" H0 O
# y( u4 N5 n- L2 O0 G+ m6 a! D( _1 ]针对这些问题,决定分享一些软、硬件项目的设计思路和实施方法,希望可以帮助大家,也祝愿各位学子,顺利毕业!( g0 q0 b% q( `- M" W' t
6 s% m' ~" e, \
o4kqlxoxeup6406105815.png
* M1 ]5 F! o+ \2 A* [- @* Q. b
5 ]- \8 w( Z+ L o
! h0 y. f8 L) o' t3 x0 ~. J对计算机技术感兴趣的小伙伴请关注公众号:美男子玩编程,公众号优先推送最新技术博文,创作不易,请各位朋友多多点赞、收藏、关注支持~
+ p1 w+ Z. `) y g% h2 }( _- R# U, D( }# S' [
项目专栏:https://blog.csdn.net/m0_38106923/category_11085518.html
! ?1 T* n, E* }5 w0 J, n博客是互联网的一种分享类型的技术产物,但是如何留着用户才是重要的,并不都是所有的功能都会涉及到“高并发”,博客的功能多样性会增强用户的体验,让用户对博客的使用产生依赖性,利用从众心态合理地开发增值功能。
5 F1 w/ |' q" H) W. T% X" P( g" |# w! Y$ z" l
本次系统的开发采用了主流的微服务架构方式,所以把控每个微服务的功能相互独立和完整是“微服务”系统的关键。由于博客的实现比较简单,所以只有涉及到“高并发”的时候需要断点分析即可,下面将介绍博客系统的一些功能。
, G9 S& [4 W; F" j3 O& F& B- {
9 c6 ?- N- y, x* P) e6 A项目工程资源请参见:https://download.csdn.net/download/m0_38106923/87849577
2 P7 c' F( q, K3 ]1 w5 \( P! U( s1
* g7 S7 v% o- R8 G项目功能
6 n: d$ ?% I4 m, t博客基本的功都具备,例如博客的核心功能:博客的发表,删除,浏览,评论,点赞等。除了这些,还可以每天定时签到提升博客的等级,充值会员提升使用体验。
0 o; V- T [% ?1 I" D/ h3 f/ \: Q& H5 f9 g
个人的安全信息也非常重要,所以我单独划分一个微服务中心来实现。同样我在每行重要的代码上都增加了明显的注释,这对于我以后的维护和扩充博客功能可以打下坚实的基础,尽可能地符合软件设计开发原则。$ W5 i' |, {* g) A5 v% a/ q
0 o) ?5 {5 h P) ^7 \关于博客的功能一共涉及到8个微服务中心:用户的个人中心:包含登录、注册、智能验证。用户的安全中心:安全信息、手机与邮箱的基本功能和安全认证的接口。用户的博客中心:发表和管理个人的博客,游客可以浏览公开的博客。用户的文件中心:发表博客需要用到的图片和个人用户的头像。用户的签到中心:博客的签到累计的经验值和签到奖励。用户的会员中心:包含普通会员和超级会员。用户的支付中心:VIP的充值功能,个人钱包功能,账单等。用户的搜索中心:根据摘要或者文章标题的关键字搜索指定的博客。3 g4 s! r6 R# [, E1 L! f- E1 X* V
[/ol]; N2 V. n) t t$ u
2, p% {+ D2 i8 W$ {4 T1 ~
项目架构设计 o: S% M; j$ T; ~9 V
cn3f11wh4ov6406105915.png
# {8 ]8 ^- _& V
+ r% q( {9 _7 V* Q; B- ^
从宏观设计来说,各个微服务中心都是一个moudle,需要注册到一个高可用的微服务注册中心上保证机器信息的正确性。
& N5 U! _3 D6 J- v8 M2 O! C# X; B( }4 G, c; e I( H
从微观运行来说,用户的请求API都经过Zuul,再由Zuul负载均衡分配给需要的微服务中心,所以Zuul也需要高可用保证用户流量可以得到回应。通过zuul网关后,请求通过Feign实现微服务之间数据的交互。
b0 e7 F& A7 x; S7 s* n% _/ ~5 B5 R7 u, R* l
当发生错误运行时,利用Hystrix的回退机制保护系统的稳定运行,不会发生级联占用效应,保证每一个请求API都可以得到响应。当发生网络不可用的情况下,需要触发用户的补偿机制,当出现不可知的错误时,也可以直接管理机器的集群来维护系统的稳定运行。
: e3 V. l; C2 B35 m/ j. N9 L4 ?* M1 B
项目数据库设计- h. d; U. e9 J- w6 j
对于用户来说,需要输入账号和密码,若是不存在可以注册自己的账号和密码。注册的时候提供30分钟填写博客信息的有效时间,不填写则直接登录。需要设置个人的手机和邮箱来绑定安全认证,若想要开通我的钱包则需要实名注册,同样也包含校园认证。丢失了个人信息,则可以用身份证申诉。每天凌晨开始可以开始签到,根据会员的不同增益不同,签到的持续天数不同经验值累加也不同。会员的开通只包含支付宝,账单是每笔消费的记录。核心表是用户博客之类的表,文章均存在数据库,不过ElasticSearch也保存了文章的标题和摘要。每个用户可以评论他人的文章,私密的文章不会出现,会员的文章会出现在会员专区。6 q: y, ]9 C8 {9 m& m
) l+ |; f8 P" i0 R% [. }0 @博客一共含有8个主功能,所以一共涉及用户表,用户信息表,安全表,头像表,签到表,签到奖励表(两种奖励),会员表,钱包表,订单表,博客表,博客分类表,博客标签表,博客图片表,博客的评论表,点赞表和收藏表17个表。
6 t0 _" u/ Y' H7 G& o5 f# f9 P# M
ojmlct0qvvp6406106016.png
! g- R! G# |' m
, {- z3 O' p# r$ z3 @& k1 N: Z3 q44 q" t0 I0 r/ F& ]2 J
项目架构实现
) P. D+ p% r3 [8 Z7 _- i4.1、Vue架构的实现
8 j& T, P2 ^, V: F- Z前端IDE采用的是WebStorm,博客的Vue主要分布如下图所示:
7 j: e0 g) D0 b+ R$ O& J: f! M$ ?! _
phu30erhydl6406106116.png
: ]# W4 l1 H% \2 g! {- H
7 a) h5 f& l: ?
Alert.js是自定义重构代码的漂亮提示框。axios.js是封装好的axios请求HTTP函数,components是Vue文件存储位置,也是Vue的组件,index.js是管理前端路由url的跳转,利用components组件与url的控制。store.js是组件状态管理的文件,由于采用的LocalStorage本地存储所以并不是主用。main.js是全局文件也是最重要的文件,管理Vue的全局配置。static/img中保存的是博客所用的图片。
1 v' F4 M5 p8 w$ _# q2 [( T不管是用IDE创建的项目还是采用常规脚手架创建的vue项目,其项目都会在根目录生成一个package.json文件,这个文件与后端的“pom”相似,这个文件包含所需要的各种包,还包含项目的配置的名称与版本对应。7 q! B1 I" ^+ m9 \5 M* o" L
3 R3 h1 v# k: l$ f1 M
博客项目中的package的完整dependencies代码如以下所示:
8 S8 m8 [/ z" c# z/ Y7 [2 w% o9 e2 ], f* Q! Z
"dependencies": { "axios": "^0.19.0", "bootstrap": "^3.3.7", "echarts": "^4.6.0", "element-ui": "^2.12.0", "font-awesome": "^4.7.0", "github-markdown-css": "^4.0.0", "highlight.js": "^9.18.1", "jquery": "^3.4.1", "marked": "^0.8.0", "mavon-editor": "^2.7.7", "popper.js": "^1.12.5", "showdown": "^1.9.1", "v-charts": "^1.19.0", "view-design": "^4.0.2", "vue": "^2.5.2", "vue-drag-verify": "^1.0.6", "vue-nocaptcha": "^0.2.8", "vue-puzzle-vcode": "^1.1.2", "vue-qr": "^2.2.1", "vue-router": "^3.0.1", "vue-schart": "^2.0.0", "vue-splitpane": "^1.0.6", "vuex": "^3.1.2" }
2 \3 m' K$ R2 [) T! |4.2、SpringCloud架构的实现
$ w# \8 S# o6 V" p. G4 A博客Maven的整体微服务中心实现的结构图如下所示:
+ m5 d3 c; ^3 E2 o6 ]8 O* v Q1 O
rd5s3cjczgj6406106216.png
$ e: P8 }8 H9 s& I4 _" E% ]/ P- M& h: E
SpringCloud是基于Java语言的工具集,SpringCloud具备拿来就用的特性,可以节省开发的配置时间,它可以在Docker等云环境中开发和部署。SpringCloud的组件比较丰富,博客使用了Eureka,Zuul,Feign,Htsrrix,trubine,Zipkin微服务组件。组件可以自由地选择,不过需要解决SpringBoot与SpringCloud之间的版本依赖才能使用。博客涉及到的Java的JDK版本是1.8,SpringBoot的版本是Spring Boot 1.5.9.RELEASE,SpringCloud的版本是Edgware SR4。后端的IDE采用的是IDEA,Maven的版本为3.6.1,任何一个版本的更改都可能会导致兼容不一致。
$ {+ p' i# l' v1 T4 v2 W9 b) l" N; }2 Y
4.3、博客的高可用的实现 g/ R: s+ l+ L/ ?4 k
博客使用了两个Zuul并且注册到高可用的服务发现中心来构造Zuul高可用集群。Eureka是所有微服务的注册中心,并且自己本身也是微服务不过需要禁止自我注册。Eureka注册中心包含每个微服务的名称,IP,端口等,由于因为的单个节点的微服务可能会发生不可用的情况下导致系统发生停机,所以采用高可用的微服务注册中心。让两个(多个)服务发现组件相互注册以达到可以保持其它微服务的调用,维持整个系统的高可用性,整个博客的Eureka服务注册中心图如下图所示:3 Q/ h+ E/ j' C
/ k/ @1 Q6 r/ m8 R
x1fwvyykgfy6406106316.png
1 l& H2 H8 @ }5 j9 r
( U# T7 }5 j& p( _
DS Replicas代表两个模块加载模拟单机代替高可用的实现,不过需要修改本地Host来模拟真实多机高可用的效果。每个微服务都具有自己的虚拟主机名以及状态来描绘微服务的显示情况。每个微服务之间通过与服务注册中心每30S心跳传递保证服务可用性。默认90S没有收到心跳则会注销该微服务。EurekaServerOne与EurekaServerTwo为两个微服务注册中心,两者相互注册到对方的服务中心上来保证Eureka的高可用稳定,从而使每一个博客的请求都可以得到响应。
; H" u- r: V/ d2 M! q$ f5 Z
5 X# k- L2 q# u- X5
. R) M5 V+ f: ~用户的个人中心
4 T) O& ^% i- d% _: f" F m5 G! n4 ]用户的个人中心相当于博客的大门,用户的首次流量都经过此处,首次负载均衡调用也是基于这个中心开始,主要涉及到用户的登录与注册的基本功能,在登录上排除恶意的攻击与干扰,保证博客登录的稳定,从而保证系统的稳定。这个中心核心功能就是权限验证,保持登录的标志,它是保持业务稳定的重要因素,后续的实现会在以上所述的三个重要功能展开来讲。4 U S r) c9 A3 W2 U
: A7 c& n/ Z. F
5.1、登录的智能验证. R! W1 o9 Q' P
Vue整合阿里云智能验证时,需要注册布局组件来动态加载JavaScript文件,不然无法使用阿里云的智能组件,前端登录智能验证的核心代码如以下所示:
7 H! P5 f- R: v
# I5 K, m2 a* w! F//动态加载阿里云的JavaScript文件src="//g.alicdn.com/sd/nvc/1.1.112/guide.js" @loaded="initCaptcha">0 I/ J+ T' F& P# N
//注册局部组件来加载阿里云的JavaScript文件components: { "remote-js": { render(createElement) { const self = this; return createElement("script", { attrs: { type: "text/javascript", src: this.src }, on: { load() { self.$emit("loaded"); } } }); }, props: { src: { type: String, required: true } } } },
: G" y: k {& V' b& {//点击智能验证的封装函数 initCaptcha() { let _this=this; let ic = new smartCaptcha({ renderTo: '#sc', width: 350, height: 42, default_txt: "请点击验证按钮", success_txt: "博客登录验证成功", fail_txt: "点击按钮重新刷新登录验证", scaning_txt: "智能检测中", success: function (data) { console.log(NVC_Opt.token); console.log(data.sessionId); console.log(data.sig); _this.aliToken=NVC_Opt.token; _this.sessionId=data.sessionId; _this.sig=data.sig; }, }); ic.init(); },/remote-js/ }" w3 ]& n8 Y. P$ Q" w( F% f
5.2、博客的登录注册
4 I2 |: h' ^: O" f! e登录是一个系统的重要的功能,也是个人隐私的重要体现,拿常见的登录有邮箱,手机,账号或,语音或者二维码登录,不过不管通过哪种登录,个人信息的安全都应该得到保护,保护个人隐私重要的是从个人做起,拒绝非法点击与输入。
- f/ I% N" m+ w8 ~8 c x5 W
& g- h$ M. r$ @拿本次博客的登录来说只需要验证账号和密码就行,个人登录是不会进行权限验证。注册成功会保持30分钟的权限验证,关于权限验证会在标题5-2-3中提到,以便后面的博客信息的操作,超过则需要重新登录去博客的个人中心填写博客信息。. f8 m1 K/ k7 o% y! L6 \, r% @
博客的登录与注册的页面如下图所示:
' q2 _. A7 W7 q* ]3 w- _
) w" v2 m2 `' M$ E. L, d
1nrx0eycoah6406106416.png
! U; p9 z* X t* m1 j! A+ R
! l- j- C. ?8 l2 d& A博客登录的用户名需要以英文子母开头,用户名和密码均不可以超过16位,注册保证两次登录密码正确就可,在此不再贴出图片累述。) @9 H$ t# U6 I- `
" X7 Y+ e+ t# z+ d+ K
5.3、登录的权限验证
6 G; B1 L4 K. Y4 Z& e1 U登陆权限控制是每个系统都应必备的功能,是保持登录状态的重要实现。微服务所有的权限验证均在一个module上,Token消时则直接回退给前端status 404失败码,成功则是执行对应的业务逻辑,注意登录的博客是不需要权限验证。# T' F. @0 c& E1 ~3 T$ i4 R- Q
# ~% G! a2 E- b# s% b" `博客使用了前后端拦截器拦截Token(登录成功的认证码),所以后端需要定义一个token验证注解,用拦截器拦截系统的url请求,再进行拦截用户的API请求,最后再验证传过来的token与Redis中token值是否一致,效验通过才可以正常访问。当用户登录成功博客后,后端返回token数据。token具有存在时间,如果用户一段时间后不在线或者操作的话,则token会失效,用户保持登录时,则不会过期。
- N( C( X- t) _/ D# Q# \; I# w: r+ X% h3 P+ s) B1 f5 a% {
Redis中会以用户的登录账号作为与token关联的认证,有效的token码可以取出用户的账号,然后再进行业务逻辑。这些redis中的key都可以自行设置一些时间,不过前端只保存token值,二次登录会覆盖Redis中的token值。
9 k! A6 k- I* I% v& Z1 R8 M
3 T$ {) @# m. R! Y6 d$ I
hjqhto21bqe6406106517.png
" I0 y2 ~! r$ _/ c" K1 q% ?: ^
8 O- D% L* D6 ~
权限验证相当于系统的第一道大门,如今的安全框架越来越丰富,例如SpringSecurity,Shiro,OAuth等,shiro->security->oauth的上手难度逐渐提升。若是需要对密码加密的,可以需根据个人开发自行配置使用对应的安全框架。: B3 }3 i; L* f4 c3 F
6
. X: h0 }0 `2 c& B1 I用户的安全中心& h c' T& f' `1 v; Q4 U% X4 }
6.1、用户的安全布局) ~3 V6 W, c ^' ~" }# o) w$ D* e
安全中心包含邮箱,手机,身份证,校园认证和其它微服务中心需要用到的认证接口。邮箱采用QQ邮箱,开启smtP 587端口发送邮箱验证码。手机采用阿里云短信API服务。两者的验证码存在的时间均为1次失效且存在10分钟。身份证需要手机号的验证。校园认证的名字需要和身份证的名字一致。除了绑定一些安全的服务,还包括三种修改密码的方式,原始密码修改新密码,邮箱重置密码,手机重置密码,身份证重置密码。个人申述包括手机号重置邮箱,旧手机更换新手机,身份证重置手机。0 w8 ~4 V4 \$ l
6 W) B( x7 F9 m7 A
ez3unymxmsj6406106617.png
4 j4 u$ i- i+ O6 o# Y4 Y& L. `' H! _1 C g# [) e& j1 z1 g
安全中心包括用户的规则规章,博客旨在分享自己的动态和经验给他人,不可以辱骂他人,以及不遵守国家的法律法规。本次博客的其它微服务中心所需要的手机认证接口均由这个微服务中心提供。
' Z" Y/ n) X$ U) I0 W6 L9 y( b0 ~# ?4 c6 @3 r8 Y6 @3 y
6.2、用户的邮箱注册
- j m4 D1 W( |; j2 l7 P+ K4 k博客采用的是免费的QQ邮箱,邮箱的yml配置如下:3 H6 i& e8 f" \- F( o* Z) j2 u
- a! L# S/ k, g( @' [
mail: host: smtp.qq.com port: 465或587 protocol: smtp username: 个人的邮箱 password: 邮箱的SMTP的密码,可在邮箱的账户中开启SMTP服务 default-encoding: UTF-8 properties: mail: debug: true #控制台开启运行日志
% h: w6 X x2 c2 o3 L. \QQ邮箱(邮箱与手机的六位验证码共用)发送验证码按钮的原代码如以下所示:+ x+ r! w0 F) t
( a/ d" F7 @4 d) r//自动生成的验证码,验证码的位数可以自己设定 public static String generateVerifyCode(int verifySize, String sources){ if(sources == null || sources.length() == 0){ sources = EMAIL_CODES;} int codesLen = sources.length(); Random rand = new Random(System.currentTimeMillis()); StringBuilder verifyCode = new StringBuilder(verifySize); for(int i = 0; i verifyCode.append(sources.charAt(rand.nextInt(codesLen - 1)));} return verifyCode.toString();} Y8 i1 i, N: C/ Z; N4 u
绑定QQ邮箱JavaScript的代码如以下所示:0 |3 c1 t( |5 x/ p4 j/ E+ p
8 F# n/ F" s9 j9 X//绑定邮箱的发送registerEmail(){let emailPatter=/[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?/;if (this.email=== '' || !emailPatter.test(this.email) || this.emailYzm === ''){this.$message.error('输入内容不能为空且邮箱要符合格式') }else { this.$http.post('/whc/blog-customer-user/emailButtonRegister',{ email: this.email, emailYzm: this.emailYzm, }).then(res=>{ console.log(res); if (res.data.success === true){ this.$notify({ title: '成功', message: '邮箱绑定成功', type: 'success', }); window.localStorage.setItem('myEmail',res.data.message); this.reload(); //this.$router.go(0); }else { this.$message.error(res.data.message); } }) } }9 e$ v9 E4 m. W" [- ^
后端发送QQ邮箱注册的验证码如以下所示:
B$ ^$ I3 E, S; C
, o3 j8 `8 F$ e% e8 H//后端QQ邮箱发送验证码的按钮服务@Override@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)public void sendEmailCode(String email) { //生成邮箱随机的6位验证码 String emailYzm= generateVerifyCode(6, EMAIL_CODES);2 p6 m# f3 e5 l2 F
//From-to,主题和信息. SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); simpleMailMessage.setFrom(FORM); simpleMailMessage.setTo(email); simpleMailMessage.setSubject(SUBJECT); simpleMailMessage.setText("你的邮箱验证码是: "+emailYzm+"本次验证码 会在10分钟后失效,请立马使用。"); //发送邮箱验证码 javaMailSender.send(simpleMailMessage);
; q5 N4 e/ k. P; Q" h+ M! C //开启Redis存入email和yzm Jedis jedisEmail = new Jedis("localhost", 6379); //设置邮箱(key)-验证码(value)的绑定,秒为单位,存在时间为10分钟。 jedisEmail.set(email,emailYzm); jedisEmail.expire(email,600); //设置验证码(key)-邮箱(value)的绑定,秒为单位,存在时间为10分钟。(双向绑定可以判断失败存入的验证码,双向保险) jedisEmail.set(emailYzm,email); jedisEmail.expire(emailYzm,600);}
6 p7 Q5 z; W; J6.3、用户的手机注册
) J6 D+ L1 _* T- \) q1 M发动短信的前端JavaScript的代码如以下所示:
4 q5 s6 Q& C: v//前端绑定手机phoneRegister(){ let phonePatterRegister=/^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\d{8}$/; if (this.phone === '' || this.phoneYzm === '' || !phonePatterRegister.test(this.phone)){ this.$message.error('手机或者验证码不符合规则'); }else { this.$http.post('/whc/blog-customer-user/phoneRegisterButton',{ phone: this.phone, phoneYzm: this.phoneYzm, }).then(res =>{ console.log(res); if (res.data.success === true){ this.$notify({ title: '成功', message: '手机绑定成功', type: 'success',}); window.localStorage.setItem('myPhone',res.data.message); this.reload(); //this.$router.go(0); }else { this.$message.error(res.data.message);}})}}; ?* t$ O9 D; P4 e9 `
5 b, t1 e( d% }: ]: o5 H后端发送验证码的代码如以下所示:; h- Z* i+ M, X* l
1 j7 S- t6 E, |$ A+ l0 `* F. ]
//生成手机的验证码 String phoneYzm= generateVerifyCode(6, EMAIL_CODES); //阿里云发送短信的API DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "个人隐私", "个人隐私"); IAcsClient client = new DefaultAcsClient(profile); CommonRequest request = new CommonRequest(); request.setMethod(MethodType.POST); request.setDomain("dysmsapi.aliyuncs.com"); request.setVersion("2017-05-25"); request.setAction("SendSms"); request.putQueryParameter("RegionId", "cn-hangzhou"); request.putQueryParameter("PhoneNumbers", phone); request.putQueryParameter("SignName", "个人隐私"); request.putQueryParameter("TemplateCode", "个人隐私"); request.putQueryParameter("TemplateParam", "{\"codeab\":\""+phoneYzm+"\"}"); //发送注册手机的验证码 try { CommonResponse response = client.getCommonResponse(request); System.out.println(response.getData()); } catch (ServerException e) { e.printStackTrace(); } catch (ClientException e) { e.printStackTrace();} //开启Redis存入phone和yzm Jedis jedisPhone = new Jedis("localhost", 6379); //设置手机(key)-验证码(value)的绑定,秒为单位,存在时间为10分钟。 jedisPhone.set(phone,phoneYzm); jedisPhone.expire(phone,600); //设置验证码(key)-手机(value)的绑定,秒为单位,存在时间为10分钟。(双向绑定可以判断失败存入的验证码,双向保险) jedisPhone.set(phoneYzm,phone); jedisPhone.expire(phoneYzm,600);}. z" b" j" s% n& [8 q* _6 k
6.4、用户的安全认证: F, L# |. v! P+ W
提供安全的认证有身份认证与校园认证,当然只是表单的提交,真实的认证需要有关部门的配合,在此只是用来模拟,校园认证需要与身份证的名字保持一致,否则无法通过。
+ _& I* k0 ?( ~6 Z4 c: V1 H5 v ?, Y3 e& R
qmx1m0qjdr56406106717.png
+ I3 G. {: T, @2 F4 ?
( Z! }6 d3 f1 l
/ h: H+ B4 X1 o3 }) P. g
2pq45qamjgu6406106817.png
, r, q' G/ S/ x9 `6 s* g3 |9 S
; t* M& v, q9 x0 K0 `6.5、用户的密码安全
1 H( v3 `/ K% s1 H1 U- K0 I当个人安全账号发生异常,可以提供修改密码,也可以重置密码。) K9 V; C9 l- d, e B/ N
9 n8 o8 j; M. e* a! g) u
a3rg0yipylg6406106917.png
% m Z1 a' H% c3 ^- S# P
' f b" W" a$ Q/ S& {! |4 S2 y+ g: O; s |
zvqpwirdaie6406107018.png
( o. \5 {9 \ n9 Y0 \
" G. a( G+ _! m
6.6、用户的账号申诉
e' e; p$ w" H) ^8 K/ O. v/ d可以使用手机号重置邮箱,也可以使用旧手机号更换新手机号。如果个人博客的手机号安全信息被盗取,手机号也可以被重置,但是需要借助身份证申诉,不过一天只可以成功申诉一次。
" L+ ]" p% R& b2 }7 L3 m# M
) }. l3 K% L X; k
kd0vqghbnyo6406107118.png
7 Z9 }) W! D# d& D6 z6 \3 k/ }+ V6 U( u
70 W2 s& O J/ r- G- y0 u8 g# r/ v
用户的文件中心
( I7 ]- s6 ?" e# W7.1、用户的头像存储
2 ~' F8 f: P. n/ S% y) ]( Q4 B9 ?当用户注册的时会需要选择个人的头像,上传的头像只能是JPG格式且大小不能超过2MB,且上传前会先查询数据库中的头像图片名是否已经存在,存在的话直接会先删除OSS中旧图片,再插入新图片,如果不存在的话,直接插入到OSS文件服务器中。头像的存储流程由前端发起file传给后端,后端接受file头像,利用二进制传给OSS文件服务器。服务器再传过来头像的外网URL地址,此时修改显示时间为10年再返还给用户,最后把头像外网URL地址保存到自己的LocalStorage本地。7 P' Z! P, q8 U) P3 y, h: \. I4 f, T
1 Q* l* k; S& j( }# l# u. _
se3ru4eolza6406107218.png
! k. _% p! m( \/ f) A: T2 L- ?* M% g8 f" q# j4 d5 ]2 ~
7.2、博客的图片存储
1 V5 \; X- y% l) Z; t发表博客时文章中会包含图片,前端获取后端的博客图片url绑定在前端文章中显示,同样url也在文章内容中一起保存到数据库中。上传图片和上传头像不同,文章需要用到的图片可以有多张,不存在覆盖问题。需要根据个人的文件服务器的存储量来权衡上传图片大小。 k, h( h2 t! Y- l% i* G0 Y, F6 O
: j, I; m$ y( ^# g. A$ {/ \
slqtqhpiu326406107318.png
, y' n, b$ O |0 K! @: }
; R$ ^$ k- w3 c+ x
' v& _2 V, ?: R# h @8' ^% v) ~1 i3 u" b7 p# ]. Z
用户的签到中心1 _" S3 c( U: D
用户的签到等级代表用的可以使用的权限,当签到累计天数和连续天数达到奖励阶段时触发一键领取奖励按钮,每次奖励每个账号只可以领取一次。整个签到的等级由经验值决定,但是每天获得经验值为1500。签到的经验值还会进行快速排序排名返还给前端,提升竞争效果,同时会员增益机制也会导致不同的叠加效果。
( g- A5 d9 H! w# a* E3 ~' a" X) t a
8 y4 m" e, K$ ~6 W o4 t每天0点之前只能签到一次,过完0点后Redis中限时凭证失效既可以再次签到,签到的经验值采用二分查找和快速排序算法进行计算最后的排名返还给用户。
5 F# B' a0 F* f3 p+ m6 [( o
: t7 [! _" v7 c0 B: i! M签到按钮的计算代码如以下所示:
: h! k2 l5 X" Q! Z! g9 G9 g8 q, R; }. V, H0 Y
//先判定是否redis中是否存在限时凭证 Jedis jedis = new Jedis("localhost", 6379); if (jedis.get(id.toString()) == null) { //获取明天0点的时间并且设置限时凭证 try { SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式 Calendar cal = Calendar.getInstance(); cal.add(Calendar.DATE, 1);//这里改为1 Date time = cal.getTime(); String tomorrow = new SimpleDateFormat("yyyy-MM-dd 00:00:00").format(time); String now = df.format(new Date()); Date d1 = df.parse(now); Date d2 = df.parse(tomorrow); //小时和分钟和秒相减 Long hour = ((d2.getTime() - d1.getTime()) / (60 * 60 * 1000)); Long minute = ((d2.getTime() - d1.getTime()) / (1000 * 60)); Long second = ((d2.getTime() - d1.getTime()) / 1000); //最后存在的秒时间 int total = (hour.intValue()) * 3600 + (minute.intValue()) * 60 + second.intValue(); //控制台观看 logger.info(tomorrow + "/n" + now + "/n" + total); //设置redis中的签到过期时间 jedis.set(id.toString(), "今天签到已经完成!"); jedis.expire(id.toString(), total); return SUCCESS; } catch (ParseException e) { e.printStackTrace();} } return FAILED;
4 {' V8 D! d# q& U* `$ \) Y经验值的排名的代码如以下所示:
/ b* d; o' B3 d; v
+ y4 a% B( Z; `# B" W* G% N//先查询所有的经验值 ListexAll=userSignMapper.selectExperience(); //先将List集合转为Long[]数组 Long[] a=exAll.stream().toArray(Long[]::new); //可以使用转换工具类,也可以自己动手写Long数组的转换 long[] quickSort = ArrayUtils.toPrimitive(a); //快速排序排序成从小到大的顺序 sortService.quickSort(quickSort,0,quickSort.length-1); //利用二分查找算法查找经验值所在的索引位置 int position=sortService.binarySearch(quickSort,myExperience); //最终排名,倒序输出。 int lastPosition=quickSort.length-position; //控制条输出你的排名 logger.info("你在经验值排行榜中的名次是:"+lastPosition); //返回给前端的排名 return lastPosition;
& | J* t# |* o3 N1 g9
7 |3 B/ @# `# w& d: r用户的会员中心8 y I: A9 l: S1 M/ `
由于会员中心与支付中心联系比较密切,所以两者的中心可以结合起来看作一个中心来观看。用户的会员中心包括普通会员和超级会员,每种方式存在三种收益方式,年费季费和月费,支付成功后均由负载均衡执行业务逻辑。由于支付不属于这个module中,所以这个module只是由其它微服务调用直接完成业务逻辑。
$ ^2 e% l# J w% T. l
( ?! K( j/ v2 o, O
n21mphrssdi6406107418.png
. D1 ~3 s% Y$ l9 a+ o$ m1 D
, D2 Y9 [: N" i4 e; \* X
10
: e4 M. p* N# |! D% e用户的支付中心
! g- l2 q: V+ m未完成实名认证时页面会转到实名认证中,当完成实名认证时,首次进入我的钱包中心会触发设置支付密码,当设置成功后,支付以及绑定个人银行卡均需要用到支付密码。可以用旧支付密码修改新密码,也可以手机重置手机密码。还可以绑定自己的银行卡,需要有关部门的配合。本次博客只允许建设银行,工商银行和中国银行,且每张银行卡只允许绑定一张。利用v-charts组件把个人的时间段的消费情况以条形图展现给用户观看。
2 M# |: z( x) k Z( `9 o! p: i, P0 I3 H c" C, d
o122ik5x2336406107519.png
' L, F& h' V) s1 \! c/ L; D! T' l! p: ~: K6 I
+ ?5 v. n3 {4 Z! Q
0d4ybvvhu2k6406107619.png
) e( }9 H# c( |
v9 n, J o5 M# u- v8 s9 k* K
当用户开通了我的钱包后,可以选择是否进行余额充值,账户余额暂时只可以用支付宝充值。所有关于金额的操作均需要在后端安全操作,前端只用来显示数据,必须使用数据库中的金额。
5 i3 g9 ?6 X1 B3 I, w0 x w/ v' v3 r
) n# ]/ m4 h- K) c' A
$ ^) ?7 B! k$ | u2 M, n
1 j i. _5 e- Y- B/ E 9 |( t: [6 F% d {
" W7 t* G9 O; W" T# [2 b0 O2 J密码为6位有效数字,可以使用原密码更换新支付密码。' o7 U* A6 B6 c# a- y
2 Z' e# `- |+ x8 b$ Q3 C 4 e0 y0 f" b5 ^- E+ {
* g# t- N) X9 M4 O0 [# K4 O _+ k) A% V
7 W5 }% n# J) R, a! T
7 p2 V, |+ x+ i! M银行卡姓名需要与实名认证的姓名一致。
$ M; ~3 ]. q( u! ?/ L# }" q$ K% x( W+ f, O1 e

) r1 L* e8 V) k( B0 ?% @) e4 V/ Y/ s3 H: t
* J! D2 B3 E K7 B# c* k+ @ + i2 W* y& K B$ \
9 E4 z3 u; C. y) X3 s- d2 X
% h# S+ i5 s4 A" H6 X
8 z$ q* ]) D/ \* B+ X0 C- d
& ]) K5 D/ Z% N8 L, q5 H/ N支付中心包含普通会员和超级会员,由于普通会员采用支付宝原始的方式,而超级会员采用支付宝的二维码方式,所以两者会在调用的时候会有所不用。6 K+ A0 n1 t7 o( z1 q
" I5 d: R1 j+ K4 A8 @* Z2 p4 A
% m/ m @" Y0 P" G$ t6 l4 p% K9 E
$ h- X% P5 `- K/ e
账单中心是分页展示给用户观看,提供当前页面,月份和全部的账单打印Csv。前端可以直接把后端的数据直接打包Csv,也可以自行后端打包Csv数据到本地。+ W1 w; Q; P3 \2 P3 N) j7 O! S
: X7 o) X; I& R) b

+ [0 \8 P! `+ I% r8 {& A/ S
, u- f' h& J) L# F4 ^. D* m( n' e2 h) j" u/ k; b

/ h8 X6 i% a( S! P* i; a' G/ A, ^% a3 i4 [
11
! | E# f- Z# |* Y6 j( H用户的博客中心
% W1 v8 q& ~+ O4 J) k9 E# ~11.1、用户的访问主页8 K: n, [7 j- }1 N* x& i+ M" S0 ~
编写博客是一个展示自我的机会,通过这个机会,可以增强个人的表达能力,还会结识一些五湖四海的博友。通过他人文章的学习,我们还可以增强个人的知识度和眼界。综上所述,用户的博客中心是博客系统的最核心功能。
0 O* W' p" i# n0 Q) f8 s; B) q v9 n5 k S/ b
用户可以分享自己的博客动态,博客旨在分享自己所学所知给他人,或者解决他人的困难。博客中心包含发表博客,查看个人博客,删除博客,更新个人博客。非本人也可以观看他人的博客,也可以评论他人的博客,所以需要用到分页功能和轮滑加载功能配合前端展示不是私密的博客给他人观看。每个用户所看到的博客都是最新发的博客,可以与他人进行学习交流。每个发表者要尊重他人的知识劳动成果,切勿抄袭并且发表不适当的文章,做一名合格的博友。
" d6 T, L8 k8 a0 k1 i ^2 y/ P# f0 ~- w+ O0 E* S( r9 r$ d
当用户输入账号密码登录后,可以看到博客的主页如下图所示,主页面可以看到发表人和发表的文章,点击文章可以进入文章的主页面进行学习交流。
" @( v3 C2 H+ C3 x( {" e9 O
g$ }2 k+ {: o0 |3 Y t: v8 F+ ~ " o6 ?: p( l9 t1 l- e
+ z5 f7 A- Y6 K8 u5 w8 G11.2、用户的文章中心" A k. e8 K7 Z! P: g1 g
用户的文章微服务中心的功能包含发表,查看,修改,删除,用户可以控制自己发表过的每一篇文章。
7 |4 |# z2 c- ]; D# s p0 M
5 B0 |3 t- I7 s. O
~; r9 W. ^- ^2 J- a+ e8 X% L
/ V$ _" n. l5 o, [4 [关于文章的增删改会在后续标题中得到详解,在此只放出用户的个人文章中心由图可以看出是用户发表过的全部文章,后端利用的是先分页后List方式,最终传送前端进行ListItem遍历显示即可。
! W. _! _! \ b
2 l! S' V: A: g7 _3 Q5 o" Z8 m由于也使用了Elasticsearch把文章分类作为存储索引,但是重要的文章信息均放在数据库中,在此只提一下,到后续的分类搜索中会详细说明。; w8 f0 a- ~; P9 }
, r& P5 O& t7 p
11.3、发表个人的博客# m4 j; f6 }# w7 Y. [: J( o" c+ u9 I
文章的的发布有许多选择,自己可以选择文章的分类和文章的标签,同样也可以设置文章的可见性。用户可以设置文章的标签,标签用来显示给游客看,用来文章的标识认证,还可以设置文章的分类,类型和保密性,每种文章的分类会发布到那个分类的专区。文章类型有三种,若是转载和翻译他人的文章需要著名地址,保护他人的知识劳动成果。只有具备会员资格才可以发送到会员专区,但是转载的文章不可以发送到会员专区,发表文章时可参考红字注意事项。6 t" _% D9 L: s0 J8 |
: b7 F4 |9 w6 ?$ w) W/ U a6 K
3 R& b6 n8 q' n3 ^9 y8 M
/ `$ I. U3 U$ F s, ^. V; I博客的发表的核心原代码如以下所示:
- Y8 y# B; } \7 u2 `4 Q, j8 p! t% }
3 N) Y" `7 A2 t* D, x/ I//获取文章的摘要markdown格式-html-summaryString words= StringFromHtmlUtil.getString(MDTool.markdown2Html(blogFrontPublish.getArticleContent()));//获取文章的摘要且摘要长度为255个字符String summary = words.length() > 240 ? words.substring(0, 240) + "......" : words;//去除转换后存在的空格String tagTar = blogFrontPublish.getArticleTag().replaceAll(" ", "");//将文章的分类写入分类表然后再插入整篇文章UserArticleCategory userArticleCategory=userArticleCategoryMapper.findAllByCategoryName(blogFrontPublish.getArticleCategory());if (userArticleCategory==null){ userArticleCategory=new UserArticleCategory(); userArticleCategory.setCategoryArticles(""); userArticleCategory.setCategoryName(blogFrontPublish.getArticleCategory());//返回获取到的自增ID userArticleCategoryMapper.insert(userArticleCategory);}//把标签写入数据库for (String tag :tagTar.split(",")) { if (tag.replaceAll(" ", "").length() == 0) { //单个标签只含空格 continue;} UserArticleTag userArticleTag = userArticleTagMapper.findAllByTagName(tag); if (userArticleTag==null){ userArticleTag=new UserArticleTag(); userArticleTag.setTagName(tag); userArticleTag.setTagArticles(""); userArticleTagMapper.insert(userArticleTag);}//转换后的值再更新得到文章表的主键 userArticleTag.setTagArticles(userArticleTag.getTagArticles()+userArticle.getId()+","); userArticle.setArticleTagsId(userArticle.getArticleTagsId()+userArticleTag.getId()+","); userArticleTagMapper.updateTagNameAndTagArticlesById(userArticleTag.getTagName(),userArticleTag.getTagArticles(),userArticleTag.getId());}4 L9 m8 n6 n3 n) g0 I' ^+ g3 W
11.4、修改个人的博客' E0 l2 F; a! Q8 q
若是需要修改个人的博客,需要进入图5-33的个人博客中心,查看发表的指定文章进入到指定文章的页面,点击编辑按钮,不是本人的文章不会出现编辑按钮,博客的编辑按钮效果图如下图所示:2 Q3 x2 Q2 |( ~9 X& a7 \8 a6 V
2 L& G0 u& M0 W E5 S2 S
[0 T) d/ f1 `- L! V3 X
9 k8 s" \9 j/ `8 j, a, _2 g2 g可以修改文章的所有的条件与内容。
4 `9 ?7 d7 Z) K* K+ e/ y
# h; U" N" Q+ t j+ L; h. m5 x; G3 o
0 ]- f" ?7 V! F8 p7 [
. j. l+ a8 X2 y s: ]) S文章的发表与修改的源码不同在于要删除之前的原属文章的分类Id与标签Id的关联,再进入文章的插入,不过文章的修改也会触发在搜索引擎上的文章信息修改,搜索引擎上的文章信息也会跟着更新,保持搜索到最新的数据。
8 U" s& G8 Q4 X; _3 X" _
; `( t) D8 Q) D* o% X F11.5、删除个人的博客 |- w2 X$ T( a; h: c* K
删除个人的博客需要删除数据库和搜索引擎上的文章,删除文章后不可恢复。 F" {5 g/ H- v
& H8 `& O; X* J4 g i7 D2 C4 b; o 1 q& G2 Q2 r4 i8 i9 w
! q. F" G" T& ?: g3 z11.6、用户的文章布局
2 _- C4 q+ `' U1 [- n8 m完整博客的显示方式是采用GitHub的代码高亮布局,可以是用户看到自己的博客是嵌入式的面板,可以给予人一种清爽的感觉。由于采用Vue,可以不用动态渲染html,使用v-html命令就可以把后端传过来的数据利用showdown转换器转换给html直接显示给用户看。
$ X9 H( v% R7 T' }& n( y9 g/ ]( T. B3 z% u) C [

( v7 Y& p- M0 C C5 L7 N9 c" H- ^" }+ C" U2 D) Z
11.7、点赞用户的文章
1 n) o- l; ] H1 ] p- T5 {互联网时代每个人都或许都点赞过他人分享的文章,本次设计是博客所以会涉及到点赞,当用户太多时需要考虑到高并发的情况。正常情况的点赞并不会给后端造成多大的负载压力,如果是热门的文章博客,用户点赞与取消点赞,评论,分享等,对于后端来说这些都会带来巨大的流量,如果后端接口支撑不住,前端得不到响应,前端无法响应就会返回404,会导致用户体验极差。
, }* t( k" d$ y
& L/ z5 T @' u- F! j
. J; B& j6 d' d( e4 C" G5 z' w
' H& n. Y; I, _; L由上图可知,核心的模块就是点赞与取消点赞,利用Redisson把多个用户的请求利用分布式锁分开请求,利用“缓存”保护数据库。若用户点赞微博,则后端会先查询是否存在点赞记录,当存在点赞记录时,分析是否是完成点赞还是已经取消了点赞。若没存在点赞记录,则完成数据库中的点赞记录更新,再把点赞结果“缓存”到Redis中,若是取消点赞,则直接会在“缓存”中删除,更新数据库中的结果。每一篇文章的点赞总数都是利用“缓存”计算得到一篇文章的结果发送给前端显示。前面的操作都是利用Redisson操作的,所以当高并发多线程请求时,分布式锁就会控制资源的并发访问,避免出现文章数据不一致的情况。- \3 B" C, A0 c% E1 t0 S
" W B5 L) Y3 Y8 v
11.8、收藏用户的文章
5 g, u# a2 r+ f. Z# r6 N+ Z用户允许收藏自己的文章,收藏的功能也借用了Redisson的分布锁来控制收藏的缓存,收藏与博客的点赞功能相似。
. x) i" V L j! j2 E, p( U# b. o
4 s5 u1 |. m' ~. C. e7 k5 b5 `- B
; v% J% ^. t& y5 x a
2 _% Z' [" y2 i; B+ V- p. @
8 x' L9 @! w% s' }" T& h1 G1 B. C
0 L6 M; j9 _. \ v
11.9、评论用户的文章2 Y! ~6 |( l; [8 J6 }! O# g
评论的实现比较简单,一级评论的用户的Id为父Id,只含有一级评论与二级评论,分页直接查看文章的所有评论。
" L8 {9 e( a G& W+ p
) \* @/ g* A; k' F 9 L- j" v" ] h* i5 t2 ~2 v# k
1 M+ N2 A. ], `3 h+ ~; p7 o! T11.10、博客的文章排行榜
5 i P& C. m: Y5 i4 ~ ^$ v文章排行榜采依旧是利用了Redisson,排行榜与标题3-7-4的点赞关联紧密,“点赞”会导致热流,形成短时间内的超高人气,把文章的排行榜放给用户看,可以增强用户的体验。不是所有的功能都要用分布式锁,排行榜不需要用到分布式锁,也不需要控制非常高的高并发流量,对于后端来说控制排行榜比较简单。$ R' c. k: S. z) j; x
4 _' X+ B5 P6 o# l; D- h' q) ?7 k
& L) J' Y( P/ p- v# v5 c7 {, u& }
排行榜需要保证查询数据库的点赞表的SQL正确,SQL错误之后的所有操作都是白费力气,这个要设置一定的范围与时间差来确保文章的间断实时性,最后要把数据库中的数据放到缓存中。对于缓存的操作,用户的点赞与取消点赞都会触发缓存中的排行榜排序。用户请求后,会查到缓存中的排行榜点赞数最多的前10篇文章,利用List的文章Id找到文章的信息,最后打包传给前端显示。排行榜算是一种实时性要求不算很高的,可以使用定时的方式主动更新缓存中排行榜记录。
+ Y4 _8 l/ R$ d! e2 o9 u$ }( O: l( y2 W- ]( ]! u

9 M* i5 J" H8 n0 y* A0 S9 d Z* Q
g8 ^0 j; H' y9 A: g! {12
" {7 [: R3 X% L {1 p博客的搜索中心
+ e* ~, |; b( @( Z) x2 H12.1、搜索引擎的应用& i5 T6 _1 t' q$ \$ x( K/ R
当微服务整合Zipkin时,运行系统会产生大量的API运行指标,而Elasticsearch(ES)作为一种存储方式,可以把那些运行API指标存储到ES中。
" K3 F6 l8 {1 m% }( [
9 n$ D! Q7 U+ c) k+ }8 {, C 6 K6 W J$ ` l. R) @
2 C$ G$ J$ h: z
Elasticsearch的index是文档索引,与数据库的“库”相似,type是文档类型,与数据库中的“表”相似,id是一个字段作为主键。, N) D/ I u( y- L: ]! h
u8 n# h) _; o- _% i
12.2、博客的分类搜索
' T8 s8 D5 u8 w: n4 E博客的搜索中心包含文章的显示的信息,例如标题和文章内容的摘要,由于把文章的整篇内容均放到数据库中,所以搜素引擎上的文章是负载均衡保存提示的信息,用户可以根据搜索界面的搜索框查询Elasticsearch上文章,根据的是文章的分类的区域和关键字,后端判关键字是否符合且存在,然后把结果传给前端显示。, [6 `5 Y* Z. f/ M- }/ k) N0 h4 @% q
! a% K. B% X. x6 m1 A/ u ! Q! A$ ?! k1 g# f+ _, [
1 i# ?0 y$ T# g3 @& d& t
0 k/ T: n5 G$ [& S3 E: n

" H4 M, ^! ^- G& g* a$ u# A/ n& c% f, w8 q# ]" V
12.3、博客的分类主页 \7 n+ I7 `' s
博客的分类主页是利用Elasticsearch上的文章信息进行遍历,用户发表的博客时候选择的分类作为Elasticsearch索引的名负载均衡到Elasticsearch,分类中心的文章每次选择20篇最新的文章传给给用户观看。7 o/ h) k9 ^; b& Y, O1 Z
6 S* c) v. m& U: K. [ ^

9 E5 F, j. _1 n3 k/ Y
) V& Q. T' G! H' i1 `' d13# r1 a# @: {/ _( D
博客的测试分析
' d$ D: L# h2 n* t13.1、博客的请求抗压性分析
2 Q [' [; ]* q7 Z# R8 o博客采用的是前后端的分离模式,所以对于前端传过来的Token,后端只要鉴定Redis中存在Token即放行API的请求,同时前端可以防止表单的多次提交,前端可以增加JS监听Button的提交,也可以使用session鉴定请求的时间间隔。不过博客大多采用Axios的异步提交,http成功时可以把Button设置为true或者直接局部刷新网页重置数据。对于博客中的支付中心,另起线程中需要防止出现业务出现错误,或者网络错误导致负载均衡出现中断错误,所以需要catch防止程序出现错误,同样还必须保存支付宝异步通知的结果防止另起线程中出现异常。对于每个用户的请求,可以采用服务器的限制流量,限制的方向可由个人选择,需要在服务器的图形化界面中可以设置。! f+ Y& c! Y% B
+ o3 _/ M% e$ v+ x, a& m1 G6 T1 P ' o0 {8 L) Z1 \
4 m6 k3 A* j3 R: x+ P2 K
13.2、博客的功能扩展性分析
& i2 O, u' Q2 t" |% z0 }& n1 p* c: A博客的前端容易扩展,由于Vue本身具有的实用的数据绑定优势,所以前端只要时间充足就可以随意扩展,还有一个很重要的原因是安全是后端来控制的。博客后端的每一个微服务的中心承担着一个功能模块,若是发生不同功能的扩充则需要多添加一个微服务的中心。当功能相同只是扩展当前的功能,则可以直接在某个微服务中心增加代码即可。编写博客时遵守了Java代码的规范,为以后的扩充打下坚实的基础。本次博客各个中心均遵守了Java的代码规范,微服务中的module划分清晰,功能扩充方便。7 }& f7 Y! _5 w9 B" \
) Y1 F% ?* P7 n) N; @5 pPom文件中的module划分如下图所示:
4 e% f2 T/ K% p5 [
; f9 o& G( R1 I+ f8 X. U
1 r2 s$ e R7 a% ? L3 [6 [7 f+ L5 j
项目工程资源请参见:https://download.csdn.net/download/m0_38106923/878495772 T- \6 J5 f$ i3 J2 T6 Q8 S Z
0 _5 R7 W. l8 |

4 R6 |$ W- e$ t# v! p) s往期推荐毕业设计So Easy:Java MySQL智能报纸阅读器APP应用
2 w @! D: H4 u4 v S毕业设计So Easy:基于C++实现网络扫描器
* i7 V7 s" S3 H: q毕业设计So Easy:基于Java Web学生选课系统, U; |( ~4 w# Z" E5 U) T# w+ [
毕业设计So Easy:基于Java语言西餐厅点餐系统
' X. J) I& w5 ^2 p0 q. D4 f毕业设计So Easy:珠穆朗玛FM音频电台APP
1 N8 W- u# X8 B
4 X3 x# P9 _6 U/ L$ t I
! F" }. E- l% h' {, O: b3 f- ~
4 t5 z& I( F" ^8 g9 Z点击阅读原文,更精彩~ |
|