苍穹外卖day02项目日志

news2024/9/28 19:22:13

1. 描述清楚新增员工的实现流程

1.1需求分析与设计

参考产品原型,设计表和接口。

1.1.1设计表

看员工管理的产品原型:

有员工姓名、账号、手机号、账号状态、最后操作时间等。

注意,操作一栏不是字段,其中的启用禁用才是。

再看添加员工的原型:

 可以发现还有性别和身份证号。

不要忘了旁边:

还有密码。

总结出了以下字段:

员工姓名name

用户名username

密码password

手机号tel

性别gender

身份证号idNumber

启用禁用状态status

更新时间update_time

这些统称为业务字段

不过除了这些,还有基础字段

主键id

创建时间create_time

创建操作人create_user

更新时间update_time

更新操作人update_user

这样就设计完了表。

作为练习,然后我们可以回过头来和设计好的表进行对比,看我们漏了哪一步没有。

可以看到,多了一个昨天加的,用来验证登录的盐值salt,其他都一样。 

1.1.2设计接口

设计接口需要设计4个东西:

  1. 请求路径
  2. 请求参数
  3. 请求方式
  4. 响应数据

对应我们这个新增员工的接口就是如下设计:

  1. 请求路径 /admin/employee(可以加/add,也可以通过请求方式确定添加操作)
  2. 请求参数 传json(如{“username”:”xxx”, “name”:””, “tel”:””, “sex(或gender)”:””})
  3. 请求方式 POST
  4. 响应数据 {“code”:””,”msg(错误信息,错了是什么原因)”:””, data:””}

注意,在公司里,接口设计或多或少都会和现在学的有些出入,这是正常的,习惯一下。

另外,正真在公司里,设计表会比较少,因为就那么几个;但设计接口会比较多。而复杂点的表,小后端的水平也设计不太来。没关系,慢慢学。

1.2代码开发

3步,分别是写Controller、写Service、写Mapper。

1.2.1写入表现层Controller

接受请求参数,调用service完成添加操作,响应结果。

代码如下:

/**
 * 员工管理
 */
@RestController
@RequestMapping("/admin/employee")
@Slf4j
@Api(tags = "硬普洛伊康戳勒 员工相关接口")
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;
    
    ...

    /**
     * 员工新增功能
     * @param employeeDTO 前端提交的参数
     * @return 成功的结果
     */
    @PostMapping
    public Result add(@RequestBody EmployeeDTO employeeDTO) {
        employeeService.addEmp(employeeDTO);
        return Result.success();
    }
}

1.2.2写入业务层Service

根据数据库中字段的要求,完善数据,调用mapper完成添加操作。

接口中:

public interface EmployeeService {

    ...

    /**
     * 员工新增功能
     */
    void addEmp(EmployeeDTO employeeDTO);
}

实现类中:

@Service
public class EmployeeServiceImpl implements EmployeeService {

    @Autowired
    private EmployeeMapper employeeMapper;

    ...

    @Override
    public void addEmp(EmployeeDTO employeeDTO) {
        // employeeDTO: username name phone sex idNumber
        // database: id password status salt create_tim create_user update_time update_user
        // our preparing work: password(described in origin) salt create_time update_time
        // our preparing work: create_user update_user(awkward somehow, do it later)

        // Create object of Employee
        Employee employee = new Employee();

        // copy data in EmployeeDTO to employee
        BeanUtils.copyProperties(employeeDTO, employee);

        // supplement data
        LocalDateTime now = LocalDateTime.now();
        employee.setUpdateTime(now);
        employee.setCreateTime(now);
        // TODO create user and update user haven't finish rightly
        employee.setCreateUser(666L);
        employee.setUpdateUser(666L);
        String password = "123456";

        // product a random salt
        String salt = UUID.randomUUID().toString().replace("-", "");
        // finish MD5 with both salt and origin password
        password = DigestUtils.md5DigestAsHex((password + salt).getBytes());
        employee.setPassword(password);
        employee.setSalt(salt);

        // insert
        employeeMapper.insertEmp(employee);
    }

}

注意那个TODO注释,可以在idea下面窗口中显示你还需要完善的代码。

