0%

SpringBoot thymelef的使用以及模版

thymelef模版配置,可以不用配置

1
2
3
4
5
6
7
8
9
spring:
thymeleaf:
cache: false
prefix: classpath:/templates/
check-template-location: true
suffix: .html
encoding: UTF-8
content-type: text/html
mode: HTML5
  1. thymelef添加依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
  2. resource/templates添加网页

    ${error}接收参数,th:action="@{/userLogin}"表单提交地址

    1
    2
    3
    4
    5
    6
    <p th:if="${error}" class="bg-danger" th:text="${error}"></p>
    <form method="post" class="layui-form" th:action="@{/userLogin}" >
    <input name="username" placeholder="用户名" >
    <input name="password" lay-verify="required" placeholder="密码">
    <input value="登录" id="login" style="width:100%;" type="submit">
    </form>
  3. 添加controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Controller
    public class LoginControl {
    @RequestMapping(value = "/login")
    public String login(Model model)
    {
    model.addAttribute("error","跳转而已"); //设置往网页传参数
    return "login"; //跳转login.html,并接受上面的参数
    }
    @RequestMapping(value = "/userLogin", method = RequestMethod.POST)
    public String userLogin(User user, Model model) {
    model.addAttribute("error", "出现异常");
    return "login"; //更新网页返回值
    }
    }

layui前端样式模版

直接复制到resource/static资源目录即可

  1. layui简单的post请求

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    <button class="layui-btn"  lay-submit="" lay-filter="addsort">增加</button>
    <script>
    layui.use(['layer', 'form'], function() {
    var layer = layui.layer //引用layer模块
    , form = layui.form;//引用form模块
    form.on('submit(addsort)', function (data) { //addsort监听提交按钮
    console.log(data.elem) //被执行事件的元素DOM对象,一般为button对象
    console.log(data.form) //被执行提交的form对象,一般在存在form标签时才会返回
    console.log(data.field) //当前容器的全部表单字段,名值对形式:{name: value}
    $.ajax({
    type: 'POST',
    url: '/img/addSort',
    dataType: 'json',
    contentType: 'application/json',
    data: JSON.stringify(data.field),
    success:function (result) {
    console.log(result);
    if (result.code==200){
    layer.msg('添加成功');
    }else {
    layer.msg(result.msg);
    }
    }
    });
    return false; //阻止表单跳转。如果需要表单跳转,去掉这段即可。
    });
    });
    </script>
  2. 表单数据加载

    1
    2
    3
    4
    5
    @RequestMapping(value = "/feedback")
    public String feedback(Model model){
    model.addAttribute("feedBacks",feedBacks);
    return "feedback";
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <table class="layui-table">
    <thead>
    <tr>
    <th>ID</th>
    <th>用户名</th>
    </thead>
    <tbody>
    <tr th:each="feedBack:${feedBacks}">
    <td th:text="${feedBack.feedbackid}"></td>
    <td th:text="${feedBack.user.username}"></td>
    </tr>
    </tbody>
    </table>
  3. 含文件图片的表单混合一起提交,原理首先使用layui绑定按钮提交

    上传图片按钮设置不自动 auto: false提交,然后存到文件里,然后表单提交的时候触发图片上传,以及绑定表单参数,设计时最好避免一起提交,设计可以先传好图片,然后填图片地址,分两次请求

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    <script>
    var uploadInst;
    var pathObj; //存文件
    layui.use(['layer', 'form'], function () {
    var layer = layui.layer
    , form = layui.form;
    form.on('submit(addpaper)', function (data) {
    if (pathObj == undefined) {
    layer.msg("没有选择壁纸");
    return;
    }
    var sortid= $("select option:checked").attr("id");
    //选了文件直接改参数上传
    uploadInst.config.data = {
    paperdetail:data.field.paperdetail,
    sortid:sortid,
    papername:data.field.papername
    };
    uploadInst.config.auto = true;
    uploadInst.upload();
    return false; //阻止表单跳转。如果需要表单跳转,去掉这段即可。
    });
    });

    layui.use(['layer', 'upload'], function () {
    var layer = layui.layer
    , upload = layui.upload;
    //普通图片上传
    uploadInst = upload.render({
    elem: '#post-photo'
    , url: '/img/uploadpaper'
    , auto: false
    , before: function (obj) {
    //预读本地文件示例,不支持ie8
    obj.preview(function (index, file, result) {
    $('#demo1').attr('src', result); //图片链接(base64)
    });
    },
    choose: function (object) {
    pathObj = object;
    }
    , done: function (res) {
    if (res.code == 200) {
    layer.msg("添加壁纸成功");
    } else {
    layer.msg("添加壁纸失败:" + res.msg);
    }
    //上传成功
    }
    });
    });
    </script>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @PostMapping("/uploadpaper")
    public Msg singleFileUpload(@RequestParam("file") MultipartFile file, @RequestParam("sortid") int sortid, @RequestParam("paperdetail") String paperdetail, @RequestParam("papername") String papername) {
    List<String> urls = new ArrayList<>();
    if (file == null) {
    return ResultUtil.error(-300, "上传文件为空");
    }
    Paper paper = new Paper();
    paper.setSortid(sortid);
    paper.setPapername(papername);
    paper.setPaperdetail(paperdetail);
    try {
    Files.write(Paths.get(uploadPath + file.getOriginalFilename()), file.getBytes());
    paper.setPaperurl(imgUrl + file.getOriginalFilename());
    paperMapper.insertPaper(paper);

    } catch (Exception e) {
    e.printStackTrace();
    return ResultUtil.error(-301, e.getMessage());
    }
    return ResultUtil.success();
    }
  4. 动态读取下拉框

    1
    2
    3
    <select name="sortid" id="sortid">
    <option th:each="arrayS:${sorts}" th:text="${arrayS.sortname}" th:id="${arrayS.sortid}">默认选项</option>
    </select>
    1
    2
    3
    4
    5
    6
    7
    @RequestMapping(value = "/addwallpaper")
    public String addwallpaper(Model model){
    List<Sort> sorts;
    sorts = paperMapper.selectSort();
    model.addAttribute("sorts",sorts);
    return "addwallpaper";
    }

springboot 基础

注解

@CrossOrigin 跨域处理

json处理

springboot集成jackson工具

简单使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//忽略编译该参数为json    
@JsonIgnore
public String getPassword() {
return password;
}
//bean to json
ObjectMapper mapper = new ObjectMapper();
User user=new User();
String jsonstr=mapper.writeValueAsString(user);

@RestController //返回实体类自动转json
public class UserControl {
@RequestMapping(value = "register", method = RequestMethod.POST)
public Msg userReg(@RequestBody User user) { //@RequestBody 请求参数为json自动转换实体类
return user;
}
}

架构模型(Spring boot+vue+Spring Security

SpringSecurity

是一个安全认证权限控制等spring框架依赖注入原理(ioc)

简单用于登陆校验
  1. 添加maven依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
  1. 新建User实体类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    public class User implements UserDetails {
    private int userid;
    private String username;
    private String password;
    //get,set省略
    @Override
    public String getUsername() {
    return username;
    }
    @JsonIgnore
    @Override
    public boolean isAccountNonExpired() {
    return true; //改为true
    }
    @JsonIgnore
    @Override
    public boolean isAccountNonLocked() {
    return true; //改为true,不然账号是锁定的
    }
    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired() {
    return true; //改为true
    }
    @JsonIgnore
    @Override
    public boolean isEnabled() {
    return true; //改为true,不然账号是禁用的
    }
    @JsonIgnore
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
    return null;
    }
    @JsonIgnore
    @Override
    public String getPassword() {
    return password;
    }
    }
  2. 新建UserMapper接口连接数据库

    1
    2
    3
    4
    5
    @Mapper
    public interface UserMapper {
    @Select("select * from user where username=#{username}")
    User loadUserByUsername(String username);
    }
  3. 新建UserService用户服务,注意实现UserDetailsService接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Service
    public class UserService implements UserDetailsService {
    @Autowired
    UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    User user = userMapper.loadUserByUsername(s);
    if (user == null) {
    throw new UsernameNotFoundException("用户名不对");
    }
    return user;
    }
    }
  4. 新建UserUtil工具类用户获取登陆后的用户信息

    1
    2
    3
    4
    5
    public class UserUtils {
    public static User getCurrentHr() {
    return (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }
    }
  5. 最重要的一步继承该类WebSecurityConfigurerAdapter,并配置,添加注解标明是配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    UserService userService;
    @Override
    public void configure(WebSecurity web) throws Exception {
    //解决静态资源被拦截的问题,下面的忽略拦截,下面的路径不会走安全验证
    web.ignoring().antMatchers("/index.html", "/static/**", "/login_p","/user/register");
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());//配置用户登陆服务,并配置密码加密方式,这里可以自定义加密方式
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
    .antMatchers("/admin/**")..hasRole("超级管理员")//改地址需要超管
    .anyRequest().authenticated()//其他地址的访问均需验证权限
    .and().formLogin()
    .loginPage("/login_p")//指定登录页是"/login_p",输入其他地址会替跳转到该页面
    .loginProcessingUrl("/login")
    .usernameParameter("username").passwordParameter("password")
    .permitAll() //指定登陆接口,只能用表单,如果要json请求要添加过滤器
    .failureHandler(new AuthenticationFailureHandler()) //登陆失败回调
    .successHandler(new AuthenticationSuccessHandler()) //登陆成功回调
    .and().logout().permitAll()
    .and().csrf().disable(); //解决非thymeleaf的form表单提交被拦截问题
    }
    }
  6. 登陆成功会自动有cookie返回,清除cookie然后测试都成功了