讲到这,老师讲了一个惨痛的经历:

有个老哥,写测试类忘改了,下单都是0.01元,给公司损失了几十上百万……

这老哥被开没开,被告没告,不知道,不过大家一定要吸取教训啊。

1.2.3写入持久层Mapper

@Mapper
public interface EmployeeMapper {

    /**
     * 根据用户名查询员工
     * @param username
     * @return
     */
    @Select("select * from employee where username = #{username}")
    Employee getByUsername(String username);

    /**
     * 添加员工
     * @param employee
     */
    void insertEmp(Employee employee);
}

对应xml文件中:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.EmployeeMapper">

    <insert id="insertEmp">
        insert into employee (name, username, password, phone, sex, id_number, create_time, update_time, create_user,
                              update_user, salt)
        value (#{name}, #{username}, #{password}, #{phone}, #{sex}, #{idNumber}, #{createTime}, #{updateTime},
            #{createUser}, #{updateUser}, #{salt})
    </insert>

</mapper>

1.2.4Swagger测试

运行项目,进入Swagger链接:

http://localhost:8080/doc.html

首先到登录接口,用管理员账户登录,获得请求头。

 到全局参数设置,加入我们的请求头。

然后进入添加员工界面,输入参数测试是否正常添加员工。 

查看显示结果:

 1.3代码完善

1.3.1异常处理

用户输入的用户名在数据库中已经存在,这种情况下会报错,需要处理。

解决方式:

在全局异常处理器中添加一个方法,专门处理这个异常;

在异常处理方法中,截取重复的用户名,响应错误信息给前端。

代码:

@ExceptionHandler
public Result sqlIntegrityConstraintViolationExceptionHandler(SQLIntegrityConstraintViolationException ex){
    String message = ex.getMessage();
    log.error("异常信息:{}", message);
    // 1. 判断异常类型是否是想处理的类型 / 是否包含Duplicate entry
    if (message.contains("Duplicate entry")) {
        // 2. 如果是,则获取异常message  Duplicate entry 'abc' for key 'employee.idx_username'
        // 3. 截取用户名信息
        String username = message.split(" ")[2];
        // 4. 拼接提示信息 “xxx用户名已存在,请重新输入”
        // 5. 返回错误信息给前端
        return Result.error(username+"用户名已存在,请重新输入");
    }
    
    return Result.error(ex.getMessage());
}

1.3.2创/修改人设置(ThreadLocal)

详见下面第三大问。

1.4测试

1.4.1接口测试

用Swagger。

测试正常添加功能:

在数据库中查看添加人情况:

添加重复名称员工,测试异常处理功能:

1.4.2前端后端联调

测试正常添加功能: 

在数据库中查看添加人情况:

添加重复名称员工,测试异常处理功能:

2. 描述清楚员工分页条件查询的实现流程

2.1需求分析

2.1.1分析产品原型扣细节

查询需要加分页和查询条件(模糊匹配,动态SQL)。

排序按照创建时间降序排列。

2.1.2接口设计

  1. 请求路径 

    /admin/employee
    至于添加子路径,那得看情况。可能有很多不同种的查询,就加路径;简单情况下,还是用get请求就行了。
    所以加路径就/admin/employee/page

  2. 请求参数 ?page=1&pageSize=10&name=zhangsan
  3. 请求方式 GET
  4. 响应数据

    {
        “code”:1
        “msg”:””,(失败才有msg)
        “data”:{
            “total(或rows)”:100,
            “records”: [
                {},
                {},
            ]
        }
    }

2.2代码开发

2.2.1Controller层

@ApiOperation(value = "员工分页查询接口")
@GetMapping("/page")
public Result page(EmployeePageQueryDTO employeePageQueryDTO) {
    PageResult pr = employeeService.pageQuery(employeePageQueryDTO);
    return Result.success(pr);
}

2.2.2Service层

@Override
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
    // 1. 设置分页参数,开启分页查询
    PageHelper.startPage(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize());
    // 2. 调用mapper执行分页查询,返回分页结果对象 Page
    Page<Employee> page = employeeMapper.selectByPageAndName(employeePageQueryDTO.getName());
    // 3. 通过分页对象Page中获取当前页的数据和总记录
    long total = page.getTotal();
    List<Employee> records = page.getResult();
    // 4. 封装当前页数据和总记录,封装进PageResult,并返回
    return new PageResult(total, records);
}

2.2.3Mapper层

<select id="selectByPageAndName" resultType="com.sky.entity.Employee">
    select * from employee
    <where>
        <if test="name!=null and name!=''">
            name like concat('%', #{name}, '%')
        </if>
    </where>
    order by create_time desc
</select>

2.3测试

2.3.1接口测试

Swagger结果:

2.3.2前后端联调

 

 

3. 如何用ThreadLocal实现员工ID的获取?

3.1简化意义

之前学过拿到请求头的方法。大致是:注入request对象,获取请求头,拿到token,再解析token拿到登入id。

这样子做是可以。但存在问题:

代码太繁琐了,后期其他地方用到id又要再次编写上面代码,冗余的地方就会很多。

于是我们用到了新方案: 利用线程对象(包含一个集合,可以实现在一个线程之间共享数据),在登录验证的拦截器中实现获取id的操作,id设置到线程对象变成共享的,使用的时候获取即可。这就是ThreadLocal技术思路。

3.2实现

我们已经定义好了一个封装了ThreadLocal的类,在common模块下的context包中,有个BaseContext。

 

 我们看看它的代码:

package com.sky.context;

public class BaseContext {
    public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    public static void setCurrentId(Long id) {
        threadLocal.set(id);
    }

    public static Long getCurrentId() {
        return threadLocal.get();
    }

    public static void removeCurrentId() {
        threadLocal.remove();
    }

}

3.2.1set方法

其中setCurrentId调用了ThreadLocal的set方法, 设置id。

3.2.2get方法

getCurrentId则调用了get方法,用于得到设置好的,或者默认的id。

3.2.3remove方法

removeCurrentId调用了remove方法,可以移除设置的参数,让内存回收。

再看入具体实现:

3.2.4拦截器中添加代码

//2、校验令牌
try {
    log.info("jwt校验:{}", token);
    Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
    Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
    log.info("当前员工id:{}", empId);
    // put hte id in thread
    BaseContext.setCurrentId(empId);  // 添加到此处
    //3、通过,放行
    return true;
} catch (Exception ex) {
    //4、不通过,响应401状态码
    response.setStatus(401);
    return false;
}

再这里,我们要获取当前员工的id,并得到。之后就可以调用ThreadLocal对象,直接得到员工id,方便插入操作人的操作。

3.2.5业务层添加代码

添加代码如下:

Long currentId = BaseContext.getCurrentId();
employee.setCreateUser(currentId);
employee.setUpdateUser(currentId);

这里就直接用保存的员工id,进行添加了。

3.2.6思考题

什么时候remove比较合适呢?代码又应该写在哪里呢?

3.3原理

我们可以看一看ThreadLocal中的set方法:

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

可以看到,它将线程和map绑定,以达到我们的用线程可以得到绑定值的效果。

再看看get方法:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

也是同上的原理,调用绑定的线程就可以获取对应的设置的值。

如果还有不清楚,可以再点进具体的类中看看,比如getMap函数、ThreadLocalMap对象中去看。 

3.4思考题答案

我们在线程结束,request结束的时候,把它remove了最好。在这之前,可能还是会用到。

所以写在拦截器的后面两个要重写的方法中即可。这里就挑第一个,postHandle。第二个的afterCompletion也可以。

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    BaseContext.removeCurrentId();
}

4. Java对象转json的日期格式如何指定?

4.1方案一

在每个日期属性上都加上格式转换的代码。

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;

4.1.1缺点

繁琐,每一个都要加。

4.2方案二

在WebMvcConfiguration中扩展SpringMVC的消息转换器,统一对日期类型进行格式处理。

使用sky-common模块中,json包下的JacksonObjectMapper类

package com.sky.json;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;

/**
 * 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
 * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
 * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
 */
public class JacksonObjectMapper extends ObjectMapper {

    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    //public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