角色权限控制

这里简单实用,没有添加角色表,user表机构

username password role
admin *** admin
zhangsan *** user
  1. 在用户实体类重写getAuthorities该方法,该方法读取用户角色并存储返回到角色数组,如果是分表,role可以存储多个依次添加即可

    1
    2
    3
    4
    5
    6
    7
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
    List<GrantedAuthority> authorities = new ArrayList<>();
    //“ROLE_”由于数据库未添加,所以这里手动添加
    authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
    return authorities;
    }
  2. 然后在配置里添加,意思是antMatchers里的地址只能是admin角色返回

    1
    2
    http.authorizeRequests()
    .antMatchers("/flower/imgUpload", "/flower/ddClass").hasRole("admin") //这两个地址需要管理员角色
  3. 现在已经能控制角色权限了,但是权限不足返回不友好,这里设置权限不足,返回提示信息

    1
    2
    3
    4
    5
    6
    .exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
    log.info("权限不足");
    }
    });
菜单角色管理

主要方法是设置过滤器

  1. 在配置文件添加

    1
    2
    3
    4
    5
    6
    7
    8
    .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
    @Override
    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
    o.setSecurityMetadataSource(urlFilterInvocationSecurityMetadataSource);
    o.setAccessDecisionManager(urlAccessDecisionManager);
    return o;
    }
    })
  2. 添加urlFilterInvocationSecurityMetadataSource

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    @Component
    public class UrlFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    @Autowired
    MenuService menuService;
    AntPathMatcher antPathMatcher = new AntPathMatcher();
    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
    //获取请求地址
    String requestUrl = ((FilterInvocation) o).getRequestUrl();
    if ("/login_p".equals(requestUrl)) {
    return null;
    }
    List<Menu> allMenu = menuService.getAllMenu();
    for (Menu menu : allMenu) {
    if (antPathMatcher.match(menu.getUrl(), requestUrl)&&menu.getRoles().size()>0) {
    List<Role> roles = menu.getRoles();
    int size = roles.size();
    String[] values = new String[size];
    for (int i = 0; i < size; i++) {
    values[i] = roles.get(i).getName();
    }
    return SecurityConfig.createList(values);
    }
    }
    //没有匹配上的资源,都是登录访问
    return SecurityConfig.createList("ROLE_LOGIN");
    }
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
    return null;
    }
    @Override
    public boolean supports(Class<?> aClass) {
    return FilterInvocation.class.isAssignableFrom(aClass);
    }
    }
  3. 添加urlAccessDecisionManager

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    @Component
    public class UrlAccessDecisionManager implements AccessDecisionManager {
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, AuthenticationException {
    Iterator<ConfigAttribute> iterator = collection.iterator();
    while (iterator.hasNext()) {
    ConfigAttribute ca = iterator.next();
    //当前请求需要的权限
    String needRole = ca.getAttribute();
    if ("ROLE_LOGIN".equals(needRole)) {
    if (authentication instanceof AnonymousAuthenticationToken) {
    throw new BadCredentialsException("未登录");
    } else
    return;
    }
    //当前用户所具有的权限
    Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
    for (GrantedAuthority authority : authorities) {
    if (authority.getAuthority().equals(needRole)) {
    return;
    }
    }
    }
    throw new AccessDeniedException("权限不足!");
    }
    @Override
    public boolean supports(ConfigAttribute configAttribute) {
    return true;
    }
    @Override
    public boolean supports(Class<?> aClass) {
    return true;
    }
    }