    public JacksonObjectMapper() {
        super();
        //收到未知属性时不报异常
        this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

        //反序列化时,属性不存在的兼容处理
        this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

        SimpleModule simpleModule = new SimpleModule()
                .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
                .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));

        //注册功能模块 例如,可以添加自定义序列化器和反序列化器
        this.registerModule(simpleModule);
    }
}

然后在配置类WebMvcConfiguration中加入以下代码:

protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    log.info("扩展消息转换器...");
    //创建一个消息转换器对象
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    //需要为消息转换器设置一个对象转换器,对象转换器可以将Java对象序列化为json数据
    converter.setObjectMapper(new JacksonObjectMapper());
    //将自己的消息转化器加入容器中
    converters.add(0,converter);
}

4.3结果验证

 

5. 描述清楚修改员工的实现流程

包括修改员工状态和编辑员工。

5.1需求分析和设计

5.1.1具体业务

修改员工状态:

用户点击启用/禁用按钮,切换用户状态。

编辑员工:

数据回显:根据id查询员工信息,并展示在编辑的表单中。

提交修改:根据id修改数据。

5.1.2设计接口

修改员工状态:

  1. 请求路径 /admin/employee/status/{status}
  2. 请求参数 ?id=1
  3. 请求方式 POST
  4. 响应数据
    {
        “code”:1,
        “msg”:””,
        “data”:null
    }

编辑员工:

  1. 请求路径 /admin/employee
  2. 请求参数 json,用EmployeeDTO接收
  3. 请求方式 PUT
  4. 响应数据 还是code、msg和data。

5.2代码开发

5.2.1Controller层

/**
 * 启用/禁用员工账号接口
 * @param status
 * @param id
 * @return
 */
@ApiOperation(value = "启用/禁用员工账号接口")
@PostMapping("/status/{status}")
public Result updateStatus(@PathVariable Integer status, Long id) {
    employeeService.updateStatus(status, id);
    return Result.success();
}

/**
 * 根据id查询员工信息
 * @param id
 * @return
 */
@GetMapping("/{id}")
public Result getByID(@PathVariable Long id) {
    Employee employee = employeeService.getById(id);
    return Result.success(employee);
}

/**
 * 编辑员工信息
 * @param employeeDTO
 * @return
 */
@PutMapping
public Result update(@RequestBody EmployeeDTO employeeDTO) {
    employeeService.update(employeeDTO);
    return Result.success();
}

5.2.2Service层

public void updateStatus(Integer status, Long id) {
    Employee employee = new Employee();
    employee.setStatus(status);
    employee.setId(id);
    employee.setUpdateTime(LocalDateTime.now());
    employee.setUpdateUser(BaseContext.getCurrentId());
    employeeMapper.updateById(employee);
}

@Override
public Employee getById(Long id) {
    return employeeMapper.getById(id);
}

@Override
public void update(EmployeeDTO employeeDTO) {
    Employee employee = new Employee();
    BeanUtils.copyProperties(employeeDTO, employee);
    employee.setUpdateTime(LocalDateTime.now());
    employee.setUpdateUser(BaseContext.getCurrentId());
    employeeMapper.updateById(employee);
}

5.2.3Mapper层

Java代码:

/**
     * 根据id修改员工状态
     * @param employee
     */
    void updateById(Employee employee);

    @Select("select * from employee where id=#{id}")
    Employee getById(Long id);

xml文件:

<update id="updateById">
    update employee
        <set>
            <if test="name!=null and name!=''">name = #{name},</if>
            <if test="username != null and name !=''">username = #{username},</if>
            <if test="password != null and name !=''">password = #{password},</if>
            <if test="phone != null">phone = #{phone},</if>
            <if test="sex != null">sex = #{sex},</if>
            <if test="idNumber != null">id_number = #{idNumber},</if>
            <if test="updateTime != null">update_time = #{updateTime},</if>
            <if test="updateUser != null">update_user = #{updateUser},</if>
            <if test="status != null">status = #{status},</if>
        </set>
    where id = #{id};
</update>

5.3测试

5.2.1接口测试

修改员工状态:

编辑员工 

5.2.2前后端联调