常见问题

  1. 输出错误User account is locked

    解决:用户类设置返回true,原因自带安全机制,如果需要实现登陆错误次数,此处根据逻辑修改,并在user表添加该字段,通过数据库记录设置是否锁定。

    1
    2
    3
    4
    @Override
    public boolean isAccountNonLocked() {
    return true;
    }
  2. 错误No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken

    解决:WebSecurityConfigurerAdapter没有配置注册userService,而且必须对密码加密,这里必须设置加密方式,可以new PasswordEncoder实现自定加密

    1
    2
    3
    4
     @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
    }
  3. 错误Encoded password does not look like BCrypt原因不识别数据库密码,有可能密码字段太短,或者被截断,另一种就是通过网页手动加密,复制进数据库也有可能不识别,解决通过代码加密插入解决

    1
    2
    BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
    String encode = encoder.encode(user.getPassword());

参考

lenve/vhr

详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/flower/imgUpload", "/flower/ddClass").hasRole("admin") //这两个地址需要管理员角色
//其他地址的访问均需验证权限
.anyRequest().authenticated()
.and().formLogin()
//指定登录页是"/login"
.loginPage("/login_p")
.loginProcessingUrl("/login").usernameParameter("username").passwordParameter("password").permitAll()
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
log.info("登陆失败");
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
ObjectMapper mapper = new ObjectMapper();
String jsonResult;
if (e instanceof UsernameNotFoundException ){
jsonResult=e.getMessage();
}else if( e instanceof BadCredentialsException) {
jsonResult="密码输入错误,登录失败!";
} else {
jsonResult="登录失败!";
log.error(e.getMessage());
}
jsonResult=mapper.writeValueAsString(ResultUtil.error(-201,jsonResult));
out.write(jsonResult);
out.flush();
out.close();
}
}).successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
log.info("登陆成功");
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
ObjectMapper mapper = new ObjectMapper();
String jsonResult=mapper.writeValueAsString(ResultUtil.success(UserUtils.getCurrentHr()));
out.write(jsonResult);
out.flush();
out.close();
}
})
.and().logout().permitAll()
.and().csrf().disable() //解决非thymeleaf的form表单提交被拦截问题
.exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
log.info("登陆失败");
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
ObjectMapper mapper = new ObjectMapper();
String jsonResult;
jsonResult="权限不足";
jsonResult=mapper.writeValueAsString(ResultUtil.error(-201,jsonResult));
out.write(jsonResult);
out.flush();
out.close();
}
});

斐讯K2 22.4.6.3 非telnet 页面直刷 Breed 详细方法(图文)

  1. 用网线连接电脑和路由器的lan口

  2. 保持路由器与internet的连通(桥接,有线都可以)

  3. 进入k2管理页面(p.to)

  4. 进入网页调试模式(chrome按F12),找到定时重启下拉05分的标签

  5. 在05的标签右键编辑

  6. 将“05”修改成为“05 | wget http://breed.hackpascal.net/breed-mt7620-phicomm-psg1208.bin”之后,鼠标移动到黑框外的空白处点击鼠标左键,结束编辑。

  7. 回到页面重新选择05,05后面多了刚刚加的,然后保存。

  8. 继续将“05”修改成“05 | mtd unlock Bootloader”,然后回到页面选择05保存。

  9. 继续将“05”修改成“05 | mtd -r write breed-mt7620-phicomm-psg1208.bin Bootloader”,然后回到页面选择05保存,等待重启,如果未重启代表失败。

  10. 拔除K2上Wan口的网线,路由器断电,持续按住路由器上的reset按钮,接通路由器电源,3秒后松开reset按钮。

  11. 在浏览器地址栏输入“http://192.168.1.1”访问Breed Web。

    注意:下次刷机只需进入brend ,步骤为

    1. 路由器断电,持续按住路由器上的reset按钮,接通路由器电源,3秒后松开reset按钮。
    2. 在浏览器地址栏输入“http://192.168.1.1”访问Breed Web。

    [RT-AC54U-GPIO-1-PSG1218-64M_3.4.3.9-099.trx](F:\xuan install.Back\K2.Back) 访问ip:http://192.168.123.1/ 管理账号:admin/admin wifi密码:1234567890

    高级设置-外部网络-外网连接类型pppoe拨号,用户名密码输入宽带账号密码,闪讯拨号插件选择重庆,其他默认,然后应用本页设置
    高级设置-系统管理-ntp服务器 分别填入红岩网校的ntp服务器202.202.43.120 202.202.43.131 手动设置时间 应用本页设置
    网络地图-点击地球的图标-重新连接 应该可以上网了

    参考http://www.tieba.com/p/5015549863

方法2

斐讯K2 V22.5.9.163官方固件定制版,集成breed,支持官版直刷【V1.4】

直接手动升级k2_163_v14_breed.bin然后进入breed

配置文件

配置文件路径resuources\application.properties或者resuources\application.yml二者存一

  1. application.propertiesapplication.yml区别

    一个是树形目录,一个是单行配置。eg:server.port=8080等效于

    1
    2
    server:
    port: 8080
  2. 配置环境变量

    1
    2
    3
    4
    diy:
    parmars: hello
    #配置文件引用say=hello
    say: ${diy.parmars}

    代码里面引用

    1
    2
    @Value("${diy.parmars}")
    private String imgUrl;​

常见配置

  1. 数据库连接配置

    1
    2
    3
    4
    5
    6
    spring:
    datasource:
    url: jdbc:mysql://10.14.0.1:3306/sqlname?useUnicode=true&characterEncoding=utf-8
    username: xxx
    password: xxxx
    driver-class-name: com.mysql.jdbc.Driver
  2. springboot访问端口配置

    1
    2
    server:
    port: 8080
  3. 静态文件路径映射配置,如果配置了spring.resources.static-location会覆盖原来默认的静态文件设置classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/file路径指本地磁盘路径,window/Mac/linux的真实目录路径,例如window为C:\\windos\\

    1
    2
    3
    spring:
    resources:
    static-locations: <原来的静态文件配置>,file:/Users/xuanleung/Pictures/
  4. 文件上传大小限制(-1不限制)(128KB)

    1
    2
    3
    4
    5
    spring: 
    servlet:
    multipart:
    max-file-size: -1
    max-request-size: -1

application.ymlbootstrap.yml区别

加载顺序bootstrap.yml ->bootstrap-xxx.yml->application.yml->application-xxx.yml

注意:多个配置文件,相同替换,不同并集

bootstrap.yml常用于一些系统级别参数,不被更改

application.yml 应用级别,可以被更改,通过config service服务

bootstrap.yml常用于应用程序引导阶段例如

  • spring config server配置
  • application.name配置

使用config server时,bootstrap.yml常用配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
application:
name: service-a
cloud:
config:
uri: http://127.0.0.1:8888
fail-fast: true
username: user
password: ${CONFIG_SERVER_PASSWORD:password}
retry:
initial-interval: 2000
max-interval: 10000
multiplier: 2
max-attempts: 10

在不考虑加载顺序,两个配置是可以通用的

多环境配置

可以在配置文件(application/bootstrap)名后加上dev、test等后缀以-分开。使用环境时加上参数--spring.profiles.active=peer1即可

eg: 新建个application-xxx.yml,运行时执行java -jar app.jar --spring.profiles.active=xxx

注意

这里如果存在多个环境配置文件,且有application/bootstrap没有后缀的原文件时会优先加载它,然后再用指定环境的配置覆盖没有后缀的,这样就会导致,如果没有后缀的文件指定了某个配置,但是有后缀的却没有设置改配置,覆盖的情况,就是取并集,导致某些设置不生效。