修改员工状态 

编辑员工

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/803897.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

沦为囚犯的“烟草女王”卢平的管理口诀:大胆设计,小心求证

沦为囚犯的“烟草女王”卢平的管理口诀&#xff1a; 大胆设计&#xff0c;小心求证 卢平是当初是湖南烟草界女强人 大致2003年听到了一句话 在管理知识稀缺的年代 当初听了有点小激动 趣讲大白话&#xff1a;管理口诀有意思 【趣讲信息科技239期】 ***************************…

面试了一个在字节工作2年的“大佬”,我蚌埠住了

昨天面试了一位在字节跳动工作2年多的开发&#xff0c;简历上写的工作截止时间是“至今”。特意问了一下&#xff0c;才知道实际是六月份已经不在职了。面试也就进行了十多分钟&#xff0c;但想跟大家分享一些站在选人的视角如何看待面试中的一些问题。 先说说面试 首先肯定是…

不断学习和提高写作水平,使公文写作更加得心应手和高效精准

不断学习和提高写作水平&#xff0c;积累经验和技巧&#xff0c;是提高公文写作能力的重要方法。 具体来说&#xff0c;可以采取以下几个方面的工作&#xff1a; 1.学习范例&#xff1a;阅读优秀的公文范例&#xff0c;学习其写作技巧和语言风格&#xff0c;以丰富自己的写作经…

低代码开发平台源码

什么是低代码开发平台&#xff1f; 低代码来源于英文“Low Code&#xff0c;它意指一种快速开发的方式&#xff0c;使用最少的代码、以最快的速度来交付应用程序。通俗的来说&#xff0c;就是所需代码数量低&#xff0c;开发人员门槛低&#xff0c;操作难度低。一般采用简单的图…

如何将 LoRaWAN 用于比赛场景

如何将 LoRaWAN 用于比赛场景 关键词 LoRaWAN 实时上报 下行同步 不丢包 组播 应用场景 学生/运动员比赛&#xff0c;射击比武&#xff0c;同步采集等 摘要 为了将 LoRaWAN 应用于&#xff1a;比赛&#xff0c;比武&#xff0c;同步采集等场景&#xff0c;应对下行同步和…

cURL error 1: Protocol “https“ not supported or disabled in libcurl

1、php项目composer update报错 2、curl -V检查 发现curl已经支持了https了 3、php版本检查 4、php插件检查 插件也已经含有openssl组件了 5、phpinfo检查 curl是否开启ssl 定位到问题所在&#xff0c;php7.4的 curl扩展不支持 https 需要重装 php7.4的curl扩展 6、curl下载 下…

Feign API模块导入的两种方式

说明&#xff1a;在微服务框架中&#xff0c;会把其他微服务用到的FeignClient统一放到一个模块里面&#xff0c;称为FeignAPI&#xff0c;其他微服务需要使用FeignClient&#xff0c;引入FeignClient的Maven坐标就可以使用。 但是只引入FeignAPI的坐标还不行&#xff0c;Feig…

【分布式】分布式唯一 ID 的 几种生成方案以及优缺点snowflake优化方案

在互联网的业务系统中&#xff0c;涉及到各种各样的ID&#xff0c;如在支付系统中就会有支付ID、退款ID等。那一般生成ID都有哪些解决方案呢&#xff1f;特别是在复杂的分布式系统业务场景中&#xff0c;我们应该采用哪种适合自己的解决方案是十分重要的。下面我们一一来列举一…

数字化新时代,VR全景拍摄与制作

导语&#xff1a; 随着科技的飞速发展&#xff0c;数字化图片正在引领新的时代潮流。在这个数字化图片的新时代&#xff0c;VR全景拍摄与制作技术正以其独特的特点和无限的优势&#xff0c;成为数字影像领域的一颗璀璨明星。让我们深入了解VR全景拍摄与制作的特点和优势&#…

PLC绝对值指令ABS()

在C语言里,ABS()指令属于基础指令,博途PLC系统也有绝对值指令。对于S7-200SMART PLC则需要自行构造,下面给出SMART PLC的绝对值指令ABS()。 1、S7-SMART PLC绝对值指令 2、STL代码 SUBROUTINE_BLOCK ABS:SBR3 TITLE=ABS()函数 VAR_INPUT x:REAL; END_VAR VAR_OUTPUT y:RE…