常见问题

  1. springboot存储中文到mysql数据库乱码

    解决:在配置文件连接数据库地方添加url: jdbc:mysql://<ip>:<端口>/<数据库名字>?useUnicode=true&characterEncoding=utf-8

  2. application.yml添加注释报错,提示找不到Failed to load property source from location 'classpath:/application.yml'

    解决:检查文件编码格式,不是utf-8修改为utf-8

  3. eureka高可用时 提示不可用unavailable-replicas ,原因是存在application.yml,且里面设置了

    1
    2
    3
    client:
    register-with-eureka: false #Eureka默认也会作为客户端尝试注册,因此需禁用注册行为
    fetch-registry: false

    然后application-peer.yml并没有设置改属性,取并集之后导致,禁止注册了

    解决:

    1. 修改application-peer.yml里的属性,并设置为true(未测试)

    2. 删除或重命名application-peer.yml(采用)

    3. application-peer.yml覆写配置,并设置为true(测试,能注册,但是还是unavailable-replicas

      解决:检查是否application.name是否设置了不一样的值,一定要设置一样的名字

参考

SpringBoot 之 静态资源路径配置

spring cloud unavailable-replicas

docker 挂载卷应用

基础知识

官网说明Use bind mounts

官网compose文件:Compose file version 3 reference

bind 挂载,就是宿主机路径对应容器路径,该挂载需要先创建目录,简称/path:/path

volume挂载,不需要提前创建目录,他是以volume形式,简称volume-name:/path

其中zZ的使用,eg:/path:/path:z

z小写的是可以容器共享

Z大写的是私有,容器不可共享

ro只读挂载

思路

利用gluterfs作为分布式文件系统做同步用,利用虚拟机挂载一个volume专门的存储硬盘

实现

常用命令
1
2
3
4
5
6
7
8
9
10
11
fdisk -l #查看磁盘
#mount [-参数] [设备名称] [挂载点]
mount /dev/sdb /mnt/test
#umount [设备名称或挂载点]
umount mnt/test
#gluster卷状态[status\start\stop\delete]
gluster volume status v-portainer
#节点添加与删除[probe\detach]
gluster peer detach home
#节点连接状态,没有卷的情况容易端口连接,删除添加节点可解决
gluster peer status
单步步骤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mkdir /dockerdata #创建数据存储盘
#虚拟机挂载该磁盘,作为存储,因为阿里云没有多余磁盘,所有不挂载,直接存到/dockerdata
mount /dev/sdb /dockerdata
df -h #查看是否挂载成功
#设置开机自动挂在该目录
echo "dev/sdb /dockerdata ext4 defaults 0 0">>/etc/fstab
mkdir /dockerdata/v-portainer
#创建分布式存储卷,force忽略在root目录创建挂在卷的警告
gluster volume create v-portainer replica 2 home:/dockerdata/v-portainer xuanps:/dockerdata/v-portainer force
#启动分布式存储卷
gluster volume start v-portainer
#创建挂载目录
mkdir /volume/v-portainer
#挂载存储卷目录,似乎挂载才会同步
mount -t glusterfs home:/v-portainer /volume/v-portainer
#设置开机挂载,两台单独设置
echo "home:/v-portainer /volume/v-portainer glusterfs defaults 0 1" >> /etc/fstab
#部署应用
docker stack deploy -c docker-compose.yml gitlab
问题总结
  1. 使用之后,存储数据受网络印象,导致部分应用因为长时间连接不上而导致,不能启动,因此搁置
  2. 如果出现节点disconnect,在disconnect节点执行systemctl restart glusterd重启

介绍

**Gluster**是一个大尺度文件系统。

主要功能
简单卷
  1. distribute volume 分布式卷,两台主机的磁盘融合一个磁盘
  2. stripe volume 条带卷,一个文件分成数据块存储到不同的地方
  3. replica volume 复制卷,一个文件分别保存到两台主机
复合卷

1+2,1+3,2+3,1+2+3

总结常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
gluster peer status     #查看集群各主机连接状态
gluster volume list #查看挂载卷信息
gluster volume list #查看卷列表
#创建挂在卷,force忽略在root目录创建挂在卷的警告
gluster volume create swarm-volume replica 3 worker:/xuan/docker/gluster-volume home:/xuan/docker/gluster-volume xuanps:/xuan/docker/gluster-volume force
gluster volume start swarm-volume #启动
gluster volume stop swarm-volume #停止
gluster volume delete swarm-volume #删除 ,了文件还会保留
#挂载本地目录到glusterfs卷(swarm-volume),在本地目录添加的会自动同步到其他挂载卷
#eg在本机mnt添加文件,其他volume-name目录也会添加mount [-参数] [设备名称] [挂载点]
mount -t glusterfs worker:/swarm-volume /mnt/
umount worker:/swarm-volume #卸载了就不会同步了
#重置,删除所有数据
systemctl stop glusterd
rm -rf /var/lib/glusterd/
systemctl start glusterd
#删除节点
gluster peer detach home

安装

准备工作:

三台局域网主机(centos7 修改主机名

hostname ip 备注
xuanps 10.14.0.1
worker 10.14.0.4
home 10.14.0.5

三台都需要安装GlusterFS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#搜索glusterfs可安装的版本
yum search centos-release-gluster
#安装最新长期稳定版本(Long Term Stable)的gluster软件
yum -y install centos-release-gluster
#安装glusterfs-server
yum --enablerepo=centos-gluster*-test install glusterfs-server
glusterfs -V #测试
systemctl enable glusterd #开机启动
systemctl start glusterd #启动
systemctl status glusterd #查看是否正常运行
#修改hosts不然不能通过主机名连接到对方
vim /etc/hosts
#----------三台都要添加如下设置--------------------------
10.14.0.1 xuanps
10.14.0.4 worker
10.14.0.5 home
#------------------------------------------------------
#从xuanps上执行下面两条,其他主机不用执行
gluster peer probe worker
gluster peer probe home
#三台都执行该命令是否都是connected
gluster peer status
#查看挂载卷信息
gluster volume info
#创建挂在卷,force忽略在root目录创建挂在卷的警告
gluster volume create volume-name replica 3 worker:/xuan/docker/gluster-volume/test home:/xuan/docker/gluster-volume/test xuanps:/xuan/docker/gluster-volume/test force
#启动
gluster volume start volume-name
#启动nfs同步,测试需验证,这里要不要开启
gluster volume set volume-name nfs.disable off
#挂载本地目录到glusterfs卷(volume-name),在本地目录添加的会自动同步到其他挂载卷
#eg在本机mnt添加文件,其他volume-name目录也会添加
mount -t glusterfs worker:/volume-name /mnt/

参考

官网:https://www.gluster.org/

文档

centos官方安装手册

基于 GlusterFS 实现 Docker 集群的分布式存储

docker 安装docker-volume-glusterfs

前提,首先安装好GlusterFS分布式文件系统,可以参考centos7 安装 GlusterFS

sapk/docker-volume-gluster安装(弃)

不足:无法删除volume,且无法复用

1
2
3
4
5
6
7
8
9
10
#安装插件,三台主机都安装(保险起见)
docker plugin install sapk/plugin-gluster
# docker volume create --driver sapk/plugin-gluster --opt voluri="<volumeserver>,<otherserver>,<otheroptionalserver>:<volumename>" --name test
#volumeserver 主机名,可以指定多个,volumenam是 GlusterFS文件系统的挂载劵名,test是swarm挂载卷名
docker volume create --driver sapk/plugin-gluster --opt voluri="worker,home,xuanps:swarm-volume" --name test
#运行ubuntu容器进行测试
docker run -v test:/mnt --rm -ti ubuntu
#进去之后创建文件,其他系统盘也能看到该文件了, 但是其他系统不会创建挂载卷
echo "hello">/mnt/testfile

docker-compose 使用

1
2
3
4
5
volumes:
some_vol:
driver: sapk/plugin-gluster
driver_opts:
voluri: "<volumeserver>:<volumename>"

calavera/docker-volume-glusterfs官方安装(废弃 测试时发现运行找不到插件,太老了,换新插件)

1
2
3
4
5
6
7
8
9
#---------------------------废弃,网络原因下载不下来--------------------------------
#为了环境干净,安装docker golang容器工具(--rm参数,运行后销毁容器)
docker run -v /tmp/bin:/go/bin \
--rm golang go get github.com/golang/example/hello/...
#测试,执行该命令会输出Hello, Go examples!,如果没输出,说明容器环境和主机环境不一致
/tmp/bin/hello
#----------------准备工作完成正式开始安装---------------------------------------------------------
#通过golang容器工具下载插件,下载到/tmp/bin目录
docker run -v /tmp/bin:/go/bin --rm golang go get github.com/calavera/docker-volume-glusterfs

手动去下载,通过sftp等工具传到服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
mv docker-volume-glusterfs_linux_amd64 docker-volume-glusterfs   #重命名
cp ./docker-volume-glusterfs /usr/bin #放到bin目录
chmod 777 /usr/bin/docker-volume-glusterfs #添加权限
#其他两台主机,然后复制到其他服务器上
scp root@10.14.0.1:~/docker-volume-glusterfs ~
#依次放到bin目录添加权限
cp ~/docker-volume-glusterfs /usr/bin
chmod 777 /usr/bin/docker-volume-glusterfs
# 三台主机都执行该命令,该命令会前端运行
docker-volume-glusterfs -servers xuanps:worker:home
# 测试使用
sudo docker run --volume-driver glusterfs --volume swarm-volume:/data alpine ash
touch /data/helo

额外的 docker 卷插件

官方插件列表

官方插件商店

rancher/convoy

主要功能快照、备份、还原

总结:适用于单节点,单主机,的本地卷管理

calavera/docker-volume-glusterfs

centos7 安装 GlusterFS

Pure Storage Docker Volume Plugin

Hedvig Docker Volume Plugin

依赖于hedvig cluster

REX-Ray

github: rexray/rexray

参考:每天5分钟玩转 OpenStack Rex-Ray

………..居然没找到一个合适的提供者

rexray/cis-nfs 待测试,NFS似乎不满足需求

参考

Docker与Golang的巧妙结合

基于 GlusterFS 实现 Docker 集群的分布式存储

Docker 应用之owncloud

官网:library/owncloud

默认会创建挂载卷-v /<mydatalocation>:/var/www/html

细分挂载卷

  • -v /<mydatalocation>/apps:/var/www/html/apps installed / modified apps
  • -v /<mydatalocation>/config:/var/www/html/config local configuration
  • -v /<mydatalocation>/data:/var/www/html/data the actual data of your ownCloud (网盘文件)

安装

1
2
3
4
5
docker pull owncloud
#默认安装,会默认创建一个挂载卷
docker run -d -p 14007:80 owncloud:8.1
#将网盘文件存储指向到oss,注意挂载目录的权限问题,否则没权限操作会报错
docker run -v /ossfs/owncloud:/var/www/html/data -d -p 14007:80 owncloud:latest

体验

虽然成功存储到了oss里面但是极度卡,就算是阿里云内网ossfs,一样的卡,因此放弃存到ossfs,可以考虑备份放到ossfs

阿里云ESC服务器挂载 OSS 文件系统

ossfs 能让您在Linux/Mac OS X 系统中把Aliyun OSS bucket 挂载到本地文件 系统中,您能够便捷的通过本地文件系统操作OSS 上的对象,实现数据的共享。

阿里云oss官方:ossfs挂载,您可以理解为把挂载的bucket当做一个ecs目录来操作的,存储文件到挂载的bucket中是占用的这个bucket的内存,不会占用您ecs的内存。

安装

  1. 下载文件ossfs_1.80.3_centos7.0_x86_64.rpm到阿里云

  2. 安装sudo yum localinstall ossfs_1.80.3_centos7.0_x86_64.rpm

  3. 写入oss配置echo my-bucket:my-access-key-id:my-access-key-secret > /etc/passwd-ossfs,例:

    1
    echo ossfs-xuan:LTAIw5M5SHnIoNcm:ci1Oj7*******ZqDziBj > /etc/passwd-ossfs
  4. 更改配置文件权限chmod 640 /etc/passwd-ossfs

  5. 创建挂载目录mkdir /ossfs

  6. 挂载ossfs ossfs-xuan /ossfs -ourl=oss-cn-shenzhen-internal.aliyuncs.com

额外的命令

1
2
3
4
#允许linux其他用户对改oss文件系统进行操作
ossfs ossfs-xuan /ossfs -ourl=oss-cn-shenzhen-internal.aliyuncs.com -o allow_other
#卸载挂载oss目录
umount /ossfs

错误

InvalidBucketName错误可以看出BucketName重复了

1
2
3
4
5
6
7
8
9
ossfs: bad request
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>InvalidBucketName</Code>
<Message>The specified bucket is not valid.</Message>
<RequestId>5A93BFD701A3E286AC09FDDD</RequestId>
<HostId>ossfs-xuan.ossfs-xuan.oss-cn-shenzhen-internal.aliyuncs.com</HostId>
<BucketName>ossfs-xuan.ossfs-xuan</BucketName>
</Error>

解决:-ourl=oss-cn-shenzhen-internal.aliyuncs.com不需要带BucketName