市值超300亿美金,SaaS独角兽Veeva如何讲好中国故事?

“全球前50的药企&#xff0c;有47家正在使用Veeva。” 提到Veeva Systems&#xff08;以下简称“Veeva”&#xff09;&#xff0c;可能很多人并不熟悉。但是生命科学业内人士都知道&#xff0c;Veeva是全球头部的行业SaaS服务商。以“为生命科学行业构建行业云”为使命&#x…

网络安全(黑客)自学——从0开始

为什么学习黑客知识&#xff1f;有的人是为了耍酷&#xff0c;有的人是为了攻击&#xff0c;更多的人是为了防御。我觉得所有人都应该了解一些安全知识&#xff0c;了解基本的进攻原理。这样才可以更好的保护自己。这也是这系列文章的初衷。让大家了解基本的进攻与防御。 一、怎…

记一次有趣的debug,VS编译器上Debug和Realease的差异

之前自己写过一个imageread的函数&#xff0c;用了好久一直没问题。最近两天&#xff0c;同事让我realease一个项目给他&#xff0c;其中就包含了我自己写的imageread函数。 我的函数就长这样&#xff0c;不包含公司的code&#xff0c;不算泄密哈。 在realse之前&#xff0c;我…

一些有意思的人工智能发展状况数据

随着大型语言模型&#xff08;LLM&#xff09;的引入&#xff0c;机器学习&#xff08;ML&#xff09;和人工智能&#xff08;AI&#xff09;首次被日常开发人员所使用。这些令人感觉很神奇的应用程序&#xff0c;甚至是拥有数十亿研发支出的&#xff0c;在以前连大型科技公司几…

探索Java API学习路线:从基础到高级的全面指南

文章目录 第一阶段&#xff1a;入门基础1. 环境准备2. 学习Java基础 第二阶段&#xff1a;熟悉常用的Java API1. Java标准库2. Java API文档 第三阶段&#xff1a;深入学习特定领域的Java API1. Java GUI API2. Java数据库连接&#xff08;JDBC&#xff09;API3. Java多线程API…

企业全渠道营销终极指南

客户不再在单一的渠道购物&#xff0c;渠道跳跃正在成为全球流行的消费者购物行为。他们用多种渠道与品牌互动&#xff0c;包括社交媒体、电子邮件等&#xff0c;这迫使企业必须采用全渠道营销策略来满足客户不断变化的需求&#xff0c;为客户提供个性化的体验&#xff0c;提高…

客户支持工具从被动到主动的演变

在当日新月异的商业环境中&#xff0c;企业需要适应不断增长的客户需求&#xff0c;优质的客户支持变得越来越重要。客户支持工具从传统系统到尖端 AI驱动解决方案的演变具有变革性&#xff0c;增强了主动和无缝的支持体验。所以&#xff0c;使用正确的客户服务工具很重要&…

【每天40分钟,我们一起用50天刷完 (剑指Offer)】第三十九天 39/50【二分】【二叉搜索数第k个节点】

专注 效率 记忆 预习 笔记 复习 做题 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&#xff09;   文章字体风格&#xff1a; 红色文字表示&#…

XRCameraSubsystem浅析

在使用ARFoundtion 4.2.8版本&#xff0c;要获取相机图像&#xff0c;还是通过ARCameraManager获取的。 大概的调用流程&#xff1a; 1. 应用脚本获取ARCameraManager对象&#xff0c;默认是挂在AR Camera下&#xff0c;向其注册回调函数。 2. ARCameraManager里会在每次Upd…

TortoiseSVN操作使用

说明 SVN常用于程序代码版本控制,由于业务需求需将生产资料通过SVN进行管控,涉及人员众多,权限分支管理需要细化,特此记录SVN的学习操作. 前言 版本控制是管理信息修改的艺术&#xff0c;它一直是程序员最重要的工具&#xff0c;程序员经常会花时间作出小的修改&#xff0c; 然…